home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 April A / Pcwk4a98.iso / PROGRAM / DELPHI16 / Calmira / Src / SRC / DIRECTRY.PAS < prev    next >
Pascal/Delphi Source File  |  1997-02-15  |  38KB  |  1,287 lines

  1. {**************************************************************************}
  2. {                                                                          }
  3. {    Calmira shell for Microsoft« Windows(TM) 3.1                          }
  4. {    Source Release 1.0                                                    }
  5. {    Copyright (C) 1997  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 Directry;
  24.  
  25. { This unit provides the main file management objects: TDirectory,
  26.   TDirItem, TFileItem, TFile and TFolder. }
  27.  
  28. interface
  29.  
  30. uses Classes, Graphics, SysUtils, Iconic, Shorts, Dialogs, Referenc,
  31.   Settings, ObjList, WinTypes, FourDOS;
  32.  
  33. type
  34.   TDirectory = class;
  35.   TDirItem   = class;
  36.   TFileItem  = class;
  37.   TFile      = class;
  38.   TFolder    = class;
  39.  
  40.   TFileBody    = TSmallStr;
  41.   { the 8 character name }
  42.  
  43.   TFileRelease = (frNone, frRemove, frFree);
  44.   { indicates if an item should be left alone, deleted from the
  45.     list or destroyed }
  46.  
  47.   ERenameError = class(Exception);
  48.   EAttribError = class(Exception);
  49.   EScanError = class(Exception);
  50.  
  51.   { TDirectory is a list of file and folder objects, that encapsulates
  52.     a DOS directory listing.
  53.  
  54.     Properties
  55.       Path - the full pathname with trailing backslash
  56.       Fullname - the full name without trailing backslash
  57.       Size - the number of bytes of disk space used by its contents
  58.       SortOrder - the way in which the contents are sorted
  59.       Filter - the file specification passed to FindFirst()
  60.       Mask - the Attr field passed to FindFirst()
  61.       Changed - True if the contents have changed since last update
  62.       Desc - string list containing file descriptions
  63.  
  64.     Events
  65.       OnUpdate - occurs when a file operation has been completed, to
  66.         notify the owning window to modify its controls and display
  67.  
  68.     Methods
  69.       Create - allocates and initializes a new object, and calls
  70.         Scan() to read in the contents of the directory it represents
  71.       Destroy - frees the contents as well as the directory object
  72.       Add - adds a TDirItem to the list
  73.       Remove - deletes a TDirItem from the list
  74.       Sort - sorts the contents depending on the SortOrder property
  75.       Update - writes file descriptions to disk, triggers the OnUpdate
  76.         event and sets the Changed property to False
  77.       Find - searches for the index of a given filename, and returns
  78.         true if found
  79.       AddItem - given a TSearchRec, constructs a suitable object to
  80.         represent the file or folder and adds it to the list
  81.       Flush - removes or frees file items with a flag that is frRemove
  82.         or frFree, and calls Update if required
  83.       CreateFolder - creates a subdirectory and adds a new TFolder
  84.         object to itself
  85. }
  86.  
  87.   TDirectory = class(TObjectList)
  88.   private
  89.     FPath : TFileName;
  90.     FSortOrder : TSortOrder;
  91.     FFilter  : TFileBody;
  92.     FMask: Integer;
  93.     FOnUpdate : TNotifyEvent;
  94.     FChanged : Boolean;
  95.     FDesc : TDescriptions;
  96.     function GetSize : Longint;
  97.     function GetFullName : TFileName;
  98.   protected
  99.     function ItemIndex(Item: TDirItem): Integer;
  100.   public
  101.     constructor Create(const APath: TFilename);
  102.     destructor Destroy; override;
  103.     function Add(Item: TDirItem): Integer;
  104.     function Remove(Item: TDirItem): Integer;
  105.     function Find(const s:string; var Index: Integer): Boolean;
  106.     procedure AddItem(const rec : TSearchrec);
  107.     procedure CreateFolder(const foldername : TFilename);
  108.     procedure Scan;
  109.     procedure Sort;
  110.     procedure Update;
  111.     procedure Flush;
  112.     property Path : TFileName read FPath write FPath;
  113.     property Fullname : TFileName read GetFullname;
  114.     property Size : Longint read GetSize;
  115.     property SortOrder : TSortOrder read FSortOrder write FSortOrder;
  116.     property Filter : TFileBody read FFilter write FFilter;
  117.     property Mask : Integer read FMask write FMask;
  118.     property OnUpdate : TNotifyEvent read FOnUpdate write FOnUpdate;
  119.     property Changed: Boolean read FChanged write FChanged;
  120.     property Desc : TDescriptions read FDesc;
  121.   end;
  122.  
  123.  
  124.   { TDirItem is a versatile abstract object that gives a lot of functionality
  125.     to its descendants.  It encapsulates a single item in a directory listing,
  126.     such as a file or folder, and handles many functions common to both.
  127.  
  128.     Properties
  129.       Dir - a pointer to the owning directory object
  130.       Filename - the 8.3 character MS-DOS filename
  131.       Extension - optional 3 character extension
  132.       Attr - MS-DOS file attributes consisting of faXXX constants
  133.       TimeStamp - the DOS date/time stamp converted to a TDateTime format
  134.       Size - size in bytes
  135.       Fullname - full pathname (e.g. c:\abc\def\123.txt)
  136.       Release - determines whether this item should be removed from the
  137.         directory or destroyed when the directory is next updated
  138.       Hint - the popup hint string, which depends on the current
  139.         user preferences
  140.       HasDesc - True if the item has a file description
  141.  
  142.     Methods
  143.       Create - initializes a new item with details obtained from DOS
  144.       SetFilename (protected) - dangerous, this one!  Turns Filename into
  145.         a 'virtual' property.
  146.       GetSearchRec - returns a TSearchRec containing the item's DOS details.
  147.       Draw - paints the item's icon and caption onto a canvas
  148.       DrawAsText - draws a row of a directory listing
  149.       GetTitle - returns the DOS filenme or file description, depending
  150.         on the current user settings and presence of description
  151.       GetStartInfo - returns a string structure suitable for adding into
  152.         the start menu
  153.       AssignRef - modifies the fields of a TReference object so that it
  154.         points to the TDirItem.  Used for making shortcuts and aliases.
  155.       EditDescription - prompts the user for a new file description and
  156.         returns true if the operation is successful
  157.       AcceptsDrops - returns True if the user can drag and drop other
  158.         objects into this one
  159.       DragDrop - called when something has been dropped into the object
  160.       LessThan - returns True if this item should be listed before the
  161.         item passed as parameter.  User in sorting/searching.
  162.  
  163.       File management methods - at present, these make sure that file
  164.       descriptions are kept updated, but could be extended to provide other
  165.       housekeeping code.
  166.  
  167.       TDirItem's methods are usually overriden and the inherited method
  168.       called immediately after a successful disk operation -- the parent
  169.       and target TDirectory objects must not be changed before calling
  170.       these methods.
  171.  
  172.       Delete - deletes an object
  173.       CopyToDirectory - copies an object to another TDirectory
  174.       CopyToPath - copies the object to a disk directory with no
  175.         corresponding TDirectory object
  176.       MoveToDirectory - moves an object to another TDirectory
  177.       MoveToPath - moves the object to a disk directory with no
  178.         corresponding TDirectory object
  179.       MoveAndRename - similar to MoveToPath, but also changes the filename.
  180.         Used to put things in the bin.
  181.   }
  182.  
  183.   TDirItem = class(TIconic)
  184.   private
  185.     FAttr : Integer;
  186.     FTimeStamp : TDateTime;
  187.     FSize : Longint;
  188.     FDir  : TDirectory;
  189.     FRelease : TFileRelease;
  190.     FHasDesc : Boolean;
  191.     function GetHint : string;
  192.     function GetFullName : TFilename;
  193.     function GetExtension : TFileExt;
  194.     procedure SetFileAttr(attrib : Integer);
  195.     function GetDescription : string;
  196.     procedure PutDescription(const value: string);
  197.   protected
  198.     procedure SetFileName(const AName: TFileBody); virtual;
  199.   public
  200.     constructor Create(const details : TSearchRec; ADir : TDirectory);
  201.     procedure Draw(Canvas: TCanvas; const Rect: TRect); override;
  202.     procedure DrawAsText(Canvas: TCanvas; const Rect: TRect); virtual;
  203.     procedure Delete; virtual;
  204.     procedure CopyToDirectory(d : TDirectory); virtual;
  205.     procedure CopyToPath(const p : TFilename); virtual;
  206.     procedure MoveToDirectory(d : TDirectory); virtual;
  207.     procedure MoveToPath(const p : TFilename); virtual;
  208.     procedure MoveAndRename(const NewName : TFilename); virtual;
  209.     function EditDescription: Boolean;
  210.     function LessThan(f : TDirItem): Boolean; virtual;
  211.     function AcceptsDrops : Boolean; virtual; abstract;
  212.     procedure AssignRef(ref: TReference); override;
  213.     procedure DragDrop(Source: TObject); virtual; abstract;
  214.     function GetTitle: string; virtual;
  215.     function GetStartInfo : string; virtual; abstract;
  216.     function GetSearchRec: TSearchRec;
  217.     property Filename : TFileBody read FName write SetFileName;
  218.     property Attr : Integer read FAttr write SetFileAttr;
  219.     property TimeStamp : TDateTime read FTimeStamp;
  220.     property Size : Longint read FSize;
  221.     property FullName : TFilename read GetFullName;
  222.     property Extension: TFileExt read GetExtension;
  223.     property Dir : TDirectory read FDir;
  224.     property Release : TFileRelease read FRelease write FRelease;
  225.     property Hint : string read GetHint;
  226.     property Description: string read GetDescription write PutDescription;
  227.   end;
  228.  
  229.  
  230.   { TFileItem is an abstract base class that encapsulates a single file.
  231.     As well as overriding many of TDirItem's methods so that they manage
  232.     files, new methods are introduced that work only on files.
  233.     This abstract class is provided so that descendants such as TAlias
  234.     can represent different kinds of file, but still have basic file
  235.     operations carried out on them. }
  236.  
  237.   TFileItem = class(TDirItem)
  238.   protected
  239.     FIsProgram : Boolean;
  240.   public
  241.     procedure DrawAsText(Canvas: TCanvas; const Rect: TRect); override;
  242.     procedure Open; override;
  243.     procedure Delete; override;
  244.     procedure CopyToDirectory(d : TDirectory); override;
  245.     procedure CopyToPath(const p : TFilename); override;
  246.     procedure MoveToDirectory(d : TDirectory); override;
  247.     procedure MoveToPath(const p : TFilename); override;
  248.     procedure MoveAndRename(const NewName : TFilename); override;
  249.     procedure OpenWith(const progname : TFilename); virtual;
  250.     procedure Duplicate(const AName: TFilename); virtual;
  251.     function LessThan(f : TDirItem): Boolean; override;
  252.   end;
  253.  
  254.   { TFile is the usual class that is instantiated to represent a
  255.     disk file.  It keeps track of whether it extracted an icon
  256.     to display itself, and if so, the icon is freed along with the
  257.     object }
  258.  
  259.   TFile = class(TFileItem)
  260.   private
  261.     FOwnIcon : Boolean;
  262.   protected
  263.     procedure AssignIcon; virtual;
  264.     procedure FreeIcon; virtual;
  265.     procedure SetFilename(const AName: TFileBody); override;
  266.     property OwnIcon : Boolean read FOwnIcon write FOwnIcon;
  267.   public
  268.     constructor Create(const details : TSearchRec; ADir : TDirectory);
  269.     destructor Destroy; override;
  270.     function AcceptsDrops : Boolean; override;
  271.     procedure DragDrop(Source : TObject); override;
  272.     procedure AssignRef(ref: TReference); override;
  273.     function GetStartInfo : string; override;
  274.   end;
  275.  
  276.   { TFolder encapsulates a subdirectory.  It overrides numerous methods
  277.     of TDirItem to handle directories, and introduces CheckPath to
  278.     verify that the folder can be put into a destination folder }
  279.  
  280.   TFolder = class(TDirItem)
  281.   private
  282.     procedure CheckPath(const p: TFilename);
  283.   protected
  284.     procedure SetFilename(const AName: TFileBody); override;
  285.   public
  286.     constructor Create(const details : TSearchRec; ADir : TDirectory);
  287.     procedure DrawAsText(Canvas: TCanvas; const Rect: TRect); override;
  288.     procedure Open; override;
  289.     procedure Delete; override;
  290.     procedure CopyToDirectory(d : TDirectory); override;
  291.     procedure CopyToPath(const p : TFilename); override;
  292.     procedure MoveToDirectory(d : TDirectory); override;
  293.     procedure MoveToPath(const p : TFilename); override;
  294.     procedure MoveAndRename(const NewName : TFilename); override;
  295.     function LessThan(f : TDirItem): Boolean; override;
  296.     procedure AssignRef(ref: TReference); override;
  297.     function AcceptsDrops : Boolean; override;
  298.     procedure DragDrop(Source : TObject); override;
  299.     function GetStartInfo : string; override;
  300.   end;
  301.  
  302.   { TFileList is a simple container for TDirItem objects.  It is
  303.     used to hold items during processing, and accumulates information
  304.     about the items as they are added.  This information is available
  305.     trought the integer properties.  The DeepScan flag determines
  306.     whether sub-folders are searched when a folder is added to the list }
  307.  
  308.   TFileList = class(TList)
  309.   private
  310.     FFileSize : Longint;
  311.     FFileCount : Integer;
  312.     FFolderCount : Integer;
  313.     FItemCount : Integer;
  314.     FDeepScan  : Boolean;
  315.   public
  316.     constructor Create;
  317.     procedure Clear;
  318.     function Add(Item:Pointer): Integer;
  319.     property FileSize : Longint read FFileSize;
  320.     property FileCount : Integer read FFileCount;
  321.     property FolderCount: Integer read FFolderCount;
  322.     property ItemCount: Integer read FItemCount write FItemCount;
  323.     property DeepScan : Boolean read FDeepScan write FDeepScan;
  324.   end;
  325.  
  326. const
  327.   faHidSys = faHidden or faSysFile;
  328.  
  329. implementation
  330.  
  331. uses ShellAPI, Forms, Controls, Progress, Resource, FileMan, WinProcs, Streamer,
  332.  Desk, Files, IniFiles, Strings, FileCtrl, MiscUtil, Alias, IconWin, Start;
  333.  
  334.  
  335. { TDirectory }
  336.  
  337. constructor TDirectory.Create(const APath: TFilename);
  338. const
  339.   Masks : array[Boolean] of Word = (0, faHidSys);
  340. begin
  341.   { initialize fields and scan directory }
  342.   inherited Create;
  343.   FDesc := TDescriptions.Create;
  344.   FMask := faDirectory or Masks[ShowHidSys];
  345.   Path := APath;
  346.   FFilter := DefaultFilter;
  347.   FSortOrder := DefaultSort;
  348.   FOnUpdate := nil;
  349.   FChanged := False;
  350. end;
  351.  
  352.  
  353. destructor TDirectory.Destroy;
  354. begin
  355.   FDesc.Free;
  356.   inherited Destroy;
  357. end;
  358.  
  359.  
  360. function TDirectory.Add(Item: TDirItem): Integer;
  361. begin
  362.   { inserts the item in sorted order }
  363.   Result := ItemIndex(Item);
  364.   Insert(Result, Item);
  365.   FChanged := True;
  366. end;
  367.  
  368.  
  369. function TDirectory.Remove(Item: TDirItem): Integer;
  370. begin
  371.   Result := inherited Remove(Item);
  372.   FChanged := True;
  373. end;
  374.  
  375.  
  376. function TDirectory.ItemIndex(Item: TDirItem): Integer;
  377. var
  378.   left, right, mid : Integer;
  379. begin
  380.   { Ordinary binary chop algorithm using the LessThan method
  381.     as comparator.  Returns the index where the item should be placed. }
  382.   left := 0;
  383.   right := Count;
  384.   while left < right do begin
  385.     mid := (left + right) shr 1;
  386.     if TDirItem(List^[mid]).LessThan(Item)
  387.     then left := mid + 1
  388.     else right := mid;
  389.   end;
  390.   Result := left;
  391. end;
  392.  
  393.  
  394.  
  395. function TDirectory.Find(const s : string; var Index: Integer): Boolean;
  396. var
  397.   i: Integer;
  398. begin
  399.   { This must use a linear search because only the filename
  400.     is provided as parameter and the directory list can be sorted in
  401.     many ways }
  402.  
  403.   for i := 0 to Count-1 do
  404.     if TDirItem(List^[i]).Filename = s then begin
  405.       Index := i;
  406.       Result := True;
  407.       Exit;
  408.     end;
  409.   Result := False;
  410. end;
  411.  
  412.  
  413. { AddItem creates a new TDirItem descendant and adds it to the directory
  414.   list.  '.' and '..' entries are discarded, and files with an extension
  415.   of ALS are assumed to be an alias, and the file is opened to check
  416.   the signature.  If no signature is found, a normal TFile is created
  417.   which is guaranteed to load. }
  418.  
  419. procedure TDirectory.AddItem(const rec : TSearchrec);
  420. var
  421.   f: TDirItem;
  422.   s: TStreamer;
  423. begin
  424.   if rec.name[1] = '.' then Exit;
  425.  
  426.   if rec.attr and faDirectory <> 0 then
  427.     f := TFolder.Create(rec, self)
  428.  
  429.   else if ExtractFileExt(rec.name) = '.ALS' then
  430.     try
  431.       s := TStreamer.Create(Path + rec.name, fmOpenRead);
  432.       if s.ReadString = AliasSignature then
  433.         f := TAlias.Create(rec, self, s)
  434.       else
  435.         f := TFile.Create(rec, self);
  436.     finally
  437.       s.Free;
  438.     end
  439.   else
  440.     f := TFile.Create(rec, self);
  441.  
  442.   Add(f);
  443. end;
  444.  
  445.  
  446. { The 4DOS descript.ion file is loaded before searching the directory
  447.   so that TDirItems can check for a description while they are initializing.
  448.   A slight problem occurs when there is no disk in the drive -- FindFirst
  449.   returns -3 fairly quickly when searching for *.*, but searching for
  450.   'descript.ion' seems to make some machines hang until a disk is inserted. }
  451.  
  452. procedure TDirectory.Scan;
  453. var
  454.   rec : TSearchRec;
  455.   code : Integer;
  456. begin
  457.   Desktop.SetCursor(crHourGlass);
  458.   ClearObjects;
  459.   try
  460.     FDesc.Clear;
  461.     if UseDescriptions then FDesc.LoadFromPath(Path);
  462.  
  463.     code := FindFirst(Path + Filter, Mask, rec);
  464.     if code = -3 then
  465.       raise EScanError.CreateFmt('Cannot open %s', [Fullname]);
  466.     while code = 0 do begin
  467.       AddItem(rec);
  468.       if KeyBreak and (GetAsyncKeyState(VK_ESCAPE) < 0) then Break;
  469.       code := FindNext(rec);
  470.     end;
  471.   finally
  472.     Desktop.ReleaseCursor;
  473.   end;
  474. end;
  475.  
  476.  
  477. function TDirectory.GetSize: Longint;
  478. var
  479.   i : Integer;
  480. begin
  481.   { counts the bytes in the files }
  482.   Result := 0;
  483.   for i := 0 to Count-1 do Inc(Result, TDirItem(List^[i]).Size);
  484. end;
  485.  
  486.  
  487. function TDirectory.GetFullname : TFileName;
  488. begin
  489.   Result := Path;
  490.   if Length(Result) > 3 then Dec(Result[0]);
  491. end;
  492.  
  493.  
  494. procedure TDirectory.CreateFolder(const foldername : TFilename);
  495. var
  496.   dest : TFilename;
  497.   rec  : TSearchrec;
  498. begin
  499.   dest := Path + foldername;
  500.  
  501.   if FFileExists(dest) then
  502.     raise EFileOpError.CreateFmt('A file called %s already exists.', [dest])
  503.  
  504.   else if FDirectoryExists(dest) then
  505.     raise EFileOpError.CreateFmt('A folder called %s already exists.', [dest])
  506.  
  507.   else begin
  508.     CreateDirectory(dest);
  509.     FindFirst(dest, faDirectory, rec);
  510.     AddItem(rec);
  511.     Update;
  512.   end;
  513. end;
  514.  
  515.  
  516. { The sorting is a simple insertion sort (utilising the binary comparison).
  517.   rather than quicksort, since directories don't usually have more than a
  518.   few hundred items.  A temporary TList is used to hold the contents while
  519.   they are being inserted back into the TDirectory }
  520.  
  521. procedure TDirectory.Sort;
  522. var
  523.   temp : TList;
  524.   i, n : Integer;
  525. begin
  526.   Desktop.SetCursor(crHourGlass);
  527.   temp := TList.Create;
  528.   try
  529.     n := Count;
  530.     temp.Capacity := n;
  531.     System.Move(List^, temp.List^, n * Sizeof(Pointer));
  532.     Clear; { just clear the list, don't use ClearObjects! }
  533.     for i := 0 to n-1 do Add(temp.List^[i]);
  534.   finally
  535.     temp.Free;
  536.     Desktop.ReleaseCursor;
  537.   end;
  538. end;
  539.  
  540.  
  541.  
  542. procedure TDirectory.Update;
  543. begin
  544.   if UseDescriptions then FDesc.SaveToPath(Path);
  545.   if Assigned(FOnUpdate) then FOnUpdate(self);
  546.   FChanged := False;
  547. end;
  548.  
  549. { Flush is called when a delete or move operation is complete.  It loops
  550.   through the list, removing items with an frRemove flag and freeing
  551.   those with an frFree flag.  The loop is in reverse because delete
  552.   operations are faster if you delete from the end of a list.
  553.  
  554.   Although it sounds neater, the items cannot be removed or freed during
  555.   the operation because the user might drag the progress box around.  This
  556.   would expose parts of the icon window which would call the TDirItems
  557.   to redraw themselves.  }
  558.  
  559. procedure TDirectory.Flush;
  560. var
  561.   i: Integer;
  562.   item : TDirItem;
  563. begin
  564.   for i := Count-1 downto 0 do begin
  565.     item := TDirItem(List^[i]);
  566.     case item.Release of
  567.       frNone   : Continue;
  568.       frRemove : TDirItem(List^[i]).Release := frNone;
  569.       frFree   : TDirItem(List^[i]).Free;
  570.     end;
  571.     Delete(i);
  572.     FChanged := True;
  573.   end;
  574.  
  575.   if FChanged then Update;
  576. end;
  577.  
  578.  
  579. { TDirItem }
  580.  
  581. constructor TDirItem.Create(const details : TSearchRec; ADir : TDirectory);
  582. begin
  583.   inherited Create;
  584.   with details do begin
  585.     FName := Lowercase(name);
  586.     FAttr := attr;
  587.     FSize := size;
  588.     FTimeStamp := TimestampToDate(time);
  589.   end;
  590.   FDir := ADir;
  591.   FRelease := frNone;
  592.   FHasDesc := UseDescriptions and (Dir.Desc.Get(Filename, self) > '');
  593. end;
  594.  
  595.  
  596. function TDirItem.GetFullName : TFilename;
  597. begin
  598.   Result := Dir.Path + Filename;
  599. end;
  600.  
  601.  
  602. function TDirItem.GetExtension : TFileExt;
  603. begin
  604.   Result := Copy(ExtractFileExt(Filename), 2, 3);
  605. end;
  606.  
  607.  
  608. procedure TDirItem.SetFileAttr(attrib : Integer);
  609. begin
  610.   if FAttr = attrib then Exit;
  611.   if FileSetAttr(Fullname, attrib) = 0 then FAttr := attrib
  612.   else raise EAttribError.CreateFmt('Unable to change attributes of %s', [Fullname]);
  613. end;
  614.  
  615.  
  616. { GetDescription makes use of the HasDesc flag to avoid performing a search
  617.   when it is known that there is no description.  Consequently, Put
  618.   must maintain this flag, and the description should not be set in any other
  619.   way.
  620.  
  621.   4DOS specifies that a ^D placed in the description string indicates that
  622.   everything following the marker is extra data used by third party programs.
  623.   Calmira doesn't need to store extra data, but the original data must be
  624.   maintained for compatibility. }
  625.  
  626. function TDirItem.GetDescription : string;
  627. var
  628.   p: Integer;
  629. begin
  630.   if FHasDesc then begin
  631.     Result := Dir.Desc.Get(Filename, self);
  632.     if IgnoreCtrlD then Exit;
  633.     p := Pos(^D, Result);
  634.     if p > 0 then Result[0] := Chr(p-1);
  635.   end
  636.   else Result := '';
  637. end;
  638.  
  639.  
  640. procedure TDirItem.PutDescription(const value: string);
  641. var
  642.   s: string;
  643.   p: Integer;
  644. begin
  645.   s := Dir.Desc.Get(Filename, self);
  646.   p := Pos(^D, s);
  647.   if p > 0 then Dir.Desc.Put(filename, self, value + Copy(s, p+1, 255))
  648.   else Dir.Desc.Put(filename, self, value);
  649.   FHasDesc := value > '';
  650. end;
  651.  
  652.  
  653. procedure TDirItem.Draw(Canvas: TCanvas; const Rect: TRect);
  654. begin
  655.   if UseDescriptions and DescCaptions then InternalDraw(Canvas, Rect, GetTitle)
  656.   else InternalDraw(Canvas, Rect, FName);
  657. end;
  658.  
  659.  
  660. procedure TDirItem.DrawAsText(Canvas: TCanvas; const Rect: TRect);
  661. var
  662.   SizeStr: string[15];
  663.   Top : Integer;
  664. begin
  665.   { This procedure just writes the text information.  Descendants
  666.     are responsible for drawing the small icon on the left }
  667.  
  668.   with Canvas do begin
  669.     Top := Rect.Top+1;
  670.     TextOut(Rect.Left + 20, Top, Filename);
  671.     SizeStr := FormatByte(Size);
  672.     TextOut(Rect.Left + SizeRight - TextWidth(SizeStr), Top, SizeStr);
  673.     TextOut(Rect.Left + DateLeft, Top, DateToStr(TimeStamp));
  674.     TextOut(Rect.Left + TimeLeft, Top, TimeToStr(TimeStamp));
  675.     TextOut(Rect.Left + AttrLeft, Top, AttrToStr(Attr));
  676.     if UseDescriptions then
  677.       TextOut(Rect.Left + AttrLeft + 32, Top, Description);
  678.   end;
  679. end;
  680.  
  681.  
  682. function TDirItem.GetTitle: string;
  683. begin
  684.   Result := Description;
  685.   if Result = '' then Result := FName;
  686. end;
  687.  
  688.  
  689. { The LessThan method is the main comparison function for sorting, and
  690.   needs to work with the four orderings and handle descriptions when they
  691.   are used as captions.  The main sort key (Type, Data, Size) is compared
  692.   first and if they are equal, the captions are compared using the
  693.   auxiliary function.  CompareText must be used because descriptions can
  694.   be in upper and lower case }
  695.  
  696. function TDirItem.LessThan(f : TDirItem): Boolean;
  697.  
  698. function CaptionLessThan: Boolean;
  699. begin
  700.   if DescCaptions then Result := CompareText(GetTitle, f.GetTitle) < 0
  701.   else Result := Filename < f.Filename;
  702. end;
  703.  
  704. var
  705.   c: Integer;
  706. begin
  707.   case Dir.SortOrder of
  708.     soType :
  709.       begin
  710.         c := CompareStr(Extension, f.Extension);
  711.         Result := (c < 0) or ((c = 0) and CaptionLessThan)
  712.       end;
  713.  
  714.     soName :
  715.       Result := CaptionLessThan;
  716.  
  717.     soSize :
  718.       Result := (Size > f.Size) or ((Size = f.Size) and CaptionLessThan);
  719.  
  720.     soDate :
  721.       Result := (TimeStamp > f.TimeStamp) or
  722.         ((TimeStamp = f.TimeStamp) and CaptionLessThan);
  723.   end;
  724. end;
  725.  
  726.  
  727. { SetFilename is the property write method for the Filename property.
  728.   It is virtual so descandants can override it to constrain the renaming.
  729.   However, it is vital that overridden property access methods call
  730.   "inherited SetFilename" rather than using the "inherited Filename"
  731.   property, which would cause an infinite recursion and stack overflow }
  732.  
  733. procedure TDirItem.SetFileName(const AName: TFileBody);
  734. var
  735.   buf : string;
  736. begin
  737.   if AName <> FName then begin
  738.     if not IsValidFilename(AName) then
  739.       raise ERenameError.CreateFmt('%s is not a valid filename', [AName])
  740.  
  741.     else begin
  742.       if RenameFile(Dir.Path + FName, Dir.Path + AName) then begin
  743.         if UseDescriptions then begin
  744.           buf := Description;
  745.           Description := '';
  746.         end;
  747.         Dir.Remove(self);
  748.         FName := AName;
  749.         Dir.Add(self);
  750.         if UseDescriptions then Description := buf;
  751.       end
  752.       else
  753.         raise ERenameError.CreateFmt('Unable to rename %s to %s',
  754.           [Fullname, AName]);
  755.     end;
  756.   end;
  757. end;
  758.  
  759.  
  760. function TDirItem.GetSearchRec: TSearchRec;
  761. begin
  762.   Result.name := Uppercase(Filename);
  763.   Result.attr := Attr;
  764.   Result.size := Size;
  765.   Result.time := DateTimeToFileDate(TimeStamp);
  766. end;
  767.  
  768.  
  769. procedure TDirItem.AssignRef(ref: TReference);
  770. begin
  771.   with Ref do begin
  772.     Target := Fullname;
  773.     Caption := GetTitle;
  774.   end;
  775. end;
  776.  
  777.  
  778. { The popup hints must show either the file description or the DOS
  779.   filename, depending on the context.  The idea is that the user can
  780.   use the hint to see information not displayed under the icon --
  781.   if a description is shown as the icon caption, the hint shows the
  782.   filename.  If the filename is the caption, a description is put
  783.   in the hint, if one exists. }
  784.  
  785. function TDirItem.GetHint : string;
  786.  
  787. procedure AddField(const s: string);
  788. begin
  789.   if Result > '' then AppendStr(Result, '  ');
  790.   AppendStr(Result, s);
  791. end;
  792.  
  793. begin
  794.   Result := '';
  795.   if UseDescriptions and HintDesc then begin
  796.     Result := Description;
  797.     if DescCaptions and (Result > '') then Result := Filename
  798.     else if Result = '' then Result := '(No description)';
  799.   end;
  800.  
  801.   if HintDate then AddField(DateToStr(TimeStamp));
  802.   if HintTime then AddField(TimeToStr(TimeStamp));
  803.   if HintAttrib then begin
  804.     if Attr and faArchive > 0 then AddField('arc');
  805.     if Attr and faReadOnly > 0 then AddField('ro');
  806.     if Attr and faHidden > 0 then AddField('hid');
  807.     if Attr and faSysFile > 0 then AddField('sys');
  808.   end;
  809. end;
  810.  
  811.  
  812. procedure TDirItem.Delete;
  813. begin
  814.   FRelease := frFree;
  815.   Dir.Desc.Put(Filename, nil, '');
  816. end;
  817.  
  818.  
  819. { The following five methods are responsible for maintaining the consistency
  820.   of file descriptions.  When a description is transferred, the destination
  821.   object is not known, so nil is passed.
  822.  
  823.   Note that Dir.Desc.Get is used rather than the Description property.  This
  824.   is because the Description property filters out data following a ^D marker,
  825.   which we must keep. }
  826.  
  827. procedure TDirItem.MoveToDirectory(d: TDirectory);
  828. begin
  829.   FRelease := frRemove;
  830.   if UseDescriptions then begin
  831.     d.Desc.Put(Filename, nil, Dir.Desc.Get(Filename, self));
  832.     Dir.Desc.Put(Filename, self, '');
  833.   end;
  834. end;
  835.  
  836.  
  837. procedure TDirItem.MoveToPath(const p: TFilename);
  838. begin
  839.   FRelease := frFree;
  840.   if UseDescriptions then begin
  841.     SharedDesc.Put(Filename, nil, Dir.Desc.Get(Filename, self));
  842.     Dir.Desc.Put(Filename, self, '');
  843.   end;
  844. end;
  845.  
  846.  
  847. procedure TDirItem.CopyToDirectory(d: TDirectory);
  848. begin
  849.   if UseDescriptions then
  850.     d.Desc.Put(Filename, nil, Dir.Desc.Get(Filename, self));
  851. end;
  852.  
  853.  
  854. procedure TDirItem.CopyToPath(const p: TFilename);
  855. begin
  856.   if UseDescriptions then
  857.     SharedDesc.Put(Filename, nil, Dir.Desc.Get(Filename, self));
  858. end;
  859.  
  860.  
  861. procedure TDirItem.MoveAndRename(const NewName : TFilename);
  862. begin
  863.   FRelease := frFree;
  864.   if UseDescriptions then
  865.     Dir.Desc.Put(Filename, nil, '');
  866. end;
  867.  
  868.  
  869. function TDirItem.EditDescription: Boolean;
  870. var buf : string;
  871. begin
  872.   buf := Description;
  873.   Result := InputQuery('Change description',
  874.     Format('Describe "%s"', [Filename]), buf);
  875.   if Result then Description:= buf;
  876. end;
  877.  
  878.  
  879. { TFileItem }
  880.  
  881. procedure TFileItem.OpenWith(const progname : TFilename);
  882. begin
  883.   if ExecuteFile(progname, QualifiedFilename(Fullname),
  884.     Dir.Fullname, 'Open', SW_SHOWNORMAL) <= 32
  885.   then ErrorMsg('Unable to open file.');
  886. end;
  887.  
  888.  
  889. procedure TFileItem.CopyToPath(const p : TFilename);
  890. begin
  891.   if CopyFile(Fullname, p + Filename) then
  892.     inherited CopyToPath(p);
  893. end;
  894.  
  895.  
  896. procedure TFileItem.CopyToDirectory(d : TDirectory);
  897. var
  898.   i: Integer;
  899. begin
  900.   if CopyFile(Fullname, d.Path + Filename) then begin
  901.     { replace any existing object with the same name }
  902.     inherited CopyToDirectory(d);
  903.     if d.Find(Filename, i) then TFileItem(d[i]).Release := frFree;
  904.     d.AddItem(GetSearchRec);
  905.   end;
  906. end;
  907.  
  908.  
  909. procedure TFileItem.MoveToDirectory(d : TDirectory);
  910. var
  911.   i: Integer;
  912. begin
  913.   if MoveFile(FullName, d.Path + Filename, Attr) then begin
  914.     inherited MoveToDirectory(d);
  915.     FDir := d;
  916.     if d.Find(Filename, i) then TDirItem(d[i]).Release := frFree;
  917.     d.Add(self);
  918.   end;
  919. end;
  920.  
  921.  
  922. procedure TFileItem.MoveToPath(const p : TFilename);
  923. begin
  924.   if MoveFile(FullName, p + Filename, Attr) then
  925.     inherited MoveToPath(p);
  926. end;
  927.  
  928.  
  929. procedure TFileItem.MoveAndRename(const NewName : TFilename);
  930. begin
  931.   if MoveFile(FullName, NewName, Attr) then
  932.     inherited MoveAndRename(NewName);
  933. end;
  934.  
  935.  
  936. procedure TFileItem.Duplicate(const AName: TFilename);
  937. var
  938.   rec: TSearchRec;
  939.   i  : Integer;
  940. begin
  941.   if not IsValidFilename(AName) then
  942.     raise EFileOpError.CreateFmt('%s is not a valid filename', [AName]);
  943.  
  944.   if CopyFile(Fullname, Dir.Path + AName) then begin
  945.     rec := GetSearchRec;
  946.     rec.Name := AName;
  947.     with Dir do begin
  948.       Desc.Put(AName, nil, Description);
  949.       if Find(AName, i) then FreeObject(i);
  950.       AddItem(rec);
  951.       Update;
  952.     end;
  953.   end;
  954. end;
  955.  
  956.  
  957. function TFileItem.LessThan(f : TDirItem): Boolean;
  958. begin
  959.   { files are always placed after folders }
  960.   Result := not (f is TFolder) and inherited LessThan(f);
  961. end;
  962.  
  963.  
  964. procedure TFileItem.DrawAsText(Canvas: TCanvas; const Rect: TRect);
  965. begin
  966.   inherited DrawAsText(Canvas, Rect);
  967.   if FIsProgram then Canvas.Draw(Rect.Left, Rect.Top, TinyProg)
  968.   else Canvas.Draw(Rect.Left, Rect.Top, TinyFile);
  969. end;
  970.  
  971.  
  972. procedure TFileItem.Delete;
  973. begin
  974.   if EraseFile(Fullname, Attr) then inherited Delete;
  975. end;
  976.  
  977.  
  978. { TFile }
  979.  
  980. constructor TFile.Create(const details : TSearchRec; ADir : TDirectory);
  981. begin
  982.   inherited Create(details, ADir);
  983.   AssignIcon;
  984. end;
  985.  
  986.  
  987. destructor TFile.Destroy;
  988. begin
  989.   FreeIcon;
  990.   inherited Destroy;
  991. end;
  992.  
  993.  
  994. procedure TFile.FreeIcon;
  995. begin
  996.   if FOwnIcon then begin
  997.     FIcon.Free;
  998.     FIcon := nil;
  999.     FOwnIcon := False;
  1000.   end;
  1001. end;
  1002.  
  1003.  
  1004. procedure TFile.AssignIcon;
  1005. var
  1006.   h : Word;
  1007.   ext : TFileExt;
  1008.   filestr : TFilename;
  1009. begin
  1010.   OwnIcon := False;
  1011.   ext := Extension;
  1012.  
  1013.   { Try and extract an icon if the file extension is in the list
  1014.     of icon file types, otherwise get a pointer to an icon from
  1015.     the resource store }
  1016.  
  1017.   if ExtensionIn(ext, IconStrings) then with Icons do begin
  1018.     h := ExtractIcon(HInstance, StrPCopy(@filestr, Fullname), 0);
  1019.     if h = 0 then FIcon := WindowsIcon
  1020.     else if h = 1 then FIcon := DOSIcon
  1021.     else begin
  1022.       FIcon := TIcon.Create;
  1023.       FIcon.Handle := h;
  1024.       OwnIcon := True;
  1025.     end;
  1026.   end
  1027.   else FIcon := Icons.Get(ext);
  1028.  
  1029.   FIsProgram := ExtensionIn(ext, programs);
  1030. end;
  1031.  
  1032.  
  1033.  
  1034. procedure TFileItem.Open;
  1035. begin
  1036.   DefaultExec(Fullname, '', Dir.Fullname, SW_SHOWNORMAL);
  1037. end;
  1038.  
  1039.  
  1040. procedure TFile.SetFilename(const AName: TFileBody);
  1041. begin
  1042.   { If the file's extension changes, it might need a different icon }
  1043.  
  1044.   FreeIcon;
  1045.   try
  1046.     inherited SetFilename(AName);
  1047.   finally
  1048.     AssignIcon;
  1049.   end;
  1050. end;
  1051.  
  1052.  
  1053. procedure TFile.AssignRef(ref: TReference);
  1054. begin
  1055.   with Ref do begin
  1056.     BeginUpdate;
  1057.     inherited AssignRef(ref);
  1058.     Kind := rkFile;
  1059.     EndUpdate;
  1060.   end;
  1061. end;
  1062.  
  1063.  
  1064. function TFile.AcceptsDrops : Boolean;
  1065. begin
  1066.   { the user can choose whether programs accept drops }
  1067.   Result := FIsProgram and ProgDrop;
  1068. end;
  1069.  
  1070.  
  1071. procedure TFile.DragDrop(Source : TObject);
  1072. begin
  1073.   FileRef.Target := Fullname;
  1074.   FileRef.DragDrop(Source);
  1075. end;
  1076.  
  1077.  
  1078. function TFile.GetStartInfo : string;
  1079. begin
  1080.   Result := PackStartInfo(Fullname, Dir.Fullname, '', 0, 0);
  1081. end;
  1082.  
  1083.  
  1084. { TFolder }
  1085.  
  1086. constructor TFolder.Create(const details : TSearchRec; ADir : TDirectory);
  1087. begin
  1088.   inherited Create(details, ADir);
  1089.   FIcon := foldericon;
  1090. end;
  1091.  
  1092.  
  1093. procedure TFolder.DrawAsText(Canvas: TCanvas; const Rect: TRect);
  1094. begin
  1095.   inherited DrawAsText(Canvas, Rect);
  1096.   Canvas.Draw(Rect.Left, Rect.Top, TinyFolder);
  1097. end;
  1098.  
  1099.  
  1100. procedure TFolder.Open;
  1101. begin
  1102.   Desktop.OpenFolderRefresh(Fullname);
  1103. end;
  1104.  
  1105.  
  1106. procedure TFolder.Delete;
  1107. begin
  1108.   Desktop.CloseSubWindows(Fullname);
  1109.   DeleteDirectory(Fullname);
  1110.   if not HDirectoryExists(Fullname) then inherited Delete;
  1111. end;
  1112.  
  1113.  
  1114. procedure TFolder.CheckPath(const p: TFilename);
  1115. var dest: TFilename;
  1116. begin
  1117.   dest := p + Filename;
  1118.  
  1119.   if Fullname = MakeDirname(p) then
  1120.     raise EFileOpError.Create('Cannot copy or move a folder onto itself')
  1121.  
  1122.   else if IsAncestorDir(Fullname, Makedirname(p)) then
  1123.     raise EFileOpError.Create('Cannot copy or move a folder into one of its sub-folders')
  1124.  
  1125.   else if FFileExists(dest) then
  1126.     raise EFileOpError.CreateFmt('Cannot copy or move folder %s because ' +
  1127.       'a file with the same name already exists', [dest])
  1128. end;
  1129.  
  1130.  
  1131. procedure TFolder.CopyToDirectory(d : TDirectory);
  1132. var
  1133.   rec: TSearchRec;
  1134.   i : Integer;
  1135. begin
  1136.   CheckPath(d.Path);
  1137.   if CopyDirectory(Fullname, d.Path + Filename) then begin
  1138.     inherited CopyToDirectory(d);
  1139.     if not d.Find(Filename, i) and
  1140.       (Findfirst(d.Path + Filename, faDirectory, rec) = 0) then
  1141.       d.AddItem(rec);
  1142.   end;
  1143. end;
  1144.  
  1145.  
  1146. procedure TFolder.CopyToPath(const p : TFilename);
  1147. begin
  1148.   CheckPath(p);
  1149.   if CopyDirectory(Fullname, p + Filename) then
  1150.     inherited CopyToPath(p);
  1151. end;
  1152.  
  1153.  
  1154. procedure TFolder.MoveToDirectory(d : TDirectory);
  1155. var
  1156.   rec: TSearchRec;
  1157.   i : Integer;
  1158. begin
  1159.   { Windows showing this folder or any descendants are closed
  1160.     first to prevent any inconsistencies }
  1161.  
  1162.   CheckPath(d.Path);
  1163.   Desktop.CloseSubWindows(Fullname);
  1164.  
  1165.   if MoveDirectory(Fullname, d.Path + Filename) then begin
  1166.     inherited MoveToDirectory(d);
  1167.     if not d.Find(Filename, i) and
  1168.       (Findfirst(d.Path + Filename, faDirectory, rec) = 0) then
  1169.       d.AddItem(rec);
  1170.     FRelease := frFree;
  1171.   end;
  1172. end;
  1173.  
  1174.  
  1175. procedure TFolder.MoveToPath(const p : TFilename);
  1176. begin
  1177.   CheckPath(p);
  1178.   Desktop.CloseSubWindows(Fullname);
  1179.   if MoveDirectory(Fullname, p + Filename) then
  1180.     inherited MoveToPath(p);
  1181. end;
  1182.  
  1183. procedure TFolder.MoveAndRename(const NewName : TFilename);
  1184. begin
  1185.   Desktop.CloseSubWindows(Fullname);
  1186.   if MoveDirectory(FullName, NewName) then
  1187.     inherited MoveAndRename(NewName);
  1188. end;
  1189.  
  1190.  
  1191.  
  1192. procedure TFolder.SetFileName(const AName: TFileBody);
  1193. var oldname: TFilename;
  1194. begin
  1195.   oldname := Fullname;
  1196.   ExitDirectory(oldname);
  1197.   inherited SetFilename(AName);
  1198.   Desktop.RenameWindows(oldname, Fullname);
  1199. end;
  1200.  
  1201.  
  1202. procedure TFolder.DragDrop(Source : TObject);
  1203. begin
  1204.   FolderRef.Target := Fullname;
  1205.   FolderRef.DragDrop(Source);
  1206. end;
  1207.  
  1208.  
  1209. function TFolder.LessThan(f : TDirItem): Boolean;
  1210. begin
  1211.   Result := (f is TFileItem) or inherited LessThan(f);
  1212. end;
  1213.  
  1214.  
  1215. procedure TFolder.AssignRef(ref: TReference);
  1216. begin
  1217.   with Ref do begin
  1218.     BeginUpdate;
  1219.     inherited AssignRef(ref);
  1220.     Kind := rkFolder;
  1221.     EndUpdate;
  1222.   end;
  1223. end;
  1224.  
  1225.  
  1226. function TFolder.AcceptsDrops: Boolean;
  1227. begin
  1228.   Result := True;
  1229. end;
  1230.  
  1231.  
  1232. function TFolder.GetStartInfo : string;
  1233. begin
  1234.   Result := PackStartInfo('$Folder ' + Fullname, '', '', 0, 0);
  1235. end;
  1236.  
  1237.  
  1238. { TFileList }
  1239.  
  1240. constructor TFileList.Create;
  1241. begin
  1242.   inherited Create;
  1243.   FFileSize := 0;
  1244.   FFileCount := 0;
  1245.   FFolderCount := 0;
  1246. end;
  1247.  
  1248.  
  1249. procedure TFileList.Clear;
  1250. begin
  1251.   inherited Clear;
  1252.   FFileSize := 0;
  1253.   FFileCount := 0;
  1254.   FFolderCount := 0;
  1255.   FItemCount := 0;
  1256. end;
  1257.  
  1258.  
  1259. function TFileList.Add(Item:Pointer): Integer;
  1260. begin
  1261.   Result := inherited Add(Item);
  1262.   if TObject(Item) is TFileItem then begin
  1263.     Inc(FFileCount);
  1264.     Inc(FItemCount);
  1265.     Inc(FFileSize, TFileItem(Item).Size);
  1266.   end
  1267.   else begin
  1268.     Inc(FFolderCount);
  1269.     Inc(FItemCount);
  1270.     if DeepScan then
  1271.       try
  1272.         Desktop.SetCursor(crHourGlass);
  1273.         with DirInfo(TFolder(Item).Fullname, True) do begin
  1274.           Inc(FFileCount, files);
  1275.           Inc(FFolderCount, dirs);
  1276.           Inc(FItemCount, files + dirs);
  1277.           Inc(FFileSize, size);
  1278.         end;
  1279.       finally
  1280.         Desktop.ReleaseCursor;
  1281.       end;
  1282.   end;
  1283. end;
  1284.  
  1285.  
  1286. end.
  1287.