home *** CD-ROM | disk | FTP | other *** search
/ PC Open 19 / pcopen19.iso / Zipped / CALMIR21.ZIP / SOURCE.ZIP / SRC / FILEMAN.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-02-20  |  20.4 KB  |  687 lines

  1. {**************************************************************************}
  2. {                                                                          }
  3. {    Calmira shell for Microsoft« Windows(TM) 3.1                          }
  4. {    Source Release 2.1                                                    }
  5. {    Copyright (C) 1997-1998 Li-Hsin Huang                                 }
  6. {                                                                          }
  7. {    This program is free software; you can redistribute it and/or modify  }
  8. {    it under the terms of the GNU General Public License as published by  }
  9. {    the Free Software Foundation; either version 2 of the License, or     }
  10. {    (at your option) any later version.                                   }
  11. {                                                                          }
  12. {    This program is distributed in the hope that it will be useful,       }
  13. {    but WITHOUT ANY WARRANTY; without even the implied warranty of        }
  14. {    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         }
  15. {    GNU General Public License for more details.                          }
  16. {                                                                          }
  17. {    You should have received a copy of the GNU General Public License     }
  18. {    along with this program; if not, write to the Free Software           }
  19. {    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             }
  20. {                                                                          }
  21. {**************************************************************************}
  22.  
  23. unit FileMan;
  24.  
  25. { FileMan contains the main file management engine used for processing
  26.   files and directories.  It provides high-level operations which can
  27.   be used easily from other units, while allowing full user interaction
  28.   and error handling.
  29.  
  30.   Application.ProcessMessages is called frequently so that the progress
  31.   bar can be updated and the user can press the Cancel button.
  32.  
  33.   Almost all of Calmira's filename strings are stored and processed
  34.   as lower case to be consistent, especially useful because there is no
  35.   case insensitive Pos() function.  Also, all filenames should be fully
  36.   qualified to avoid ambiguities.
  37. }
  38.  
  39. interface
  40.  
  41. uses Classes, SysUtils, Forms, Dialogs, WinTypes;
  42.  
  43. type
  44.   EFileOpError = class(Exception);
  45.  
  46. const
  47.   faProtected = faReadOnly or faHidden or faSysFile;
  48.   faFileDir   = faAnyFile and not faVolumeID;
  49.  
  50. function CopyFile(const Filename, Destname : TFilename): Boolean;
  51. function MoveFile(const Filename, Destname : TFilename; attr: Integer): Boolean;
  52. function CopyDirectory(const Dirname, Destname : TFilename): Boolean;
  53. function MoveDirectory(const Dirname, Destname : TFilename): Boolean;
  54. function DeleteDirectory(const Dirname: TFilename): Boolean;
  55. function EraseFile(const Filename: string; attr: Integer): Boolean;
  56. procedure CreateDirectory(const Dirname: TFilename);
  57. procedure RemoveDirectory(const Dirname : TFilename);
  58. procedure ProcessFiles(files: TStrings; const dest: TFilename);
  59. procedure ExitDirectory(const Dirname : TFilename);
  60.  
  61. function CurrentDirectory : TFilename;
  62.  
  63. function DefaultExec(FileName, Params, DefaultDir: string;
  64.   ShowCmd: Word): Integer;
  65. { Encapsulates ShellExecute.  If a filename with no associated
  66.   program is encountered, the default viewer is used to open the file.  Also,
  67.   DOS environment strings are inserted into each string before they are
  68.   passed to Windows }
  69.  
  70. procedure OpenFileWith(const filename: TFilename; command : string);
  71.  
  72. function ExtensionIn(const ext : TFileExt; const list: string): Boolean;
  73. { Searches a string containing file extensions separated by
  74.   spaces.  It is case sensitive. }
  75.  
  76. function ConfirmFolder(const s: string): TModalResult;
  77.  
  78. function IsPrintManager(Wnd : HWND): Boolean;
  79.  
  80. procedure PrintFile(const filename : TFilename);
  81.  
  82. procedure YesToAll;
  83. procedure NoToAll;
  84. procedure BackgroundProcess;
  85.  
  86. { ExtensionIn searches a string containing file extensions separated by
  87.   spaces.  It is case sensitive. }
  88.  
  89. var BytesTransferred : Longint;
  90.  
  91.  
  92.  
  93. implementation
  94.  
  95. uses Replace, Controls, FileCtrl, Progress, WinProcs, Settings, 
  96.  Desk, AskDrop, Files, Strings, MiscUtil, Drives, Environs,
  97.  CompSys, Internet, FourDOS, Locale;
  98.  
  99. var
  100.   CopyAllFiles  : Boolean;
  101.   MoveAllFiles  : Boolean;
  102.   DelAllFiles   : Boolean;
  103.   RepAllFiles   : Boolean;
  104.   MoveAllProt   : Boolean;
  105.   DelAllProt    : Boolean;
  106.   CopyAllFolders: Boolean;
  107.   MoveAllFolders: Boolean;
  108.   DelAllFolders : Boolean;
  109.  
  110.  
  111. procedure NoToAll;
  112. begin
  113.   CopyAllFiles  := False;
  114.   MoveAllFiles  := False;
  115.   DelAllFiles   := False;
  116.   RepAllFiles   := False;
  117.   MoveAllProt   := False;
  118.   DelAllProt    := False;
  119.   CopyAllFolders:= False;
  120.   MoveAllFolders:= False;
  121.   DelAllFolders := False;
  122. end;
  123.  
  124. procedure YesToAll;
  125. begin
  126.   CopyAllFiles  := True;
  127.   MoveAllFiles  := True;
  128.   DelAllFiles   := True;
  129.   RepAllFiles   := True;
  130.   MoveAllProt   := True;
  131.   DelAllProt    := True;
  132.   CopyAllFolders:= True;
  133.   MoveAllFolders:= True;
  134.   DelAllFolders := True;
  135. end;
  136.  
  137.  
  138. function CanReplace(Filename, Destname: TFilename): Boolean;
  139. begin
  140.   { Returns True if the user specifies that the destination file
  141.     (which must exist) can be replaced. }
  142.  
  143.   if ReplaceBox = nil then ReplaceBox := TReplaceBox.Create(Application);
  144.  
  145.   case ReplaceBox.Query(Filename, Destname) of
  146.     mrYes   : Result := True;
  147.     mrNo    : begin ProgressBox.UpdateGauge; Result := False; end;
  148.     mrAll   : begin Result := True; RepAllFiles := True; end;
  149.     mrCancel: Abort;
  150.   end;
  151. end;
  152.  
  153.  
  154.  
  155. function ProtectBox(const name, op: string): Word;
  156. begin
  157.   { Asks the user for confirmation before deleting or moving
  158.     a protected file }
  159.  
  160.   Desktop.SetCursor(crDefault);
  161.   try
  162.     Result := MsgDialogResFmt(SQueryAffectProtected, [name, op],
  163.        mtConfirmation, mbYesNoCancel + [mbAll], 0);
  164.   finally
  165.     Desktop.ReleaseCursor;
  166.   end;
  167. end;
  168.  
  169.  
  170. function ConfirmSingleOperation(Ask: Boolean; var All: Boolean;
  171.   const prompt, filename: string): Boolean;
  172. begin
  173.   Result := True;
  174.   if Ask and not All then begin
  175.     Desktop.SetCursor(crDefault);
  176.     try
  177.       case MsgDialog(Format('%s %s ?', [prompt, filename]),
  178.        mtConfirmation, [mbYes, mbNo, mbAll, mbCancel], 0) of
  179.         mrNo    : Result := False;
  180.         mrAll   : All := True;
  181.         mrCancel: Abort;
  182.       end;
  183.     finally
  184.       Desktop.ReleaseCursor;
  185.     end;
  186.   end;
  187. end;
  188.  
  189.  
  190. function CopyFile(const Filename, Destname : TFilename): Boolean;
  191. begin
  192.   Result := False;
  193.   ProgressBox.CheckForAbort;
  194.   ProgressBox.Updatelabel(Filename, Destname);
  195.  
  196.   if not ConfirmSingleOperation(ConfirmCopyFile, CopyAllFiles,
  197.     LoadStr(SCopyFile), Filename) then Exit;
  198.  
  199.   if Filename = Destname then
  200.     raise EFileOpError.CreateResFmt(SCannotCopyFileToSelf, [Filename]);
  201.  
  202.   if ConfirmReplace and not RepAllFiles and FFileExists(Destname)
  203.     and not CanReplace(Filename, Destname) then Exit;
  204.  
  205.   Application.ProcessMessages;
  206.   try
  207.     FCopyFile(Filename, Destname); { low-level copy in Files.pas }
  208.     ProgressBox.UpdateGauge;
  209.   except on EWriteAccessDenied do
  210.     if FileSetAttr(Destname, 0) < 0 then { try removing protection bits }
  211.       raise
  212.     else begin
  213.       FCopyFile(Filename, Destname);     { attempt the copy again }
  214.       ProgressBox.UpdateGauge;
  215.     end;
  216.   end;
  217.   Result := True;
  218. end;
  219.  
  220.  
  221. procedure CreateDirectory(const Dirname: TFilename);
  222. begin
  223.   try
  224.     MkDir(Dirname);
  225.   except on EInOutError do
  226.     raise EFileOpError.CreateResFmt(SCannotCreateFolder, [Dirname])
  227.   end;
  228. end;
  229.  
  230.  
  231. procedure CreateDirectoryMerge(const Dirname: TFilename);
  232. begin
  233.   { Similar to CreateDirectory, but used when copying or moving
  234.     whole directory structures.  If the destination already exists,
  235.     then the contents will be merged, in which case any window showing
  236.     the destination must be refreshed afterwards }
  237.  
  238.   if not FDirectoryExists(Dirname) then CreateDirectory(Dirname)
  239.   else Desktop.RefreshList.Add(Dirname);
  240. end;
  241.  
  242. function CurrentDirectory : TFilename;
  243. begin
  244.   GetDir(0, Result);
  245.   Result := Lowercase(Result);
  246. end;
  247.  
  248.  
  249. procedure ExitDirectory(const Dirname : TFilename);
  250. const
  251.   NewDir : string[3] = 'c:\';
  252. var
  253.   current : TFilename;
  254. begin
  255.   { If the current logged directory is somewhere inside Dirname,
  256.     the directory is changed to the Windows directory.  This is required
  257.     because directories cannot be deleted or renamed while they are logged }
  258.  
  259.   GetDir(DriveNumber(Dirname[1]), current);
  260.   current := Lowercase(current);
  261.   if (current = Dirname) or IsAncestorDir(Dirname, current) then begin
  262.     NewDir[1] := Dirname[1];
  263.     ChDir(NewDir);
  264.     ChDir(MakeDirname(WinPath));
  265.   end;
  266. end;
  267.  
  268.  
  269. procedure RemoveDirectory(const Dirname : TFilename);
  270. begin
  271.   { EInOutError is thrown away because the user may choose not to
  272.     delete a specific file during a directory-delete, in which case
  273.     the parent dir can't be removed.  We want to prevent the entire
  274.     operation from being aborted due to this. }
  275.  
  276.   try
  277.     ExitDirectory(Dirname);
  278.     RmDir(Dirname);
  279.   except
  280.     on EInOutError do;
  281.   end;
  282. end;
  283.  
  284.  
  285. function MoveFile(const Filename, Destname : TFilename; attr: Integer): Boolean;
  286. begin
  287.   Result := False;
  288.   ProgressBox.CheckForAbort;
  289.   ProgressBox.UpdateLabel(Filename, Destname);
  290.  
  291.   if not ConfirmSingleOperation(ConfirmMoveFile, MoveAllFiles,
  292.     LoadStr(SMoveFile), Filename) then Exit;
  293.  
  294.   if Filename = Destname then
  295.     raise EFileOpError.CreateResFmt(SCannotMoveFileToSelf, [Filename]);
  296.  
  297.   if attr < 0 then attr := FileGetAttr(Filename);
  298.  
  299.   { Check for read-only, hidden or system file }
  300.  
  301.   if (attr and faProtected > 0) and ConfirmProtect and not MoveAllProt then
  302.     case ProtectBox(Filename, LoadStr(SMove)) of
  303.       mrNo    : begin ProgressBox.UpdateGauge; exit; end;
  304.       mrCancel: Abort;
  305.       mrAll   : MoveAllProt := True;
  306.     end;
  307.  
  308.   { If destination already exists, ask before replacing it.  If the
  309.     user says "yes", try deleting it so that the move can be performed
  310.     by a rename operation.  If the first delete fails, reset the attributes
  311.     and try again }
  312.  
  313.   if FFileExists(Destname) then
  314.     if not ConfirmReplace or RepAllFiles or CanReplace(Filename, Destname) then begin
  315.       if not DeleteFile(Destname) then
  316.         if (FileSetAttr(Destname, 0) < 0) or not DeleteFile(Destname) then
  317.           raise EFileOpError.CreateResFmt(SCannotReplaceFile, [Destname])
  318.     end
  319.     else Exit;
  320.  
  321.   Application.ProcessMessages;
  322.  
  323.   { Files on the same drive are moved using a rename.  Those on
  324.     different drives are copied, and the original is deleted afterwards. }
  325.  
  326.   if (UpCase(Filename[1]) <> UpCase(Destname[1])) or
  327.     not RenameFile(Filename, Destname) then begin
  328.  
  329.     FCopyFile(Filename, Destname);
  330.  
  331.     { first make sure the file can have read-write access }
  332.     if (attr and faReadOnly > 0) and (FileSetAttr(Filename, 0) < 0) then
  333.       raise EFileOpError.CreateResFmt(SCannotMoveFile, [Filename]);
  334.  
  335.     if not DeleteFile(Filename) then
  336.       raise EFileOpError.CreateResFmt(SCannotMoveFile, [Filename]);
  337.   end;
  338.  
  339.   ProgressBox.UpdateGauge;
  340.   Result := True;
  341. end;
  342.  
  343.  
  344.  
  345. function CopyDirectory(const Dirname, Destname : TFilename):Boolean;
  346. var
  347.   source, target: TFileName;
  348.   code : Integer;
  349.   rec : TSearchRec;
  350. begin
  351.   { CopyDirectory recursively scans a directory structure and recreates
  352.     the contents elsewhere.  Both CreateDirectoryMerge and CopyFile will
  353.     raise exceptions on error, which terminates this procedure.
  354.  
  355.     We must check that Destname is not the same as, or a subdirectory of
  356.     Dirname, otherwise you will cause an infinite recursion, which XCOPY
  357.     calls a cyclic copy :
  358.  
  359.     e.g. CopyDirectory('c:\windows', 'c:\windows\temp') }
  360.  
  361.   Result:= False;
  362.   ProgressBox.CheckForAbort;
  363.  
  364.   if (Dirname = Destname) or IsAncestorDir(Dirname, Destname) then
  365.     raise EFileOpError.CreateRes(SCannotCyclicCopy);
  366.  
  367.   if not ConfirmSingleOperation(ConfirmCopyFolder, CopyAllFolders,
  368.     LoadStr(SCopyFolder), Dirname) then Exit;
  369.  
  370.   CreateDirectoryMerge(Destname);
  371.  
  372.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  373.   while code = 0 do begin
  374.     if rec.name[1] <> '.' then begin
  375.       rec.name := Lowercase(rec.name);
  376.       source := Dirname + '\' + rec.name;
  377.       target := Destname + '\' + rec.name;
  378.  
  379.       if UseDescriptions and (rec.name = DescriptionFile) and FFileExists(target) then
  380.         MergeDescriptionFiles(Destname, Dirname)
  381.       else begin
  382.         if rec.attr and faDirectory <> 0 then
  383.           Result := CopyDirectory(source, target)
  384.         else begin
  385.           CopyFile(source, target);
  386.           Inc(BytesTransferred, rec.size);
  387.         end;
  388.       end;
  389.     end;
  390.     code := FindNext(rec);
  391.   end;
  392.   Result := True;
  393. end;
  394.  
  395.  
  396. function MoveDirectory(const Dirname, Destname : TFilename): Boolean;
  397. var
  398.   source, target: TFilename;
  399.   code : Integer;
  400.   rec : TSearchRec;
  401. begin
  402.   { The structure of this is very similar to CopyDirectory, and the
  403.     same rules about cyclic copying applies }
  404.  
  405.   Result := False;
  406.   ProgressBox.CheckForAbort;
  407.  
  408.   if (Dirname = Destname) or IsAncestorDir(Dirname, Destname) then
  409.     raise EFileOpError.CreateRes(SCannotCyclicMove);
  410.  
  411.   if not ConfirmSingleOperation(ConfirmMoveFolder, MoveAllFolders,
  412.     LoadStr(SMoveFolder), Dirname) then Exit;
  413.  
  414.   CreateDirectoryMerge(Destname);
  415.  
  416.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  417.   while code = 0 do begin
  418.     if rec.name[1] <> '.' then begin
  419.       rec.name := Lowercase(rec.name);
  420.       source := Dirname + '\' + rec.name;
  421.       target := Destname + '\' + rec.name;
  422.  
  423.       if UseDescriptions and (rec.name = DescriptionFile) then begin
  424.         MergeDescriptionFiles(Destname, Dirname);
  425.         FileSetAttr(source, 0);
  426.         DeleteFile(source);
  427.       end
  428.       else begin
  429.         if rec.attr and faDirectory <> 0 then
  430.           Result := MoveDirectory(source, target)
  431.         else begin
  432.           Result := MoveFile(source, target, rec.attr);
  433.           Inc(BytesTransferred, rec.size);
  434.         end;
  435.       end;
  436.     end;
  437.     code := FindNext(rec);
  438.   end;
  439.  
  440.   RemoveDirectory(Dirname);
  441.   Result := True;
  442. end;
  443.  
  444.  
  445. function DeleteDirectory(const Dirname: TFilename): Boolean;
  446. var
  447.   target: TFilename;
  448.   code  : Integer;
  449.   rec   : TSearchRec;
  450. begin
  451.   Result := False;
  452.   ProgressBox.CheckForAbort;
  453.  
  454.   if not ConfirmSingleOperation(ConfirmDelFolder, DelAllFolders,
  455.     LoadStr(SDeleteFolder), Dirname) then Exit;
  456.  
  457.   code := FindFirst(Dirname + '\*.*', faFileDir, rec);
  458.   while code = 0 do begin
  459.     if rec.name[1] <> '.' then begin
  460.       target := Dirname + '\' + Lowercase(rec.name);
  461.       if rec.attr and faDirectory <> 0 then Result := DeleteDirectory(target)
  462.       else EraseFile(target, rec.attr);
  463.     end;
  464.     code := FindNext(rec);
  465.   end;
  466.  
  467.   RemoveDirectory(Dirname);
  468.   Result := True;
  469. end;
  470.  
  471.  
  472.  
  473. function EraseFile(const Filename: string; attr: Integer): Boolean;
  474. begin
  475.   Result := False;
  476.   ProgressBox.CheckForAbort;
  477.  
  478.   if not ConfirmSingleOperation(ConfirmDelFile, DelAllFiles,
  479.     LoadStr(SDeleteFile), Filename) then Exit;
  480.  
  481.   if attr = -1 then attr := FileGetAttr(Filename);
  482.  
  483.   if attr and faProtected <> 0 then
  484.     if ConfirmProtect and not DelAllProt then
  485.       case ProtectBox(Filename, LoadStr(SDelete)) of
  486.         mrYes    : FileSetAttr(Filename, 0);
  487.         mrNo     : begin
  488.                      ProgressBox.UpdateGauge;
  489.                      Exit;
  490.                    end;
  491.         mrCancel : Abort;
  492.         mrAll    : begin
  493.                      DelAllProt := True;
  494.                      FileSetAttr(Filename, 0);
  495.                    end;
  496.       end
  497.     else FileSetAttr(Filename, 0);
  498.  
  499.   if not DeleteFile(Filename) then
  500.     raise EFileOpError.CreateResFmt(SCannotDeleteFile, [Filename]);
  501.  
  502.   ProgressBox.UpdateGauge;
  503.   Result := True;
  504. end;
  505.  
  506.  
  507. procedure ProcessFiles(files: TStrings; const dest: TFilename);
  508. var
  509.   i : Integer;
  510.   CopyDroppedFiles: Boolean;
  511.   destpath : TFilename;
  512. begin
  513.   { Mainly used to handle file drops from other programs.  A list of
  514.     filenames will be copied or moved after asking the user, and all
  515.     affected windows are refreshed.
  516.  
  517.     Note that file descriptions are NOT preserved. }
  518.  
  519.   i := 0;
  520.   while i < files.Count do
  521.     if not FileExists(files[i]) then files.Delete(i)
  522.     else inc(i);
  523.  
  524.   if files.Count = 0 then
  525.     raise EFileOpError.CreateRes(SNoFilesFound);
  526.  
  527.   destpath := MakePath(dest);
  528.  
  529.   try
  530.     AskDropBox := TAskDropBox.Create(Application);
  531.     case AskDropBox.ShowModal of
  532.       mrOK : CopyDroppedFiles := True;
  533.       mrYes: CopyDroppedFiles := False;
  534.       mrCancel: Abort;
  535.     end
  536.   finally
  537.     AskDropBox.Free;
  538.     AskDropBox := nil;
  539.   end;
  540.  
  541.   if CopyDroppedFiles then ProgressBox.Init(foCopy, files.Count)
  542.   else ProgressBox.Init(foMove, files.Count);
  543.  
  544.   try
  545.     NoToAll;
  546.     for i := 0 to files.Count-1 do begin
  547.       if CopyDroppedFiles then
  548.         CopyFile(files[i], destpath + ExtractFilename(files[i]))
  549.       else begin
  550.         MoveFile(files[i], destpath + ExtractFilename(files[i]), -1);
  551.         Desktop.RefreshList.Add(ExtractFileDir(files[i]));
  552.       end;
  553.     end;
  554.     Desktop.RefreshList.Add(dest);
  555.   finally
  556.     ProgressBox.Hide;
  557.     Desktop.RefreshNow;
  558.     PlaySound(Sounds.Values['NotifyCompletion']);
  559.   end;
  560. end;
  561.  
  562.  
  563. procedure OpenURL(const url: string);
  564. var command: string;
  565. begin
  566.   command := ini.ReadString('Internet', 'CommandLine', '');
  567.   if command > '' then begin
  568.     Environment.Values['url'] := url;
  569.     command := EnvironSubst(command);
  570.     WinExec(StringAsPChar(command), SW_SHOW);
  571.     Environment.Values['url'] := '';
  572.   end
  573.   else Computer.BrowserLink.OpenURL(url);
  574. end;
  575.  
  576. function DefaultExec(Filename, Params, DefaultDir: string;
  577.   ShowCmd: Word): Integer;
  578. var
  579.   s: string;
  580. begin
  581.   if Filename = '' then Exit;
  582.  
  583.   { Substitute environment variables }
  584.   Filename := EnvironSubst(Filename);
  585.   Params := EnvironSubst(Params);
  586.   DefaultDir := EnvironSubst(DefaultDir);
  587.  
  588.   if IsURL(filename) then begin
  589.     OpenURL(filename);
  590.     Exit;
  591.   end;
  592.  
  593.   if EnableWinScripts and
  594.     (CompareText(ExtractFileExt(Filename), WinScriptExtension) = 0) then begin
  595.     Computer.ExecuteScript(Filename, False);
  596.     Exit;
  597.   end;
  598.  
  599.   Result := ExecuteFile(Filename, Params, DefaultDir, 'Open', ShowCmd);
  600.  
  601.   { ShellExecute sometimes return error code 2 (file not found), for a
  602.     file with no extension.  Code 31 means that no associated program
  603.     exists. }
  604.  
  605.   if (Result = 31) or ((Result = 2) and FileExists(Filename)) then begin
  606.     if DefaultProg > ''  then begin
  607.       Result := ExecuteFile(EnvironSubst(DefaultProg), QualifiedFilename(Filename),
  608.         DefaultDir, 'Open', SW_SHOW);
  609.       if Result <= 32 then ErrorMsgRes(SCannotRunDefViewer)
  610.     end
  611.     else ErrorMsgRes(SFileNotAssociated);
  612.   end
  613.   else
  614.     if Result <= 32 then ErrorMsgResFmt(SCannotRunOrView, [Filename]);
  615.  
  616. end;
  617.  
  618. procedure OpenFileWith(const filename: TFilename; command : string);
  619. var
  620.   prog : TFilename;
  621. begin
  622.   prog := GetWord(command, ' ');
  623.   Environment.Values['filename'] := QualifiedFilename(filename);
  624.  
  625.   if Pos('%filename%', command) = 0 then AppendStr(command, ' %filename%');
  626.  
  627.   ShowHourGlass;
  628.   if ExecuteFile(EnvironSubst(prog), EnvironSubst(command),
  629.     ExtractFileDir(filename), 'Open', SW_SHOW) <= 32
  630.   then
  631.     ErrorMsgResFmt(SCannotOpenFileWith, [filename, prog]);
  632.  
  633.   Environment.Values['filename'] := '';
  634. end;
  635.  
  636.  
  637.  
  638. function ExtensionIn(const ext : TFileExt; const list: string): Boolean;
  639. var temp: string[5];
  640. begin
  641.   temp[0] := ext[0];
  642.   Inc(temp[0], 2);
  643.   temp[1] := ' ';
  644.   temp[2] := ext[1];
  645.   temp[3] := ext[2];
  646.   temp[4] := ext[3];
  647.   temp[Length(temp)] := ' ';
  648.   Result := Pos(temp, list) > 0;
  649. end;
  650.  
  651. function ConfirmFolder(const s: string): TModalResult;
  652. begin
  653.   Result := mrYes;
  654.   if not HDirectoryExists(s) then begin
  655.     Result := MsgDialogResFmt(SConfirmNewFolder, [s],
  656.       mtConfirmation, [mbYes, mbNo, mbCancel], 0);
  657.     if Result = mrYes then begin
  658.       ForceDirectories(s);
  659.       Desktop.Refresh(ExtractFileDir(s));
  660.     end;
  661.   end;
  662. end;
  663.  
  664.  
  665. function IsPrintManager(Wnd : HWND): Boolean;
  666. var
  667.   filename : TFilename;
  668. begin
  669.   filename[0] := Chr(GetModuleFilename(GetWindowWord(Wnd, GWW_HINSTANCE),
  670.     @filename[1], 78));
  671.   Result := CompareText(ExtractFilename(filename), 'PRINTMAN.EXE') = 0;
  672. end;
  673.  
  674. procedure PrintFile(const filename : TFilename);
  675. begin
  676.   ExecuteFile(filename, '', '', 'Print', SW_SHOW);
  677. end;
  678.  
  679. procedure BackgroundProcess;
  680. begin
  681.   Application.ProcessMessages;
  682.   Progressbox.CheckForAbort;
  683. end;
  684.  
  685.  
  686. end.
  687.