{ /*************************************************************************** ShellCtrls.pas ------------ ***************************************************************************/ ***************************************************************************** * * * This file is part of the Lazarus Component Library (LCL) * * * * See the file COPYING.modifiedLGPL.txt, included in this distribution, * * for details about the copyright. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * ***************************************************************************** } unit ShellCtrls; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Graphics, ComCtrls, FileUtil; type { TObjectTypes } TObjectType = (otFolders, otNonFolders, otHidden); TObjectTypes = set of TObjectType; { Forward declaration of the classes } TCustomShellTreeView = class; TCustomShellListView = class; { TCustomShellTreeView } TCustomShellTreeView = class(TCustomTreeView) private FObjectTypes: TObjectTypes; FShellListView: TCustomShellListView; { Setters and getters } procedure SetShellListView(const Value: TCustomShellListView); protected { Other internal methods } function CanExpand(Node: TTreeNode): Boolean; override; procedure DoSelectionChanged; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; function PopulateTreeNodeWithFiles( ANode: TTreeNode; ANodePath: string): Boolean; procedure PopulateWithBaseFiles; public { Basic methods } constructor Create(AOwner: TComponent); override; destructor Destroy; override; { Methods specific to Lazarus - useful for other classes } class function GetBasePath: string; class procedure GetFilesInDir(const ABaseDir: string; AMask: string; AObjectTypes: TObjectTypes; AResult: TStrings); function GetPathFromNode(ANode: TTreeNode): string; { Properties } property ObjectTypes: TObjectTypes read FObjectTypes write FObjectTypes; property ShellListView: TCustomShellListView read FShellListView write SetShellListView; end; { TShellTreeView } TShellTreeView = class(TCustomShellTreeView) published { TCustomTreeView properties } property Align; property Anchors; property AutoExpand; property BorderSpacing; //property BiDiMode; property BackgroundColor; property BorderStyle; property BorderWidth; property Color; property Constraints; property Enabled; property ExpandSignType; property Font; //property ParentBiDiMode; property ParentColor default False; property ParentFont; property ParentShowHint; property PopupMenu; property ReadOnly; property RightClickSelect; property RowSelect; property ScrollBars; property SelectionColor; property ShowButtons; property ShowHint; property ShowLines; property ShowRoot; property TabOrder; property TabStop default True; property Tag; property ToolTips; property Visible; property OnChange; property OnChanging; property OnClick; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnSelectionChanged; property OnShowHint; property OnUTF8KeyPress; property Options; property TreeLineColor; property TreeLinePenStyle; property ExpandSignColor; { TCustomShellTreeView properties } property ObjectTypes; property ShellListView; end; { TCustomShellListView } TCustomShellListView = class(TCustomListView) private FMask: string; FObjectTypes: TObjectTypes; FRoot: string; FShellTreeView: TCustomShellTreeView; procedure SetMask(const AValue: string); procedure SetShellTreeView(const Value: TCustomShellTreeView); procedure SetRoot(const Value: string); protected procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); override; procedure PopulateWithRoot; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetPathFromItem(ANode: TListItem): string; public property Mask: string read FMask write SetMask; // Can be used to conect to other controls property ObjectTypes: TObjectTypes read FObjectTypes write FObjectTypes; property Root: string read FRoot write SetRoot; property ShellTreeView: TCustomShellTreeView read FShellTreeView write SetShellTreeView; end; { TShellListView } TShellListView = class(TCustomShellListView) published { TCustomListView properties The same as TListView excluding data properties } property Align; property Anchors; property BorderSpacing; property BorderStyle; property BorderWidth; // property Checkboxes; property Color default clWindow; // property Columns; // property ColumnClick; property Constraints; property DragCursor; property DragMode; // property DefaultItemHeight; // property DropTarget; property Enabled; // property FlatScrollBars; property Font; // property FullDrag; // property GridLines; property HideSelection; // property HotTrack; // property HotTrackStyles; // property HoverTime; // property Items; property LargeImages; property MultiSelect; // property OwnerData; // property OwnerDraw; property ParentColor default False; property ParentFont; property ParentShowHint; property PopupMenu; property ReadOnly; property RowSelect; property ScrollBars; property ShowColumnHeaders; property ShowHint; // property ShowWorkAreas; property SmallImages; property SortColumn; property SortType; property StateImages; property TabStop; property TabOrder; property ToolTips; property Visible; property ViewStyle; // property OnAdvancedCustomDraw; // property OnAdvancedCustomDrawItem; // property OnAdvancedCustomDrawSubItem; property OnChange; property OnClick; property OnColumnClick; property OnCompare; property OnContextPopup; // property OnCustomDraw; // property OnCustomDrawItem; // property OnCustomDrawSubItem; property OnDblClick; property OnDeletion; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnResize; property OnSelectItem; property OnStartDrag; property OnUTF8KeyPress; { TCustomShellListView properties } property ObjectTypes; property Root; property ShellTreeView; end; procedure Register; implementation {$ifdef windows} uses Windows; {$endif} { uses ShlObj; // $I shellctrlswin32.inc procedure PopulateTreeViewWithShell(ATreeView: TCustomShellTreeView); var ShellFolder: IShellFolder = nil; Win32ObjectTypes: Integer; // pidl: LPITEMIDLIST; pidlParent: LPITEMIDLIST; begin SHGetSpecialFolderLocation(0, CSIDL_DESKTOP, @pidl); SHGetDesktopFolder(ShellFolder); if ShellFolder = nil then Exit; // Converts the control data into Windows constants Win32ObjectTypes := 0; if otFolders in ATreeView.ObjectTypes then Win32ObjectTypes := Win32ObjectTypes or SHCONTF_FOLDERS; if otNonFolders in ATreeView.ObjectTypes then Win32ObjectTypes := Win32ObjectTypes or SHCONTF_NONFOLDERS; if otHidden in ATreeView.ObjectTypes then Win32ObjectTypes := Win32ObjectTypes or SHCONTF_INCLUDEHIDDEN; // Now gets the name of the desktop folder } { TCustomShellTreeView } procedure TCustomShellTreeView.SetShellListView( const Value: TCustomShellListView); begin if FShellListView=Value then exit; if FShellListView<>nil then FShellListView.RemoveFreeNotification(Self); FShellListView := Value; if FShellListView<>nil then FShellListView.FreeNotification(Self); // Update the pair, it will then update itself // in the setter of this property // Updates only if necessary to avoid circular calls of the setters if Value.ShellTreeView <> Self then Value.ShellTreeView := Self; end; function TCustomShellTreeView.CanExpand(Node: TTreeNode): Boolean; begin Node.DeleteChildren; Result:=inherited CanExpand(Node); if Result then Result := PopulateTreeNodeWithFiles(Node, GetPathFromNode(Node)); end; procedure TCustomShellTreeView.DoSelectionChanged; begin inherited DoSelectionChanged; if Assigned(FShellListView) then begin FShellListView.Root := GetPathFromNode(Selected); FShellListView.Refresh; // Repaint end; end; procedure TCustomShellTreeView.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if AComponent=FShellListView then FShellListView:=nil; end; constructor TCustomShellTreeView.Create(AOwner: TComponent); begin inherited Create(AOwner); // Initial property values ObjectTypes:= [otFolders]; // Populates the base dirs PopulateWithBaseFiles(); end; destructor TCustomShellTreeView.Destroy; begin inherited Destroy; end; { Helper routine. Finds all files/directories directly inside a directory. Does not recurse inside subdirectories. } class procedure TCustomShellTreeView.GetFilesInDir(const ABaseDir: string; AMask: string; AObjectTypes: TObjectTypes; AResult: TStrings); var DirInfo: TSearchRec; FindResult: Integer; IsDirectory, IsValidDirectory, IsHidden, AddFile: Boolean; ObjectData: TObject; SearchStr: string; MaskStr: string; begin if Trim(AMask) = '' then MaskStr := AllFilesMask else MaskStr := AMask; SearchStr := IncludeTrailingPathDelimiter(ABaseDir) + MaskStr; FindResult := FindFirst(SearchStr, faAnyFile, DirInfo); while FindResult = 0 do begin Application.ProcessMessages; IsDirectory := (DirInfo.Attr and FaDirectory = FaDirectory); IsValidDirectory := (DirInfo.Name <> '.') and (DirInfo.Name <> '..'); IsHidden := (DirInfo.Attr and faHidden = faHidden); // First check if we show hidden files if IsHidden then AddFile := (otHidden in AObjectTypes) else AddFile := True; // If it is a directory, check if it is a valid one if IsDirectory then AddFile := AddFile and ((otFolders in AObjectTypes) and IsValidDirectory) else AddFile := AddFile and (otNonFolders in AObjectTypes); // Mark if it is a directory (ObjectData <> nil) if IsDirectory then ObjectData := AResult else ObjectData := nil; // AddFile identifies if the file is valid or not if AddFile then AResult.AddObject(DirInfo.Name, ObjectData); FindResult := FindNext(DirInfo); end; SysUtils.FindClose(DirInfo); end; class function TCustomShellTreeView.GetBasePath: string; begin {$if defined(windows) and not defined(wince)} Result := ''; {$endif} {$ifdef wince} Result := '\'; {$endif} {$ifdef unix} Result := '/'; {$endif} end; { Returns true if at least one item was added, false otherwise } function TCustomShellTreeView.PopulateTreeNodeWithFiles( ANode: TTreeNode; ANodePath: string): Boolean; var i: Integer; Files: TStringList; NewNode: TTreeNode; begin Files := TStringList.Create; try GetFilesInDir(ANodePath, AllFilesMask, FObjectTypes, Files); Result := Files.Count > 0; for i := 0 to Files.Count - 1 do begin NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil); //@Files.Strings[i]); NewNode.HasChildren := Files.Objects[i] <> nil; // This marks if the node is a directory end; finally Files.Free; end; end; procedure TCustomShellTreeView.PopulateWithBaseFiles; {$if defined(windows) and not defined(wince)} const DRIVE_UNKNOWN = 0; DRIVE_NO_ROOT_DIR = 1; DRIVE_REMOVABLE = 2; DRIVE_FIXED = 3; DRIVE_REMOTE = 4; DRIVE_CDROM = 5; DRIVE_RAMDISK = 6; var r: LongWord; Drives: array[0..128] of char; pDrive: PChar; NewNode: TTreeNode; begin // avoids crashes in the IDE by not populating during design if (csDesigning in ComponentState) then Exit; r := GetLogicalDriveStrings(SizeOf(Drives), Drives); if r = 0 then Exit; if r > SizeOf(Drives) then Exit; // raise Exception.Create(SysErrorMessage(ERROR_OUTOFMEMORY)); pDrive := Drives; while pDrive^ <> #0 do begin // r := GetDriveType(pDrive); NewNode := Items.AddChildObject(nil, pDrive, pDrive); NewNode.HasChildren := True; Inc(pDrive, 4); end; end; {$else} begin // avoids crashes in the IDE by not populating during design if (csDesigning in ComponentState) then Exit; PopulateTreeNodeWithFiles(nil, GetBasePath()); end; {$endif} function TCustomShellTreeView.GetPathFromNode(ANode: TTreeNode): string; var rootDir : String; begin // If nothing is selected, then the base is selected if ANode = nil then Exit(GetBasePath()); // In the future use ANode.Data instead of ANode.Text rootDir := PChar(ANode.Text); while (ANode.Parent <> nil)do begin ANode := ANode.Parent; if (PChar(ANode.Text) <> PathDelim) then rootDir := PChar(ANode.Text) + PathDelim + rootDir else rootDir := PChar(ANode.Text) + rootDir; end; // Check, maybe the base path won't be necessary in the future // if the base directory is added to the items list Result := GetBasePath + rootDir; end; { TCustomShellListView } procedure TCustomShellListView.SetShellTreeView( const Value: TCustomShellTreeView); begin if FShellTreeView <> Value then begin if FShellTreeView<>nil then FShellTreeView.RemoveFreeNotification(Self); FShellTreeView := Value; if FShellTreeView<>nil then FShellTreeView.FreeNotification(Self); Clear; if Value <> nil then begin FRoot := Value.GetPathFromNode(Value.Selected); PopulateWithRoot; end; end; // Also update the pair, but only if necessary to avoid circular calls of the setters if Value.ShellListView <> Self then Value.ShellListView := Self; end; procedure TCustomShellListView.SetMask(const AValue: string); begin if AValue <> FMask then begin FMask := AValue; Clear; Items.Clear; PopulateWithRoot(); end; end; procedure TCustomShellListView.SetRoot(const Value: string); begin if FRoot <> Value then begin FRoot := Value; Clear; Items.Clear; PopulateWithRoot(); end; end; procedure TCustomShellListView.ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); begin inherited ChangeBounds(ALeft,ATop,AWidth,AHeight); {$ifdef DEBUG_SHELLCTRLS} debugLn([':>TCustomShellListView.HandleResize']); {$endif} // The correct check is with count, // if Column[0] <> nil then // will raise an exception if Self.Columns.Count < 3 then Exit; Column[0].Width := (70 * AWidth) div 100; Column[1].Width := (15 * AWidth) div 100; Column[2].Width := (15 * AWidth) div 100; {$ifdef DEBUG_SHELLCTRLS} debugLn([':