Open file in OS X - macos

In Delphi, I would like to open a file in OS X. My approach is as follows:
const
Filename = 'test.bmp';
procedure SaveAndOpen;
begin
Chart.SaveToBitmapFile(Filename);
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', Filename, '', '', SW_Normal);
{$ELSE}
_System(Filename);
{$ENDIF}
end;
But nothing happens. What am I doing wrong?

This article from Embarcadero's Malcolm Groves covers this topic: Opening files and URLs in default applications in OS X.
In summary, all you need is this:
uses
Macapi.Appkit, // for NSWorkspace
Macapi.Foundation; // for NSSTR
....
var
Workspace: NSWorkspace; // interface, no need for explicit destruction
....
Workspace := TNSWorkspace.Create;
Workspace.openFile(NSSTR(FileName));
For sake of completeness, should you wish to open a URL rather than a file, then you call openURL instead:
Workspace.openURL(NSSTR(URL));
Regarding your Windows code, I would recommend not using ShellExecute. That function does not have reasonable error reporting. Use ShellExecuteEx in its place.
And finally, you should probably abstract this functionality away so that it can be re-used by other parts of your program. You want to write that IFDEF as few times as possible.

You must add the open verb like so
_System(PAnsiChar('open ' + Filename));

Related

SelectDirectory does not include drives on some machines

The following code gets different results on different machines. One machine just gives the desktop folder (not desired) the other gives the desktop folder and Computer, mapped drives (desired).
procedure TForm1.Button1Click(Sender: TObject);
var
Directory : String;
begin
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
end;
One one machine it gives:
On another it gives:
This feels like a windows setting, but I am not sure where to start. Using Delphi XE, Windows 10.
Any thoughts are appreciated. Thanks for your time.
Workaround
Use a TFileOpenDialog instead*.
Set FileOpenDialog1.Options:= [fdoPickFolders,fdoPathMustExist]
Now you have a dialog that:
Always works.
Allows copy paste
*) Not to be confused with the TOpenDialog, which does not allow you to only select folders.
Solution for Windows XP
Note that the new TFileOpenDialog only works for Vista and above.
Your program will not work on XP if you include this control.
If you start the dialog on XP it will generate an EPlatformVersionException.
You may want to use the following code instead if you want to be backward compatible:
uses JclSysInfo; //because you have XE use JCL.
...
var
WinMajorVer: Integer;
Directory: string;
FileDialog: TFileOpenDialog;
begin
WinMajorVer:= GetWindowsMajorVersionNumber;
if WinMajorVer < 6 then begin //pre-vista
//To show the root Desktop namespace, you should be setting the Root parameter to an empty string ('') instead of 'Desktop'
FileCtrl.SelectDirectory('Caption', '', Directory, [sdNewUI, sdShowEdit]);
end else begin
FileDialog:= TFileOpenDialog.Create(self);
try
FileDialog.Options:= [fdoPickFolders,fdoPathMustExist];
if FileDialog.Execute then Directory:= FileOpenDialog1.FileName;
finally
FileDialog.Free;
end;
end;
Result:= Directory;
end;
Recommended reading:
detect windows version
EDIT
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
The 'Desktop' goes into the Root parameter, which is handled like so:
...
SHGetDesktopFolder(IDesktopFolder);
IDesktopFolder.ParseDisplayName(Application.Handle, nil,
Root, Eaten, RootItemIDList, Flags);
...
Here's what MSDN for IDesktopFolder.ParseDisplayName has to say:
pszDisplayName [in]
Type: LPWSTR
A null-terminated Unicode string with the display name. Because each Shell folder defines its own parsing syntax, the form this string can take may vary. The desktop folder, for instance, accepts paths such as "C:\My Docs\My File.txt". It also will accept references to items in the namespace that have a GUID associated with them using the "::{GUID}" syntax.
Note that the documentation states that the desktop folder will accept paths and guids. It does not accept 'Desktop'. Because that's neither.
The fact that 'Desktop' as a root works on one system but not another is some undocumented fix made in an older/newer version of the IDesktopFolder interface.
Technical solution
Use '' as a 'root' as shown in my code above.
Obviously SelectDirectory is a really bad design by Microsoft that should never be used. It just sucks in so many ways. I recommend it not be used whenever possible.

Read/Write to file on old IBM PS/2 in turbo pascal 5.5

The Question: I recently acquired a 1989 IBM PS2 and I am trying move large files from my newer UNIX-based machine to this IBM via floppy. I have a bash script that splits my files into ~2MB chunks, now I am trying to write a pascal program to reconstruct these files after they have been transferred.
I am unable to find the correct read/write to file methods on this computer. I have tried various pascal tutorial sites, but they are all for newer versions (the site I followed with File Handling In Pascal). I am able to create an empty file (as described below), but I am unable to write to it. Does anyone know the correct pascal read and write methods for this type of computer?
I know this is an obscure question, so thank you in advance for any help you can give me!
The Details:
The current test code that creates a file correctly is this:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
end.
This is some test code that does not work, the method's append() and close() could not be found:
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
append(outFile);
writeln('this should be in the file');
close(outFile);
end.
This is an alternative that also did not work, the writeln() method only ever prints to the terminal. But otherwise this does compile.
program testingFiles;
uses Crt, Win;
const FILE_NAME = 'testFile.txt';
var outFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
writeln('this should be in the file');
close(outFile);
end.
The system: As was previously mentioned, this is a 1989 IBM PS2.
It has Windows 3.0 installed and can also run DOS and MS-DOS terminals.
It has Microsoft SMARTDrive Disk Cache version 3.06
It has Turbo Pascal 5.5 installed and I am using turbo as my command line pascal editor. (the readme was last updated in 1989)
It has Turbo debugger 1.5 installed.
Again, I know this is an obscure question, so thank you in advance for any help you can give me!
My Pascal memory is VERY rusty... but as other have pointed out, here is what you should consider:
program testingFiles;
uses Crt, System;
//No need of importin Win Win is for Windows enviorment, however I'm not sure if you need to use System, Sysutils or was there a Dos class???
const FILE_NAME = 'testFile.txt';
var outFile,inFile : File;
begin
writeln('creating file ...');
Assign(outFile, FILE_NAME);
rewrite(outFile);
//Now Open the first chunk of the file you want to concatenate
AssignFile(inFile, "fisrt_chunk.dat");
reset(inFile);
while not eof(inFile) do
begin
readln(inFile, s);
writeln(outFile,s);
end;
close(inFile);
end.
I don't have Turbo/Borland Pascal installed any longer so I couldn't compile it myself, no promise that it will work it is more like an idea:
Key thing to remember, readln and writeln will ALWAYS add a return at the end of the string/line, read and write on the other hand will leave the cursor wherever it is without jumping to a new line.
Here's some old Delphi code that should be at least close to syntax-compatible that will give you the gist of copying a file (with limited error checking and resource handling in case of error - I'll leave that as an exercise for you). It works to copy both binary and text content.
program Project2;
uses
SysUtils;
var
NumRead, NumWritten: LongInt;
pBuff : PChar;
SrcFile, DstFile: File;
const
BuffSize = 2048; // 2K buffer. Remember not much RAM available
InFileName = 'somefile.txt';
OutFileName = 'newfile.txt';
begin
NumRead := 0;
NumWritten := 0;
AssignFile(SrcFile, InFileName);
AssignFile(DstFile, OutFileName);
// Allocate memory for the buffer
GetMem(pBuff, BuffSize);
FileMode := 0; // Make input read-only
Reset( SrcFile, 1 );
FileMode := 2; // Output file read/write
Rewrite( DstFile, 1 );
repeat
// Read a buffer full from input
BlockRead(SrcFile, pBuff^, BuffSize, NumRead);
// Write it to output
BlockWrite(DstFile, pBuff^, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
// Cleanup stuff. Should be protected in a try..finally,
// of course.
CloseFile(SrcFile);
CloseFile(DstFile);
FreeMem(pBuff);
end.
The above code compiles under Delphi 2007 currently (the oldest version I have installed). (See the note below.)
As a side note, this was from an archived version of some code I had that compiled both for 16-bit Delphi 1 and was extended to also compile under 32-bit Delphi 2 back in the mid-to-late 90s. It's still hanging around in my source repositories in an old tagged branch. I think I need to do some pruning. :-) I cleaned it up to remove some other functionality and removed a lot of {$IFDEF WIN32} ... {$ELSE} ... {$ENDIF} stuff before posting.)

How to set folder display name with e.g. "SHGetSetFolderCustomSettings()"?

Looks like SHGetSetFolderCustomSettings allows you to set an icon, a tooltip, a web view template and stuff, but I could not find how to set the LocalizedResourceName in the associated desktop.ini (see SHFOLDERCUSTOMSETTINGS structure).
Therefore I am currently writing to desktop.ini directly, however this comes with a caveat:
Explorer does not properly update its views even when you tell it to refresh with F5 or Ctrl+R.
This is what I want to write, using Python (though non-Python code should be less of an issue):
[.ShellClassInfo]
LocalizedResourceName=My Folder Name
InfoTip=A customized folder
Any ideas how to set the folder name and have Explorer properly update it ?
I have tried with SHChangeNotify(SHCNE_ALLEVENTS, SHCNF_PATH, path, path), but this does not seem to update the display name (and also with SHCNE_RENAMEFOLDER, SHCNE_RENAMEITEM, SHCNE_UPDATEDIR, SHCNE_UPDATEITEM).
(The worst approach would probably be to edit the desktop.ini twice... once directly, then with that API function... rather not what I want).
About the why (I guess at least one of you will ask):
I am storing project data using GUIDs as folder names.
The user should however see a friendly name that can also be used for sorting (and maybe even be able to edit it without interfering with the internal name).
Furthermore, the low-level file system layout should be backwards-compatible with older versions of the software.
Use simple call of IShellFolder.SetNameOf:
procedure UpdateLocalizedResourceName(const ADirectory, ANewResourceName: UnicodeString);
var
Desktop: IShellFolder;
Eaten: DWORD;
DirIDList1, Child, NewChild: PItemIDList;
Attr: DWORD;
Folder: IShellFolder;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := 0;
OleCheck(Desktop.ParseDisplayName(0, nil, PWideChar(ADirectory), Eaten, DirIDList1, Attr));
try
OleCheck(SHBindToParent(DirIDList1, IShellFolder, Pointer(Folder), Child));
try
OleCheck(Folder.SetNameOf(0, Child, PWideChar(ANewResourceName), SHGDN_INFOLDER, NewChild));
CoTaskMemFree(NewChild);
finally
Folder := nil;
end;
finally
CoTaskMemFree(DirIDList1);
end;
finally
Desktop := nil;
end;
end;
UPDATE
Important notice! LocalizedResourceName parameter must exists in desktop.ini before you call UpdateLocalizedResourceName. Otherwise SetNameOf function fails.

how to internationalize a delphi application [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Translate application
What is the best way to internationalize my application written in delphi xe2 ?
I've seen the stringtable resource but I'm worried because I've got the feeling that the implementation could be time consuming and laborious.
Are there other equally valid methods to do this?
Maybe not the best tool for translations, but I'm using GNU Gettext for many years.
The process is quite simple:
You run dxgettext to extract strings
You translate or give for translation the files
I personally love poEdit tool to translate and manage translation repository
Optional : You merge your translation files into the final EXE
OR you put the translation files in subdirectories and that's it !
http://dxgettext.po.dk/
Update:
1/ GNU Gettext is included in JCL/JVCL library, you just need to activate this option at startup.
2/ Gnu Gettext can translate everything in the library, as VCL, JCL/JVCL also ! It's not just limited to your code !
One option is to use the Integrated Translation Environment in Delphi:
http://docwiki.embarcadero.com/RADStudio/XE3/en/Localizing_Applications_by_Using_Translation_Manager_Overview
Here you can find two articles about this theme:
Multilingua application with GNU gettext
Multilingua application with standard method (IDE)
You can find other methods and commencial components (i have used TsiLang components -excellent library-)
A Greeting.
I don't know is this the best way of internationalize an application, but for me it worked. It's a kind of home made.
I created an xml file, which is the dictionary containing the translations, but you can use any other format, from json, to xls (maybe this is the best). Than implemented a class which read the translations from this file, and a way to register procedures in case of the language is changed runtime, which is I think a good feature.
TDictionary = class
private
fOnChanged: TSignal;
fLanguage: String;
procedure setLanguage( const Value: String );
public
procedure loadFromFile( filename: string );
function getTranslation( id: string; lang: string = '' ): string;
property language: String read fLanguage write setLanguage;
property onChanged: TSignal read fonChanged;
end;
...
function TDictionary.getTranslation( id: string; lang: string = '' ): string;
begin
if lang = '' then
lang := Language; // use the default lang.
// read the translation from the file.
end;
procedure TDictionary.setLanguage( const Value: String );
begin
fLanguage := Value;
onChanged.Execute;
end;
TSignal is a class which register methods, and if you call Execute executes all the registered methods, Maybe in xe2 you have something built in for this, in delphi7 I had to create this class myself, but it's fun to implement.
in a form's createForm:
procedure TMyClass.doTranslate( dictionary: TObject );
begin
with dictionary as TDictionary do
begin
caption := dictionary.getTranslation( 'myclass.caption' );
button.caption := dictionary.getTranslation( 'some id' );
end;
// or you can go through Controls array, and automate the assignment.
end;
procedure TMyClass.FormCreate(Sender: TObject);
begin
Dictionary.onChanged.register( doTranslate );
doTranslate( dictionary );
end;
procedure TMyClass.FormDestroy(Sender: TObject);
begin
Dictionary.onChanged.deregister( doTranslate );
end;
As you can see, this is not a working example what you can copy and paste, I just wanted to show you the idea behind. if something is not clear, comment it, and I can extend my answer.
Some notes:
I think it's important to have the translations in utf8 format.
using xls makes the localizers live easier, and your too, if they ruin your xml file (If the translator is not prof., It can happen that you get back your xml file in microsoft word format)
you can put your dictionary file into resource, and load from there.
Pros
This way you can change the language runtime
if you need another language, you just need to edit the dictionary file.
Cons
If you have many forms, it's a nightmare to connect all the labels, buttons, etc, but you can automate it in smart ways.
It slows down your app a little, but not much, if changing your app language is not happening so often.
There is a product called sisulizer, it works after you have build the executable fies I think. Haven't tried it but I've read a lot about it.
Have a look at this

Do Windows shortcuts support very long argument lengths?

I am trying to create a shortcut (on the Desktop) that contains a long argument string (> MAX_PATH).
The MSDN documentation clearly states that for Unicode string the string can be longer than MAX_PATH.
The resulting shortcut is cut exactly after MAX_PATH characters (that is the Path + the Arguments).
Is there something wrong with my implementation or is this some Windows limitation?
procedure CreateShortcut(APath: WideString;
AWorkingDirectory: WideString; AArguments: WideString; ADescription: WideString;
ALinkFileName: WideString);
var
IObject : IUnknown;
ISLink : IShellLinkW;
IPFile : IPersistFile;
begin
IObject := CreateComObject(CLSID_ShellLink);
ISLink := IObject as IShellLinkW;
ISLink.SetPath( PWideChar(APath));
ISLink.SetWorkingDirectory(PWideChar(AWorkingDirectory));
ISLink.SetArguments( PWideChar(AArguments));
ISLink.SetDescription( PWideChar(ADescription));
IPFile := IObject as IPersistFile;
IPFile.Save(PWideChar(ALinkFileName), False);
end;
PS: OS is Windows XP (and above).
It turns out that this issue is in fact solely a limitation in the Explorer shell dialog. The generated shortcut file does not have a 260 character limitation. It's simply that the dialog refuse to display a Target with more characters than that. Presumably it calls GetPath with a fixed length buffer.
procedure TForm11.Button1Click(Sender: TObject);
var
sl: IShellLinkW;
pf: IPersistFile;
begin
CoCreateInstance(CLSID_ShellLink, nil,
CLSCTX_INPROC_SERVER, IID_IShellLinkW, sl);
sl.SetPath('c:\desktop\test.bat');
sl.SetWorkingDirectory('c:\desktop\');
sl.SetArguments(PChar(StringOfChar('x', 300)+'_the_end'));
pf := sl as IPersistFile;
pf.Save('c:\desktop\test.lnk', False);
end;
My test.bat looks like this:
echo %1> test.out
The resulting test.out goes right the way to _the_end!
Thanks all who contributed to this thread - it helped me immensely.
However, if I may, I would like to add the below information I discovered in crafting my solution:
On Windows 7 Enterprise ~SP1, it would seem that using VBS to create the shortcut there is still a limit on maximum characters in (at least) the arguments field. I tested up to 1023 chars before it got trunicated. I presume the same limit would apply to the Delphi method likewise.
On Windows XP Professional ~SP3, while the VBS method will create a shortcut longer than 260 characters (lnk file contains the data), it seems to trunicate it at about this number when executing it.

Resources