Explorar o código

UPD: Create ShellTreeView dynamically
FIX: ShellTreeView don't update path (with Lazarus < 1.6)

Alexander Koblov %!s(int64=9) %!d(string=hai) anos
pai
achega
eabe3c4e87
Modificáronse 4 ficheiros con 1517 adicións e 78 borrados
  1. 48 50
      src/fmain.lfm
  2. 1 1
      src/fmain.lrt
  3. 71 27
      src/fmain.pas
  4. 1397 0
      src/ushellctrls.pas

+ 48 - 50
src/fmain.lfm

@@ -44,38 +44,29 @@ object frmMain: TfrmMain
     OnToolItemShortcutsHint = MainToolBarToolItemShortcutsHint
     GlyphSize = 16
   end
-  object ShellTreeView: TShellTreeView
+  object TreePanel: TPanel
     Left = 0
-    Height = 191
+    Height = 194
     Top = 20
     Width = 121
     Align = alLeft
-    FileSortType = fstAlphabet
-    ReadOnly = True
-    RightClickSelect = True
-    ScrollBars = ssAutoBoth
+    BevelOuter = bvNone
     TabOrder = 0
-    OnAdvancedCustomDrawItem = ShellTreeViewAdvancedCustomDrawItem
-    OnDblClick = ShellTreeViewDblClick
-    OnKeyDown = ShellTreeViewKeyDown
-    OnMouseUp = ShellTreeViewMouseUp
-    Options = [tvoAutoItemHeight, tvoHideSelection, tvoKeepCollapsedNodes, tvoReadOnly, tvoRightClickSelect, tvoShowButtons, tvoShowLines, tvoShowRoot, tvoToolTips]
-    ObjectTypes = [otFolders]
   end
   object TreeSplitter: TSplitter
     Left = 121
-    Height = 191
+    Height = 194
     Top = 20
     Width = 5
   end
   object pnlMain: TPanel
     Left = 126
-    Height = 191
+    Height = 194
     Top = 20
     Width = 634
     Align = alClient
     BevelOuter = bvNone
-    ClientHeight = 191
+    ClientHeight = 194
     ClientWidth = 634
     TabOrder = 5
     object pnlDisk: TPanel
@@ -159,24 +150,24 @@ object frmMain: TfrmMain
     end
     object pnlNotebooks: TPanel
       Left = 0
-      Height = 167
+      Height = 170
       Top = 24
       Width = 634
       Align = alClient
       BevelOuter = bvNone
-      ClientHeight = 167
+      ClientHeight = 170
       ClientWidth = 634
       FullRepaint = False
       TabOrder = 1
       OnResize = pnlNotebooksResize
       object pnlLeft: TPanel
         Left = 0
-        Height = 167
+        Height = 170
         Top = 0
         Width = 511
         Align = alLeft
         BevelOuter = bvNone
-        ClientHeight = 167
+        ClientHeight = 170
         ClientWidth = 511
         TabOrder = 0
         OnDblClick = pnlLeftRightDblClick
@@ -297,12 +288,12 @@ object frmMain: TfrmMain
       end
       object pnlRight: TPanel
         Left = 511
-        Height = 167
+        Height = 170
         Top = 0
         Width = 123
         Align = alClient
         BevelOuter = bvNone
-        ClientHeight = 167
+        ClientHeight = 170
         ClientWidth = 123
         TabOrder = 1
         OnDblClick = pnlLeftRightDblClick
@@ -438,7 +429,7 @@ object frmMain: TfrmMain
   object PanelAllProgress: TPanel
     Left = 0
     Height = 0
-    Top = 211
+    Top = 214
     Width = 760
     Align = alBottom
     AutoSize = True
@@ -449,7 +440,7 @@ object frmMain: TfrmMain
     Cursor = crVSplit
     Left = 0
     Height = 3
-    Top = 211
+    Top = 214
     Width = 760
     Align = alBottom
     AutoSnap = False
@@ -460,35 +451,35 @@ object frmMain: TfrmMain
   object pnlCommand: TPanel
     AnchorSideBottom.Control = LogSplitter
     Left = 0
-    Height = 81
-    Top = 214
+    Height = 78
+    Top = 217
     Width = 760
     Align = alBottom
     AutoSize = True
     BevelOuter = bvNone
-    ClientHeight = 81
+    ClientHeight = 78
     ClientWidth = 760
     FullRepaint = False
     TabOrder = 8
     object pnlCmdLine: TPanel
       Left = 0
-      Height = 27
+      Height = 24
       Top = 54
       Width = 760
       Align = alClient
       AutoSize = True
       BevelOuter = bvNone
       ChildSizing.TopBottomSpacing = 2
-      ClientHeight = 27
+      ClientHeight = 24
       ClientWidth = 760
       TabOrder = 1
       object lblCommandPath: TLabel
         AnchorSideTop.Control = edtCommand
         AnchorSideTop.Side = asrCenter
         Left = 0
-        Height = 15
-        Top = 6
-        Width = 24
+        Height = 14
+        Top = 5
+        Width = 28
         Caption = 'Path'
         ParentColor = False
         ShowAccelChar = False
@@ -499,13 +490,13 @@ object frmMain: TfrmMain
         AnchorSideTop.Control = pnlCmdLine
         AnchorSideRight.Control = pnlCmdLine
         AnchorSideRight.Side = asrBottom
-        Left = 28
-        Height = 23
+        Left = 32
+        Height = 20
         Top = 2
-        Width = 732
+        Width = 728
         Anchors = [akTop, akLeft, akRight]
         BorderSpacing.Left = 4
-        ItemHeight = 15
+        ItemHeight = 14
         OnExit = edtCommandExit
         OnKeyDown = edtCommandKeyDown
         TabOrder = 0
@@ -884,11 +875,19 @@ object frmMain: TfrmMain
         ShortCut = 24642
       end>
     MouseActions = <>
+    MouseTextActions = <>
     MouseSelActions = <>
     VisibleSpecialChars = [vscSpace, vscTabAtLast]
     ReadOnly = True
     RightEdge = 0
     ScrollBars = ssVertical
+    SelectedColor.BackPriority = 50
+    SelectedColor.ForePriority = 50
+    SelectedColor.FramePriority = 50
+    SelectedColor.BoldPriority = 50
+    SelectedColor.ItalicPriority = 50
+    SelectedColor.UnderlinePriority = 50
+    SelectedColor.StrikeOutPriority = 50
     BracketHighlightStyle = sbhsBoth
     BracketMatchColor.Background = clNone
     BracketMatchColor.Foreground = clNone
@@ -925,55 +924,55 @@ object frmMain: TfrmMain
       Left = 0
       Height = 20
       Top = 0
-      Width = 89
+      Width = 88
       Action = actView
       Flat = True
     end
     object btnF4: TSpeedButton
-      Left = 89
+      Left = 88
       Height = 20
       Top = 0
-      Width = 84
+      Width = 83
       Action = actEdit
       Flat = True
     end
     object btnF5: TSpeedButton
-      Left = 173
+      Left = 171
       Height = 20
       Top = 0
-      Width = 92
+      Width = 91
       Action = actCopy
       Flat = True
     end
     object btnF6: TSpeedButton
-      Left = 265
+      Left = 262
       Height = 20
       Top = 0
-      Width = 94
+      Width = 91
       Action = actRename
       Flat = True
     end
     object btnF7: TSpeedButton
-      Left = 359
+      Left = 353
       Height = 20
       Top = 0
-      Width = 112
+      Width = 117
       Action = actMakeDir
       Caption = 'Directory'
       Flat = True
     end
     object btnF8: TSpeedButton
-      Left = 471
+      Left = 470
       Height = 20
       Top = 0
-      Width = 97
+      Width = 100
       Caption = 'Delete'
       Flat = True
       OnClick = btnF8Click
       OnMouseDown = btnF8MouseDown
     end
     object btnF9: TSpeedButton
-      Left = 568
+      Left = 570
       Height = 20
       Top = 0
       Width = 110
@@ -982,10 +981,10 @@ object frmMain: TfrmMain
       Flat = True
     end
     object btnF10: TSpeedButton
-      Left = 678
+      Left = 680
       Height = 20
       Top = 0
-      Width = 82
+      Width = 80
       Action = actExit
       Caption = 'Exit'
       Flat = True
@@ -2387,7 +2386,6 @@ object frmMain: TfrmMain
       Caption = 'Close Duplicate Tabs'
       OnExecute = actExecute
     end
-
   end
   object pmHotList: TPopupMenu
     Images = imgLstDirectoryHotlist

+ 1 - 1
src/fmain.lrt

@@ -136,7 +136,6 @@ TFRMMAIN.ACTSETTABOPTIONNORMAL.CAPTION=&Normal
 TFRMMAIN.ACTSETTABOPTIONPATHLOCKED.CAPTION=&Locked
 TFRMMAIN.ACTSETTABOPTIONPATHRESETS.CAPTION=Locked with &Directory Changes Allowed
 TFRMMAIN.ACTSETTABOPTIONDIRSINNEWTAB.CAPTION=Locked with Directories Opened in New &Tabs
-TFRMMAIN.ACTCLOSEDUPLICATETABS.CAPTION=Close Duplicate Tabs
 TFRMMAIN.ACTLEFTBRIEFVIEW.CAPTION=Brief view on left panel
 TFRMMAIN.ACTLEFTCOLUMNSVIEW.CAPTION=Columns view on left panel
 TFRMMAIN.ACTLEFTTHUMBVIEW.CAPTION=Thumbnails view on left panel
@@ -225,6 +224,7 @@ TFRMMAIN.ACTUNIVERSALSINGLEDIRECTSORT.CAPTION=Sort according to parameters
 TFRMMAIN.ACTCOUNTDIRCONTENT.CAPTION=Sho&w Occupied Space
 TFRMMAIN.ACTTOGGLEFULLSCREENCONSOLE.CAPTION=Toggle fullscreen mode console
 TFRMMAIN.ACTTREEVIEW.CAPTION=&Tree View Panel
+TFRMMAIN.ACTCLOSEDUPLICATETABS.CAPTION=Close Duplicate Tabs
 TFRMMAIN.TBEDIT.CAPTION=Edit
 TFRMMAIN.TBDELETE.CAPTION=Delete
 TFRMMAIN.TBCHANGEDIR.CAPTION=CD

+ 71 - 27
src/fmain.pas

@@ -40,10 +40,10 @@ unit fMain;
 interface
 
 uses
-  Graphics, Forms, Menus, Controls, StdCtrls, ExtCtrls, ActnList, ShellCtrls,
+  Graphics, Forms, Menus, Controls, StdCtrls, ExtCtrls, ActnList,
   Buttons, SysUtils, Classes, SynEdit, LCLType, ComCtrls, LResources,
   KASToolBar, KASComboBox, uCmdBox, uFilePanelSelect, uBriefFileView,
-  uFileView, uFileSource, uFileViewNotebook, uFile,
+  uFileView, uFileSource, uFileViewNotebook, uFile, LCLVersion,
   uOperationsManager, uFileSourceOperation, uDrivesList, uTerminal, DCClassesUtf8,
   DCXmlConfig, uDrive, uDriveWatcher, uDCVersion, uMainCommands, uFormCommands,
   uOperationsPanel, KASToolItems, uKASToolItemsExtended, uCmdLineParams, uOSForms
@@ -457,8 +457,9 @@ type
     actFileSpliter: TAction;
     pmToolBar: TPopupMenu;
     MainTrayIcon: TTrayIcon;
+    TreePanel: TPanel;
     TreeSplitter: TSplitter;
-    ShellTreeView: TShellTreeView;
+    ShellTreeView: TCustomTreeView;
 
     procedure actExecute(Sender: TObject);
     procedure btnF8MouseDown(Sender: TObject; Button: TMouseButton;
@@ -708,6 +709,7 @@ type
                                         out DestPath, DestMask: String); overload;
     procedure SetActiveFrame(panel: TFilePanelSelect);
     procedure SetActiveFrame(FileView: TFileView);
+    procedure UpdateShellTreeView;
     procedure UpdateTreeViewPath;
     procedure UpdateTreeView;
     procedure UpdateDiskCount;
@@ -768,7 +770,7 @@ implementation
 
 uses
   uFileProcs, uShellContextMenu,
-  Math, LCLIntf, LCLVersion, Dialogs, uGlobs, uLng, uMasks, fCopyMoveDlg, uQuickViewPanel,
+  Math, LCLIntf, Dialogs, uGlobs, uLng, uMasks, fCopyMoveDlg, uQuickViewPanel,
   uShowMsg, uDCUtils, uLog, uGlobsPaths, LCLProc, uOSUtils, uPixMapManager,
   uDragDropEx, uKeyboard, uFileSystemFileSource, fViewOperations, uMultiListFileSource,
   uFileSourceOperationTypes, uFileSourceCopyOperation, uFileSourceMoveOperation,
@@ -782,6 +784,12 @@ uses
   {$ELSE}
   , uColumnsFileView
   {$ENDIF}
+  // TODO: remove when switch to Lazarus 1.6
+  {$IF (lcl_fullversion < 1060000)}
+  , uShellCtrls
+  {$ELSE}
+  , ShellCtrls
+  {$ENDIF}
   ;
 
 const
@@ -930,12 +938,6 @@ begin
 
   MainFormCreate(Self);
 
-  // Separate tree
-  ShellTreeView.Images := TImageList.Create(Self);
-  ShellTreeView.Images.Width := gIconsSize;
-  ShellTreeView.Images.Height := gIconsSize;
-  ShellTreeView.Images.Add(PixMapManager.GetFolderIcon(gIconsSize, ShellTreeView.Color), nil);
-
   // Load command line history
   edtCommand.Items.Assign(glsCmdLineHistory);
 
@@ -1373,7 +1375,7 @@ procedure TfrmMain.ShellTreeViewDblClick(Sender: TObject);
 begin
   ShellTreeView.Tag := 1;
   try
-    SetFileSystemPath(ActiveFrame, ShellTreeView.Path);
+    SetFileSystemPath(ActiveFrame, (ShellTreeView as TShellTreeView).Path);
   finally
     ShellTreeView.Tag := 0;
   end;
@@ -3836,12 +3838,59 @@ begin
   end;
 end;
 
+type
+  TCustomShellTreeViewCrack = class(TCustomShellTreeView);
+
+procedure TfrmMain.UpdateShellTreeView;
+begin
+  actTreeView.Checked := gSeparateTree;
+  TreeSplitter.Visible := gSeparateTree;
+  TreePanel.Visible := gSeparateTree;
+
+  if gSeparateTree and (ShellTreeView = nil) then
+  begin
+    ShellTreeView := TShellTreeView.Create(TreePanel);
+    ShellTreeView.Parent := TreePanel;
+    ShellTreeView.Align := alClient;
+    ShellTreeView.ScrollBars := ssAutoBoth;
+
+    with ShellTreeView as TShellTreeView do
+    begin
+      ReadOnly := True;
+      RightClickSelect := True;
+      TCustomShellTreeViewCrack(ShellTreeView).PopulateWithBaseFiles;
+
+      Images := TImageList.Create(Self);
+      Images.Width := gIconsSize;
+      Images.Height := gIconsSize;
+      Images.Add(PixMapManager.GetFolderIcon(gIconsSize, ShellTreeView.Color), nil);
+
+      OnKeyDown := @ShellTreeViewKeyDown;
+      OnMouseUp := @ShellTreeViewMouseUp;
+      OnDblClick := @ShellTreeViewDblClick;
+      OnAdvancedCustomDrawItem := @ShellTreeViewAdvancedCustomDrawItem;
+
+      Options := Options - [tvoThemedDraw];
+      Options := Options + [tvoReadOnly, tvoRightClickSelect];
+    end;
+  end;
+
+  if gSeparateTree then
+  begin
+    ShellTreeView.Font.Color := gForeColor;
+    ShellTreeView.BackgroundColor := gBackColor;
+    ShellTreeView.SelectionColor := gCursorColor;
+    FontOptionsToFont(gFonts[dcfMain], ShellTreeView.Font);
+  end;
+end;
+
 procedure TfrmMain.UpdateTreeViewPath;
 begin
-  if (gSeparateTree = False) or (ShellTreeView.Tag <> 0) then Exit;
+  if (gSeparateTree = False) then Exit;
+  if (ShellTreeView.Tag <> 0) then Exit;
   if (fspDirectAccess in ActiveFrame.FileSource.Properties) then
   try
-    ShellTreeView.Path := ActiveFrame.CurrentPath;
+    (ShellTreeView as TShellTreeView).Path := ActiveFrame.CurrentPath;
   except
     // Skip
   end;
@@ -3849,10 +3898,14 @@ end;
 
 procedure TfrmMain.UpdateTreeView;
 begin
-  if gShowSystemFiles then
-    ShellTreeView.ObjectTypes:= ShellTreeView.ObjectTypes + [otHidden]
-  else begin
-    ShellTreeView.ObjectTypes:= ShellTreeView.ObjectTypes - [otHidden];
+  if (ShellTreeView = nil) then Exit;
+  with (ShellTreeView as TShellTreeView) do
+  begin
+    if gShowSystemFiles then
+      ObjectTypes:= ObjectTypes + [otHidden]
+    else begin
+      ObjectTypes:= ObjectTypes - [otHidden];
+    end;
   end;
 end;
 
@@ -4534,16 +4587,7 @@ begin
     end;
 
     // Separate tree
-    actTreeView.Checked := gSeparateTree;
-    TreeSplitter.Visible := gSeparateTree;
-    ShellTreeView.Visible := gSeparateTree;
-    if gSeparateTree then
-    begin
-      ShellTreeView.Font.Color := gForeColor;
-      ShellTreeView.BackgroundColor := gBackColor;
-      ShellTreeView.SelectionColor := gCursorColor;
-      FontOptionsToFont(gFonts[dcfMain], ShellTreeView.Font);
-    end;
+    UpdateShellTreeView;
 
     // Operations panel and menu
     if (gPanelOfOp = False) then

+ 1397 - 0
src/ushellctrls.pas

@@ -0,0 +1,1397 @@
+{
+ /***************************************************************************
+                                   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 license.
+ *****************************************************************************
+}
+unit uShellCtrls;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Graphics, LCLType, AvgLvlTree,
+  ComCtrls, FileUtil, LazFileUtils, LazUtf8, LCLStrConsts;
+
+{$if defined(Windows) or defined(darwin)}
+{$define CaseInsensitiveFilenames}
+{$endif}
+{$IF defined(CaseInsensitiveFilenames) or defined(darwin)}
+{$DEFINE NotLiteralFilenames}
+{$ENDIF}
+
+type
+
+  { TObjectTypes }
+
+  TObjectType = (otFolders, otNonFolders, otHidden);
+
+  TObjectTypes = set of TObjectType;
+
+  TFileSortType = (fstNone, fstAlphabet, fstFoldersFirst);
+
+  { Forward declaration of the classes }
+
+  TCustomShellTreeView = class;
+  TCustomShellListView = class;
+
+  { TCustomShellTreeView }
+
+  TCustomShellTreeView = class(TCustomTreeView)
+  private
+    FObjectTypes: TObjectTypes;
+    FRoot: string;
+    FShellListView: TCustomShellListView;
+    FFileSortType: TFileSortType;
+    FInitialRoot: String;
+    { Setters and getters }
+    function GetPath: string;
+    procedure SetFileSortType(const AValue: TFileSortType);
+    procedure SetObjectTypes(AValue: TObjectTypes);
+    procedure SetPath(AValue: string);
+    procedure SetRoot(const AValue: string);
+    procedure SetShellListView(const Value: TCustomShellListView);
+  protected
+    procedure Loaded; override;
+    { Other methods specific to Lazarus }
+    function  PopulateTreeNodeWithFiles(
+      ANode: TTreeNode; ANodePath: string): Boolean;
+    procedure PopulateWithBaseFiles;
+    procedure DoSelectionChanged; override;
+    function CanExpand(Node: TTreeNode): Boolean; override;
+  public
+    { Basic methods }
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+
+    { Methods specific to Lazarus - useful for other classes }
+    class function  GetBasePath: string;
+    function  GetRootPath: string;
+    class procedure GetFilesInDir(const ABaseDir: string;
+      AMask: string; AObjectTypes: TObjectTypes; AResult: TStrings; AFileSortType: TFileSortType = fstNone);
+    { Other methods specific to Lazarus }
+    function  GetPathFromNode(ANode: TTreeNode): string;
+    function  GetSelectedNodePath: string; deprecated 'Use property Path instead';
+    procedure Refresh(ANode: TTreeNode); overload;
+
+    { Properties }
+    property ObjectTypes: TObjectTypes read FObjectTypes write SetObjectTypes;
+    property ShellListView: TCustomShellListView read FShellListView write SetShellListView;
+    property FileSortType: TFileSortType read FFileSortType write SetFileSortType;
+    property Root: string read FRoot write SetRoot;
+    property Path: string read GetPath write SetPath;
+
+    { Protected properties which users may want to access, see bug 15374 }
+    property Items;
+  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 FileSortType;
+    property HideSelection;
+    property HotTrack;
+    property Images;
+    property Indent;
+    //property ParentBiDiMode;
+    property ParentColor default False;
+    property ParentFont;
+    property ParentShowHint;
+    property PopupMenu;
+    property ReadOnly;
+    property RightClickSelect;
+    property Root;
+    property RowSelect;
+    property ScrollBars;
+    property SelectionColor;
+    property ShowButtons;
+    property ShowHint;
+    property ShowLines;
+    property ShowRoot;
+    property StateImages;
+    property TabOrder;
+    property TabStop default True;
+    property Tag;
+    property ToolTips;
+    property Visible;
+    property OnAdvancedCustomDraw;
+    property OnAdvancedCustomDrawItem;
+    property OnChange;
+    property OnChanging;
+    property OnClick;
+    property OnCollapsed;
+    property OnCollapsing;
+    property OnCustomDraw;
+    property OnCustomDrawItem;
+    property OnDblClick;
+    property OnEdited;
+    property OnEditing;
+    property OnEnter;
+    property OnExit;
+    property OnExpanded;
+    property OnExpanding;
+    property OnGetImageIndex;
+    property OnGetSelectedIndex;
+    property OnKeyDown;
+    property OnKeyPress;
+    property OnKeyUp;
+    property OnMouseDown;
+    property OnMouseEnter;
+    property OnMouseLeave;
+    property OnMouseMove;
+    property OnMouseUp;
+    property OnMouseWheel;
+    property OnMouseWheelDown;
+    property OnMouseWheelUp;
+    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;
+    { Setters and getters }
+    procedure SetMask(const AValue: string);
+    procedure SetShellTreeView(const Value: TCustomShellTreeView);
+    procedure SetRoot(const Value: string);
+  protected
+    { Methods specific to Lazarus }
+    procedure PopulateWithRoot();
+    procedure Resize; override;
+  public
+    { Basic methods }
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    { Methods specific to Lazarus }
+    function GetPathFromItem(ANode: TListItem): string;
+    { Properties }
+    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;
+    { Protected properties which users may want to access, see bug 15374 }
+    property Items;
+  end;
+
+  { TShellListView }
+
+  TShellListView = class(TCustomShellListView)
+  public
+    property Columns;
+  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 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 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 default vsReport;
+//    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 OnMouseEnter;
+    property OnMouseLeave;
+    property OnMouseMove;
+    property OnMouseUp;
+    property OnMouseWheel;
+    property OnMouseWheelDown;
+    property OnMouseWheelUp;
+    property OnResize;
+    property OnSelectItem;
+    property OnStartDrag;
+    property OnUTF8KeyPress;
+    { TCustomShellListView properties }
+    property ObjectTypes;
+    property Root;
+    property ShellTreeView;
+  end;
+
+  EShellCtrl = class(Exception);
+  EInvalidPath = class(EShellCtrl);
+
+function DbgS(OT: TObjectTypes): String; overload;
+
+procedure Register;
+
+implementation
+
+{$ifdef windows}
+uses Windows;
+{$endif}
+
+
+
+function DbgS(OT: TObjectTypes): String; overload;
+begin
+  Result := '[';
+  if (otFolders in OT) then Result := Result + 'otFolders,';
+  if (otNonFolders in OT) then Result := Result + 'otNonFolders,';
+  if (otHidden in OT) then Result := Result + 'otHidden';
+  if Result[Length(Result)] = ',' then System.Delete(Result, Length(Result), 1);
+  Result := Result + ']';
+end;
+
+
+{
+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);
+var
+  Tmp: TCustomShellListView;
+begin
+  if FShellListView = Value then Exit;
+
+  if Assigned(FShellListView) then
+  begin
+    Tmp := FShellListView;
+    FShellListView := nil;
+    Tmp.ShellTreeView := nil;
+  end;
+
+  FShellListView := Value;
+
+  // 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 Assigned(Value) and (Value.ShellTreeView <> Self) then
+    Value.ShellTreeView := Self;
+end;
+
+procedure TCustomShellTreeView.Loaded;
+begin
+  inherited Loaded;
+  if (FInitialRoot = '') then
+    PopulateWithBaseFiles()
+  else
+    SetRoot(FInitialRoot);
+end;
+
+procedure TCustomShellTreeView.SetRoot(const AValue: string);
+var
+  RootNode: TTreeNode;
+begin
+  if FRoot=AValue then exit;
+  if (csLoading in ComponentState) then
+  begin
+    FInitialRoot := AValue;
+    Exit;
+  end;
+  //Delphi raises an unspecified exception in this case, but don't crash the IDE at designtime
+  if not (csDesigning in ComponentState)
+     and (AValue <> '')
+     and not DirectoryExistsUtf8(ExpandFilenameUtf8(AValue)) then
+     Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidRoot,[ExpandFileNameUtf8(AValue)]);
+  if (AValue = '') then
+    FRoot := GetBasePath
+  else
+    FRoot:=AValue;
+  Items.Clear;
+  if FRoot = '' then
+  begin
+    PopulateWithBaseFiles()
+  end
+  else
+  begin
+    //Add a node for Root and expand it (issue #0024230)
+    //Make FRoot contain fully qualified pathname, we need it later in GetPathFromNode()
+    FRoot := ExpandFileNameUtf8(FRoot);
+    //Set RootNode.Text to AValue so user can choose if text is fully qualified path or not
+    RootNode := Items.AddChild(nil, AValue);
+    RootNode.HasChildren := True;
+    RootNode.Expand(False);
+  end;
+  if Assigned(ShellListView) then
+    ShellListView.Root := FRoot;
+end;
+
+// ToDo: Optimize, now the tree is populated in constructor, SetRoot and SetFileSortType.
+// For some reason it does not show in performance really.
+procedure TCustomShellTreeView.SetFileSortType(const AValue: TFileSortType);
+var
+  RootNode: TTreeNode;
+  CurrPath: String;
+begin
+  if FFileSortType=AValue then exit;
+  FFileSortType:=AValue;
+  if (csLoading in ComponentState) then Exit;
+  CurrPath := GetPath;
+  try
+    BeginUpdate;
+    Items.Clear;
+    if FRoot = '' then
+      PopulateWithBaseFiles()
+    else
+    begin
+      RootNode := Items.AddChild(nil, FRoot);
+      RootNode.HasChildren := True;
+      RootNode.Expand(False);
+      try
+       SetPath(CurrPath);
+      except
+        // CurrPath may have been removed in the mean time by another process, just ignore
+        on E: EInvalidPath do ;//
+      end;
+    end;
+  finally
+    EndUpdate;
+  end;
+end;
+
+procedure TCustomShellTreeView.SetObjectTypes(AValue: TObjectTypes);
+var
+  CurrPath: String;
+begin
+  if FObjectTypes = AValue then Exit;
+  FObjectTypes := AValue;
+  if (csLoading in ComponentState) then Exit;
+  CurrPath := GetPath;
+  try
+    BeginUpdate;
+    Refresh(nil);
+    try
+       SetPath(CurrPath);
+    except
+      // CurrPath may have been removed in the mean time by another process, just ignore
+      on E: EInvalidPath do ;//
+    end;
+  finally
+    EndUpdate;
+  end;
+end;
+
+function TCustomShellTreeView.CanExpand(Node: TTreeNode): Boolean;
+var
+  OldAutoExpand: Boolean;
+begin
+  Result:=inherited CanExpand(Node);
+  if not Result then exit;
+  OldAutoExpand:=AutoExpand;
+  AutoExpand:=False;
+  Node.DeleteChildren;
+  Result := PopulateTreeNodeWithFiles(Node, GetPathFromNode(Node));
+  AutoExpand:=OldAutoExpand;
+end;
+
+constructor TCustomShellTreeView.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FInitialRoot := '';
+
+  // Initial property values
+  FObjectTypes:= [otFolders];
+
+  // Populating the base dirs is done in Loaded
+end;
+
+destructor TCustomShellTreeView.Destroy;
+begin
+  ShellListView := nil;
+  inherited Destroy;
+end;
+
+type
+  { TFileItem }
+  TFileItem = class(TObject)
+    Name: string;
+    isFolder: Boolean;
+    //more data to sort by size, date... etc
+    constructor Create(const DirInfo: TSearchRec);
+  end;
+
+{ TFileItem }
+
+constructor TFileItem.Create(const DirInfo:TSearchRec);
+begin
+  Name:=DirInfo.Name;
+  isFolder:=DirInfo.Attr and FaDirectory > 0;
+end;
+
+function FilesSortAlphabet(p1, p2: Pointer): Integer;
+var
+  f1, f2: TFileItem;
+begin
+  f1:=TFileItem(p1);
+  f2:=TFileItem(p2);
+  Result:=CompareText(f1.Name, f2.Name);
+end;
+
+function FilesSortFoldersFirst(p1,p2: Pointer): Integer;
+var
+  f1, f2: TFileItem;
+begin
+  f1:=TFileItem(p1);
+  f2:=TFileItem(p2);
+  if f1.isFolder=f2.isFolder then
+    Result:=FilesSortAlphabet(p1,p2)
+  else begin
+    if f1.isFolder then Result:=-1
+    else Result:=1;
+  end;
+
+end;
+
+function STVCompareFiles(f1, f2: Pointer): integer;
+begin
+  Result:=CompareFilenames(AnsiString(f1),AnsiString(f2));
+end;
+
+{ Helper routine.
+  Finds all files/directories directly inside a directory.
+  Does not recurse inside subdirectories.
+
+  AMask may contain multiple file masks separated by ;
+  Don't add a final ; after the last mask.
+}
+class procedure TCustomShellTreeView.GetFilesInDir(const ABaseDir: string;
+  AMask: string; AObjectTypes: TObjectTypes; AResult: TStrings; AFileSortType: TFileSortType);
+var
+  DirInfo: TSearchRec;
+  FindResult: Integer;
+  IsDirectory, IsValidDirectory, IsHidden, AddFile: Boolean;
+  ObjectData: TObject;
+  SearchStr: string;
+  MaskStr: string;
+  Files: TList;
+  FileItem: TFileItem;
+  i: Integer;
+  MaskStrings: TStringList;
+  FileTree: TAvgLvlTree;
+  ShortFilename: AnsiString;
+  j: Integer;
+  {$if defined(windows) and not defined(wince)}
+  ErrMode : LongWord;
+  {$endif}
+begin
+  {$if defined(windows) and not defined(wince)}
+  // disables the error dialog, while enumerating not-available drives
+  // for example listing A: path, without diskette present.
+  // WARNING: Since Application.ProcessMessages is called, it might effect some operations!
+  ErrMode:=SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOALIGNMENTFAULTEXCEPT or SEM_NOGPFAULTERRORBOX or SEM_NOOPENFILEERRORBOX);
+  try
+  {$endif}
+
+  if Trim(AMask) = '' then MaskStr := AllFilesMask
+  else MaskStr := AMask;
+
+  // The string list implements support for multiple masks separated
+  // by semi-comma ";"
+  MaskStrings := TStringList.Create;
+  FileTree:=TAvgLvlTree.Create(@STVCompareFiles);
+  try
+    MaskStrings.Delimiter := ';';
+    MaskStrings.DelimitedText := MaskStr;
+
+    if AFileSortType=fstNone then Files:=nil
+    else Files:=TList.Create;
+
+    j:=0;
+    for i := 0 to MaskStrings.Count - 1 do
+    begin
+      if MaskStrings.IndexOf(MaskStrings[i]) < i then Continue; // From patch from bug 17761: TShellListView Mask: duplicated items if mask is " *.ext;*.ext "
+      SearchStr := IncludeTrailingPathDelimiter(ABaseDir) + MaskStrings.Strings[i];
+
+      FindResult := FindFirstUTF8(SearchStr, faAnyFile, DirInfo);
+
+      while FindResult = 0 do
+      begin
+        inc(j);
+        if j=100 then
+        begin
+          Application.ProcessMessages;
+          j:=0;
+        end;
+
+        ShortFilename := DirInfo.Name;
+
+        IsDirectory := (DirInfo.Attr and FaDirectory = FaDirectory);
+
+        IsValidDirectory := (ShortFilename <> '.') and (ShortFilename <> '..');
+
+        IsHidden := (DirInfo.Attr and faHidden = faHidden);
+        //LinuxToWinAttr already does this in FF/FN
+        //{$IFDEF Unix}
+        //if (DirInfo.Name<>'') and (DirInfo.Name[1]='.') then
+        //  IsHidden:=true;
+        //{$ENDIF}
+
+        // 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);
+
+        // AddFile identifies if the file is valid or not
+        if AddFile then
+        begin
+          if not Assigned(Files) then begin
+            // Mark if it is a directory (ObjectData <> nil)
+            if IsDirectory then ObjectData := AResult
+            else ObjectData := nil;
+            if FileTree.Find(Pointer(ShortFilename))=nil then
+            begin
+              // From patch from bug 17761: TShellListView Mask: duplicated items if mask is " *.ext;*.ext "
+              FileTree.Add(Pointer(ShortFilename));
+              AResult.AddObject(ShortFilename, ObjectData);
+            end;
+          end else
+            Files.Add ( TFileItem.Create(DirInfo));
+        end;
+
+        FindResult := FindNextUTF8(DirInfo);
+      end;
+
+      FindCloseUTF8(DirInfo);
+    end;
+  finally
+    FileTree.Free;
+    MaskStrings.Free;
+  end;
+
+  if Assigned(Files) then begin
+    Objectdata:=AResult;
+
+    case AFileSortType of
+      fstAlphabet:     Files.Sort(@FilesSortAlphabet);
+      fstFoldersFirst: Files.Sort(@FilesSortFoldersFirst);
+    end;
+
+    for i:=0 to Files.Count-1 do
+    begin
+      FileItem:=TFileItem(Files[i]);
+      if (i < Files.Count - 1) and (TFileItem(Files[i]).Name = TFileItem(Files[i + 1]).Name) then
+        Continue; // cause Files is sorted // From patch from bug 17761: TShellListView Mask: duplicated items if mask is " *.ext;*.ext "
+      if FileItem.isFolder then
+        AResult.AddObject(FileItem.Name, ObjectData)
+      else
+        AResult.AddObject(FileItem.Name, nil);
+    end;
+    for i:=0 to Files.Count-1 do
+      TFileItem(Files[i]).Free;
+    Files.Free;
+  end;
+
+  {$if defined(windows) and not defined(wince)}
+  finally
+     SetErrorMode(ErrMode);
+  end;
+  {$endif}
+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;
+
+function TCustomShellTreeView.GetRootPath: string;
+begin
+  if FRoot <> '' then
+    Result := FRoot
+  else
+    Result := GetBasePath();
+  if Result <> '' then
+    Result := IncludeTrailingPathDelimiter(Result);
+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;
+
+   function HasSubDir(Const ADir: String): Boolean;
+   var
+     SR: TSearchRec;
+     FindRes: LongInt;
+     Attr: Longint;
+     IsHidden: Boolean;
+   begin
+     Result:=False;
+     try
+       Attr := faDirectory;
+       if (otHidden in fObjectTypes) then Attr := Attr or faHidden;
+       FindRes := FindFirstUTF8(AppendPathDelim(ADir) + AllFilesMask, Attr , SR);
+       while (FindRes = 0) do
+       begin
+         if ((SR.Attr and faDirectory <> 0) and (SR.Name <> '.') and
+            (SR.Name <> '..')) then
+         begin
+           IsHidden := ((Attr and faHidden) > 0);
+           //LinuxToWinAttr already does this in FF/FN
+           //{$IFDEF Unix}
+           //if (SR.Name<>'') and (SR.Name[1]='.') then
+           //  IsHidden := True;
+           //{$ENDIF}
+           if not (IsHidden and (not ((otHidden in fObjectTypes)))) then
+           begin
+             Result := True;
+             Break;
+           end;
+         end;
+         FindRes := FindNextUtf8(SR);
+       end;
+     finally
+       FindCloseUTF8(SR);
+     end; //try
+   end;
+
+begin
+  Result := False;
+  // avoids crashes in the IDE by not populating during design
+  if (csDesigning in ComponentState) then Exit;
+
+  Files := TStringList.Create;
+  try
+    GetFilesInDir(ANodePath, AllFilesMask, FObjectTypes, Files, FFileSortType);
+    Result := Files.Count > 0;
+
+    for i := 0 to Files.Count - 1 do
+    begin
+      NewNode := Items.AddChildObject(ANode, Files.Strings[i], nil); //@Files.Strings[i]);
+      // This marks if the node is a directory (not wether or not there are files in the folder!)
+      // We need this info (is it a folder?) elsewhere.
+      if (fObjectTypes * [otNonFolders] = []) then
+        NewNode.HasChildren := ((Files.Objects[i] <> nil) and
+                               HasSubDir(AppendpathDelim(ANodePath)+Files[i]))
+      else
+        NewNode.HasChildren := (Files.Objects[i] <> nil);
+    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, ExcludeTrailingBackslash(pDrive), pDrive);
+    NewNode.HasChildren := True;
+
+    Inc(pDrive, 4);
+  end;
+end;
+{$else}
+var
+  NewNode: TTreeNode;
+begin
+  // avoids crashes in the IDE by not populating during design
+  // also do not populate before loading is done
+  if ([csDesigning, csLoading] * ComponentState <> []) then Exit;
+
+  // This allows showing "/" in Linux, but in Windows it makes no sense to show the base
+  if GetBasePath() <> '' then
+  begin
+    NewNode := Items.AddChild(nil, GetBasePath());
+    NewNode.HasChildren := True;
+    PopulateTreeNodeWithFiles(NewNode, GetBasePath());
+    NewNode.Expand(False);
+  end
+  else
+    PopulateTreeNodeWithFiles(nil, GetBasePath());
+end;
+{$endif}
+
+procedure TCustomShellTreeView.DoSelectionChanged;
+var
+  ANode: TTreeNode;
+  IsDirectory, MustBeDirectory: Boolean;
+  CurrentNodePath: String;
+begin
+  inherited DoSelectionChanged;
+  ANode := Selected;
+  if Assigned(FShellListView) and Assigned(ANode) then
+  begin
+    //You cannot rely on HasChildren here, because it can become FALSE when user
+    //clicks the expand sign and folder is empty
+    //Issue 0027571
+    MustBeDirectory := not (otNonFolders in FObjectTypes);
+    CurrentNodePath := ChompPathDelim(GetPathFromNode(ANode));
+    IsDirectory := MustBeDirectory or DirectoryExistsUtf8(CurrentNodePath);
+    if IsDirectory then
+    begin
+      //Note: the folder may have been deleted in the mean time
+      //an exception will be raised by the next line in that case
+      FShellListView.Root := GetPathFromNode(ANode)
+    end
+    else
+    begin
+      //At this point we cannot tell if item used to be a folder or a file
+      if not FileExistsUtf8(CurrentNodePath) then
+        Raise EShellCtrl.CreateFmt(sShellCtrlsSelectedItemDoesNotExists,[CurrentNodePath]);
+      if Assigned(Anode.Parent) then
+        FShellListView.Root := GetPathFromNode(ANode.Parent)
+      else
+        FShellListView.Root := '';
+    end;
+  end;
+end;
+
+function TCustomShellTreeView.GetPathFromNode(ANode: TTreeNode): string;
+begin
+  Result := '';
+  if Assigned(ANode) then
+  begin
+    // Build the path. In the future use ANode.Data instead of ANode.Text
+    if (ANode.Parent = nil) and (GetRootPath <> '') then
+      //This node is RootNode and GetRooPath contains fully qualified root path
+      Result := ''
+    else
+      Result := ANode.Text;
+    while (ANode.Parent <> nil) do
+    begin
+      ANode := ANode.Parent;
+      if (ANode.Parent = nil) and (GetRootPath <> '') then
+        //Node.Text of rootnode may not be fully qualified
+        Result := GetRootPath + Result
+      else
+        Result := IncludeTrailingPathDelimiter(ANode.Text) + Result;
+    end;
+    if not FilenameIsAbsolute(Result) then
+      Result := GetRootPath() + Result;    // Include root directory
+  end;
+end;
+
+function TCustomShellTreeView.GetSelectedNodePath: string;
+begin
+  Result := GetPathFromNode(Selected);
+end;
+
+procedure TCustomShellTreeView.Refresh(ANode: TTreeNode);
+//nil will refresh root
+var
+  RootNodeText: String;
+  IsRoot: Boolean;
+begin
+  if (Items.Count = 0) then Exit;
+  //writeln('GetFirstVisibleNode.Text = "',Items.GetFirstVisibleNode.Text,'"');
+  IsRoot := (ANode = nil) or ((ANode = Items.GetFirstVisibleNode) and (GetRootPath <> ''));
+  //writeln('IsRoot = ',IsRoot);
+  if (ANode = nil) and (GetRootPath <> '') then ANode := Items.GetFirstVisibleNode;
+  if IsRoot then
+  begin
+    if Assigned(ANode) then
+      RootNodeText := ANode.Text  //this may differ from FRoot, so don't use FRoot here
+    else
+      RootNodeText := GetRootPath;
+    //writeln('IsRoot = TRUE, RootNodeText = "',RootNodeText,'"');
+    FRoot := #0; //invalidate FRoot
+    SetRoot(RootNodeText); //re-initialize the entire tree
+  end
+  else
+  begin
+    ANode.Expand(False);
+  end;
+end;
+
+function TCustomShellTreeView.GetPath: string;
+begin
+  Result := GetPathFromNode(Selected);
+end;
+
+{
+SetPath: Path can be
+- Absolute like '/usr/lib'
+- Relative like 'foo/bar'
+  This can be relative to:
+  - Self.Root (which takes precedence over)
+  - Current directory
+}
+procedure TCustomShellTreeView.SetPath(AValue: string);
+var
+  sl: TStringList;
+  Node: TTreeNode;
+  i: integer;
+  FQRootPath, RelPath: String;
+  RootIsAbsolute: Boolean;
+  IsRelPath: Boolean;
+
+  function GetAdjustedNodeText(ANode: TTreeNode): String;
+  begin
+    if (ANode = Items.GetFirstVisibleNode) and (FQRootPath <> '') then
+    begin
+      if not RootIsAbsolute then
+        Result := ''
+      else
+        Result := FQRootPath;
+    end
+    else Result := ANode.Text;
+  end;
+
+  function Exists(Fn: String): Boolean;
+  //Fn should be fully qualified
+  var
+    Attr: LongInt;
+    Dirs: TStringList;
+    i: Integer;
+  begin
+    Result := False;
+    Attr := FileGetAttrUtf8(Fn);
+    //writeln('TCustomShellTreeView.SetPath.Exists: Attr = ', Attr);
+    if (Attr = -1) then Exit;
+    if not (otNonFolders in FObjectTypes) then
+      Result := ((Attr and faDirectory) > 0)
+    else
+      Result := True;
+    //writeln('TCustomShellTreeView.SetPath.Exists: Result = ',Result);
+  end;
+
+  function PathIsDriveRoot({%H-}Path: String): Boolean;  {$if not (defined(windows) and not defined(wince))}inline;{$endif}
+  //At least Win7 reports faHidden on all physical drive-roots (e.g. C:\)
+  begin
+    {$if defined(windows) and not defined(wince)}
+    Result := (Length(Path) = 3) and
+              (Upcase(Path[1]) in ['A'..'Z']) and
+              (Path[2] = DriveSeparator) and
+              (Path[3] in AllowDirectorySeparators);
+    {$else}
+    Result := False;
+    {$endif windows}
+  end;
+
+  function ContainsHiddenDir(Fn: String): Boolean;
+  var
+    i: Integer;
+    Attr: LongInt;
+    Dirs: TStringList;
+    RelPath: String;
+  begin
+    //if fn=root then always return false
+    if (CompareFileNames(Fn, FQRootPath) = 0) then
+      Result := False
+    else
+    begin
+      Attr := FileGetAttrUtf8(Fn);
+      Result := ((Attr and faHidden) = faHidden) and not PathIsDriveRoot(Fn);
+      if not Result then
+      begin
+        //it also is not allowed that any folder above is hidden
+        Fn := ChompPathDelim(Fn);
+        Fn := ExtractFileDir(Fn);
+        Dirs := TStringList.Create;
+        try
+          Dirs.StrictDelimiter := True;
+          Dirs.Delimiter := PathDelim;
+          Dirs.DelimitedText := Fn;
+          Fn := '';
+          for i := 0 to Dirs.Count - 1 do
+          begin
+            if (i = 0) then
+              Fn := Dirs.Strings[i]
+            else
+              Fn := Fn + PathDelim + Dirs.Strings[i];
+            if (Fn = '') then Continue;
+            RelPath := CreateRelativePath(Fn, FQRootPath, False, True);
+            //don't check if Fn now is "higher up the tree" than the current root
+            if (RelPath = '') or ((Length(RelPath) > 1) and (RelPath[1] = '.') and (RelPath[2] = '.')) then
+            begin
+              //writeln('Fn is higher: ',Fn);
+              Continue;
+            end;
+            {$if defined(windows) and not defined(wince)}
+            if (Length(Fn) = 2) and (Fn[2] = ':') then Continue;
+            {$endif}
+            Attr := FileGetAttrUtf8(Fn);
+            if (Attr <> -1) and ((Attr and faHidden) > 0) and not PathIsDriveRoot(Fn) then
+            begin
+              Result := True;
+              //writeln('TCustomShellTreeView.SetPath.Exists: a subdir is hidden: Result := False');
+              Break;
+            end;
+          end;
+        finally
+          Dirs.Free;
+        end;
+      end;
+    end;
+  end;
+
+begin
+  RelPath := '';
+
+  //writeln('SetPath: GetRootPath = "',getrootpath,'"',' AValue=',AValue);
+
+  if (GetRootPath <> '') then
+    //FRoot is already Expanded in SetRoot, just add PathDelim if needed
+    FQRootPath := AppendPathDelim(GetRootPath)
+  else
+    FQRootPath := '';
+  RootIsAbsolute := (FQRootPath = '') or (FQRootPath = PathDelim)
+                    or ((Length(FQRootPath) = 3) and (FQRootPath[2] = ':') and (FQRootPath[3] = PathDelim));
+
+  //writeln('SetPath: FQRootPath = ',fqrootpath);
+  //writeln('SetPath: RootIsAbsolute = ',RootIsAbsolute);
+
+  //IsRelPath := not FileNameIsAbsolute(AValue);
+
+  //writeln('SetPath: IsRelPath = ',not FileNameIsAbsolute(AValue));
+
+  if not FileNameIsAbsolute(AValue) then
+  begin
+    if Exists(FQRootPath + AValue) then
+    begin
+      //Expand it, since it may be in the form of ../../foo
+      AValue := ExpandFileNameUtf8(FQRootPath + AValue);
+    end
+    else
+    begin
+      //don't expand Avalue yet, we may need it in error message
+      if not Exists(ExpandFileNameUtf8(AValue)) then
+        Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidPath,[ExpandFileNameUtf8(FQRootPath + AValue)]);
+      //Directory (or file) exists
+      //Make it fully qualified
+      AValue := ExpandFileNameUtf8(AValue);
+    end;
+  end
+  else
+  begin
+    //AValue is an absoulte path to begin with
+    //if not DirectoryExistsUtf8(AValue) then
+    if not Exists(AValue) then
+      Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidPath,[AValue]);
+  end;
+
+  //AValue now is a fully qualified path and it exists
+  //Now check if it is a subdirectory of FQRootPath
+  //RelPath := CreateRelativePath(AValue, FQRootPath, False);
+  IsRelPath := (FQRootPath = '') or TryCreateRelativePath(AValue, FQRootPath, False, True, RelPath);
+
+  //writeln('TCustomShellTreeView.SetPath: ');
+  //writeln('  IsRelPath = ',IsRelPath);
+  //writeln('  RelPath = "',RelPath,'"');
+  //writeln('  FQRootPath = "',FQRootPath,'"');
+
+
+  if (not IsRelpath) or ((RelPath <> '') and ((Length(RelPath) > 1) and (RelPath[1] = '.') and (RelPath[2] = '.'))) then
+  begin
+    // CreateRelativePath retruns a string beginning with ..
+    // so AValue is not a subdirectory of FRoot
+    Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidPathRelative,[AValue, FQRootPath]);
+  end;
+
+  if (RelPath = '') and (FQRootPath = '') then
+    RelPath := AValue;
+  //writeln('RelPath = ',RelPath);
+  if (RelPath = '') then
+  begin
+    //writeln('Root selected');
+    Node := Items.GetFirstVisibleNode;
+    if Assigned(Node) then
+    begin
+      Node.Expanded := True;
+      Node.Selected := True;
+    end;
+    Exit;
+  end;
+
+  if not (otHidden in FObjectTypes) and ContainsHiddenDir(AValue) then
+    Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidPath,[AValue, FQRootPath]);
+
+  sl := TStringList.Create;
+  sl.Delimiter := PathDelim;
+  sl.StrictDelimiter := True;
+  sl.DelimitedText := RelPath;
+  if (sl.Count > 0) and (sl[0] = '') then  // This happens when root dir is empty
+    sl[0] := PathDelim;                    //  and PathDelim was the first char
+  if (sl.Count > 0) and (sl[sl.Count-1] = '') then sl.Delete(sl.Count-1); //remove last empty string
+  if (sl.Count = 0) then
+  begin
+    sl.Free;
+    Exit;
+  end;
+
+  //for i := 0 to sl.Count - 1 do writeln('sl[',i:2,']="',sl[i],'"');
+
+
+  BeginUpdate;
+  try
+    Node := Items.GetFirstVisibleNode;
+    //if assigned(node) then writeln('GetFirstVisibleNode = ',GetAdjustedNodeText(Node));
+    //Root node doesn't have Siblings in this case, we need one level deeper
+    if (GetRootPath <> '') and Assigned(Node) then
+    begin
+      //writeln('Root node doesn''t have Siblings');
+      Node := Node.GetFirstVisibleChild;
+      //writeln('Node = ',GetAdjustedNodeText(Node));
+      //if RootIsAbsolute then sl.Delete(0);
+    end;
+
+    for i := 0 to sl.Count-1 do
+    begin
+      {
+      write('i=',i,' sl[',i,']=',sl[i],' ');
+      if Node <> nil then write('GetAdjustedNodeText = ',GetAdjustedNodeText(Node))
+      else  write('Node = NIL');
+      writeln;
+      }
+      while (Node <> Nil) and
+            {$IF defined(CaseInsensitiveFilenames) or defined(NotLiteralFilenames)}
+            (Utf8LowerCase(GetAdjustedNodeText(Node)) <> Utf8LowerCase(sl[i]))
+            {$ELSE}
+            (GetAdjustedNodeText(Node) <> sl[i])
+            {$ENDIF}
+            do
+            begin
+              //write('  i=',i,' "',GetAdjustedNodeText(Node),' <> ',sl[i],' -> GetNextVisibleSibling -> ');
+              Node := Node.GetNextVisibleSibling;
+              {
+              if Node <> nil then write('GetAdjustedNodeText = ',GetAdjustedNodeText(Node))
+              else  write('Node = NIL');
+              writeln;
+              }
+            end;
+      if Node <> Nil then
+      begin
+        Node.Expanded := True;
+        Node.Selected := True;
+        Node := Node.GetFirstVisibleChild;
+      end
+      else
+        Break;
+    end;
+  finally
+    sl.free;
+    EndUpdate;
+  end;
+end;
+
+
+{ TCustomShellListView }
+
+procedure TCustomShellListView.SetShellTreeView(
+  const Value: TCustomShellTreeView);
+var
+  Tmp: TCustomShellTreeView;
+begin
+  if FShellTreeView = Value then Exit;
+  if FShellTreeView <> nil then
+  begin
+    Tmp := FShellTreeView;
+    FShellTreeView := nil;
+    Tmp.ShellListView := nil;
+  end;
+
+  FShellTreeView := Value;
+
+  if not (csDestroying in ComponentState) then
+    Clear;
+
+  if Value <> nil then
+  begin
+    FRoot := Value.GetPathFromNode(Value.Selected);
+    PopulateWithRoot();
+
+    // Also update the pair, but only if necessary to avoid circular calls of the setters
+    if Value.ShellListView <> Self then Value.ShellListView := Self;
+  end;
+
+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
+    //Delphi raises an unspecified exception in this case, but don't crash the IDE at designtime
+    if not (csDesigning in ComponentState)
+       and (Value <> '')
+       and not DirectoryExistsUtf8(ExpandFilenameUtf8(Value)) then
+       Raise EInvalidPath.CreateFmt(sShellCtrlsInvalidRoot,[Value]);
+    FRoot := Value;
+    Clear;
+    Items.Clear;
+    PopulateWithRoot();
+  end;
+end;
+
+constructor TCustomShellListView.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+
+  // Initial property values
+  ViewStyle := vsReport;
+  ObjectTypes := [otNonFolders];
+
+  Self.Columns.Add;
+  Self.Columns.Add;
+  Self.Columns.Add;
+  Self.Column[0].Caption := sShellCtrlsName;
+  Self.Column[1].Caption := sShellCtrlsSize;
+  Self.Column[2].Caption := sShellCtrlsType;
+  // Initial sizes, necessary under Windows CE
+  Resize;
+end;
+
+destructor TCustomShellListView.Destroy;
+begin
+  ShellTreeView := nil;
+  inherited Destroy;
+end;
+
+procedure TCustomShellListView.PopulateWithRoot();
+var
+  i: Integer;
+  Files: TStringList;
+  NewItem: TListItem;
+  CurFileName, CurFilePath: string;
+  CurFileSize: Int64;
+begin
+  // avoids crashes in the IDE by not populating during design
+  if (csDesigning in ComponentState) then Exit;
+
+  // Check inputs
+  if Trim(FRoot) = '' then Exit;
+
+  Files := TStringList.Create;
+  try
+    TCustomShellTreeView.GetFilesInDir(FRoot, FMask, FObjectTypes, Files);
+
+    for i := 0 to Files.Count - 1 do
+    begin
+      NewItem := Items.Add;
+      CurFileName := Files.Strings[i];
+      CurFilePath := IncludeTrailingPathDelimiter(FRoot) + CurFileName;
+      // First column - Name
+      NewItem.Caption := CurFileName;
+      // Second column - Size
+      // The raw size in bytes is stored in the data part of the item
+      CurFileSize := FileSize(CurFilePath); // in Bytes
+      NewItem.Data := Pointer(PtrInt(CurFileSize));
+      if CurFileSize < 1024 then
+        NewItem.SubItems.Add(Format(sShellCtrlsBytes, [IntToStr(CurFileSize)]))
+      else if CurFileSize < 1024 * 1024 then
+        NewItem.SubItems.Add(Format(sShellCtrlsKB, [IntToStr(CurFileSize div 1024)]))
+      else
+        NewItem.SubItems.Add(Format(sShellCtrlsMB, [IntToStr(CurFileSize div (1024 * 1024))]));
+      // Third column - Type
+      NewItem.SubItems.Add(ExtractFileExt(CurFileName));
+    end;
+    Sort;
+  finally
+    Files.Free;
+  end;
+end;
+
+procedure TCustomShellListView.Resize;
+begin
+  inherited Resize;
+  {$ifdef DEBUG_SHELLCTRLS}
+    WriteLn(':>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;
+
+  // If the space available is small,
+  // alloc a larger percentage to the secondary
+  // fields
+  if Width < 400 then
+  begin
+    Column[0].Width := (50 * Width) div 100;
+    Column[1].Width := (25 * Width) div 100;
+    Column[2].Width := (25 * Width) div 100;
+  end
+  else
+  begin
+    Column[0].Width := (70 * Width) div 100;
+    Column[1].Width := (15 * Width) div 100;
+    Column[2].Width := (15 * Width) div 100;
+  end;
+
+  {$ifdef DEBUG_SHELLCTRLS}
+    WriteLn(':<TCustomShellListView.HandleResize C0.Width=',
+     Column[0].Width, ' C1.Width=', Column[1].Width,
+     ' C2.Width=', Column[2].Width);
+  {$endif}
+end;
+
+function TCustomShellListView.GetPathFromItem(ANode: TListItem): string;
+begin
+  Result := IncludeTrailingPathDelimiter(FRoot) + ANode.Caption;
+end;
+
+procedure Register;
+begin
+  RegisterComponents('Misc',[TShellTreeView, TShellListView]);
+end;
+
+end.