DriveView.pas 83 KB

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