1
0

DriveView.pas 84 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985
  1. unit DriveView;
  2. {==================================================================
  3. Component TDriveView / Version 2.6, January 2000
  4. ==================================================================
  5. Description:
  6. ============
  7. Displays the the directory structure of all drives as treeview
  8. with shell icons. Complete drag&Drop support for files and
  9. directories.
  10. Author:
  11. =======
  12. (c) Ingo Eckel 1998, 1999
  13. Sodener Weg 38
  14. 65812 Bad Soden
  15. Germany
  16. Modifications (for WinSCP):
  17. ===========================
  18. (c) Martin Prikryl 2004
  19. V2.6:
  20. - Shows "shared"-symbol with directories
  21. - Delphi5 compatible
  22. For detailed documentation and history see TDriveView.htm.
  23. {==================================================================}
  24. interface
  25. { Define ENHVALIDATE to scan all existing directories on a detected filesystem change:}
  26. {.$DEFINE ENHVALIDATE}
  27. {Required compiler options for TDriveView:}
  28. {$A+,B-,X+,H+,P+}
  29. {$WARN SYMBOL_PLATFORM OFF}
  30. uses
  31. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ComObj,
  32. Dialogs, ComCtrls, ShellApi, CommCtrl, ExtCtrls, ActiveX, ShlObj,
  33. DirView, ShellDialogs, DragDrop, DragDropFilesEx, FileChanges, FileOperator,
  34. DiscMon, IEDriveInfo, IEListView, BaseUtils, CustomDirView,
  35. CustomDriveView, System.Generics.Collections, CompThread;
  36. const
  37. msThreadChangeDelay = 50;
  38. ErrorNodeNA = '%s: Node not assigned';
  39. {Flags used by TDriveView.RefreshRootNodes:}
  40. dvdsFloppy = 8; {Include floppy drives}
  41. dvdsRereadAllways = 16; {Refresh drivestatus in any case}
  42. WM_USER_SUBDIRREADER = WM_USER_SHCHANGENOTIFY + 1;
  43. type
  44. EInvalidDirName = class(Exception);
  45. ENodeNotAssigned = class(Exception);
  46. TDriveStatus = class
  47. Scanned: Boolean; {Drive allready scanned?}
  48. Verified: Boolean; {Drive completly scanned?}
  49. RootNode: TTreeNode; {Rootnode to drive}
  50. RootNodeIndex: Integer;
  51. DiscMonitor: TDiscMonitor; {Monitor thread}
  52. ChangeTimer: TTimer; {Change timer for the monitor thread}
  53. DefaultDir: string; {Current directory}
  54. end;
  55. TDriveStatusPair = TPair<string, TDriveStatus>;
  56. TScanDirInfo = record
  57. SearchNewDirs: Boolean;
  58. StartNode: TTreeNode;
  59. DriveType: Integer;
  60. end;
  61. PScanDirInfo = ^TScanDirInfo;
  62. TDriveView = class;
  63. TSubDirReaderSchedule = class
  64. Node: TTreeNode;
  65. Path: string;
  66. Deleted: Boolean;
  67. Processed: Boolean;
  68. end;
  69. TNodeData = class
  70. private
  71. FDirName: string;
  72. FAttr: Integer;
  73. FScanned: Boolean;
  74. FData: Pointer;
  75. FIsRecycleBin: Boolean;
  76. FIconEmpty: Boolean;
  77. FSchedule: TSubDirReaderSchedule;
  78. public
  79. DelayedSrec: TSearchRec;
  80. DelayedExclude: TStringList;
  81. constructor Create;
  82. destructor Destroy; override;
  83. property DirName: string read FDirName write FDirName;
  84. property Attr: Integer read FAttr write FAttr;
  85. property Scanned: Boolean read FScanned write FScanned;
  86. property Data: Pointer read FData write FData;
  87. property IsRecycleBin: Boolean read FIsRecycleBin;
  88. property IconEmpty: Boolean read FIconEmpty write FIconEmpty;
  89. property Schedule: TSubDirReaderSchedule read FSchedule write FSchedule;
  90. end;
  91. TDriveTreeNode = class(TTreeNode)
  92. procedure Assign(Source: TPersistent); override;
  93. end;
  94. TSubDirReaderThread = class(TCompThread)
  95. public
  96. destructor Destroy; override;
  97. procedure Terminate; override;
  98. protected
  99. constructor Create(DriveView: TDriveView);
  100. procedure Add(Node: TTreeNode; Path: string);
  101. procedure Delete(Node: TTreeNode);
  102. function Detach: Integer;
  103. procedure Reattach(Count: Integer);
  104. procedure Execute; override;
  105. private
  106. FDriveView: TDriveView;
  107. FEvent: THandle;
  108. FQueue: TStack<TSubDirReaderSchedule>;
  109. FResults: TQueue<TSubDirReaderSchedule>;
  110. FSection: TRTLCriticalSection;
  111. FTimer: TTimer;
  112. FWindowHandle: HWND;
  113. procedure TriggerEvent;
  114. procedure ScheduleProcess;
  115. procedure Process;
  116. function ProcessResult: Boolean;
  117. procedure Timer(Sender: TObject);
  118. procedure WndProc(var Msg: TMessage);
  119. function DetachList(List: TEnumerable<TSubDirReaderSchedule>): Integer;
  120. procedure DestroyScheduleList(List: TEnumerable<TSubDirReaderSchedule>);
  121. end;
  122. TTreeNodeArray = array of TTreeNode;
  123. TDriveView = class(TCustomDriveView)
  124. private
  125. FDriveStatus: TObjectDictionary<string, TDriveStatus>;
  126. FConfirmDelete: Boolean;
  127. FConfirmOverwrite: Boolean;
  128. FWatchDirectory: Boolean;
  129. FDirectory: string;
  130. FShowVolLabel: Boolean;
  131. FVolDisplayStyle: TVolumeDisplayStyle;
  132. FChangeFlag: Boolean;
  133. FLastDir: string;
  134. FValidateFlag: Boolean;
  135. FSysColorChangePending: Boolean;
  136. FCreating: Boolean;
  137. FForceRename: Boolean;
  138. FRenameNode: TTreeNode;
  139. FLastRenameName: string;
  140. FPrevSelected: TTreeNode;
  141. FPrevSelectedIndex: Integer;
  142. FChangeTimerSuspended: Integer;
  143. FSubDirReaderThread: TSubDirReaderThread;
  144. FDelayedNodes: TStringList;
  145. FDelayedNodeTimer: TTimer;
  146. FRecreateScheduledCount: Integer;
  147. {Additional events:}
  148. FOnDisplayContextMenu: TNotifyEvent;
  149. FOnNeedHiddenDirectories: TNotifyEvent;
  150. {used components:}
  151. FDirView: TDirView;
  152. FFileOperator: TFileOperator;
  153. FChangeInterval: Cardinal;
  154. {Drag&drop:}
  155. FLastPathCut: string;
  156. {Drag&drop helper functions:}
  157. procedure SignalDirDelete(Sender: TObject; Files: TStringList);
  158. function GetSubDir(var SRec: TSearchRec): Boolean;
  159. function FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
  160. function FindNextSubDir(var SRec: TSearchRec): Boolean;
  161. procedure ReadSubDirs(Node: TTreeNode);
  162. procedure CancelDelayedNode(Node: TTreeNode);
  163. procedure DelayedNodeTimer(Sender: TObject);
  164. function ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
  165. procedure UpdateDelayedNodeTimer;
  166. function DoSearchSubDirs(
  167. ParentNode: TTreeNode; Path: string; Level: Integer; ExistingOnly: Boolean;
  168. var SelectionHierarchy: TTreeNodeArray; var SelectionHierarchyHeight: Integer): TTreeNode;
  169. function SearchSubDirs(
  170. ParentNode: TTreeNode; Path: string; Level: Integer; ExistingOnly: Boolean;
  171. var SelectionHierarchy: TTreeNodeArray; var SelectionHierarchyHeight: Integer): TTreeNode;
  172. {Callback-functions used by iteratesubtree:}
  173. function CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boolean;
  174. procedure DeleteNode(Node: TTreeNode);
  175. { Notification procedures used by component TDiscMonitor: }
  176. procedure ChangeDetected(Sender: TObject; const Directory: string;
  177. var SubdirsChanged: Boolean);
  178. procedure ChangeInvalid(Sender: TObject; const Directory: string; const ErrorStr: string);
  179. {Notification procedure used by component TTimer:}
  180. procedure ChangeTimerOnTimer(Sender: TObject);
  181. protected
  182. procedure SetSelected(Node: TTreeNode);
  183. procedure SetWatchDirectory(Value: Boolean);
  184. procedure SetShowVolLabel(ShowIt: Boolean);
  185. procedure SetDirView(Value: TDirView);
  186. procedure SetDirectory(Value: string); override;
  187. function DoScanDir(FromNode: TTreeNode): Boolean;
  188. procedure AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec);
  189. procedure CreateWatchThread(Drive: string);
  190. function NodeWatched(Node: TTreeNode): Boolean;
  191. procedure TerminateWatchThread(Drive: string);
  192. function WatchThreadActive: Boolean; overload;
  193. function WatchThreadActive(Drive: string): Boolean; overload;
  194. procedure SubscribeDriveNotifications(Drive: string);
  195. procedure DriveRemoved(Drive: string);
  196. procedure DriveRemoving(Drive: string);
  197. procedure RefreshRootNodes(Floppy: Boolean = False);
  198. procedure DriveNotification(Notification: TDriveNotification; Drive: string);
  199. function DirAttrMask: Integer;
  200. function CreateDriveStatus: TDriveStatus;
  201. procedure ValidateDirectoryEx(Node: TTreeNode; Recurse: TRecursiveScan;
  202. NewDirs: Boolean); override;
  203. procedure RebuildTree; override;
  204. procedure CreateWnd; override;
  205. procedure DestroyWnd; override;
  206. procedure Edit(const Item: TTVItem); override;
  207. procedure WMUserRename(var Message: TMessage); message WM_USER_RENAME;
  208. procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE;
  209. function GetCustomDirView: TCustomDirView; override;
  210. procedure SetCustomDirView(Value: TCustomDirView); override;
  211. function NodePath(Node: TTreeNode): string; override;
  212. function NodeIsRecycleBin(Node: TTreeNode): Boolean; override;
  213. function NodePathExists(Node: TTreeNode): Boolean; override;
  214. function NodeColor(Node: TTreeNode): TColor; override;
  215. function FindPathNode(Path: string): TTreeNode; override;
  216. function DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTreeNode;
  217. function CreateNode: TTreeNode; override;
  218. function DDSourceEffects: TDropEffectSet; override;
  219. procedure DDChooseEffect(KeyState: Integer; var Effect: Integer; PreferredEffect: Integer); override;
  220. function DragCompleteFileList: Boolean; override;
  221. function DDExecute: TDragResult; override;
  222. function CanPasteFromClipBoard: Boolean;
  223. procedure PasteFromClipBoard(Node: TTreeNode);
  224. procedure PerformDragDropFileOperation(Node: TTreeNode; Effect: Integer); override;
  225. procedure ClearCutState;
  226. public
  227. property Images;
  228. property StateImages;
  229. property Items stored False;
  230. property Selected Write SetSelected stored False;
  231. property DragImageList: TDragImageList read FDragImageList;
  232. property LastPathCut: string read FLastPathCut;
  233. procedure EmptyClipboard; dynamic;
  234. {Drive handling:}
  235. function GetDriveStatus(Drive: string): TDriveStatus;
  236. function GetDriveTypetoNode(Node: TTreeNode): Integer; {Returns DRIVE_CDROM etc..}
  237. function GetDriveToNode(Node: TTreeNode): string;
  238. function GetDriveText(Drive: string): string;
  239. procedure ScanDrive(Drive: string);
  240. function GetDrives: TStrings;
  241. {Node handling:}
  242. procedure SetImageIndex(Node: TTreeNode); virtual;
  243. function FindNodeToPath(Path: string): TTreeNode;
  244. function TryFindNodeToPath(Path: string): TTreeNode;
  245. function RootNode(Node: TTreeNode): TTreeNode;
  246. function GetDirName(Node: TTreeNode): string;
  247. function GetDisplayName(Node: TTreeNode): string;
  248. function NodePathName(Node: TTreeNode): string; override;
  249. constructor Create(AOwner: TComponent); override;
  250. destructor Destroy; override;
  251. {Menu-handling:}
  252. procedure DisplayContextMenu(Node: TTreeNode; Point: TPoint); override;
  253. procedure DisplayPropertiesMenu(Node: TTreeNode); override;
  254. {Watchthread handling:}
  255. procedure StartWatchThread;
  256. procedure StopWatchThread;
  257. procedure SuspendChangeTimer;
  258. procedure ResumeChangeTimer;
  259. procedure StartAllWatchThreads;
  260. procedure StopAllWatchThreads;
  261. procedure ValidateCurrentDirectoryIfNotMonitoring;
  262. (* Modified Events: *)
  263. procedure GetImageIndex(Node: TTreeNode); override;
  264. function CanEdit(Node: TTreeNode): Boolean; override;
  265. function CanChange(Node: TTreeNode): Boolean; override;
  266. function CanExpand(Node: TTreeNode): Boolean; override;
  267. procedure Delete(Node: TTreeNode); override;
  268. procedure Loaded; override;
  269. procedure KeyPress(var Key: Char); override;
  270. procedure Change(Node: TTreeNode); override;
  271. published
  272. {Additional properties:}
  273. {Current selected directory:}
  274. property Directory;
  275. {Confirm deleting directories:}
  276. property ConfirmDelete: Boolean read FConfirmDelete write FConfirmDelete default True;
  277. {Confirm overwriting directories:}
  278. property ConfirmOverwrite: Boolean read FConfirmOverwrite write FConfirmOverwrite default True;
  279. {Enable automatic update on filesystem changes:}
  280. property WatchDirectory: Boolean read FWatchDirectory write SetWatchDirectory default False;
  281. {Linked component TDirView:}
  282. property DirView: TDirView read FDirView write SetDirView;
  283. {Show the volume labels of drives:}
  284. property ShowVolLabel: Boolean read FShowVolLabel write SetShowVolLabel default True;
  285. {Additional events:}
  286. property OnDisplayContextMenu: TNotifyEvent read FOnDisplayContextMenu
  287. write FOnDisplayContextMenu;
  288. property OnBusy;
  289. property DDLinkOnExeDrag;
  290. property TargetPopUpMenu;
  291. property OnDDDragEnter;
  292. property OnDDDragLeave;
  293. property OnDDDragOver;
  294. property OnDDDrop;
  295. property OnDDQueryContinueDrag;
  296. property OnDDGiveFeedback;
  297. property OnDDDragDetect;
  298. property OnDDProcessDropped;
  299. property OnDDError;
  300. property OnDDExecuted;
  301. property OnDDFileOperation;
  302. property OnDDFileOperationExecuted;
  303. property Align;
  304. property Anchors;
  305. property AutoExpand;
  306. property BiDiMode;
  307. property BorderStyle;
  308. property BorderWidth;
  309. property ChangeDelay;
  310. property Color;
  311. property Ctl3D;
  312. property Constraints;
  313. property DoubleBuffered;
  314. {Delphi's drag&drop is not compatible with the OLE windows drag&drop:}
  315. property DragKind;
  316. property DragCursor;
  317. property DragMode Default dmAutomatic;
  318. property OnDragDrop;
  319. property OnDragOver;
  320. property Enabled;
  321. property Font;
  322. property HideSelection;
  323. property HotTrack;
  324. property Indent;
  325. property ParentBiDiMode;
  326. property ParentColor;
  327. property ParentCtl3D;
  328. property ParentDoubleBuffered;
  329. property ParentFont;
  330. property ParentShowHint;
  331. property PopupMenu;
  332. property ReadOnly;
  333. property RightClickSelect;
  334. property RowSelect;
  335. property ShowButtons;
  336. property ShowHint;
  337. property ShowLines;
  338. property TabOrder;
  339. property TabStop default True;
  340. property ToolTips;
  341. property Visible;
  342. property OnChange;
  343. property OnChanging;
  344. property OnClick;
  345. property OnCollapsing;
  346. property OnCollapsed;
  347. property OnCompare;
  348. property OnDblClick;
  349. property OnDeletion;
  350. property OnEdited;
  351. property OnEditing;
  352. property OnEndDock;
  353. property OnEndDrag;
  354. property OnEnter;
  355. property OnExit;
  356. property OnExpanding;
  357. property OnExpanded;
  358. property OnGetImageIndex;
  359. property OnGetSelectedIndex;
  360. property OnKeyDown;
  361. property OnKeyPress;
  362. property OnKeyUp;
  363. property OnMouseDown;
  364. property OnMouseMove;
  365. property OnMouseUp;
  366. property OnStartDock;
  367. property OnStartDrag;
  368. property OnNeedHiddenDirectories: TNotifyEvent read FOnNeedHiddenDirectories write FOnNeedHiddenDirectories;
  369. end;
  370. var
  371. DriveViewLoadingTooLongLimit: Integer = 0;
  372. procedure Register;
  373. implementation
  374. uses
  375. PasTools, UITypes, SyncObjs, IOUtils, System.DateUtils;
  376. type
  377. PInt = ^Integer;
  378. procedure Register;
  379. begin
  380. RegisterComponents('DriveDir', [TDriveView]);
  381. end; {Register}
  382. constructor TNodeData.Create;
  383. begin
  384. inherited;
  385. FAttr := 0;
  386. FScanned := False;
  387. FDirName := '';
  388. FIsRecycleBin := False;
  389. FIconEmpty := True;
  390. FSchedule := nil;
  391. DelayedExclude := nil;
  392. end; {TNodeData.Create}
  393. destructor TNodeData.Destroy;
  394. begin
  395. Assert(not Assigned(FSchedule));
  396. SetLength(FDirName, 0);
  397. inherited;
  398. end; {TNodeData.Destroy}
  399. { TSubDirReaderThread }
  400. constructor TSubDirReaderThread.Create(DriveView: TDriveView);
  401. begin
  402. inherited Create(True);
  403. FDriveView := DriveView;
  404. FSection.Initialize;
  405. FEvent := CreateEvent(nil, False, False, nil);
  406. FQueue := TStack<TSubDirReaderSchedule>.Create;
  407. FResults := TQueue<TSubDirReaderSchedule>.Create;
  408. FTimer := TTimer.Create(FDriveView);
  409. FTimer.Enabled := False;
  410. FTimer.Interval := 200;
  411. FTimer.OnTimer := Timer;
  412. FWindowHandle := AllocateHWnd(WndProc);
  413. end;
  414. procedure TSubDirReaderThread.DestroyScheduleList(List: TEnumerable<TSubDirReaderSchedule>);
  415. var
  416. Schedule: TSubDirReaderSchedule;
  417. begin
  418. for Schedule in List do
  419. begin
  420. if not Schedule.Deleted then
  421. TNodeData(Schedule.Node.Data).Schedule := nil;
  422. Schedule.Free;
  423. end;
  424. List.Destroy;
  425. end;
  426. destructor TSubDirReaderThread.Destroy;
  427. begin
  428. inherited;
  429. DeallocateHWnd(FWindowHandle);
  430. DestroyScheduleList(FQueue);
  431. DestroyScheduleList(FResults);
  432. CloseHandle(FEvent);
  433. FTimer.Destroy;
  434. FSection.Destroy;
  435. end;
  436. procedure TSubDirReaderThread.WndProc(var Msg: TMessage);
  437. begin
  438. if Msg.Msg = WM_USER_SUBDIRREADER then
  439. ScheduleProcess
  440. else
  441. Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
  442. end;
  443. procedure TSubDirReaderThread.Process;
  444. var
  445. Started: DWORD;
  446. Elapsed: Integer;
  447. Later: Boolean;
  448. begin
  449. Started := GetTickCount;
  450. Later := False;
  451. while (not Later) and ProcessResult do
  452. begin
  453. Elapsed := GetTickCount - Started;
  454. Later := (Elapsed < 0) or (Elapsed > 20);
  455. end;
  456. if not Later then
  457. FTimer.Enabled := False;
  458. end;
  459. procedure TSubDirReaderThread.Timer(Sender: TObject);
  460. begin
  461. Process;
  462. end;
  463. procedure TSubDirReaderThread.Add(Node: TTreeNode; Path: string);
  464. var
  465. NodeData: TNodeData;
  466. Schedule: TSubDirReaderSchedule;
  467. begin
  468. if Suspended then
  469. Resume;
  470. FSection.Enter;
  471. try
  472. NodeData := TNodeData(Node.Data);
  473. Assert(not Assigned(NodeData.Schedule));
  474. Schedule := TSubDirReaderSchedule.Create;
  475. Schedule.Node := Node;
  476. Schedule.Path := Path;
  477. Schedule.Deleted := False;
  478. Schedule.Processed := False;
  479. FQueue.Push(Schedule);
  480. NodeData.Schedule := Schedule;
  481. finally
  482. FSection.Leave;
  483. end;
  484. TriggerEvent;
  485. end;
  486. procedure TSubDirReaderThread.Delete(Node: TTreeNode);
  487. var
  488. NodeData: TNodeData;
  489. begin
  490. FSection.Enter;
  491. try
  492. NodeData := TNodeData(Node.Data);
  493. if Assigned(NodeData.Schedule) then
  494. begin
  495. NodeData.Schedule.Deleted := True;
  496. NodeData.Schedule := nil;
  497. end;
  498. finally
  499. FSection.Leave;
  500. end;
  501. TriggerEvent;
  502. end;
  503. function TSubDirReaderThread.DetachList(List: TEnumerable<TSubDirReaderSchedule>): Integer;
  504. var
  505. Schedule: TSubDirReaderSchedule;
  506. begin
  507. Result := 0;
  508. for Schedule in List do
  509. begin
  510. if Schedule.Deleted then Schedule.Free
  511. else
  512. begin
  513. Assert(Schedule.Processed = (List = FResults));
  514. Schedule.Node := nil;
  515. Inc(Result);
  516. end;
  517. end;
  518. end;
  519. function TSubDirReaderThread.Detach: Integer;
  520. begin
  521. // block thread while handle is being recreated
  522. FSection.Enter;
  523. try
  524. Result :=
  525. DetachList(FQueue) +
  526. DetachList(FResults);
  527. FQueue.Clear;
  528. FResults.Clear;
  529. except
  530. FSection.Leave;
  531. raise;
  532. end;
  533. end;
  534. procedure TSubDirReaderThread.Reattach(Count: Integer);
  535. var
  536. Node: TTreeNode;
  537. Schedule: TSubDirReaderSchedule;
  538. begin
  539. try
  540. if Count > 0 then
  541. begin
  542. Node := FDriveView.Items.GetFirstNode;
  543. while Assigned(Node) do
  544. begin
  545. Schedule := TNodeData(Node.Data).Schedule;
  546. if Assigned(Schedule) then
  547. begin
  548. Assert(not Assigned(Schedule.Node));
  549. Schedule.Node := Node;
  550. if not Schedule.Processed then
  551. FQueue.Push(Schedule)
  552. else
  553. FResults.Enqueue(Schedule);
  554. Assert(Count > 0);
  555. // Can be optimized to stop once Count = 0
  556. Dec(Count);
  557. end;
  558. Node := Node.GetNext;
  559. end;
  560. if Count <> 0 then Assert(False); // shut up
  561. end;
  562. finally
  563. FSection.Leave;
  564. end;
  565. TriggerEvent;
  566. ScheduleProcess;
  567. end;
  568. procedure TSubDirReaderThread.Terminate;
  569. begin
  570. inherited;
  571. TriggerEvent;
  572. end;
  573. procedure TSubDirReaderThread.TriggerEvent;
  574. begin
  575. SetEvent(FEvent);
  576. end;
  577. function TSubDirReaderThread.ProcessResult: Boolean;
  578. var
  579. Node: TTreeNode;
  580. NodeData: TNodeData;
  581. Schedule: TSubDirReaderSchedule;
  582. begin
  583. FSection.Enter;
  584. try
  585. Result := (FResults.Count > 0);
  586. if Result then
  587. begin
  588. Schedule := FResults.Dequeue;
  589. if not Schedule.Deleted then
  590. begin
  591. Assert(Schedule.Processed);
  592. Node := Schedule.Node;
  593. Node.HasChildren := False;
  594. NodeData := TNodeData(Node.Data);
  595. NodeData.Scanned := not Node.HasChildren; // = True
  596. Assert(NodeData.Schedule = Schedule);
  597. NodeData.Schedule := nil;
  598. end;
  599. Schedule.Free;
  600. end;
  601. finally
  602. FSection.Leave;
  603. end;
  604. end;
  605. procedure TSubDirReaderThread.ScheduleProcess;
  606. begin
  607. // process the first batch immediatelly, to make it more likely that the first seen subdirectories
  608. // will immediatelly show correct status
  609. Process;
  610. FTimer.Enabled := True;
  611. end;
  612. procedure TSubDirReaderThread.Execute;
  613. var
  614. SRec: TSearchRec;
  615. HasSubDirs: Boolean;
  616. NodeData: TNodeData;
  617. Schedule: TSubDirReaderSchedule;
  618. DelayStart, DelayStartStep: Integer;
  619. begin
  620. DelayStart := 3000;
  621. DelayStartStep := 100;
  622. while (DelayStart > 0) and (not Terminated) do
  623. begin
  624. Sleep(DelayStartStep);
  625. Dec(DelayStart, DelayStartStep)
  626. end;
  627. while not Terminated do
  628. begin
  629. WaitForSingleObject(FEvent, INFINITE);
  630. while not Terminated do
  631. begin
  632. FSection.Enter;
  633. try
  634. if FQueue.Count = 0 then
  635. begin
  636. Break;
  637. end
  638. else
  639. begin
  640. Schedule := FQueue.Pop;
  641. if Schedule.Deleted then
  642. begin
  643. Schedule.Free;
  644. // Can be optimized to loop within locked critical section until first non-deleted schedule is found
  645. Continue;
  646. end;
  647. Assert(not Schedule.Processed);
  648. end
  649. finally
  650. FSection.Leave;
  651. end;
  652. HasSubDirs := FDriveView.FindFirstSubDir(IncludeTrailingBackslash(Schedule.Path) + '*.*', SRec);
  653. FindClose(SRec);
  654. FSection.Enter;
  655. try
  656. if Schedule.Deleted then
  657. begin
  658. Schedule.Free;
  659. end
  660. else
  661. begin
  662. Schedule.Processed := True;
  663. if not HasSubDirs then // optimization
  664. begin
  665. FResults.Enqueue(Schedule);
  666. if FResults.Count = 1 then
  667. PostMessage(FWindowHandle, WM_USER_SUBDIRREADER, 0, 0);
  668. end
  669. else
  670. begin
  671. // can happen only if the tree handle is just being recreated
  672. if Assigned(Schedule.Node) then
  673. begin
  674. NodeData := TNodeData(Schedule.Node.Data);
  675. NodeData.Schedule := nil;
  676. end;
  677. Schedule.Free;
  678. end;
  679. end;
  680. finally
  681. FSection.Leave;
  682. end;
  683. end;
  684. end;
  685. end;
  686. { TDriveTreeNode }
  687. // Not sure if this is ever used (possibly only then "assigning" tree view to another instance, what never do).
  688. // It is NOT used when recreating a tree view handle - for that a node is serialized and deserialized,
  689. // including a pointer to TNodeData. See csRecreating condition in TDriveView.Delete.
  690. procedure TDriveTreeNode.Assign(Source: TPersistent);
  691. var
  692. SourceData: TNodeData;
  693. NewData: TNodeData;
  694. begin
  695. Assert(False);
  696. inherited Assign(Source);
  697. if not Deleting and (Source is TTreeNode) then
  698. begin
  699. SourceData := TNodeData(TTreeNode(Source).Data);
  700. NewData := TNodeData.Create();
  701. NewData.DirName := SourceData.DirName;
  702. NewData.Attr := SourceData.Attr;
  703. NewData.Scanned := SourceData.Scanned;
  704. NewData.Data := SourceData.Data;
  705. NewData.FIsRecycleBin := SourceData.FIsRecycleBin;
  706. NewData.IconEmpty := SourceData.IconEmpty;
  707. TTreeNode(Source).Data := NewData;
  708. end;
  709. end;
  710. { TDriveView }
  711. constructor TDriveView.Create(AOwner: TComponent);
  712. var
  713. Drive: TRealDrive;
  714. begin
  715. inherited;
  716. FCreating := True;
  717. FDriveStatus := TObjectDictionary<string, TDriveStatus>.Create([doOwnsValues]);
  718. FChangeInterval := MSecsPerSec;
  719. for Drive := FirstDrive to LastDrive do
  720. begin
  721. FDriveStatus.Add(Drive, CreateDriveStatus);
  722. end;
  723. FFileOperator := TFileOperator.Create(Self);
  724. FSubDirReaderThread := TSubDirReaderThread.Create(Self);
  725. FDelayedNodes := TStringList.Create;
  726. FDelayedNodeTimer := TTimer.Create(Self);
  727. UpdateDelayedNodeTimer;
  728. FDelayedNodeTimer.Interval := 250;
  729. FDelayedNodeTimer.OnTimer := DelayedNodeTimer;
  730. FShowVolLabel := True;
  731. FChangeFlag := False;
  732. FLastDir := EmptyStr;
  733. FValidateFlag := False;
  734. FSysColorChangePending := False;
  735. FConfirmDelete := True;
  736. FDirectory := EmptyStr;
  737. FForceRename := False;
  738. FLastRenameName := '';
  739. FRenameNode := nil;
  740. FPrevSelected := nil;
  741. FPrevSelectedIndex := -1;
  742. FChangeTimerSuspended := 0;
  743. FRecreateScheduledCount := -1;
  744. FConfirmOverwrite := True;
  745. FLastPathCut := '';
  746. FStartPos.X := -1;
  747. FStartPos.Y := -1;
  748. FDragPos := FStartPos;
  749. DriveInfo.AddHandler(DriveNotification);
  750. FDragDropFilesEx.ShellExtensions.DragDropHandler := True;
  751. end; {Create}
  752. destructor TDriveView.Destroy;
  753. var
  754. DriveStatusPair: TDriveStatusPair;
  755. begin
  756. DriveInfo.RemoveHandler(DriveNotification);
  757. for DriveStatusPair in FDriveStatus do
  758. begin
  759. var DriveStatus := DriveStatusPair.Value;
  760. if Assigned(DriveStatus.DiscMonitor) then
  761. FreeAndNil(DriveStatus.DiscMonitor);
  762. if Assigned(DriveStatus.ChangeTimer) then
  763. FreeAndNil(DriveStatus.ChangeTimer);
  764. end;
  765. FDriveStatus.Free;
  766. if Assigned(FFileOperator) then
  767. FFileOperator.Free;
  768. FSubDirReaderThread.Free;
  769. Assert(FDelayedNodes.Count = 0);
  770. FreeAndNil(FDelayedNodes);
  771. inherited Destroy;
  772. end; {Destroy}
  773. function TDriveView.CreateDriveStatus: TDriveStatus;
  774. begin
  775. Result := TDriveStatus.Create;
  776. Result.Scanned := False;
  777. Result.Verified := False;
  778. Result.RootNode := nil;
  779. Result.RootNodeIndex := -1;
  780. Result.DiscMonitor := nil;
  781. Result.DefaultDir := EmptyStr;
  782. {ChangeTimer: }
  783. Result.ChangeTimer := TTimer.Create(Self);
  784. Result.ChangeTimer.Interval := 0;
  785. Result.ChangeTimer.Enabled := False;
  786. Result.ChangeTimer.OnTimer := ChangeTimerOnTimer;
  787. end;
  788. procedure TDriveView.DriveRemoving(Drive: string);
  789. begin
  790. DriveRemoved(Drive);
  791. TerminateWatchThread(Drive);
  792. end;
  793. procedure TDriveView.CreateWnd;
  794. var
  795. DriveStatus: TDriveStatus;
  796. begin
  797. inherited;
  798. FDragDropFilesEx.SourceEffects := [deCopy, deMove, deLink];
  799. FDragDropFilesEx.TargetEffects := [deCopy, deMove, deLink];
  800. if FPrevSelectedIndex >= 0 then
  801. begin
  802. FPrevSelected := Items[FPrevSelectedIndex];
  803. FPrevSelectedIndex := -1;
  804. end;
  805. for DriveStatus in FDriveStatus.Values do
  806. begin
  807. if DriveStatus.RootNodeIndex >= 0 then
  808. begin
  809. DriveStatus.RootNode := Items[DriveStatus.RootNodeIndex];
  810. DriveStatus.RootNodeIndex := -1;
  811. end;
  812. end;
  813. UpdateDelayedNodeTimer;
  814. if FRecreateScheduledCount >= 0 then
  815. begin
  816. FSubDirReaderThread.Reattach(FRecreateScheduledCount);
  817. FRecreateScheduledCount := -1;
  818. end;
  819. end; {CreateWnd}
  820. procedure TDriveView.DestroyWnd;
  821. var
  822. DriveStatus: TDriveStatus;
  823. I: Integer;
  824. begin
  825. FDelayedNodeTimer.Enabled := False;
  826. for I := 0 to FDelayedNodes.Count - 1 do
  827. FDelayedNodes.Objects[I] := nil;
  828. if not (csRecreating in ControlState) then
  829. begin
  830. FSubDirReaderThread.Terminate;
  831. FSubDirReaderThread.WaitFor;
  832. end
  833. else
  834. if CreateWndRestores then
  835. begin
  836. Assert(FRecreateScheduledCount < 0);
  837. // Have to use field, instead of local variable in CM_RECREATEWND handler,
  838. // as CM_RECREATEWND is not invoked, when the recreation is trigerred recursivelly from parent
  839. // control/form.
  840. FRecreateScheduledCount := FSubDirReaderThread.Detach;
  841. if Items.Count > 0 then // redundant test?
  842. begin
  843. FPrevSelectedIndex := -1;
  844. if Assigned(FPrevSelected) then
  845. begin
  846. FPrevSelectedIndex := FPrevSelected.AbsoluteIndex;
  847. FPrevSelected := nil;
  848. end;
  849. for DriveStatus in FDriveStatus.Values do
  850. begin
  851. DriveStatus.RootNodeIndex := -1;
  852. if Assigned(DriveStatus.RootNode) then
  853. begin
  854. DriveStatus.RootNodeIndex := DriveStatus.RootNode.AbsoluteIndex;
  855. DriveStatus.RootNode := nil;
  856. end;
  857. end;
  858. end;
  859. end;
  860. inherited;
  861. end;
  862. function TDriveView.NodeColor(Node: TTreeNode): TColor;
  863. begin
  864. Result := clDefaultItemColor;
  865. var NodeData := TNodeData(Node.Data);
  866. if not Node.Selected then
  867. begin
  868. {Colored display of compressed directories:}
  869. if (NodeData.Attr and FILE_ATTRIBUTE_COMPRESSED) <> 0 then
  870. begin
  871. if SupportsDarkMode and DarkMode then Result := clSkyBlue
  872. else Result := clBlue;
  873. end
  874. else
  875. {Dimmed display, if hidden-atrribut set:}
  876. if FDimmHiddenDirs and ((NodeData.Attr and FILE_ATTRIBUTE_HIDDEN) <> 0) then
  877. Result := clGrayText
  878. end;
  879. end;
  880. function TDriveView.GetCustomDirView: TCustomDirView;
  881. begin
  882. Result := DirView;
  883. end;
  884. procedure TDriveView.SetCustomDirView(Value: TCustomDirView);
  885. begin
  886. DirView := Value as TDirView;
  887. end;
  888. function TDriveView.NodePath(Node: TTreeNode): string;
  889. var
  890. ParentNode: TTreeNode;
  891. begin
  892. if not Assigned(Node) then
  893. raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDirPath']));
  894. Result := GetDirName(Node);
  895. Node := Node.Parent;
  896. while Assigned(Node) do
  897. begin
  898. ParentNode := Node.Parent;
  899. if Assigned(ParentNode) then
  900. Result := GetDirName(Node) + '\' + Result
  901. else
  902. Result := GetDirName(Node) + Result;
  903. Node := ParentNode;
  904. end;
  905. if IsRootPath(Result) then
  906. Result := ExcludeTrailingBackslash(Result);
  907. end;
  908. {NodePathName: Returns the complete path to Node with trailing backslash on rootnodes:
  909. C:\ ,C:\WINDOWS, C:\WINDOWS\SYSTEM }
  910. function TDriveView.NodePathName(Node: TTreeNode): string;
  911. begin
  912. Result := NodePath(Node);
  913. if IsRootPath(Result) then
  914. Result := IncludeTrailingBackslash(Result);
  915. end; {NodePathName}
  916. function TDriveView.NodeIsRecycleBin(Node: TTreeNode): Boolean;
  917. begin
  918. Result := TNodeData(Node.Data).IsRecycleBin;
  919. end;
  920. function TDriveView.NodePathExists(Node: TTreeNode): Boolean;
  921. begin
  922. Result := DirectoryExists(ApiPath(NodePathName(Node)));
  923. end;
  924. function TDriveView.CanEdit(Node: TTreeNode): Boolean;
  925. begin
  926. Result := inherited CanEdit(Node) or FForceRename;
  927. if Result then
  928. begin
  929. Result := Assigned(Node.Parent) and
  930. (not TNodeData(Node.Data).IsRecycleBin) and
  931. (not ReadOnly) and
  932. (FDragDropFilesEx.DragDetectStatus <> ddsDrag) and
  933. ((TNodeData(Node.Data).Attr and (faReadOnly or faSysFile)) = 0) and
  934. (UpperCase(Node.Text) = UpperCase(GetDirName(Node)));
  935. end;
  936. FForceRename := False;
  937. end; {CanEdit}
  938. procedure TDriveView.Edit(const Item: TTVItem);
  939. var
  940. Node: TTreeNode;
  941. Info: string;
  942. i: Integer;
  943. begin
  944. Node := GetNodeFromHItem(Item);
  945. if (Length(Item.pszText) > 0) and (Item.pszText <> Node.Text) then
  946. begin
  947. if StrContains(coInvalidDosChars, Item.pszText) then
  948. begin
  949. Info := coInvalidDosChars;
  950. for i := Length(Info) downto 1 do
  951. System.Insert(Space, Info, i);
  952. if Length(Item.pszText) > 0 then
  953. raise EInvalidDirName.Create(SErrorInvalidName + Space + Info);
  954. Exit;
  955. end;
  956. StopWatchThread;
  957. if Assigned(DirView) then
  958. DirView.StopWatchThread;
  959. FFileOperator.Flags := FileOperatorDefaultFlags + [foNoConfirmation];
  960. FFileOperator.Operation := foRename;
  961. FFileOperator.OperandFrom.Clear;
  962. FFileOperator.OperandTo.Clear;
  963. FFileOperator.OperandFrom.Add(NodePath(Node));
  964. FFileOperator.OperandTo.Add(IncludeTrailingBackslash(NodePath(Node.Parent)) + Item.pszText);
  965. try
  966. if FFileOperator.Execute then
  967. begin
  968. Node.Text := Item.pszText;
  969. TNodeData(Node.Data).DirName := Item.pszText;
  970. SortChildren(Node.Parent, False);
  971. inherited;
  972. end
  973. else
  974. begin
  975. if FileOrDirExists(IncludeTrailingBackslash(NodePath(Node.Parent)) + Item.pszText) then
  976. Info := SErrorRenameFileExists + Item.pszText
  977. else
  978. Info := SErrorRenameFile + Item.pszText;
  979. MessageBeep(MB_ICONHAND);
  980. if MessageDlg(FormatLastOSError(Info), mtError, [mbOK, mbAbort], 0) = mrOK then
  981. begin
  982. FLastRenameName := Item.pszText;
  983. FRenameNode := Node;
  984. PostMessage(Self.Handle, WM_USER_RENAME, 0, 0);
  985. end;
  986. end;
  987. finally
  988. StartWatchThread;
  989. if Assigned(DirView) then
  990. begin
  991. DirView.Reload2;
  992. DirView.StartWatchThread;
  993. end;
  994. end;
  995. end;
  996. end; {Edit}
  997. procedure TDriveView.WMUserRename(var Message: TMessage);
  998. begin
  999. if Assigned(FRenameNode) then
  1000. begin
  1001. FForceRename := True;
  1002. TreeView_EditLabel(Handle, FRenameNode.ItemID);
  1003. SetWindowText(TreeView_GetEditControl(Self.Handle), PChar(FLastRenameName));
  1004. FRenameNode := nil;
  1005. end;
  1006. end; {WMUserRename}
  1007. function TDriveView.CanExpand(Node: TTreeNode): Boolean;
  1008. var
  1009. SubNode: TTreeNode;
  1010. Drive: string;
  1011. SaveCursor: TCursor;
  1012. begin
  1013. Result := inherited CanExpand(Node);
  1014. Drive := GetDriveToNode(Node);
  1015. if Node.HasChildren then
  1016. begin
  1017. if (not Assigned(Node.Parent)) and
  1018. (not GetDriveStatus(Drive).Scanned) and
  1019. DriveInfo.IsFixedDrive(Drive) then
  1020. begin
  1021. SubNode := Node.GetFirstChild;
  1022. if not Assigned(SubNode) then
  1023. begin
  1024. ScanDrive(Drive);
  1025. SubNode := Node.GetFirstChild;
  1026. Node.HasChildren := Assigned(SubNode);
  1027. Result := Node.HasChildren;
  1028. if not Assigned(GetDriveStatus(Drive).DiscMonitor) then
  1029. CreateWatchThread(Drive);
  1030. end;
  1031. end
  1032. else
  1033. begin
  1034. SaveCursor := Screen.Cursor;
  1035. Screen.Cursor := crHourGlass;
  1036. try
  1037. if (not TNodeData(Node.Data).Scanned) and DoScanDir(Node) then
  1038. begin
  1039. ReadSubDirs(Node);
  1040. end;
  1041. finally
  1042. Screen.Cursor := SaveCursor;
  1043. end;
  1044. end;
  1045. end;
  1046. end; {CanExpand}
  1047. procedure TDriveView.GetImageIndex(Node: TTreeNode);
  1048. begin
  1049. if TNodeData(Node.Data).IconEmpty then
  1050. SetImageIndex(Node);
  1051. inherited;
  1052. end; {GetImageIndex}
  1053. procedure TDriveView.Loaded;
  1054. begin
  1055. inherited;
  1056. {Create the drive nodes:}
  1057. RefreshRootNodes(True);
  1058. {Set the initial directory:}
  1059. if (Length(FDirectory) > 0) and DirectoryExists(ApiPath(FDirectory)) then
  1060. Directory := FDirectory;
  1061. FCreating := False;
  1062. end; {Loaded}
  1063. function TDriveView.CreateNode: TTreeNode;
  1064. begin
  1065. Result := TDriveTreeNode.Create(Items);
  1066. end;
  1067. procedure TDriveView.Delete(Node: TTreeNode);
  1068. var
  1069. NodeData: TNodeData;
  1070. begin
  1071. if Node = FPrevSelected then
  1072. FPrevSelected := nil;
  1073. NodeData := nil;
  1074. if Assigned(Node) and Assigned(Node.Data) then
  1075. NodeData := TNodeData(Node.Data);
  1076. Node.Data := nil;
  1077. inherited;
  1078. if Assigned(NodeData) and not (csRecreating in ControlState) then
  1079. begin
  1080. FSubDirReaderThread.Delete(Node);
  1081. if Assigned(NodeData.DelayedExclude) then
  1082. begin
  1083. CancelDelayedNode(Node);
  1084. FDelayedNodes.Delete(FDelayedNodes.IndexOfObject(Node));
  1085. UpdateDelayedNodeTimer;
  1086. end;
  1087. NodeData.Destroy;
  1088. end;
  1089. end; {OnDelete}
  1090. procedure TDriveView.KeyPress(var Key: Char);
  1091. begin
  1092. inherited;
  1093. if Assigned(Selected) then
  1094. begin
  1095. if Pos(Key, coInvalidDosChars) <> 0 then
  1096. begin
  1097. Beep;
  1098. Key := #0;
  1099. end;
  1100. end;
  1101. end; {KeyPress}
  1102. function TDriveView.CanChange(Node: TTreeNode): Boolean;
  1103. var
  1104. Path: string;
  1105. Drive: string;
  1106. begin
  1107. Result := inherited CanChange(Node);
  1108. if not Reading and not (csRecreating in ControlState) then
  1109. begin
  1110. if Result and Assigned(Node) then
  1111. begin
  1112. Path := NodePathName(Node);
  1113. if Path <> FLastDir then
  1114. begin
  1115. Drive := DriveInfo.GetDriveKey(Path);
  1116. DriveInfo.ReadDriveStatus(Drive, dsSize or dsImageIndex);
  1117. if not DriveInfo.Get(Drive).DriveReady then
  1118. begin
  1119. MessageDlg(Format(SDriveNotReady, [Drive]), mtError, [mbOK], 0);
  1120. Result := False;
  1121. end
  1122. else
  1123. try
  1124. CheckCanOpenDirectory(Path);
  1125. except
  1126. Application.HandleException(Self);
  1127. Result := False;
  1128. end;
  1129. end;
  1130. end;
  1131. if Result and (csDestroying in ComponentState) then
  1132. begin
  1133. Result := False;
  1134. end;
  1135. if Result and
  1136. (not FCanChange) and
  1137. Assigned(Node) and
  1138. Assigned(Node.Data) and
  1139. Assigned(Selected) and
  1140. Assigned(Selected.Data) then
  1141. begin
  1142. DropTarget := Node;
  1143. Result := False;
  1144. end
  1145. else
  1146. begin
  1147. DropTarget := nil;
  1148. end;
  1149. end;
  1150. end; {CanChange}
  1151. procedure TDriveView.Change(Node: TTreeNode);
  1152. var
  1153. Drive: string;
  1154. OldSerial: DWORD;
  1155. NewDir: string;
  1156. PrevDrive: string;
  1157. begin
  1158. if not Reading and not (csRecreating in ControlState) then
  1159. begin
  1160. if Assigned(Node) then
  1161. begin
  1162. NewDir := NodePathName(Node);
  1163. if NewDir <> FLastDir then
  1164. begin
  1165. Drive := DriveInfo.GetDriveKey(NewDir);
  1166. if Length(FLastDir) > 0 then
  1167. PrevDrive := DriveInfo.GetDriveKey(FLastDir)
  1168. else
  1169. PrevDrive := '';
  1170. FChangeFlag := True;
  1171. FLastDir := NewDir;
  1172. // Most of this is done already in CanChange and possibly redundant here
  1173. OldSerial := DriveInfo.Get(Drive).DriveSerial;
  1174. DriveInfo.ReadDriveStatus(Drive, dsSize or dsImageIndex);
  1175. if Assigned(FDirView) and (FDirView.Path <> NewDir) then
  1176. FDirView.Path := NewDir;
  1177. var DriveInfoRec := DriveInfo.Get(Drive);
  1178. if DriveInfoRec.DriveReady then
  1179. begin
  1180. if not DirectoryExists(ApiPath(NewDir)) then
  1181. begin
  1182. // Unlikely to ever happen, as CanChange already tests the directory
  1183. ValidateDirectory(GetDriveStatus(Drive).RootNode);
  1184. Exit;
  1185. end;
  1186. GetDriveStatus(Drive).DefaultDir := IncludeTrailingBackslash(NewDir);
  1187. if PrevDrive <> Drive then
  1188. begin
  1189. if (PrevDrive <> '') and
  1190. (DriveInfo.Get(PrevDrive).DriveType = DRIVE_REMOVABLE) then
  1191. begin
  1192. TerminateWatchThread(PrevDrive);
  1193. end;
  1194. {Drive serial has changed or is missing: allways reread the drive:}
  1195. if (DriveInfoRec.DriveSerial <> OldSerial) or (DriveInfoRec.DriveSerial = 0) then
  1196. begin
  1197. if TNodeData(GetDriveStatus(Drive).RootNode.Data).Scanned then
  1198. ScanDrive(Drive);
  1199. end;
  1200. end;
  1201. StartWatchThread;
  1202. end
  1203. else {Drive not ready:}
  1204. begin
  1205. GetDriveStatus(Drive).RootNode.DeleteChildren;
  1206. GetDriveStatus(Drive).DefaultDir := EmptyStr;
  1207. end;
  1208. end;
  1209. if (not Assigned(FPrevSelected)) or (not FPrevSelected.HasAsParent(Node)) then
  1210. Node.Expand(False);
  1211. FPrevSelected := Node;
  1212. ValidateCurrentDirectoryIfNotMonitoring;
  1213. end;
  1214. end;
  1215. inherited;
  1216. end; {Change}
  1217. procedure TDriveView.SetImageIndex(Node: TTreeNode);
  1218. var
  1219. FileInfo: TShFileInfo;
  1220. Drive, NodePath: string;
  1221. begin
  1222. if Assigned(Node) and TNodeData(Node.Data).IconEmpty then
  1223. begin
  1224. NodePath := NodePathName(Node);
  1225. Drive := DriveInfo.GetDriveKey(NodePath);
  1226. if not Assigned(Node.Parent) then
  1227. begin
  1228. var ImageIndex := DriveInfo.Get(Drive).ImageIndex;
  1229. if ImageIndex = 0 then
  1230. begin
  1231. DriveInfo.ReadDriveStatus(Drive, dsImageIndex);
  1232. Node.ImageIndex := DriveInfo.Get(Drive).ImageIndex;
  1233. end
  1234. else Node.ImageIndex := ImageIndex;
  1235. Node.SelectedIndex := Node.ImageIndex;
  1236. end
  1237. else
  1238. begin
  1239. if DriveInfo.Get(Drive).DriveType = DRIVE_REMOTE then
  1240. begin
  1241. Node.ImageIndex := StdDirIcon;
  1242. Node.SelectedIndex := StdDirSelIcon;
  1243. end
  1244. else
  1245. begin
  1246. try
  1247. SHGetFileInfo(PChar(NodePath), 0, FileInfo, SizeOf(FileInfo),
  1248. SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
  1249. if (FileInfo.iIcon < Images.Count) and (FileInfo.iIcon > 0) then
  1250. begin
  1251. Node.ImageIndex := FileInfo.iIcon;
  1252. SHGetFileInfo(PChar(NodePath), 0, FileInfo, SizeOf(FileInfo),
  1253. SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_OPENICON);
  1254. Node.SelectedIndex := FileInfo.iIcon;
  1255. end
  1256. else
  1257. begin
  1258. Node.ImageIndex := StdDirIcon;
  1259. Node.SelectedIndex := StdDirSelIcon;
  1260. end;
  1261. except
  1262. Node.ImageIndex := StdDirIcon;
  1263. Node.SelectedIndex := StdDirSelIcon;
  1264. end;
  1265. end;
  1266. end;
  1267. end; {IconEmpty}
  1268. TNodeData(Node.Data).IconEmpty := False;
  1269. end; {SetImageIndex}
  1270. function TDriveView.GetDriveText(Drive: string): string;
  1271. begin
  1272. if FShowVolLabel and (Length(DriveInfo.GetPrettyName(Drive)) > 0) then
  1273. begin
  1274. case FVolDisplayStyle of
  1275. doPrettyName: Result := DriveInfo.GetPrettyName(Drive);
  1276. doDisplayName: Result := DriveInfo.GetDisplayName(Drive);
  1277. end; {Case}
  1278. end
  1279. else
  1280. begin
  1281. Result := DriveInfo.GetSimpleName(Drive);
  1282. end;
  1283. end; {GetDriveText}
  1284. function CompareDrive(List: TStringList; Index1, Index2: Integer): Integer;
  1285. var
  1286. Drive1, Drive2: string;
  1287. RealDrive1, RealDrive2: Boolean;
  1288. begin
  1289. Drive1 := List[Index1];
  1290. Drive2 := List[Index2];
  1291. RealDrive1 := DriveInfo.IsRealDrive(Drive1);
  1292. RealDrive2 := DriveInfo.IsRealDrive(Drive2);
  1293. if RealDrive1 = RealDrive2 then
  1294. begin
  1295. Result := CompareText(Drive1, Drive2);
  1296. end
  1297. else
  1298. if RealDrive1 and (not RealDrive2) then
  1299. begin
  1300. Result := -1;
  1301. end
  1302. else
  1303. begin
  1304. Result := 1;
  1305. end;
  1306. end;
  1307. function TDriveView.GetDrives: TStrings;
  1308. var
  1309. DriveStatusPair: TDriveStatusPair;
  1310. Drives: TStringList;
  1311. begin
  1312. Drives := TStringList.Create;
  1313. { We could iterate only .Keys here, but that crashes IDE for some reason }
  1314. for DriveStatusPair in FDriveStatus do
  1315. begin
  1316. Drives.Add(DriveStatusPair.Key);
  1317. end;
  1318. Drives.CustomSort(CompareDrive);
  1319. Result := Drives;
  1320. end;
  1321. procedure TDriveView.DriveRemoved(Drive: string);
  1322. var
  1323. NewDrive: Char;
  1324. begin
  1325. if (Directory <> '') and (Directory[1] = Drive) then
  1326. begin
  1327. if DriveInfo.IsRealDrive(Drive) then NewDrive := Drive[1]
  1328. else NewDrive := SystemDrive;
  1329. repeat
  1330. if NewDrive < SystemDrive then NewDrive := SystemDrive
  1331. else
  1332. if NewDrive = SystemDrive then NewDrive := LastDrive
  1333. else Dec(NewDrive);
  1334. DriveInfo.ReadDriveStatus(NewDrive, dsSize or dsImageIndex);
  1335. if NewDrive = Drive then
  1336. begin
  1337. Break;
  1338. end;
  1339. if DriveInfo.Get(NewDrive).Valid and DriveInfo.Get(NewDrive).DriveReady and Assigned(GetDriveStatus(NewDrive).RootNode) then
  1340. begin
  1341. Directory := NodePathName(GetDriveStatus(NewDrive).RootNode);
  1342. break;
  1343. end;
  1344. until False;
  1345. if not Assigned(Selected) then
  1346. begin
  1347. Directory := NodePathName(GetDriveStatus(SystemDrive).RootNode);
  1348. end;
  1349. end;
  1350. end;
  1351. procedure TDriveView.DriveNotification(Notification: TDriveNotification; Drive: string);
  1352. begin
  1353. case Notification of
  1354. dnRefresh:
  1355. RefreshRootNodes;
  1356. dnRemoving:
  1357. // Lame way to reduce rick, we modify the treee while it's being updated already.
  1358. // It might be better to post an update message to the message loop.
  1359. if WatchThreadActive(Drive) then
  1360. DriveRemoving(Drive);
  1361. end;
  1362. end;
  1363. procedure TDriveView.RefreshRootNodes(Floppy: Boolean);
  1364. var
  1365. Drives: TStrings;
  1366. NewText: string;
  1367. SaveCursor: TCursor;
  1368. WasValid: Boolean;
  1369. NodeData: TNodeData;
  1370. DriveStatus: TDriveStatus;
  1371. NextDriveNode: TTreeNode;
  1372. Index: Integer;
  1373. Drive: string;
  1374. begin
  1375. SaveCursor := Screen.Cursor;
  1376. Screen.Cursor := crHourGlass;
  1377. Drives := nil;
  1378. try
  1379. Drives := GetDrives;
  1380. NextDriveNode := nil;
  1381. for Index := Drives.Count - 1 downto 0 do
  1382. begin
  1383. Drive := Drives[Index];
  1384. DriveStatus := GetDriveStatus(Drive);
  1385. if Floppy or DriveInfo.IsFixedDrive(Drive) then
  1386. begin
  1387. WasValid := Assigned(DriveStatus.RootNode);
  1388. if DriveInfo.Get(Drive).Valid then
  1389. begin
  1390. if not WasValid then
  1391. {New drive has arrived: insert new rootnode:}
  1392. begin
  1393. { Create root directory node }
  1394. NodeData := TNodeData.Create;
  1395. NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
  1396. if Assigned(NextDriveNode) then
  1397. DriveStatus.RootNode := Items.InsertObject(NextDriveNode, '', NodeData)
  1398. else
  1399. DriveStatus.RootNode := Items.AddObject(nil, '', NodeData);
  1400. DriveStatus.RootNode.Text := GetDisplayName(DriveStatus.RootNode);
  1401. DriveStatus.RootNode.HasChildren := True;
  1402. DriveStatus.Scanned := False;
  1403. DriveStatus.Verified := False;
  1404. end
  1405. else
  1406. if DriveStatus.RootNode.ImageIndex <> DriveInfo.Get(Drive).ImageIndex then
  1407. begin {WasValid = True}
  1408. DriveStatus.RootNode.ImageIndex := DriveInfo.Get(Drive).ImageIndex;
  1409. DriveStatus.RootNode.SelectedIndex := DriveInfo.Get(Drive).ImageIndex;
  1410. end;
  1411. if Assigned(DriveStatus.RootNode) then
  1412. begin
  1413. NewText := GetDisplayName(DriveStatus.RootNode);
  1414. if DriveStatus.RootNode.Text <> NewText then
  1415. DriveStatus.RootNode.Text := NewText;
  1416. end;
  1417. end
  1418. else
  1419. if WasValid then
  1420. {Drive has been removed => delete rootnode:}
  1421. begin
  1422. DriveRemoved(Drive);
  1423. DriveStatus.Scanned := False;
  1424. DriveStatus.Verified := False;
  1425. DriveStatus.RootNode.Delete;
  1426. DriveStatus.RootNode := nil;
  1427. end;
  1428. end;
  1429. if Assigned(DriveStatus.RootNode) then
  1430. NextDriveNode := DriveStatus.RootNode;
  1431. end;
  1432. finally
  1433. Screen.Cursor := SaveCursor;
  1434. Drives.Free;
  1435. end;
  1436. end; {RefreshRootNodes}
  1437. procedure TDriveView.AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec);
  1438. var
  1439. NewNode: TTreeNode;
  1440. NodeData: TNodeData;
  1441. begin
  1442. NodeData := TNodeData.Create;
  1443. NodeData.Attr := SRec.Attr;
  1444. NodeData.DirName := SRec.Name;
  1445. NodeData.FIsRecycleBin :=
  1446. (SRec.Attr and faSysFile <> 0) and
  1447. (not Assigned(ParentNode.Parent)) and
  1448. (SameText(SRec.Name, 'RECYCLED') or
  1449. SameText(SRec.Name, 'RECYCLER') or
  1450. SameText(SRec.Name, '$RECYCLE.BIN'));
  1451. NodeData.Scanned := False;
  1452. NewNode := Self.Items.AddChildObject(ParentNode, '', NodeData);
  1453. NewNode.Text := GetDisplayName(NewNode);
  1454. NewNode.HasChildren := True;
  1455. if GetDriveTypeToNode(ParentNode) <> DRIVE_REMOTE then
  1456. FSubDirReaderThread.Add(NewNode, IncludeTrailingBackslash(ParentPath) + SRec.Name);
  1457. end; {AddChildNode}
  1458. function TDriveView.GetDriveStatus(Drive: string): TDriveStatus;
  1459. begin
  1460. if not FDriveStatus.TryGetValue(Drive, Result) then
  1461. begin
  1462. Result := CreateDriveStatus;
  1463. FDriveStatus.Add(Drive, Result);
  1464. end;
  1465. end; {GetDriveStatus}
  1466. function TDriveView.DoScanDir(FromNode: TTreeNode): Boolean;
  1467. begin
  1468. Result := not TNodeData(FromNode.Data).IsRecycleBin;
  1469. end; {DoScanDir}
  1470. function TDriveView.DirAttrMask: Integer;
  1471. begin
  1472. Result := faDirectory or faSysFile;
  1473. if ShowHiddenDirs then
  1474. Result := Result or faHidden;
  1475. end;
  1476. procedure TDriveView.ScanDrive(Drive: string);
  1477. begin {ScanDrive}
  1478. ValidateDirectory(FindNodeToPath(DriveInfo.GetDriveRoot(Drive)));
  1479. GetDriveStatus(Drive).Scanned := True;
  1480. GetDriveStatus(Drive).Verified := False;
  1481. end; {ScanDrive}
  1482. function ExtractFirstName(S: string): string;
  1483. var
  1484. I: Integer;
  1485. begin
  1486. I := Pos('\', S);
  1487. if I = 0 then
  1488. I := Length(S);
  1489. Result := System.Copy(S, 1, I);
  1490. end;
  1491. function TDriveView.DoSearchSubDirs(
  1492. ParentNode: TTreeNode; Path: string; Level: Integer; ExistingOnly: Boolean;
  1493. var SelectionHierarchy: TTreeNodeArray; var SelectionHierarchyHeight: Integer): TTreeNode;
  1494. var
  1495. Node: TTreeNode;
  1496. Dir: string;
  1497. begin
  1498. {Extract first directory from path:}
  1499. Dir := ExtractFirstName(Path);
  1500. System.Delete(Path, 1, Length(Dir));
  1501. if Dir[Length(Dir)] = '\' then
  1502. SetLength(Dir, Pred(Length(Dir)));
  1503. // Optimization. Avoid iterating possibly thousands of nodes,
  1504. // when the node we are looking for is the selected node or its ancestor.
  1505. // This is often the case, when navigating under node that has lot of siblings.
  1506. // Typically, when navigating in user's profile folder, and there are many [thousands] other user profile folders.
  1507. if (SelectionHierarchyHeight > 0) and
  1508. // Change of selection might indicate that the tree was rebuilt meanwhile and
  1509. // the references in SelectionHierarchy might not be valid anymore
  1510. (Selected = SelectionHierarchy[SelectionHierarchyHeight - 1]) and
  1511. (Level < SelectionHierarchyHeight) and
  1512. (Uppercase(GetDirName(SelectionHierarchy[Level])) = Dir) then
  1513. begin
  1514. Result := SelectionHierarchy[Level];
  1515. end
  1516. else
  1517. begin
  1518. // Paths have diverted
  1519. SelectionHierarchyHeight := 0;
  1520. Node := ParentNode.GetFirstChild;
  1521. if (not Assigned(Node)) and (not ExistingOnly) then
  1522. begin
  1523. ValidateDirectoryEx(ParentNode, rsRecursiveExisting, True);
  1524. Node := ParentNode.GetFirstChild;
  1525. end;
  1526. Result := nil;
  1527. while (not Assigned(Result)) and Assigned(Node) do
  1528. begin
  1529. if UpperCase(GetDirName(Node)) = Dir then
  1530. begin
  1531. Result := Node;
  1532. end
  1533. else
  1534. begin
  1535. Node := ParentNode.GetNextChild(Node);
  1536. end;
  1537. end;
  1538. end;
  1539. if Assigned(Result) and (Length(Path) > 0) then
  1540. begin
  1541. Result := SearchSubDirs(Result, Path, Level + 1, ExistingOnly, SelectionHierarchy, SelectionHierarchyHeight);
  1542. end;
  1543. end;
  1544. function TDriveView.SearchSubDirs(
  1545. ParentNode: TTreeNode; Path: string; Level: Integer; ExistingOnly: Boolean;
  1546. var SelectionHierarchy: TTreeNodeArray; var SelectionHierarchyHeight: Integer): TTreeNode;
  1547. var
  1548. ParentPath, SubPath: string;
  1549. SRec: TSearchRec;
  1550. ParentNodeData: TNodeData;
  1551. begin
  1552. Result := nil;
  1553. if Length(Path) > 0 then
  1554. begin
  1555. ParentNodeData := TNodeData(ParentNode.Data);
  1556. if (not ParentNodeData.Scanned) and (not ExistingOnly) then
  1557. begin
  1558. ReadSubDirs(ParentNode);
  1559. end;
  1560. Result := DoSearchSubDirs(ParentNode, Path, Level, ExistingOnly, SelectionHierarchy, SelectionHierarchyHeight);
  1561. if (not Assigned(Result)) and (not ExistingOnly) then
  1562. begin
  1563. ParentPath := NodePath(ParentNode);
  1564. if DirectoryExists(ApiPath(IncludeTrailingBackslash(ParentPath) + Path)) then
  1565. begin
  1566. SubPath := IncludeTrailingBackslash(ParentPath) + ExcludeTrailingBackslash(ExtractFirstName(Path));
  1567. if FindFirstSubDir(SubPath, SRec) then
  1568. begin
  1569. AddChildNode(ParentNode, ParentPath, SRec);
  1570. if Assigned(ParentNodeData.DelayedExclude) then
  1571. ParentNodeData.DelayedExclude.Add(SRec.Name);
  1572. SortChildren(ParentNode, False);
  1573. FindClose(SRec);
  1574. end;
  1575. Result := DoSearchSubDirs(ParentNode, Path, Level, ExistingOnly, SelectionHierarchy, SelectionHierarchyHeight);
  1576. end;
  1577. end;
  1578. end;
  1579. end; {SearchSubDirs}
  1580. function TDriveView.DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTreeNode;
  1581. var
  1582. SelectionHierarchy: TTreeNodeArray;
  1583. SelectionHierarchyHeight: Integer;
  1584. Drive: string;
  1585. P, I: Integer;
  1586. RootNode, Node: TTreeNode;
  1587. begin {FindNodeToPath}
  1588. Result := nil;
  1589. if Length(Path) < 3 then
  1590. Exit;
  1591. // Particularly when used by TDirView to delegate browsing to
  1592. // hidden drive view, the handle may not be created
  1593. HandleNeeded;
  1594. Drive := DriveInfo.GetDriveKey(Path);
  1595. // Likely a network drive
  1596. if (not Assigned(GetDriveStatus(Drive).RootNode)) and
  1597. // Side effect of this is drive refresh that adds the network drive to the trees
  1598. DriveInfo.Get(Drive).ValidButHiddenByDrivePolicy then
  1599. begin
  1600. // This refreshes the drives again
  1601. DriveInfo.OverrideDrivePolicy(Drive);
  1602. end;
  1603. // if not assigned now, it must be that the drive already existed in DriveInfo, but it didn't make it to this view
  1604. // (possible a network drive opened in another panel before)
  1605. if not Assigned(GetDriveStatus(Drive).RootNode) then
  1606. begin
  1607. // Refresh the view drives to add the new drive and also explorer's drive drop down.
  1608. // Overkill, as we know exactly what drive to add (so not need to check all drives)
  1609. DriveInfo.DriveRefresh;
  1610. end;
  1611. if Assigned(GetDriveStatus(Drive).RootNode) then
  1612. begin
  1613. if DriveInfo.IsRealDrive(Drive) then
  1614. begin
  1615. System.Delete(Path, 1, 3);
  1616. end
  1617. else
  1618. if IsUncPath(Path) then
  1619. begin
  1620. System.Delete(Path, 1, 2);
  1621. P := Pos('\', Path);
  1622. if P = 0 then
  1623. begin
  1624. Path := '';
  1625. end
  1626. else
  1627. begin
  1628. System.Delete(Path, 1, P);
  1629. P := Pos('\', Path);
  1630. if P = 0 then
  1631. begin
  1632. Path := '';
  1633. end
  1634. else
  1635. begin
  1636. System.Delete(Path, 1, P);
  1637. end;
  1638. end;
  1639. end
  1640. else
  1641. begin
  1642. raise EConvertError.Create(Format(ErrorInvalidDrive, [Path]))
  1643. end;
  1644. if Length(Path) > 0 then
  1645. begin
  1646. if (not GetDriveStatus(Drive).Scanned) and (not ExistingOnly) then
  1647. begin
  1648. ScanDrive(Drive);
  1649. end;
  1650. Node := Selected;
  1651. RootNode := GetDriveStatus(Drive).RootNode;
  1652. if not Assigned(Node) then
  1653. begin
  1654. SelectionHierarchyHeight := 0;
  1655. end
  1656. else
  1657. begin
  1658. SelectionHierarchyHeight := Node.Level + 1;
  1659. SetLength(SelectionHierarchy, SelectionHierarchyHeight);
  1660. for I := SelectionHierarchyHeight - 1 downto 0 do
  1661. begin
  1662. SelectionHierarchy[I] := Node;
  1663. Node := Node.Parent;
  1664. end;
  1665. Assert(Selected = SelectionHierarchy[SelectionHierarchyHeight - 1]);
  1666. // Different drive - nothing to optimize
  1667. if RootNode <> SelectionHierarchy[0] then
  1668. SelectionHierarchyHeight := 0;
  1669. end;
  1670. Result := SearchSubDirs(RootNode, UpperCase(Path), 1, ExistingOnly, SelectionHierarchy, SelectionHierarchyHeight);
  1671. end
  1672. else Result := GetDriveStatus(Drive).RootNode;
  1673. end;
  1674. end; {FindNodetoPath}
  1675. function TDriveView.FindNodeToPath(Path: string): TTreeNode;
  1676. begin
  1677. Result := DoFindNodeToPath(Path, False);
  1678. end;
  1679. function TDriveView.TryFindNodeToPath(Path: string): TTreeNode;
  1680. begin
  1681. Result := DoFindNodeToPath(Path, True);
  1682. end;
  1683. function TDriveView.GetSubDir(var SRec: TSearchRec): Boolean;
  1684. begin
  1685. Result := True;
  1686. while Result and
  1687. ((SRec.Name = '.' ) or
  1688. (SRec.Name = '..') or
  1689. ((SRec.Attr and faDirectory) = 0)) do
  1690. begin
  1691. if FindNext(SRec) <> 0 then
  1692. begin
  1693. Result := False;
  1694. end;
  1695. end;
  1696. end;
  1697. function TDriveView.FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
  1698. begin
  1699. Result := (FindFirstEx(ApiPath(Path), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS, FindExSearchLimitToDirectories) = 0);
  1700. if Result then
  1701. begin
  1702. Result := GetSubDir(SRec);
  1703. if not Result then FindClose(SRec);
  1704. end;
  1705. end;
  1706. function TDriveView.FindNextSubDir(var SRec: TSearchRec): Boolean;
  1707. begin
  1708. Result := (FindNext(SRec) = 0) and GetSubDir(SRec);
  1709. end;
  1710. function TDriveView.ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
  1711. var
  1712. Start: TDateTime;
  1713. Cont: Boolean;
  1714. Path: string;
  1715. Count: Integer;
  1716. DelayedExclude: TStringList;
  1717. begin
  1718. Start := Now;
  1719. Path := NodePath(Node);
  1720. Result := True;
  1721. Count := 0;
  1722. DelayedExclude := TNodeData(Node.Data).DelayedExclude;
  1723. // At least from SetDirectory > DoFindNodeToPath and CanExpand, this is not called within BeginUpdate/EndUpdate block.
  1724. // But in any case, adding it here makes expanding (which calls CanExpand) noticeably slower, when there are lot of nodes,
  1725. // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
  1726. repeat
  1727. if (not Assigned(DelayedExclude)) or
  1728. (DelayedExclude.IndexOf(SRec.Name) < 0) then
  1729. begin
  1730. AddChildNode(Node, Path, SRec);
  1731. Inc(Count);
  1732. end;
  1733. Cont := FindNextSubDir(SRec);
  1734. // There are two other directory reading loops, where this is not checked
  1735. if Cont and
  1736. ((Count mod CheckInterval) = 0) and
  1737. (Limit > 0) and
  1738. (MilliSecondsBetween(Now, Start) > Limit) then
  1739. begin
  1740. Result := False;
  1741. Cont := False;
  1742. end
  1743. until not Cont;
  1744. if Result then
  1745. FindClose(Srec);
  1746. end;
  1747. procedure TDriveView.DelayedNodeTimer(Sender: TObject);
  1748. var
  1749. Node: TTreeNode;
  1750. NodeData: TNodeData;
  1751. begin
  1752. Assert(FDelayedNodes.Count > 0);
  1753. if FDelayedNodes.Count > 0 then
  1754. begin
  1755. // Control was recreated
  1756. if not Assigned(FDelayedNodes.Objects[0]) then
  1757. begin
  1758. FDelayedNodes.Objects[0] := TryFindNodeToPath(FDelayedNodes.Strings[0]);
  1759. end;
  1760. Node := TTreeNode(FDelayedNodes.Objects[0]);
  1761. if not Assigned(Node) then
  1762. begin
  1763. FDelayedNodes.Delete(0);
  1764. end
  1765. else
  1766. begin
  1767. NodeData := TNodeData(Node.Data);
  1768. if ReadSubDirsBatch(Node, NodeData.DelayedSrec, 10, 50) then
  1769. begin
  1770. FreeAndNil(NodeData.DelayedExclude);
  1771. FDelayedNodes.Delete(0);
  1772. SortChildren(Node, False);
  1773. end;
  1774. end;
  1775. end;
  1776. UpdateDelayedNodeTimer;
  1777. end;
  1778. procedure TDriveView.UpdateDelayedNodeTimer;
  1779. begin
  1780. FDelayedNodeTimer.Enabled := HandleAllocated and (FDelayedNodes.Count > 0);
  1781. end;
  1782. procedure TDriveView.ReadSubDirs(Node: TTreeNode);
  1783. var
  1784. SRec: TSearchRec;
  1785. NodeData: TNodeData;
  1786. Path: string;
  1787. CheckInterval, Limit: Integer;
  1788. begin
  1789. NodeData := TNodeData(Node.Data);
  1790. Path := NodePath(Node);
  1791. if not FindFirstSubDir(IncludeTrailingBackslash(Path) + '*.*', SRec) then
  1792. begin
  1793. Node.HasChildren := False;
  1794. end
  1795. else
  1796. begin
  1797. CheckInterval := 100;
  1798. Limit := DriveViewLoadingTooLongLimit * 1000;
  1799. if not Showing then
  1800. begin
  1801. Limit := Limit div 10;
  1802. CheckInterval := CheckInterval div 10;
  1803. end;
  1804. if not ReadSubDirsBatch(Node, SRec, CheckInterval, Limit) then
  1805. begin
  1806. NodeData.DelayedSrec := SRec;
  1807. NodeData.DelayedExclude := TStringList.Create;
  1808. NodeData.DelayedExclude.CaseSensitive := False;
  1809. NodeData.DelayedExclude.Sorted := True;
  1810. FDelayedNodes.AddObject(Path, Node);
  1811. Assert(FDelayedNodes.Count < 20); // if more, something went likely wrong
  1812. UpdateDelayedNodeTimer;
  1813. end;
  1814. SortChildren(Node, False);
  1815. end;
  1816. NodeData.Scanned := True;
  1817. Application.ProcessMessages;
  1818. end; {ReadSubDirs}
  1819. procedure TDriveView.CancelDelayedNode(Node: TTreeNode);
  1820. var
  1821. NodeData: TNodeData;
  1822. begin
  1823. NodeData := TNodeData(Node.Data);
  1824. FindClose(NodeData.DelayedSrec);
  1825. FreeAndNil(NodeData.DelayedExclude);
  1826. end;
  1827. procedure TDriveView.DeleteNode(Node: TTreeNode);
  1828. var
  1829. ValidNode: TTreeNode;
  1830. begin
  1831. if Assigned(Selected) and Assigned(Node.Parent) and
  1832. ((Selected = Node) or Selected.HasAsParent(Node)) then
  1833. begin
  1834. ValidNode := Node.Parent;
  1835. while (not NodePathExists(ValidNode)) and Assigned(ValidNode.Parent) do
  1836. ValidNode := ValidNode.Parent;
  1837. Selected := ValidNode;
  1838. end;
  1839. if DropTarget = Node then
  1840. DropTarget := nil;
  1841. Node.Delete;
  1842. end;
  1843. function TDriveView.CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boolean;
  1844. var
  1845. WorkNode: TTreeNode;
  1846. DelNode: TTreeNode;
  1847. SRec: TSearchRec;
  1848. SrecList: TStringList;
  1849. SubDirList: TStringList;
  1850. R: Boolean;
  1851. Index: Integer;
  1852. NewDirFound: Boolean;
  1853. ParentDir: string;
  1854. NodeData: TNodeData;
  1855. ScanDirInfo: PScanDirInfo;
  1856. begin {CallBackValidateDir}
  1857. Result := True;
  1858. if (not Assigned(Node)) or (not Assigned(Node.Data)) then
  1859. Exit;
  1860. NewDirFound := False;
  1861. ScanDirInfo := PScanDirInfo(Data);
  1862. {Check, if directory still exists: (but not with root directory) }
  1863. if Assigned(Node.Parent) and (ScanDirInfo^.StartNode = Node) and
  1864. (not NodePathExists(Node)) then
  1865. begin
  1866. DeleteNode(Node);
  1867. Node := nil;
  1868. Exit;
  1869. end;
  1870. WorkNode := Node.GetFirstChild;
  1871. NodeData := TNodeData(Node.Data);
  1872. if NodeData.Scanned and Assigned(WorkNode) then
  1873. {if node was already scanned: check wether the existing subnodes are still alive
  1874. and add all new subdirectories as subnodes:}
  1875. begin
  1876. if DoScanDir(Node) then
  1877. begin
  1878. ParentDir := IncludeTrailingBackslash(NodePath(Node));
  1879. {Build list of existing subnodes:}
  1880. SubDirList := TStringList.Create;
  1881. SubDirList.CaseSensitive := True; // We want to reflect changes in subfolder name case
  1882. while Assigned(WorkNode) do
  1883. begin
  1884. SubDirList.Add(TNodeData(WorkNode.Data).DirName);
  1885. WorkNode := Node.GetNextChild(WorkNode);
  1886. end;
  1887. // Nodes are sorted using natural sorting, while TStringList.Find uses simple sorting
  1888. SubDirList.Sort;
  1889. SRecList := TStringList.Create;
  1890. SRecList.CaseSensitive := True;
  1891. R := FindFirstSubDir(ParentDir + '*.*', SRec);
  1892. while R do
  1893. begin
  1894. SrecList.Add(Srec.Name);
  1895. if not SubDirList.Find(Srec.Name, Index) then
  1896. {Subnode does not exists: add it:}
  1897. begin
  1898. AddChildNode(Node, ParentDir, SRec);
  1899. NewDirFound := True;
  1900. end;
  1901. R := FindNextSubDir(Srec);
  1902. end;
  1903. FindClose(Srec);
  1904. Sreclist.Sort;
  1905. {Remove not existing subnodes:}
  1906. WorkNode := Node.GetFirstChild;
  1907. while Assigned(WorkNode) do
  1908. begin
  1909. if not Assigned(WorkNode.Data) or
  1910. not SrecList.Find(TNodeData(WorkNode.Data).DirName, Index) then
  1911. begin
  1912. DelNode := WorkNode;
  1913. WorkNode := Node.GetNextChild(WorkNode);
  1914. DeleteNode(DelNode);
  1915. end
  1916. else
  1917. begin
  1918. if (SrecList[Index] <> TNodeData(WorkNode.Data).DirName) then
  1919. begin
  1920. {Case of directory letters has changed:}
  1921. TNodeData(WorkNode.Data).DirName := SrecList[Index];
  1922. WorkNode.Text := SrecList[Index];
  1923. end;
  1924. WorkNode := Node.GetNextChild(WorkNode);
  1925. end;
  1926. end;
  1927. SrecList.Free;
  1928. SubDirList.Free;
  1929. {Sort subnodes:}
  1930. if NewDirFound then
  1931. SortChildren(Node, False);
  1932. end;
  1933. end
  1934. else
  1935. {Node was not already scanned:}
  1936. if (ScanDirInfo^.SearchNewDirs or
  1937. NodeData.Scanned or
  1938. (Node = ScanDirInfo^.StartNode)) and
  1939. DoScanDir(Node) then
  1940. begin
  1941. ReadSubDirs(Node);
  1942. end;
  1943. end; {CallBackValidateDir}
  1944. procedure TDriveView.RebuildTree;
  1945. var
  1946. Drive: string;
  1947. begin
  1948. for Drive in FDriveStatus.Keys do
  1949. begin
  1950. var DriveStatus := GetDriveStatus(Drive);
  1951. if Assigned(DriveStatus.RootNode) and DriveStatus.Scanned then
  1952. ValidateDirectory(DriveStatus.RootNode);
  1953. end;
  1954. end;
  1955. procedure TDriveView.ValidateCurrentDirectoryIfNotMonitoring;
  1956. begin
  1957. if Assigned(Selected) and
  1958. not Assigned(GetDriveStatus(GetDriveToNode(Selected)).DiscMonitor) then
  1959. begin
  1960. ValidateDirectory(Selected);
  1961. end;
  1962. end;
  1963. procedure TDriveView.ValidateDirectoryEx(Node: TTreeNode; Recurse: TRecursiveScan;
  1964. NewDirs: Boolean);
  1965. var
  1966. Info: PScanDirInfo;
  1967. SelDir: string;
  1968. SaveCursor: TCursor;
  1969. RestartWatchThread: Boolean;
  1970. SaveCanChange: Boolean;
  1971. CurrentPath: string;
  1972. Drive: string;
  1973. begin
  1974. if Assigned(Node) and Assigned(Node.Data) and
  1975. (not FValidateFlag) and DoScanDir(Node) then
  1976. begin
  1977. SelDir := Directory;
  1978. SaveCursor := Screen.Cursor;
  1979. if Self.Focused and (Screen.Cursor <> crHourGlass) then
  1980. Screen.Cursor := crHourGlass;
  1981. CurrentPath := NodePath(Node);
  1982. Drive := DriveInfo.GetDriveKey(CurrentPath);
  1983. if not Assigned(Node.Parent) then
  1984. GetDriveStatus(Drive).ChangeTimer.Enabled := False;
  1985. RestartWatchThread := WatchThreadActive;
  1986. try
  1987. if WatchThreadActive then
  1988. StopWatchThread;
  1989. FValidateFlag := True;
  1990. FSysColorChangePending := False;
  1991. New(Info);
  1992. Info^.StartNode := Node;
  1993. Info^.SearchNewDirs := NewDirs;
  1994. Info^.DriveType := DriveInfo.Get(Drive).DriveType;
  1995. SaveCanChange := FCanChange;
  1996. FCanChange := True;
  1997. FChangeFlag := False;
  1998. Items.BeginUpdate;
  1999. try
  2000. IterateSubTree(Node, CallBackValidateDir, Recurse, coScanStartNode, Info);
  2001. finally
  2002. Items.EndUpdate;
  2003. end;
  2004. FValidateFlag := False;
  2005. if (not Assigned(Selected)) and (Length(SelDir) > 0) then
  2006. Directory := ExtractFileDrive(SelDir);
  2007. if (SelDir <> Directory) and (not FChangeFlag) then
  2008. Change(Selected);
  2009. FCanChange := SaveCanChange;
  2010. Dispose(Info);
  2011. finally
  2012. if RestartWatchThread and FWatchDirectory and not WatchThreadActive then
  2013. StartWatchThread;
  2014. if Screen.Cursor <> SaveCursor then
  2015. Screen.Cursor := SaveCursor;
  2016. if FSysColorChangePending then
  2017. begin
  2018. FSysColorChangePending := False;
  2019. if HandleAllocated then Perform(CM_SYSCOLORCHANGE, 0, 0);
  2020. end;
  2021. end;
  2022. end;
  2023. end; {ValidateDirectoryEx}
  2024. function TDriveView.GetDriveTypeToNode(Node: TTreeNode): Integer;
  2025. begin
  2026. Assert(Assigned(Node));
  2027. Result := DriveInfo.Get(GetDriveToNode(Node)).DriveType;
  2028. end; {GetDriveTypeToNode}
  2029. procedure TDriveView.CreateWatchThread(Drive: string);
  2030. begin
  2031. if csDesigning in ComponentState then
  2032. Exit;
  2033. if (not Assigned(GetDriveStatus(Drive).DiscMonitor)) and
  2034. FWatchDirectory and
  2035. (DriveInfo.Get(Drive).DriveType <> DRIVE_REMOTE) then
  2036. begin
  2037. var DiscMonitor := TDiscMonitor.Create(Self);
  2038. GetDriveStatus(Drive).DiscMonitor := DiscMonitor;
  2039. DiscMonitor.ChangeDelay := msThreadChangeDelay;
  2040. DiscMonitor.SubTree := True;
  2041. DiscMonitor.Filters := [moDirName];
  2042. DiscMonitor.OnChange := ChangeDetected;
  2043. DiscMonitor.OnInvalid := ChangeInvalid;
  2044. DiscMonitor.SetDirectory(DriveInfo.GetDriveRoot(Drive));
  2045. DiscMonitor.Open;
  2046. SubscribeDriveNotifications(Drive);
  2047. end;
  2048. end; {CreateWatchThread}
  2049. procedure TDriveView.SetWatchDirectory(Value: Boolean);
  2050. begin
  2051. if FWatchDirectory <> Value then
  2052. begin
  2053. FWatchDirectory := Value;
  2054. if (not (csDesigning in ComponentState)) and Value then
  2055. StartAllWatchThreads
  2056. else
  2057. StopAllWatchThreads;
  2058. end;
  2059. end; {SetAutoScan}
  2060. procedure TDriveView.SetDirView(Value: TDirView);
  2061. begin
  2062. if Assigned(FDirView) then
  2063. FDirView.DriveView := nil;
  2064. FDirView := Value;
  2065. if Assigned(FDirView) then
  2066. FDirView.DriveView := Self;
  2067. end; {SetDirView}
  2068. function TDriveView.NodeWatched(Node: TTreeNode): Boolean;
  2069. var
  2070. Drive: string;
  2071. begin
  2072. Drive := GetDriveToNode(Node);
  2073. Result := WatchThreadActive(Drive);
  2074. end; {NodeWatched}
  2075. procedure TDriveView.ChangeInvalid(Sender: TObject; const Directory: string;
  2076. const ErrorStr: string);
  2077. var
  2078. Drive: string;
  2079. begin
  2080. Drive := DriveInfo.GetDriveKey((Sender as TDiscMonitor).Directories[0]);
  2081. GetDriveStatus(Drive).DiscMonitor.Close;
  2082. end; {DirWatchChangeInvalid}
  2083. procedure TDriveView.ChangeDetected(Sender: TObject; const Directory: string;
  2084. var SubdirsChanged: Boolean);
  2085. var
  2086. DirChanged: string;
  2087. begin
  2088. if Sender is TDiscMonitor then
  2089. begin
  2090. DirChanged := (Sender as TDiscMonitor).Directories[0];
  2091. if Length(DirChanged) > 0 then
  2092. begin
  2093. var ChangeTimer := GetDriveStatus(DriveInfo.GetDriveKey(DirChanged)).ChangeTimer;
  2094. ChangeTimer.Interval := 0;
  2095. ChangeTimer.Interval := FChangeInterval;
  2096. ChangeTimer.Enabled := True;
  2097. end;
  2098. end;
  2099. end; {DirWatchChangeDetected}
  2100. procedure TDriveView.ChangeTimerOnTimer(Sender: TObject);
  2101. var
  2102. DriveStatusPair: TDriveStatusPair;
  2103. begin
  2104. if (FChangeTimerSuspended = 0) and (Sender is TTimer) then
  2105. begin
  2106. for DriveStatusPair in FDriveStatus do
  2107. begin
  2108. if DriveStatusPair.Value.ChangeTimer = Sender then
  2109. begin
  2110. // Messages are processed during ValidateDirectory, so we may detect another change while
  2111. // updating the directory. Prevent the recursion.
  2112. // But retry the update afterwards (by reenabling the timer in ChangeDetected)
  2113. SuspendChangeTimer;
  2114. try
  2115. var ChangeTimer := DriveStatusPair.Value.ChangeTimer;
  2116. ChangeTimer.Interval := 0;
  2117. ChangeTimer.Enabled := False;
  2118. if Assigned(DriveStatusPair.Value.RootNode) then
  2119. begin
  2120. {Check also collapsed (invisible) subdirectories:}
  2121. ValidateDirectory(DriveStatusPair.Value.RootNode);
  2122. end;
  2123. finally
  2124. ResumeChangeTimer;
  2125. end;
  2126. end;
  2127. end;
  2128. end;
  2129. end; {ChangeTimerOnTimer}
  2130. procedure TDriveView.SubscribeDriveNotifications(Drive: string);
  2131. begin
  2132. // As previously, subscribe drive notification only once the drive is at least once visited.
  2133. // Shouldn't do much harm to subscribe always, but just in case.
  2134. DriveInfo.SubscribeDriveNotifications(Drive);
  2135. end;
  2136. procedure TDriveView.StartWatchThread;
  2137. var
  2138. Drive: string;
  2139. begin
  2140. if (csDesigning in ComponentState) or
  2141. not Assigned(Selected) or
  2142. not FWatchDirectory then Exit;
  2143. Drive := GetDriveToNode(Selected);
  2144. var DriveStatus := GetDriveStatus(Drive);
  2145. if not Assigned(DriveStatus.DiscMonitor) then
  2146. CreateWatchThread(Drive);
  2147. if Assigned(DriveStatus.DiscMonitor) and (not DriveStatus.DiscMonitor.Enabled) then
  2148. DriveStatus.DiscMonitor.Enabled := True;
  2149. SubscribeDriveNotifications(Drive);
  2150. end; {StartWatchThread}
  2151. procedure TDriveView.StopWatchThread;
  2152. var
  2153. Drive: string;
  2154. begin
  2155. if Assigned(Selected) then
  2156. begin
  2157. Drive := GetDriveToNode(Selected);
  2158. var DriveStatus := GetDriveStatus(Drive);
  2159. if Assigned(DriveStatus.DiscMonitor) then
  2160. DriveStatus.DiscMonitor.Enabled := False;
  2161. end;
  2162. end; {StopWatchThread}
  2163. procedure TDriveView.SuspendChangeTimer;
  2164. begin
  2165. Inc(FChangeTimerSuspended);
  2166. end;
  2167. procedure TDriveView.ResumeChangeTimer;
  2168. begin
  2169. Assert(FChangeTimerSuspended > 0);
  2170. Dec(FChangeTimerSuspended);
  2171. end;
  2172. procedure TDriveView.TerminateWatchThread(Drive: string);
  2173. begin
  2174. var DriveStatus := GetDriveStatus(Drive);
  2175. if Assigned(DriveStatus.DiscMonitor) then
  2176. begin
  2177. DriveStatus.DiscMonitor.Free;
  2178. DriveStatus.DiscMonitor := nil;
  2179. end;
  2180. end; {StopWatchThread}
  2181. procedure TDriveView.StartAllWatchThreads;
  2182. var
  2183. DriveStatusPair: TDriveStatusPair;
  2184. Drive: string;
  2185. begin
  2186. if (csDesigning in ComponentState) or (not FWatchDirectory) then
  2187. Exit;
  2188. for DriveStatusPair in FDriveStatus do
  2189. begin
  2190. var DriveStatus := DriveStatusPair.Value;
  2191. if DriveStatus.Scanned then
  2192. begin
  2193. if not Assigned(DriveStatus.DiscMonitor) then
  2194. CreateWatchThread(DriveStatusPair.Key);
  2195. if Assigned(DriveStatus.DiscMonitor) and (not DriveStatus.DiscMonitor.Active) then
  2196. begin
  2197. DriveStatus.DiscMonitor.Open;
  2198. SubscribeDriveNotifications(DriveStatusPair.Key);
  2199. end;
  2200. end;
  2201. end;
  2202. if Assigned(Selected) then
  2203. begin
  2204. Drive := GetDriveToNode(Selected);
  2205. if not DriveInfo.IsFixedDrive(Drive) then
  2206. begin
  2207. StartWatchThread;
  2208. end;
  2209. end;
  2210. end; {StartAllWatchThreads}
  2211. procedure TDriveView.StopAllWatchThreads;
  2212. var
  2213. DriveStatusPair: TDriveStatusPair;
  2214. begin
  2215. if (csDesigning in ComponentState) or (not FWatchDirectory) then
  2216. Exit;
  2217. for DriveStatusPair in FDriveStatus do
  2218. begin
  2219. var DriveStatus := DriveStatusPair.Value;
  2220. if Assigned(DriveStatus.DiscMonitor) then
  2221. begin
  2222. DriveStatus.DiscMonitor.Close;
  2223. end;
  2224. end;
  2225. end; {StopAllWatchThreads}
  2226. function TDriveView.WatchThreadActive(Drive: string): Boolean;
  2227. begin
  2228. Result := FWatchDirectory and
  2229. Assigned(GetDriveStatus(Drive).DiscMonitor) and
  2230. GetDriveStatus(Drive).DiscMonitor.Active and
  2231. GetDriveStatus(Drive).DiscMonitor.Enabled;
  2232. end; {WatchThreadActive}
  2233. function TDriveView.WatchThreadActive: Boolean;
  2234. var
  2235. Drive: string;
  2236. begin
  2237. if not Assigned(Selected) then
  2238. begin
  2239. Result := False;
  2240. Exit;
  2241. end;
  2242. Drive := GetDriveToNode(Selected);
  2243. Result := WatchThreadActive(Drive);
  2244. end; {WatchThreadActive}
  2245. function TDriveView.FindPathNode(Path: string): TTreeNode;
  2246. var
  2247. PossiblyHiddenPath: string;
  2248. Attrs: Integer;
  2249. begin
  2250. if Assigned(FOnNeedHiddenDirectories) and
  2251. (not ShowHiddenDirs) and
  2252. DirectoryExistsFix(Path) then // do not even bother if the path does not exist
  2253. begin
  2254. PossiblyHiddenPath := ExcludeTrailingPathDelimiter(Path);
  2255. while (PossiblyHiddenPath <> '') and
  2256. (not IsRootPath(PossiblyHiddenPath)) do // Drives have hidden attribute
  2257. begin
  2258. Attrs := FileGetAttr(PossiblyHiddenPath, False);
  2259. if (Attrs and faHidden) = faHidden then
  2260. begin
  2261. if Assigned(FOnNeedHiddenDirectories) then
  2262. begin
  2263. FOnNeedHiddenDirectories(Self);
  2264. end;
  2265. Break;
  2266. end
  2267. else
  2268. begin
  2269. PossiblyHiddenPath := ExtractFileDir(PossiblyHiddenPath);
  2270. end;
  2271. end;
  2272. end;
  2273. {Find existing path or parent path of not existing path:}
  2274. repeat
  2275. Result := FindNodeToPath(Path);
  2276. if not Assigned(Result) then
  2277. Path := ExtractFilePath(ExcludeTrailingBackslash(Path));
  2278. until Assigned(Result) or (Length(Path) < 3);
  2279. end;
  2280. procedure TDriveView.SetDirectory(Value: string);
  2281. begin
  2282. Value := IncludeTrailingBackslash(Value);
  2283. FDirectory := Value;
  2284. inherited;
  2285. if Assigned(Selected) and (not Assigned(Selected.Parent)) then
  2286. begin
  2287. if not GetDriveStatus(GetDriveToNode(Selected)).Scanned then
  2288. ScanDrive(GetDriveToNode(Selected));
  2289. end;
  2290. end; {SetDirectory}
  2291. function TDriveView.GetDirName(Node: TTreeNode): string;
  2292. begin
  2293. if Assigned(Node) and Assigned(Node.Data) then
  2294. Result := TNodeData(Node.Data).DirName
  2295. else
  2296. Result := '';
  2297. end; {GetDirName}
  2298. {GetDrive: returns the drive of the Node.}
  2299. function TDriveView.GetDriveToNode(Node: TTreeNode): string;
  2300. var
  2301. Path: string;
  2302. begin
  2303. if (not Assigned (Node)) or (not Assigned(Node.Data)) then
  2304. raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDrive']));
  2305. Path := NodePath(Node);
  2306. Result := DriveInfo.GetDriveKey(Path);
  2307. end; {GetDrive}
  2308. {RootNode: returns the rootnode to the Node:}
  2309. function TDriveView.RootNode(Node: TTreeNode): TTreeNode;
  2310. begin
  2311. Result := Node;
  2312. if not Assigned(Node) then
  2313. raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['RootNode']));
  2314. while Assigned(Result.Parent) do
  2315. Result := Result.Parent;
  2316. end; {RootNode}
  2317. function TDriveView.GetDisplayName(Node: TTreeNode): string;
  2318. begin
  2319. Result := '';
  2320. if (not Assigned(Node)) or (not Assigned(Node.Data)) then
  2321. raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDisplayName']));
  2322. if not Assigned(Node.Parent) then Result := GetDriveText(GetDriveToNode(Node))
  2323. else
  2324. begin
  2325. Result := GetDirName(Node);
  2326. end;
  2327. end; {GetDisplayName}
  2328. procedure TDriveView.SetShowVolLabel(ShowIt: Boolean);
  2329. begin
  2330. if ShowIt = FShowVolLabel then
  2331. Exit;
  2332. FShowVolLabel := ShowIt;
  2333. RefreshRootNodes(True);
  2334. end; {SetShowVolLabel}
  2335. procedure TDriveView.DisplayContextMenu(Node: TTreeNode; Point: TPoint);
  2336. var
  2337. Verb: string;
  2338. DirWatched: Boolean;
  2339. begin
  2340. DirWatched := NodeWatched(Node) and WatchThreadActive;
  2341. Assert(Node <> nil);
  2342. if Node <> Selected then
  2343. DropTarget := Node;
  2344. Verb := EmptyStr;
  2345. if Assigned(FOnDisplayContextMenu) then
  2346. FOnDisplayContextMenu(Self);
  2347. ShellDisplayContextMenu(FParentForm.Handle, Point, NodePathName(Node),
  2348. CanEdit(Node), Verb, False);
  2349. if Verb = shcRename then Node.EditText
  2350. else
  2351. if Verb = shcCut then
  2352. begin
  2353. ClearCutState;
  2354. LastClipBoardOperation := cboCut;
  2355. FLastPathCut := NodePathName(Node);
  2356. Node.Cut := True;
  2357. end
  2358. else
  2359. if Verb = shcCopy then
  2360. begin
  2361. ClearCutState;
  2362. LastClipBoardOperation := cboCopy;
  2363. end
  2364. else
  2365. if Verb = shcPaste then
  2366. PasteFromClipBoard(Node);
  2367. DropTarget := nil;
  2368. if not DirWatched then
  2369. ValidateDirectory(Node);
  2370. end; {DisplayContextMenu (2)}
  2371. procedure TDriveView.DisplayPropertiesMenu(Node: TTreeNode);
  2372. begin
  2373. Assert(Assigned(Node));
  2374. ShellExecuteContextCommand(FParentForm.Handle, shcProperties, NodePathName(Node));
  2375. end; {ContextMenu}
  2376. procedure TDriveView.SetSelected(Node: TTreeNode);
  2377. begin
  2378. if Node <> Selected then
  2379. begin
  2380. FChangeFlag := False;
  2381. FCanChange := True;
  2382. inherited Selected := Node;
  2383. if not FChangeFlag then
  2384. Change(Selected);
  2385. end;
  2386. end; {SetSelected}
  2387. {Called by TFileDeleteThread, when a file deletion was detected by the D&D receiving application:}
  2388. procedure TDriveView.SignalDirDelete(Sender: TObject; Files: TStringList);
  2389. begin
  2390. if Files.Count > 0 then
  2391. ValidateDirectory(FindNodeToPath(Files[0]));
  2392. end; {SignalDirDelete}
  2393. function TDriveView.DDSourceEffects: TDropEffectSet;
  2394. begin
  2395. if not Assigned(FDragNode.Parent) then
  2396. Result := [deLink]
  2397. else
  2398. Result := [deLink, deCopy, deMove];
  2399. end;
  2400. procedure TDriveView.DDChooseEffect(KeyState: Integer; var Effect: Integer; PreferredEffect: Integer);
  2401. begin
  2402. if DropTarget = nil then Effect := DROPEFFECT_NONE
  2403. else
  2404. if (KeyState and (MK_CONTROL or MK_SHIFT) = 0) and (PreferredEffect = 0) then
  2405. begin
  2406. if FDragDrive <> '' then
  2407. begin
  2408. if FExeDrag and DriveInfo.IsFixedDrive(GetDriveToNode(DropTarget)) and DriveInfo.IsFixedDrive(FDragDrive) then
  2409. begin
  2410. Effect := DROPEFFECT_LINK;
  2411. end
  2412. else
  2413. if (Effect = DROPEFFECT_COPY) and
  2414. (SameText(FDragDrive, GetDriveToNode(DropTarget)) and
  2415. (FDragDropFilesEx.AvailableDropEffects and DROPEFFECT_MOVE <> 0)) then
  2416. begin
  2417. Effect := DROPEFFECT_MOVE;
  2418. end;
  2419. end;
  2420. end;
  2421. inherited;
  2422. end;
  2423. function TDriveView.DragCompleteFileList: Boolean;
  2424. begin
  2425. Result := (GetDriveTypeToNode(FDragNode) <> DRIVE_REMOVABLE);
  2426. end;
  2427. function TDriveView.DDExecute: TDragResult;
  2428. var
  2429. WatchThreadOK: Boolean;
  2430. DragParentPath: string;
  2431. DragPath: string;
  2432. begin
  2433. WatchThreadOK := WatchThreadActive;
  2434. Result := FDragDropFilesEx.Execute(nil);
  2435. if (Result = drMove) and (not WatchThreadOK) then
  2436. begin
  2437. DragPath := NodePathName(FDragNode);
  2438. if Assigned(FDragNode.Parent) then
  2439. DragParentPath := NodePathName(FDragNode.Parent)
  2440. else
  2441. DragParentPath := DragPath;
  2442. if Assigned(FDragNode.Parent) or (DragParentPath <> NodePathName(Selected.Parent)) then
  2443. begin
  2444. FDragNode := FindNodeToPath(DragPath);
  2445. if Assigned(FDragNode) then
  2446. begin
  2447. FDragFileList.Clear;
  2448. FDragFileList.Add(DragPath);
  2449. TFileDeleteThread.Create(FDragFileList, MaxWaitTimeOut, SignalDirDelete);
  2450. end;
  2451. end;
  2452. end;
  2453. end;
  2454. procedure TDriveView.PerformDragDropFileOperation(Node: TTreeNode; Effect: Integer);
  2455. var
  2456. Index: Integer;
  2457. SourcePath: string;
  2458. SourceParentPath: string;
  2459. SourceIsDirectory: Boolean;
  2460. SaveCursor: TCursor;
  2461. SourceNode, TargetNode: TTreeNode;
  2462. TargetPath: string;
  2463. IsRecycleBin: Boolean;
  2464. begin
  2465. TargetPath := NodePathName(Node);
  2466. IsRecycleBin := NodeIsRecycleBin(Node);
  2467. if FDragDropFilesEx.FileList.Count = 0 then
  2468. Exit;
  2469. SaveCursor := Screen.Cursor;
  2470. Screen.Cursor := crHourGlass;
  2471. SourcePath := EmptyStr;
  2472. try
  2473. if (Effect = DROPEFFECT_COPY) or (Effect = DROPEFFECT_MOVE) then
  2474. begin
  2475. StopAllWatchThreads;
  2476. if Assigned(FDirView) then
  2477. FDirView.StopWatchThread;
  2478. if Assigned(DropSourceControl) and
  2479. (DropSourceControl is TDirView) and
  2480. (DropSourceControl <> FDirView) then
  2481. begin
  2482. TDirView(DropSourceControl).StopWatchThread;
  2483. end;
  2484. if DropFiles(
  2485. DragDropFilesEx, Effect, FFileOperator, TargetPath, IsRecycleBin, ConfirmDelete, ConfirmOverwrite,
  2486. Self, OnDDFileOperation, SourcePath, SourceIsDirectory) then
  2487. begin
  2488. if Assigned(FOnDDFileOperationExecuted) then
  2489. FOnDDFileOperationExecuted(Self, Effect, SourcePath, TargetPath);
  2490. end;
  2491. ClearDragFileList(FDragDropFilesEx.FileList);
  2492. // TDirView.PerformDragDropFileOperation validates the SourcePath and that actually seems correct
  2493. SourceParentPath := ExtractFilePath(ExcludeTrailingBackslash(SourcePath));
  2494. end
  2495. else
  2496. if Effect = DROPEFFECT_LINK then
  2497. { Create Link requested: }
  2498. begin
  2499. for Index := 0 to FDragDropFilesEx.FileList.Count - 1 do
  2500. begin
  2501. if not DropLink(PFDDListItem(FDragDropFilesEx.FileList[Index]), TargetPath) then
  2502. begin
  2503. DDError(DDCreateShortCutError);
  2504. end;
  2505. end;
  2506. end;
  2507. if Effect = DROPEFFECT_MOVE then
  2508. Items.BeginUpdate;
  2509. {Update source directory, if move-operation was performed:}
  2510. if ((Effect = DROPEFFECT_MOVE) or IsRecycleBin) then
  2511. begin
  2512. // See comment in corresponding operation in TDirView.PerformDragDropFileOperation
  2513. SourceNode := TryFindNodeToPath(SourceParentPath);
  2514. if Assigned(SourceNode) then
  2515. ValidateDirectory(SourceNode);
  2516. end;
  2517. {Update subdirectories of target directory:}
  2518. TargetNode := FindNodeToPath(TargetPath);
  2519. if Assigned(TargetNode) then
  2520. ValidateDirectory(TargetNode)
  2521. else
  2522. ValidateDirectory(GetDriveStatus(DriveInfo.GetDriveKey(TargetPath)).RootNode);
  2523. if Effect = DROPEFFECT_MOVE then
  2524. Items.EndUpdate;
  2525. {Update linked component TDirView:}
  2526. if Assigned(FDirView) and (not FDirView.WatchThreadActive) then
  2527. begin
  2528. case Effect of
  2529. DROPEFFECT_COPY,
  2530. DROPEFFECT_LINK:
  2531. if (IncludeTrailingBackslash(TargetPath) = IncludeTrailingBackslash(DirView.Path)) then
  2532. FDirView.Reload2;
  2533. DROPEFFECT_MOVE:
  2534. if (IncludeTrailingBackslash(TargetPath) = IncludeTrailingBackslash(DirView.Path)) or
  2535. (IncludeTrailingBackslash(SourceParentPath) = IncludeTrailingBackslash(DirView.Path)) then
  2536. begin
  2537. if FDirView <> DropSourceControl then FDirView.Reload2;
  2538. end;
  2539. end; {Case}
  2540. end;
  2541. {Update the DropSource control, if files are moved and it is a TDirView:}
  2542. if (Effect = DROPEFFECT_MOVE) and (DropSourceControl is TDirView) then
  2543. begin
  2544. TDirView(DropSourceControl).ValidateSelectedFiles;
  2545. end;
  2546. finally
  2547. FFileOperator.OperandFrom.Clear;
  2548. FFileOperator.OperandTo.Clear;
  2549. StartAllWatchThreads;
  2550. if Assigned(FDirView) and (not FDirView.WatchThreadActive) then
  2551. FDirView.StartWatchThread;
  2552. if Assigned(DropSourceControl) and (DropSourceControl is TDirView) and
  2553. (not TDirView(DropSourceControl).WatchThreadActive) then
  2554. TDirView(DropSourceControl).StartWatchThread;
  2555. Screen.Cursor := SaveCursor;
  2556. end;
  2557. end; {PerformDragDropFileOperation}
  2558. {Clipboard operations:}
  2559. procedure TDriveView.ClearCutState;
  2560. begin
  2561. if FLastPathCut <> '' then
  2562. begin
  2563. var Node := FindNodeToPath(FLastPathCut);
  2564. if Assigned(Node) then
  2565. begin
  2566. Node.Cut := False;
  2567. end;
  2568. FLastPathCut := '';
  2569. end;
  2570. LastClipBoardOperation := cboNone;
  2571. if Assigned(FDirView) and
  2572. FDirView.AnyCut then // prevent recursion
  2573. begin
  2574. FDirView.EmptyClipboard;
  2575. end;
  2576. end;
  2577. procedure TDriveView.EmptyClipboard;
  2578. begin
  2579. if Windows.OpenClipBoard(0) then
  2580. begin
  2581. Windows.EmptyClipBoard;
  2582. Windows.CloseClipBoard;
  2583. ClearCutState;
  2584. end;
  2585. end; {EmptyClipBoard}
  2586. function TDriveView.CanPasteFromClipBoard: Boolean;
  2587. begin
  2588. Result := False;
  2589. if Assigned(Selected) and Windows.OpenClipboard(0) then
  2590. begin
  2591. Result := IsClipboardFormatAvailable(CF_HDROP);
  2592. Windows.CloseClipBoard;
  2593. end;
  2594. end; {CanPasteFromClipBoard}
  2595. procedure TDriveView.PasteFromClipBoard(Node: TTreeNode);
  2596. begin
  2597. ClearDragFileList(FDragDropFilesEx.FileList);
  2598. if CanPasteFromClipBoard and FDragDropFilesEx.GetFromClipBoard then
  2599. begin
  2600. case LastClipBoardOperation of
  2601. cboCopy,
  2602. cboNone:
  2603. begin
  2604. PerformDragDropFileOperation(Node, DROPEFFECT_COPY);
  2605. if Assigned(FOnDDExecuted) then
  2606. FOnDDExecuted(Self, DROPEFFECT_COPY);
  2607. end;
  2608. cboCut:
  2609. begin
  2610. PerformDragDropFileOperation(Node, DROPEFFECT_MOVE);
  2611. if Assigned(FOnDDExecuted) then
  2612. FOnDDExecuted(Self, DROPEFFECT_MOVE);
  2613. EmptyClipBoard;
  2614. end;
  2615. end;
  2616. end;
  2617. end; {PasteFromClipBoard}
  2618. procedure TDriveView.CMSysColorChange(var Message: TMessage);
  2619. begin
  2620. if not FValidateFlag then
  2621. begin
  2622. inherited;
  2623. end
  2624. else
  2625. begin
  2626. // Do not recreate the handle, if we are just iterating nodes, at that invalidates the node objects.
  2627. // This is not perfect, as the handle can be recreated for other reasons.
  2628. // But system color change is by far the most common case.
  2629. FSysColorChangePending := True;
  2630. end;
  2631. end;
  2632. end.