home *** CD-ROM | disk | FTP | other *** search
/ PC Plus SuperCD (UK) 2000 March / pcp161b.iso / handson / archive / Issue153 / delphi / copydelp.exe / Diskbrowser / dbrowse.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-03-21  |  18.6 KB  |  548 lines

  1. unit dbrowse;
  2.    { PC Plus sample Delphi 2.0 file manager.
  3.    Author: Huw Collingbourne
  4.  
  5.    ------------IMPORTANT DISCLAIMER----------------
  6.    This application can erase files from your disk!
  7.    It is provided as a sample program and it is not
  8.    guaranteed to be flawless, safe or fit for any
  9.    purpose whatsoever. In short, while I don't
  10.    believe it will damage or corrupt your files,
  11.    I do not guarantee that it won't!
  12.    ------------------------------------------------
  13.  
  14.    Functionality:
  15.    * Displays TreeView directory window
  16.    * Displays ListView of files with appropriate icons
  17.    * Splitter panel lets you resize windows at run-time
  18.    * Double-click to excute selected file
  19.    * Single-click file name to rename file
  20.    * Press the DELETE key to erase selected file
  21.    * Popup menu provides alternatives to keystrokes
  22.    * DriveComboBox lets you log onto a new disk
  23.    * Drag and drop file copy and move
  24.    }
  25.  
  26. interface
  27.  
  28. uses
  29.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  30.   StdCtrls, ComCtrls, ExtCtrls, ShellAPI,
  31.   CommCtrl, FileCtrl,
  32.   MyFMXUtils, Menus, SHLObj;
  33.  
  34. type
  35.   TForm1 = class(TForm)
  36.     StatusBar1: TStatusBar;
  37.     ImageList1: TImageList;
  38.     HeaderPanel: TPanel;
  39.     DriveComboBox1: TDriveComboBox;
  40.     PopupMenu1: TPopupMenu;
  41.     Delete: TMenuItem;
  42.     Rename: TMenuItem;
  43.     MainPanel: TPanel;
  44.     ListView1: TListView;
  45.     TreeView1: TTreeView;
  46.     SplitterPanel: TPanel;
  47.     DirPanel: TPanel;
  48.     procedure TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  49.       var AllowExpansion: Boolean);
  50.     procedure FormCreate(Sender: TObject);
  51.     procedure TreeView1Click(Sender: TObject);
  52.     procedure ListView1Click(Sender: TObject);
  53.     procedure ListView1DblClick(Sender: TObject);
  54.     procedure SplitterPanelMouseDown(Sender: TObject; Button: TMouseButton;
  55.       Shift: TShiftState; X, Y: Integer);
  56.     procedure FormResize(Sender: TObject);
  57.     procedure DriveComboBox1Change(Sender: TObject);
  58.     procedure DeleteClick(Sender: TObject);
  59.     procedure RenameClick(Sender: TObject);
  60.     procedure ListView1Edited(Sender: TObject; Item: TListItem;
  61.       var S: string);
  62.     procedure ListView1Change(Sender: TObject; Item: TListItem;
  63.       Change: TItemChange);
  64.     procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  65.       Shift: TShiftState; X, Y: Integer);
  66.     procedure TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  67.       State: TDragState; var Accept: Boolean);
  68.     procedure TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
  69.   private
  70.     { Private declarations }
  71.   public
  72.     { Public declarations }
  73.     CurrDirName : string;
  74.     CurrFileName : string;
  75.     ItemEdited : boolean;
  76.     function isDirectory( sr : TSearchRec ) : boolean;
  77.     function excludeFromListView( sr : TSearchRec ) : boolean;
  78.     function hasSubDirectories( d : string ) : boolean;
  79.     function fullPath( Node : TTreeNode ) : string;
  80.     procedure addSubDirs( rootDir : string;  Node   : TTreeNode );
  81.     procedure updatePanels;
  82.     procedure updateListView;
  83.   end;
  84.  
  85. const    // default root directory in TreeView
  86.   driveChar = 'C';
  87.   defaultDir = 'C:';
  88.          // minimum width allowed for TreeView or ListView
  89.   MINWIDTH = 100;
  90.          // DragDrop Move or Copy?
  91.   FMove : boolean = false; 
  92.  
  93. var
  94.   Form1: TForm1;
  95.  
  96. implementation
  97.  
  98.  
  99. {$R *.DFM}
  100.  
  101. function DiskInDrive(Drive: Char): Boolean;
  102. { Check to see if a disk is in the drive specified.
  103.   This code is taken from Borland's TechInfo help file }   
  104. var
  105.   ErrorMode: word;
  106. begin
  107.   { make it upper case }
  108.   if Drive in ['a'..'z'] then Dec(Drive, $20);
  109.   { make sure it's a letter }
  110.   if not (Drive in ['A'..'Z']) then
  111.     raise EConvertError.Create('Not a valid drive ID');
  112.   { turn off critical errors }
  113.   ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
  114.   try
  115.     { drive 1 = a, 2 = b, 3 = c, etc. }
  116.     if DiskSize(Ord(Drive) - $40) = -1 then
  117.       Result := False
  118.     else
  119.       Result := True;
  120.   finally
  121.     { restore old error mode }
  122.     SetErrorMode(ErrorMode);
  123.   end;
  124. end;
  125.  
  126.  
  127. function GetFileInfo( fn : string ) : TSHFileInfo;
  128. { Return a TSHFileInfo with information on the file, fn.
  129.   The precise type of information is controlled by the flags
  130.   passed to the SHGetFileInfo function }
  131. var
  132.   FileInfo : TSHFileInfo;
  133. begin
  134.   FillChar(FileInfo, SizeOf(FileInfo), #0);
  135.   SHGetFileInfo(PChar(fn),0, FileInfo, SizeOf(FileInfo), SHGFI_SYSICONINDEX or
  136.                                                          SHGFI_TYPENAME or
  137.                                                          SHGFI_ATTRIBUTES );
  138.   result := FileInfo;
  139. end;
  140.  
  141.  
  142.  
  143. procedure TForm1.updateListView;
  144. { Displays file names from current directory in the ListView.
  145.   This procedure uses data supplied by a TSearchRec and a TSHFileInfo
  146.   structure to retrive and display file information such as the
  147.   file date, size, type and attributes. }
  148. var
  149.    NewItem : TListItem;
  150.    Result : integer;
  151.    SearchRec: TSearchRec;
  152.    TypeName : string;     { file type: e.g 'Text document' or 'Delphi Project'}
  153.    FileInfo : TSHFileInfo;
  154.    ByteSize : integer;    { file size   }
  155.    AttStr   : string;     { string representation of file attributes }
  156.    Attributes : integer;  { integer representation of file attributes }
  157. begin
  158.     ListView1.Items.BeginUpdate; // turn off update for speed
  159.     ListView1.Items.Clear;
  160.     ListView1.Items.EndUpdate;   // turn it on again
  161.     Result := FindFirst(currDirName+'*.*', faAnyFile, SearchRec);
  162.     while Result = 0 do
  163.     begin // filter any unwanted files from display
  164.       if not excludeFromListView( SearchRec ) then
  165.       begin
  166.          // first retrieve file information
  167.         FileInfo :=  GetFileInfo(currDirName + Searchrec.Name );
  168.         TypeName := FileInfo.szTypeName;
  169.         AttStr:='';
  170.         Attributes := SearchRec.Attr;
  171.         if (Attributes and faReadOnly > 0) then AttStr := AttStr + 'R';
  172.         if (Attributes and faHidden  > 0) then AttStr := AttStr + 'H';
  173.         if (Attributes and faSysFile > 0) then AttStr := AttStr + 'S';
  174.         if (Attributes and faArchive > 0) then AttStr := AttStr + 'A';
  175. (* // Note: we could retrieve a set of attributes from FileInfo.
  176.        However, these attributes are both more numerous and different
  177.        from standard file attributes (e.g. they don't include an Archive flag but
  178.        they do have Removable-media ghosted icon flags). I don't feel that
  179.        most of these are not appropriate in this application. You can
  180.        find a list of all these constants in Delphi's SHLOBJ.PAS unit.
  181.        This is an example of how you would use FileInfo attributes:
  182.           Attributes := FileInfo.dwAttributes;
  183.          if (Attributes and SFGAO_READONLY >0) then AttStr := AttStr + 'R';
  184. *)
  185.  
  186.         ByteSize := SearchRec.Size div 1024;
  187.         if ByteSize = 0 then ByteSize := 1;
  188.         // then create a new item in the ListView
  189.         NewItem := ListView1.Items.Add;      // add a file name in lowercase
  190.         NewItem.Caption := LowerCase(SearchRec.Name);
  191.         NewItem.ImageIndex := FileInfo.iIcon; // add an appropriate icon
  192.         // then add subitems in the ListView columns to display file info
  193.         NewItem.SubItems.Add(IntToStr(ByteSize)+'KB');
  194.         NewItem.SubItems.Add(TypeName);
  195.         NewItem.SubItems.Add(AttStr);
  196.         NewItem.SubItems.Add(DateTimeToStr(FileDateToDateTime( SearchRec.Time )));
  197.       end;
  198.       Result := FindNext(SearchRec);
  199.     end;
  200.     FindClose(SearchRec);
  201. end;
  202.  
  203. procedure TForm1.updatePanels;
  204. { display path in DirPanel. Show number of files (and subdirectories if the
  205.   current Node is expanded) in the Statusbar panels }
  206. var
  207.   panel0text : string;
  208. begin
  209.   DirPanel.Caption := currDirName;
  210.   panel0text := IntToStr(ListView1.Items.Count) + ' files';
  211.   if TreeView1.Selected.Expanded then
  212.      panel0text := panel0text +
  213.                 ' [' + IntToStr( TreeView1.Selected.Count ) + ' subdirectories]';
  214.  
  215.   StatusBar1.Panels.Items[0].Text := panel0text;
  216. end;
  217.  
  218. function TForm1.excludeFromListView( sr : TSearchRec ) : boolean;
  219. { Don't display these files in the file list }
  220. begin
  221.     if isDirectory( sr ) then
  222.        excludeFromListView := true
  223.     else if ((sr.Name = '.') or (sr.Name = '..')) then
  224.      excludeFromListView := true
  225.   else excludeFromListView := false;
  226. end;
  227.  
  228. function TForm1.isDirectory( sr : TSearchRec ) : boolean;
  229. { test if current file is a directory }
  230. begin
  231.    isDirectory := ((sr.Attr and faDirectory > 0)
  232.                   and
  233.                   (sr.Name <> '.') and (sr.Name <> '..'));
  234. end;
  235.  
  236. function TForm1.fullPath( Node : TTreeNode ) : string;
  237. var
  238.   ANode, FirstNode : TTreeNode;
  239.   path : string;
  240. begin
  241.   ANode := Node;
  242.   path := ANode.Text + '\';
  243.   FirstNode := TreeView1.Items.GetFirstNode;
  244.   while ANode <> FirstNode do
  245.   begin
  246.      ANode := ANode.Parent;
  247.      path := ANode.Text + '\' + path;
  248.   end;
  249.   fullPath := path;
  250. end;
  251.  
  252. function TForm1.hasSubDirectories( d : string ) : boolean;
  253. { test in the directory indicated by the string 'd' contains
  254.  subdirectories. If so, return true }
  255. var
  256.   SearchRec: TSearchRec;
  257.   SResult : integer;
  258.   noSubDirFound : boolean;
  259. begin    // find first file, Result is 0 if successful
  260.     noSubDirFound := true;
  261.     SResult := FindFirst(d + '\*.*', faDirectory, SearchRec);
  262.     while ((SResult = 0) and (noSubDirFound)) do
  263.     begin
  264.       if isDirectory(SearchRec) then noSubDirFound := false;
  265.       SResult := FindNext(SearchRec);
  266.     end;
  267.     FindClose(SearchRec);
  268.     hasSubDirectories := (noSubDirFound = false);
  269. end;
  270.  
  271. procedure TForm1.addSubDirs( rootDir : string;  Node : TTreeNode );
  272. { add directory names as branches of the TreeView . If a directory contains
  273.   any subdirectories, a '+' is placed alongside the directory name in
  274.   the TreeView }
  275. var
  276.   SearchRec: TSearchRec;
  277.   Result : integer;
  278.   NewNode : TTreeNode;
  279. begin    // find first file, Result is 0 if successful
  280.     Result := FindFirst(rootDir + '*.*', faDirectory, SearchRec);
  281.     while Result = 0 do
  282.     begin
  283.       if isDirectory(SearchRec) then
  284.       begin  // if directory is found add it as a child of prev node in TreeView
  285.          NewNode := TreeView1.Items.AddChild(Node, SearchRec.Name);
  286.          NewNode.SelectedIndex := 1;
  287.          if (hasSubDirectories( rootDir + SearchRec.Name ) ) then
  288.             // making HasChildren true adds a '+' to the directory node
  289.             // even if that node hasn't really got any child nodes
  290.             NewNode.HasChildren := true;
  291.       end; // then continue searching }
  292.       Result := FindNext(SearchRec);
  293.     end;
  294.     FindClose(SearchRec);
  295.     Node.AlphaSort;
  296. end;
  297.  
  298. procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  299.   var AllowExpansion: Boolean);
  300. begin
  301.     if Node.GetFirstChild = nil then
  302.        addSubDirs(fullPath(Node) , Node );
  303.   Node.Selected := true; { if expanding the node, select it }
  304. end;
  305.  
  306. procedure TForm1.FormCreate(Sender: TObject);
  307. var
  308.   ImageListHandle : THandle;
  309.   FileInfo : TSHFileInfo;
  310.  
  311. begin
  312.   // Get system ImageList so that we can retrieve small icons for each file type
  313.   ImageListHandle := SHGetFileInfo('',
  314.                            0,
  315.                            FileInfo,
  316.                            SizeOf(FileInfo),
  317.                            SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  318.   SendMessage(ListView1.Handle, LVM_SETIMAGELIST, LVSIL_SMALL, ImageListHandle);
  319.   //!! The next line causes an OnChange event which will fire up
  320.   //!! DriveComboBox1Change which, in turn, populates the TreeView and ListView
  321.   DriveComboBox1.ItemIndex := 1; // C:\ drive
  322.   // Set Form1 variables to default values
  323.   currFileName := '';
  324.   DriveComboBox1.Drive := driveChar;
  325.   currDirName := DefaultDir;
  326.   ItemEdited := false;
  327. end;
  328.  
  329. procedure TForm1.TreeView1Click(Sender: TObject);
  330. begin
  331.  // if the same directory node was clicked, do nothing
  332.     if fullpath(TreeView1.Selected) <> currDirName then
  333.     begin
  334.        currDirName := fullpath(TreeView1.Selected);
  335.        updateListView;
  336.        updatePanels;
  337.     end;
  338. end;
  339.  
  340. procedure TForm1.ListView1Click(Sender: TObject);
  341. { if a file is selected then update the currFileName variable }
  342. begin
  343.   if ListView1.Selected <> nil then
  344.      currFileName := ListView1.Selected.Caption
  345.   else
  346.      currFileName := '';
  347.   StatusBar1.Panels.Items[1].Text := currFileName;
  348. end;
  349.  
  350. procedure TForm1.ListView1DblClick(Sender: TObject);
  351. begin
  352.   if ListView1.Selected <> nil then // Check that a file name is Selected
  353.      ExecuteFile(ListView1.Selected.Caption, '',
  354.                  fullPath(TreeView1.Selected), SW_SHOW);
  355. end;
  356.  
  357. procedure TForm1.SplitterPanelMouseDown(Sender: TObject; Button: TMouseButton;
  358.   Shift: TShiftState; X, Y: Integer);
  359. const
  360.   SC_DragMove = $F012; { a magic number }
  361. var
  362.   BottomMargin : integer;
  363. begin
  364.   ListView1.align := alNone; // turn off align during movement
  365.   SplitterPanel.align := alNone;
  366.                        // resize panel so that the top and bottom edges
  367.                        // don't appear if the mouse is moved vertically
  368.   BottomMargin := Form1.ClientHeight - Y;
  369.   SplitterPanel.Top := -BottomMargin;
  370.   SplitterPanel.Height := Form1.ClientHeight * 2;
  371.   Form1.Update;        // update to ensure Memo contents are repainted
  372.   ReleaseCapture;
  373.                        // --- Move the splitter panel ---
  374.   SplitterPanel.perform(WM_SysCommand, SC_DragMove, 0);
  375.                        // make sure Memos aren't resized beneath a minimum width
  376.   if SplitterPanel.Left <= MINWIDTH then
  377.      SplitterPanel.Left := MINWIDTH
  378.   else if (Form1.ClientWidth - (SplitterPanel.Left + SplitterPanel.Width)) <= MINWIDTH then
  379.      SplitterPanel.Left := Form1.ClientWidth - (SplitterPanel.Width+MINWIDTH);
  380.                        // restore default alignments
  381.   TreeView1.Width := SplitterPanel.Left;
  382.   SplitterPanel.align := alLeft;
  383.   ListView1.align := alClient;
  384.  
  385. end;
  386.  
  387. procedure TForm1.FormResize(Sender: TObject);
  388. { Don't allow the user to resize the form below a sensible minimum size }
  389. begin
  390.    if Form1.Width < (MINWIDTH * 2) then
  391.    begin
  392.       Form1.Width := (MINWIDTH * 2);
  393.       TreeView1.Width := MINWIDTH;
  394.  
  395.    end;
  396. end;
  397.  
  398. procedure TForm1.DriveComboBox1Change(Sender: TObject);
  399. { When a new drive has been selected, populate the TreeView with
  400.   the directories on that drive }
  401. var
  402.    Node : TTreeNode;
  403.    currDir : string;
  404. begin
  405.   // When  DriveComboBox1 ItemIndex is set during Form creation,
  406.   // Drive will = ''
  407.   if DriveComboBox1.Drive = '' then
  408.      currDir := defaultDir
  409.   else
  410.      currDir := DriveComboBox1.Drive + ':' ;
  411.  
  412.   if not DiskInDrive( currDir[1] ) then
  413.      ShowMessage( 'No Disk in: ' + currDir )
  414.   else
  415.   begin
  416.     TreeView1.Items.Clear;
  417.     Node := TreeView1.Items.Add(TreeView1.Selected, currDir);
  418.  
  419.     Node.SelectedIndex := 1;
  420.     Node.Selected := true;
  421.     addSubDirs(currDir+'\', Node);
  422.     TreeView1.AlphaSort;
  423.               // Expand(false) means - don't recurse to expand all child items
  424.               // Expand(true) would havethe effect of logging (and expanding)
  425.               // all subdirectories beneath the root!
  426.     TreeView1.Items[0].Expand(false);
  427.     currDirName := fullPath(TreeView1.Selected);
  428.     updateListView;
  429.     updatePanels;
  430.   end;
  431.  
  432. end;
  433.  
  434. procedure TForm1.DeleteClick(Sender: TObject);
  435. { Delete the file whose name is selected }
  436. begin
  437.   if ListView1.Selected <> nil then // Check that a file name is Selected
  438.     if MessageDlg('Delete '+ListView1.Selected.Caption+'?', mtConfirmation,
  439.                   [mbYes, mbNo],0) = mrYes then
  440.                   if DeleteFile(CurrDirName+ListView1.Selected.Caption) then
  441.                   begin
  442.                      updateListView;
  443.                      updatePanels;
  444.                   end
  445.                   else ShowMessage( 'Cannot delete this file!');
  446. end;
  447.  
  448. procedure TForm1.RenameClick(Sender: TObject);
  449. begin
  450.   if ListView1.Selected = nil then
  451.      ShowMessage('There is no file name selected!' )
  452.   else
  453.      ListView1.Selected.EditCaption;
  454. end;
  455.  
  456. procedure TForm1.ListView1Edited(Sender: TObject; Item: TListItem;
  457.   var S: string);
  458. begin
  459.    ItemEdited := True; { set a flag to show that item caption has been edited }
  460. end;
  461.  
  462. procedure TForm1.ListView1Change(Sender: TObject; Item: TListItem;
  463.   Change: TItemChange);
  464. { if an item's caption has just been edited (ItemEdited = true) try
  465.   to rename the file. If the Rename operation fails, restore the
  466.   previous caption (curreFileName) and display an error message }
  467. var
  468.    newname : string;
  469.    badname : boolean;  // true if a bad or blank file name is entered
  470. begin
  471.   badname := false;
  472.   if ItemEdited = true then
  473.   begin
  474.     ItemEdited := false;
  475.     if ListView1.Selected = nil then
  476.       ShowMessage ('Nothing is selected' )
  477.     else
  478.     begin  // if ListView1.Selected <> nil
  479.       newname := Item.Caption;
  480.       if newname = '' then
  481.          badname := true
  482.       else
  483.       if RenameFile(currDirName+currFileName, currDirName+newname) then
  484.         currFileName := newname
  485.       else
  486.          badname := true;
  487.       // If File Renaming failed, restore old name
  488.       if badname then
  489.       begin
  490.         ListView1.ItemFocused.Caption := currFileName;
  491.         ShowMessage('Cannot rename ' + currDirName+currFileName +
  492.                    ' to ' + currDirName+newname );
  493.       end; // end if RenameFile fails
  494.     end;  // end if ListView1.Selected <> nil
  495.   end; // end if ItemEdited = true
  496. end;
  497.  
  498.  
  499. // ===== DRAG & DROP =====
  500. procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  501.   Shift: TShiftState; X, Y: Integer);
  502. begin
  503.   if Button = mbLeft then
  504.   begin
  505.   if ssCtrl in Shift then
  506.      FMove := false
  507.   else
  508.      FMove := true;{ User wants to copy if Ctrl was held down - else move }
  509.      with Sender as TListView do
  510.      begin
  511.        if Selected <> nil then
  512.           BeginDrag(false);
  513.      end;
  514.   end;
  515. end;
  516.  
  517. procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  518.   State: TDragState; var Accept: Boolean);
  519. begin
  520.   if Source is TListView then
  521.      Accept := true;
  522. end;
  523.  
  524. procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
  525. var
  526.    srcfile, destdir, action : string;
  527. begin
  528.   srcfile := currDirName+ListView1.Selected.Caption;
  529.   destdir := fullpath(TreeView1.GetNodeAt(X, Y))+ListView1.Selected.Caption;
  530.   if FMove then action := 'Move' else action := 'Copy';
  531.   if Source is TListView then
  532.       if MessageDlg(action + ' ' + srcfile + ' to ' +
  533.          destdir + '?',
  534.          mtInformation, [mbYes, mbNo], 0) = mrYes then
  535.          begin
  536.            if FMove then
  537.            begin
  538.               MoveFile(srcfile,destdir);
  539.               updateListView;
  540.            end
  541.            else
  542.                CopyFile(srcfile,destdir);
  543.            updatePanels;
  544.          end;
  545. end;
  546.  
  547. end.
  548.