DriveView.pas 86 KB

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