12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071 |
- unit DriveView;
- {==================================================================
- Component TDriveView / Version 2.6, January 2000
- ==================================================================
- Description:
- ============
- Displays the the directory structure of all drives as treeview
- with shell icons. Complete drag&Drop support for files and
- directories.
- Author:
- =======
- (c) Ingo Eckel 1998, 1999
- Sodener Weg 38
- 65812 Bad Soden
- Germany
- Modifications (for WinSCP):
- ===========================
- (c) Martin Prikryl 2004
- V2.6:
- - Shows "shared"-symbol with directories
- - Delphi5 compatible
- For detailed documentation and history see TDriveView.htm.
- {==================================================================}
- interface
- { Define ENHVALIDATE to scan all existing directories on a detected filesystem change:}
- {.$DEFINE ENHVALIDATE}
- {Required compiler options for TDriveView:}
- {$A+,B-,X+,H+,P+}
- {$WARN SYMBOL_PLATFORM OFF}
- uses
- Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ComObj,
- Dialogs, ComCtrls, ShellApi, CommCtrl, ExtCtrls, ActiveX, ShlObj,
- DirView, ShellDialogs, DragDrop, DragDropFilesEx, FileChanges, FileOperator,
- DiscMon, IEDriveInfo, IEListView, BaseUtils, CustomDirView,
- CustomDriveView, System.Generics.Collections, CompThread;
- const
- msThreadChangeDelay = 50;
- ErrorNodeNA = '%s: Node not assigned';
- {Flags used by TDriveView.RefreshRootNodes:}
- dvdsFloppy = 8; {Include floppy drives}
- dvdsRereadAllways = 16; {Refresh drivestatus in any case}
- WM_USER_SUBDIRREADER = WM_USER_SHCHANGENOTIFY + 1;
- type
- EInvalidDirName = class(Exception);
- ENodeNotAssigned = class(Exception);
- TDriveStatus = class
- Scanned: Boolean; {Drive allready scanned?}
- Verified: Boolean; {Drive completly scanned?}
- RootNode: TTreeNode; {Rootnode to drive}
- RootNodeIndex: Integer;
- DiscMonitor: TDiscMonitor; {Monitor thread}
- ChangeTimer: TTimer; {Change timer for the monitor thread}
- DefaultDir: string; {Current directory}
- end;
- TDriveStatusPair = TPair<string, TDriveStatus>;
- TScanDirInfo = record
- SearchNewDirs: Boolean;
- StartNode: TTreeNode;
- DriveType: Integer;
- end;
- PScanDirInfo = ^TScanDirInfo;
- TDriveView = class;
- TSubDirReaderSchedule = class
- Node: TTreeNode;
- Path: string;
- Deleted: Boolean;
- Processed: Boolean;
- end;
- TNodeData = class
- private
- FDirName: string;
- FAttr: Integer;
- FScanned: Boolean;
- FData: Pointer;
- FIsRecycleBin: Boolean;
- FIconEmpty: Boolean;
- FSchedule: TSubDirReaderSchedule;
- public
- DelayedSrec: TSearchRec;
- DelayedExclude: TStringList;
- constructor Create;
- destructor Destroy; override;
- property DirName: string read FDirName write FDirName;
- property Attr: Integer read FAttr write FAttr;
- property Scanned: Boolean read FScanned write FScanned;
- property Data: Pointer read FData write FData;
- property IsRecycleBin: Boolean read FIsRecycleBin;
- property IconEmpty: Boolean read FIconEmpty write FIconEmpty;
- property Schedule: TSubDirReaderSchedule read FSchedule write FSchedule;
- end;
- TDriveTreeNode = class(TTreeNode)
- procedure Assign(Source: TPersistent); override;
- end;
- TSubDirReaderThread = class(TCompThread)
- public
- destructor Destroy; override;
- procedure Terminate; override;
- protected
- constructor Create(DriveView: TDriveView);
- procedure Add(Node: TTreeNode; Path: string);
- procedure Delete(Node: TTreeNode);
- function Detach: Integer;
- procedure Reattach(Count: Integer);
- procedure Execute; override;
- private
- FDriveView: TDriveView;
- FEvent: THandle;
- FQueue: TStack<TSubDirReaderSchedule>;
- FResults: TQueue<TSubDirReaderSchedule>;
- FSection: TRTLCriticalSection;
- FTimer: TTimer;
- FWindowHandle: HWND;
- procedure TriggerEvent;
- procedure ScheduleProcess;
- procedure Process;
- function ProcessResult: Boolean;
- procedure Timer(Sender: TObject);
- procedure WndProc(var Msg: TMessage);
- end;
- TDriveView = class(TCustomDriveView)
- private
- FDriveStatus: TObjectDictionary<string, TDriveStatus>;
- FConfirmDelete: Boolean;
- FConfirmOverwrite: Boolean;
- FWatchDirectory: Boolean;
- FDirectory: string;
- FShowVolLabel: Boolean;
- FVolDisplayStyle: TVolumeDisplayStyle;
- FChangeFlag: Boolean;
- FLastDir: string;
- FValidateFlag: Boolean;
- FSysColorChangePending: Boolean;
- FCreating: Boolean;
- FForceRename: Boolean;
- FRenameNode: TTreeNode;
- FLastRenameName: string;
- FPrevSelected: TTreeNode;
- FPrevSelectedIndex: Integer;
- FChangeTimerSuspended: Integer;
- FSubDirReaderThread: TSubDirReaderThread;
- FDelayedNodes: TStringList;
- FDelayedNodeTimer: TTimer;
- {Additional events:}
- FOnDisplayContextMenu: TNotifyEvent;
- FOnNeedHiddenDirectories: TNotifyEvent;
- {used components:}
- FDirView: TDirView;
- FFileOperator: TFileOperator;
- FChangeInterval: Cardinal;
- {Drag&drop:}
- FLastPathCut: string;
- {Drag&drop helper functions:}
- procedure SignalDirDelete(Sender: TObject; Files: TStringList);
- function GetSubDir(var SRec: TSearchRec): Boolean;
- function FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
- function FindNextSubDir(var SRec: TSearchRec): Boolean;
- procedure ReadSubDirs(Node: TTreeNode);
- procedure CancelDelayedNode(Node: TTreeNode);
- procedure DelayedNodeTimer(Sender: TObject);
- function ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
- procedure UpdateDelayedNodeTimer;
- {Callback-functions used by iteratesubtree:}
- function CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boolean;
- procedure DeleteNode(Node: TTreeNode);
- { Notification procedures used by component TDiscMonitor: }
- procedure ChangeDetected(Sender: TObject; const Directory: string;
- var SubdirsChanged: Boolean);
- procedure ChangeInvalid(Sender: TObject; const Directory: string; const ErrorStr: string);
- {Notification procedure used by component TTimer:}
- procedure ChangeTimerOnTimer(Sender: TObject);
- protected
- procedure SetSelected(Node: TTreeNode);
- procedure SetWatchDirectory(Value: Boolean);
- procedure SetShowVolLabel(ShowIt: Boolean);
- procedure SetDirView(Value: TDirView);
- procedure SetDirectory(Value: string); override;
- function DoScanDir(FromNode: TTreeNode): Boolean;
- procedure AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec);
- procedure CreateWatchThread(Drive: string);
- function NodeWatched(Node: TTreeNode): Boolean;
- procedure TerminateWatchThread(Drive: string);
- function WatchThreadActive: Boolean; overload;
- function WatchThreadActive(Drive: string): Boolean; overload;
- procedure SubscribeDriveNotifications(Drive: string);
- procedure DriveRemoved(Drive: string);
- procedure DriveRemoving(Drive: string);
- procedure RefreshRootNodes(Floppy: Boolean = False);
- procedure DriveNotification(Notification: TDriveNotification; Drive: string);
- function DirAttrMask: Integer;
- function CreateDriveStatus: TDriveStatus;
- procedure ValidateDirectoryEx(Node: TTreeNode; Recurse: TRecursiveScan;
- NewDirs: Boolean); override;
- procedure RebuildTree; override;
- procedure SetLastPathCut(Path: string);
- function GetCanUndoCopyMove: Boolean; virtual;
- procedure CreateWnd; override;
- procedure DestroyWnd; override;
- procedure Edit(const Item: TTVItem); override;
- procedure WMUserRename(var Message: TMessage); message WM_USER_RENAME;
- procedure CMRecreateWnd(var Msg: TMessage); message CM_RECREATEWND;
- procedure CMSysColorChange(var Message: TMessage); message CM_SYSCOLORCHANGE;
- function GetCustomDirView: TCustomDirView; override;
- procedure SetCustomDirView(Value: TCustomDirView); override;
- function NodePath(Node: TTreeNode): string; override;
- function NodeIsRecycleBin(Node: TTreeNode): Boolean; override;
- function NodePathExists(Node: TTreeNode): Boolean; override;
- function NodeColor(Node: TTreeNode): TColor; override;
- function FindPathNode(Path: string): TTreeNode; override;
- function DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTreeNode;
- function CreateNode: TTreeNode; override;
- function DDSourceEffects: TDropEffectSet; override;
- procedure DDChooseEffect(KeyState: Integer; var Effect: Integer; PreferredEffect: Integer); override;
- function DragCompleteFileList: Boolean; override;
- function DDExecute: TDragResult; override;
- public
- property Images;
- property StateImages;
- property Items stored False;
- property Selected Write SetSelected stored False;
- property DragImageList: TDragImageList read FDragImageList;
- property CanUndoCopyMove: Boolean read GetCanUndoCopyMove;
- property DDFileOperator: TFileOperator read FFileOperator;
- property LastPathCut: string read FLastPathCut write SetLastPathCut;
- function UndoCopyMove: Boolean; dynamic;
- procedure EmptyClipboard; dynamic;
- function CopyToClipBoard(Node: TTreeNode): Boolean; dynamic;
- function CutToClipBoard(Node: TTreeNode): Boolean; dynamic;
- function CanPasteFromClipBoard: Boolean; dynamic;
- function PasteFromClipBoard(TargetPath: string = ''): Boolean; dynamic;
- procedure PerformDragDropFileOperation(Node: TTreeNode; Effect: Integer); override;
- {Drive handling:}
- function GetDriveStatus(Drive: string): TDriveStatus;
- function GetDriveTypetoNode(Node: TTreeNode): Integer; {Returns DRIVE_CDROM etc..}
- function GetDriveToNode(Node: TTreeNode): string;
- function GetDriveText(Drive: string): string;
- procedure ScanDrive(Drive: string);
- function GetDrives: TStrings;
- {Node handling:}
- procedure SetImageIndex(Node: TTreeNode); virtual;
- function FindNodeToPath(Path: string): TTreeNode;
- function TryFindNodeToPath(Path: string): TTreeNode;
- function RootNode(Node: TTreeNode): TTreeNode;
- function GetDirName(Node: TTreeNode): string;
- function GetDisplayName(Node: TTreeNode): string;
- function NodePathName(Node: TTreeNode): string; override;
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- {Menu-handling:}
- procedure DisplayContextMenu(Node: TTreeNode; Point: TPoint); override;
- procedure DisplayPropertiesMenu(Node: TTreeNode); override;
- {Watchthread handling:}
- procedure StartWatchThread;
- procedure StopWatchThread;
- procedure SuspendChangeTimer;
- procedure ResumeChangeTimer;
- procedure StartAllWatchThreads;
- procedure StopAllWatchThreads;
- procedure ValidateCurrentDirectoryIfNotMonitoring;
- (* Modified Events: *)
- procedure GetImageIndex(Node: TTreeNode); override;
- function CanEdit(Node: TTreeNode): Boolean; override;
- function CanChange(Node: TTreeNode): Boolean; override;
- function CanExpand(Node: TTreeNode): Boolean; override;
- procedure Delete(Node: TTreeNode); override;
- procedure Loaded; override;
- procedure KeyPress(var Key: Char); override;
- procedure Change(Node: TTreeNode); override;
- published
- {Additional properties:}
- {Current selected directory:}
- property Directory;
- {Confirm deleting directories:}
- property ConfirmDelete: Boolean read FConfirmDelete write FConfirmDelete default True;
- {Confirm overwriting directories:}
- property ConfirmOverwrite: Boolean read FConfirmOverwrite write FConfirmOverwrite default True;
- {Enable automatic update on filesystem changes:}
- property WatchDirectory: Boolean read FWatchDirectory write SetWatchDirectory default False;
- {Linked component TDirView:}
- property DirView: TDirView read FDirView write SetDirView;
- {Show the volume labels of drives:}
- property ShowVolLabel: Boolean read FShowVolLabel write SetShowVolLabel default True;
- {Additional events:}
- property OnDisplayContextMenu: TNotifyEvent read FOnDisplayContextMenu
- write FOnDisplayContextMenu;
- property OnBusy;
- property DDLinkOnExeDrag;
- property TargetPopUpMenu;
- property OnDDDragEnter;
- property OnDDDragLeave;
- property OnDDDragOver;
- property OnDDDrop;
- property OnDDQueryContinueDrag;
- property OnDDGiveFeedback;
- property OnDDDragDetect;
- property OnDDProcessDropped;
- property OnDDError;
- property OnDDExecuted;
- property OnDDFileOperation;
- property OnDDFileOperationExecuted;
- property Align;
- property Anchors;
- property AutoExpand;
- property BiDiMode;
- property BorderStyle;
- property BorderWidth;
- property ChangeDelay;
- property Color;
- property Ctl3D;
- property Constraints;
- property DoubleBuffered;
- {Delphi's drag&drop is not compatible with the OLE windows drag&drop:}
- property DragKind;
- property DragCursor;
- property DragMode Default dmAutomatic;
- property OnDragDrop;
- property OnDragOver;
- property Enabled;
- property Font;
- property HideSelection;
- property HotTrack;
- property Indent;
- property ParentBiDiMode;
- property ParentColor;
- property ParentCtl3D;
- property ParentDoubleBuffered;
- property ParentFont;
- property ParentShowHint;
- property PopupMenu;
- property ReadOnly;
- property RightClickSelect;
- property RowSelect;
- property ShowButtons;
- property ShowHint;
- property ShowLines;
- property TabOrder;
- property TabStop default True;
- property ToolTips;
- property Visible;
- property OnChange;
- property OnChanging;
- property OnClick;
- property OnCollapsing;
- property OnCollapsed;
- property OnCompare;
- property OnDblClick;
- property OnDeletion;
- property OnEdited;
- property OnEditing;
- property OnEndDock;
- property OnEndDrag;
- property OnEnter;
- property OnExit;
- property OnExpanding;
- property OnExpanded;
- property OnGetImageIndex;
- property OnGetSelectedIndex;
- property OnKeyDown;
- property OnKeyPress;
- property OnKeyUp;
- property OnMouseDown;
- property OnMouseMove;
- property OnMouseUp;
- property OnStartDock;
- property OnStartDrag;
- property OnNeedHiddenDirectories: TNotifyEvent read FOnNeedHiddenDirectories write FOnNeedHiddenDirectories;
- end;
- var
- DriveViewLoadingTooLongLimit: Integer = 0;
- procedure Register;
- implementation
- uses
- PasTools, UITypes, SyncObjs, IOUtils, System.DateUtils;
- type
- PInt = ^Integer;
- procedure Register;
- begin
- RegisterComponents('DriveDir', [TDriveView]);
- end; {Register}
- constructor TNodeData.Create;
- begin
- inherited;
- FAttr := 0;
- FScanned := False;
- FDirName := '';
- FIsRecycleBin := False;
- FIconEmpty := True;
- FSchedule := nil;
- DelayedExclude := nil;
- end; {TNodeData.Create}
- destructor TNodeData.Destroy;
- begin
- Assert(not Assigned(FSchedule));
- SetLength(FDirName, 0);
- inherited;
- end; {TNodeData.Destroy}
- { TSubDirReaderThread }
- constructor TSubDirReaderThread.Create(DriveView: TDriveView);
- begin
- inherited Create(True);
- FDriveView := DriveView;
- FSection.Initialize;
- FEvent := CreateEvent(nil, False, False, nil);
- FQueue := TStack<TSubDirReaderSchedule>.Create;
- FResults := TQueue<TSubDirReaderSchedule>.Create;
- FTimer := TTimer.Create(FDriveView);
- FTimer.Enabled := False;
- FTimer.Interval := 200;
- FTimer.OnTimer := Timer;
- FWindowHandle := AllocateHWnd(WndProc);
- end;
- destructor TSubDirReaderThread.Destroy;
- procedure DestroyScheduleList(List: TEnumerable<TSubDirReaderSchedule>);
- var
- Schedule: TSubDirReaderSchedule;
- begin
- for Schedule in List do
- begin
- if not Schedule.Deleted then
- TNodeData(Schedule.Node.Data).Schedule := nil;
- Schedule.Free;
- end;
- List.Destroy;
- end;
- begin
- inherited;
- DeallocateHWnd(FWindowHandle);
- DestroyScheduleList(FQueue);
- DestroyScheduleList(FResults);
- CloseHandle(FEvent);
- FTimer.Destroy;
- FSection.Destroy;
- end;
- procedure TSubDirReaderThread.WndProc(var Msg: TMessage);
- begin
- if Msg.Msg = WM_USER_SUBDIRREADER then
- ScheduleProcess
- else
- Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
- end;
- procedure TSubDirReaderThread.Process;
- var
- Started: DWORD;
- Elapsed: Integer;
- Later: Boolean;
- begin
- Started := GetTickCount;
- Later := False;
- while (not Later) and ProcessResult do
- begin
- Elapsed := GetTickCount - Started;
- Later := (Elapsed < 0) or (Elapsed > 20);
- end;
- if not Later then
- FTimer.Enabled := False;
- end;
- procedure TSubDirReaderThread.Timer(Sender: TObject);
- begin
- Process;
- end;
- procedure TSubDirReaderThread.Add(Node: TTreeNode; Path: string);
- var
- NodeData: TNodeData;
- Schedule: TSubDirReaderSchedule;
- begin
- if Suspended then
- Resume;
- FSection.Enter;
- try
- NodeData := TNodeData(Node.Data);
- Assert(not Assigned(NodeData.Schedule));
- Schedule := TSubDirReaderSchedule.Create;
- Schedule.Node := Node;
- Schedule.Path := Path;
- Schedule.Deleted := False;
- Schedule.Processed := False;
- FQueue.Push(Schedule);
- NodeData.Schedule := Schedule;
- finally
- FSection.Leave;
- end;
- TriggerEvent;
- end;
- procedure TSubDirReaderThread.Delete(Node: TTreeNode);
- var
- NodeData: TNodeData;
- begin
- FSection.Enter;
- try
- NodeData := TNodeData(Node.Data);
- if Assigned(NodeData.Schedule) then
- begin
- NodeData.Schedule.Deleted := True;
- NodeData.Schedule := nil;
- end;
- finally
- FSection.Leave;
- end;
- TriggerEvent;
- end;
- function TSubDirReaderThread.Detach: Integer;
- procedure DetachList(List: TEnumerable<TSubDirReaderSchedule>);
- var
- Schedule: TSubDirReaderSchedule;
- begin
- for Schedule in List do
- begin
- if Schedule.Deleted then Schedule.Free
- else
- begin
- Assert(Schedule.Processed = (List = FResults));
- Schedule.Node := nil;
- Inc(Result);
- end;
- end;
- end;
- begin
- // block thread while handle is being recreated
- FSection.Enter;
- try
- Result := 0;
- DetachList(FQueue);
- DetachList(FResults);
- FQueue.Clear;
- FResults.Clear;
- except
- FSection.Leave;
- raise;
- end;
- end;
- procedure TSubDirReaderThread.Reattach(Count: Integer);
- var
- Node: TTreeNode;
- Schedule: TSubDirReaderSchedule;
- begin
- try
- if Count > 0 then
- begin
- Node := FDriveView.Items.GetFirstNode;
- while Assigned(Node) do
- begin
- Schedule := TNodeData(Node.Data).Schedule;
- if Assigned(Schedule) then
- begin
- Assert(not Assigned(Schedule.Node));
- Schedule.Node := Node;
- if not Schedule.Processed then
- FQueue.Push(Schedule)
- else
- FResults.Enqueue(Schedule);
- Assert(Count > 0);
- // Can be optimized to stop once Count = 0
- Dec(Count);
- end;
- Node := Node.GetNext;
- end;
- if Count = 0 then Assert(False); // shut up
- end;
- finally
- FSection.Leave;
- end;
- TriggerEvent;
- ScheduleProcess;
- end;
- procedure TSubDirReaderThread.Terminate;
- begin
- inherited;
- TriggerEvent;
- end;
- procedure TSubDirReaderThread.TriggerEvent;
- begin
- SetEvent(FEvent);
- end;
- function TSubDirReaderThread.ProcessResult: Boolean;
- var
- Node: TTreeNode;
- NodeData: TNodeData;
- Schedule: TSubDirReaderSchedule;
- begin
- FSection.Enter;
- try
- Result := (FResults.Count > 0);
- if Result then
- begin
- Schedule := FResults.Dequeue;
- if not Schedule.Deleted then
- begin
- Assert(Schedule.Processed);
- Node := Schedule.Node;
- Node.HasChildren := False;
- NodeData := TNodeData(Node.Data);
- NodeData.Scanned := not Node.HasChildren; // = True
- Assert(NodeData.Schedule = Schedule);
- NodeData.Schedule := nil;
- end;
- Schedule.Free;
- end;
- finally
- FSection.Leave;
- end;
- end;
- procedure TSubDirReaderThread.ScheduleProcess;
- begin
- // process the first batch immediatelly, to make it more likely that the first seen subdirectories
- // will immediatelly show correct status
- Process;
- FTimer.Enabled := True;
- end;
- procedure TSubDirReaderThread.Execute;
- var
- SRec: TSearchRec;
- HasSubDirs: Boolean;
- NodeData: TNodeData;
- Schedule: TSubDirReaderSchedule;
- DelayStart, DelayStartStep: Integer;
- begin
- DelayStart := 3000;
- DelayStartStep := 100;
- while (DelayStart > 0) and (not Terminated) do
- begin
- Sleep(DelayStartStep);
- Dec(DelayStart, DelayStartStep)
- end;
- while not Terminated do
- begin
- WaitForSingleObject(FEvent, INFINITE);
- while not Terminated do
- begin
- FSection.Enter;
- try
- if FQueue.Count = 0 then
- begin
- Break;
- end
- else
- begin
- Schedule := FQueue.Pop;
- if Schedule.Deleted then
- begin
- Schedule.Free;
- // Can be optimized to loop within locked critical section until first non-deleted schedule is found
- Continue;
- end;
- Assert(not Schedule.Processed);
- end
- finally
- FSection.Leave;
- end;
- HasSubDirs := FDriveView.FindFirstSubDir(IncludeTrailingBackslash(Schedule.Path) + '*.*', SRec);
- FindClose(SRec);
- FSection.Enter;
- try
- if Schedule.Deleted then
- begin
- Schedule.Free;
- end
- else
- begin
- Schedule.Processed := True;
- if not HasSubDirs then // optimization
- begin
- FResults.Enqueue(Schedule);
- if FResults.Count = 1 then
- PostMessage(FWindowHandle, WM_USER_SUBDIRREADER, 0, 0);
- end
- else
- begin
- // can happen only if the tree handle is just being recreated
- if Assigned(Schedule.Node) then
- begin
- NodeData := TNodeData(Schedule.Node.Data);
- NodeData.Schedule := nil;
- end;
- Schedule.Free;
- end;
- end;
- finally
- FSection.Leave;
- end;
- end;
- end;
- end;
- { TDriveTreeNode }
- // Not sure if this is ever used (possibly only then "assigning" tree view to another instance, what never do).
- // It is NOT used when recreating a tree view handle - for that a node is serialized and deserialized,
- // including a pointer to TNodeData. See csRecreating condition in TDriveView.Delete.
- procedure TDriveTreeNode.Assign(Source: TPersistent);
- var
- SourceData: TNodeData;
- NewData: TNodeData;
- begin
- Assert(False);
- inherited Assign(Source);
- if not Deleting and (Source is TTreeNode) then
- begin
- SourceData := TNodeData(TTreeNode(Source).Data);
- NewData := TNodeData.Create();
- NewData.DirName := SourceData.DirName;
- NewData.Attr := SourceData.Attr;
- NewData.Scanned := SourceData.Scanned;
- NewData.Data := SourceData.Data;
- NewData.FIsRecycleBin := SourceData.FIsRecycleBin;
- NewData.IconEmpty := SourceData.IconEmpty;
- TTreeNode(Source).Data := NewData;
- end;
- end;
- { TDriveView }
- constructor TDriveView.Create(AOwner: TComponent);
- var
- Drive: TRealDrive;
- begin
- inherited;
- FCreating := True;
- FDriveStatus := TObjectDictionary<string, TDriveStatus>.Create([doOwnsValues]);
- FChangeInterval := MSecsPerSec;
- for Drive := FirstDrive to LastDrive do
- begin
- FDriveStatus.Add(Drive, CreateDriveStatus);
- end;
- FFileOperator := TFileOperator.Create(Self);
- FSubDirReaderThread := TSubDirReaderThread.Create(Self);
- FDelayedNodes := TStringList.Create;
- FDelayedNodeTimer := TTimer.Create(Self);
- UpdateDelayedNodeTimer;
- FDelayedNodeTimer.Interval := 250;
- FDelayedNodeTimer.OnTimer := DelayedNodeTimer;
- FShowVolLabel := True;
- FChangeFlag := False;
- FLastDir := EmptyStr;
- FValidateFlag := False;
- FSysColorChangePending := False;
- FConfirmDelete := True;
- FDirectory := EmptyStr;
- FForceRename := False;
- FLastRenameName := '';
- FRenameNode := nil;
- FPrevSelected := nil;
- FPrevSelectedIndex := -1;
- FChangeTimerSuspended := 0;
- FConfirmOverwrite := True;
- FLastPathCut := '';
- FStartPos.X := -1;
- FStartPos.Y := -1;
- FDragPos := FStartPos;
- DriveInfo.AddHandler(DriveNotification);
- with FDragDropFilesEx do
- begin
- ShellExtensions.DragDropHandler := True;
- end;
- end; {Create}
- destructor TDriveView.Destroy;
- var
- DriveStatusPair: TDriveStatusPair;
- begin
- DriveInfo.RemoveHandler(DriveNotification);
- for DriveStatusPair in FDriveStatus do
- begin
- with DriveStatusPair.Value do
- begin
- if Assigned(DiscMonitor) then
- FreeAndNil(DiscMonitor);
- if Assigned(ChangeTimer) then
- FreeAndNil(ChangeTimer);
- end;
- end;
- FDriveStatus.Free;
- if Assigned(FFileOperator) then
- FFileOperator.Free;
- FSubDirReaderThread.Free;
- Assert(FDelayedNodes.Count = 0);
- FreeAndNil(FDelayedNodes);
- inherited Destroy;
- end; {Destroy}
- function TDriveView.CreateDriveStatus: TDriveStatus;
- begin
- Result := TDriveStatus.Create;
- with Result do
- begin
- Scanned := False;
- Verified := False;
- RootNode := nil;
- RootNodeIndex := -1;
- DiscMonitor := nil;
- DefaultDir := EmptyStr;
- {ChangeTimer: }
- ChangeTimer := TTimer.Create(Self);
- ChangeTimer.Interval := 0;
- ChangeTimer.Enabled := False;
- ChangeTimer.OnTimer := ChangeTimerOnTimer;
- end;
- end;
- procedure TDriveView.DriveRemoving(Drive: string);
- begin
- DriveRemoved(Drive);
- TerminateWatchThread(Drive);
- end;
- procedure TDriveView.CreateWnd;
- var
- DriveStatus: TDriveStatus;
- begin
- inherited;
- FDragDropFilesEx.SourceEffects := [deCopy, deMove, deLink];
- FDragDropFilesEx.TargetEffects := [deCopy, deMove, deLink];
- if FPrevSelectedIndex >= 0 then
- begin
- FPrevSelected := Items[FPrevSelectedIndex];
- FPrevSelectedIndex := -1;
- end;
- for DriveStatus in FDriveStatus.Values do
- with DriveStatus do
- begin
- if RootNodeIndex >= 0 then
- begin
- RootNode := Items[RootNodeIndex];
- RootNodeIndex := -1;
- end;
- end;
- UpdateDelayedNodeTimer;
- end; {CreateWnd}
- procedure TDriveView.DestroyWnd;
- var
- DriveStatus: TDriveStatus;
- I: Integer;
- begin
- FDelayedNodeTimer.Enabled := False;
- for I := 0 to FDelayedNodes.Count - 1 do
- FDelayedNodes.Objects[I] := nil;
- if not (csRecreating in ControlState) then
- begin
- FSubDirReaderThread.Terminate;
- FSubDirReaderThread.WaitFor;
- end
- else
- if CreateWndRestores and (Items.Count > 0) then
- begin
- FPrevSelectedIndex := -1;
- if Assigned(FPrevSelected) then
- begin
- FPrevSelectedIndex := FPrevSelected.AbsoluteIndex;
- FPrevSelected := nil;
- end;
- for DriveStatus in FDriveStatus.Values do
- with DriveStatus do
- begin
- RootNodeIndex := -1;
- if Assigned(RootNode) then
- begin
- RootNodeIndex := RootNode.AbsoluteIndex;
- RootNode := nil;
- end;
- end;
- end;
- inherited;
- end;
- function TDriveView.NodeColor(Node: TTreeNode): TColor;
- begin
- Result := clDefaultItemColor;
- with TNodeData(Node.Data) do
- if not Node.Selected then
- begin
- {Colored display of compressed directories:}
- if (Attr and FILE_ATTRIBUTE_COMPRESSED) <> 0 then
- begin
- if SupportsDarkMode and DarkMode then Result := clSkyBlue
- else Result := clBlue;
- end
- else
- {Dimmed display, if hidden-atrribut set:}
- if FDimmHiddenDirs and ((Attr and FILE_ATTRIBUTE_HIDDEN) <> 0) then
- Result := clGrayText
- end;
- end;
- function TDriveView.GetCustomDirView: TCustomDirView;
- begin
- Result := DirView;
- end;
- procedure TDriveView.SetCustomDirView(Value: TCustomDirView);
- begin
- DirView := Value as TDirView;
- end;
- function TDriveView.NodePath(Node: TTreeNode): string;
- var
- ParentNode: TTreeNode;
- begin
- if not Assigned(Node) then
- raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDirPath']));
- Result := GetDirName(Node);
- Node := Node.Parent;
- while Assigned(Node) do
- begin
- ParentNode := Node.Parent;
- if Assigned(ParentNode) then
- Result := GetDirName(Node) + '\' + Result
- else
- Result := GetDirName(Node) + Result;
- Node := ParentNode;
- end;
- if IsRootPath(Result) then
- Result := ExcludeTrailingBackslash(Result);
- end;
- {NodePathName: Returns the complete path to Node with trailing backslash on rootnodes:
- C:\ ,C:\WINDOWS, C:\WINDOWS\SYSTEM }
- function TDriveView.NodePathName(Node: TTreeNode): string;
- begin
- Result := NodePath(Node);
- if IsRootPath(Result) then
- Result := IncludeTrailingBackslash(Result);
- end; {NodePathName}
- function TDriveView.NodeIsRecycleBin(Node: TTreeNode): Boolean;
- begin
- Result := TNodeData(Node.Data).IsRecycleBin;
- end;
- function TDriveView.NodePathExists(Node: TTreeNode): Boolean;
- begin
- Result := DirectoryExists(ApiPath(NodePathName(Node)));
- end;
- function TDriveView.CanEdit(Node: TTreeNode): Boolean;
- begin
- Result := inherited CanEdit(Node) or FForceRename;
- if Result then
- begin
- Result := Assigned(Node.Parent) and
- (not TNodeData(Node.Data).IsRecycleBin) and
- (not ReadOnly) and
- (FDragDropFilesEx.DragDetectStatus <> ddsDrag) and
- ((TNodeData(Node.Data).Attr and (faReadOnly or faSysFile)) = 0) and
- (UpperCase(Node.Text) = UpperCase(GetDirName(Node)));
- end;
- FForceRename := False;
- end; {CanEdit}
- procedure TDriveView.Edit(const Item: TTVItem);
- var
- Node: TTreeNode;
- Info: string;
- i: Integer;
- begin
- Node := GetNodeFromHItem(Item);
- if (Length(Item.pszText) > 0) and (Item.pszText <> Node.Text) then
- begin
- if StrContains(coInvalidDosChars, Item.pszText) then
- begin
- Info := coInvalidDosChars;
- for i := Length(Info) downto 1 do
- System.Insert(Space, Info, i);
- if Length(Item.pszText) > 0 then
- raise EInvalidDirName.Create(SErrorInvalidName + Space + Info);
- Exit;
- end;
- StopWatchThread;
- if Assigned(DirView) then
- DirView.StopWatchThread;
- with FFileOperator do
- begin
- Flags := FileOperatorDefaultFlags + [foNoConfirmation];
- Operation := foRename;
- OperandFrom.Clear;
- OperandTo.Clear;
- OperandFrom.Add(NodePath(Node));
- OperandTo.Add(IncludeTrailingBackslash(NodePath(Node.Parent)) + Item.pszText);
- end;
- try
- if FFileOperator.Execute then
- begin
- Node.Text := Item.pszText;
- TNodeData(Node.Data).DirName := Item.pszText;
- SortChildren(Node.Parent, False);
- inherited;
- end
- else
- begin
- if FileOrDirExists(IncludeTrailingBackslash(NodePath(Node.Parent)) + Item.pszText) then
- Info := SErrorRenameFileExists + Item.pszText
- else
- Info := SErrorRenameFile + Item.pszText;
- MessageBeep(MB_ICONHAND);
- if MessageDlg(FormatLastOSError(Info), mtError, [mbOK, mbAbort], 0) = mrOK then
- begin
- FLastRenameName := Item.pszText;
- FRenameNode := Node;
- PostMessage(Self.Handle, WM_USER_RENAME, 0, 0);
- end;
- end;
- finally
- StartWatchThread;
- if Assigned(DirView) then
- begin
- DirView.Reload2;
- DirView.StartWatchThread;
- end;
- end;
- end;
- end; {Edit}
- procedure TDriveView.WMUserRename(var Message: TMessage);
- begin
- if Assigned(FRenameNode) then
- begin
- FForceRename := True;
- TreeView_EditLabel(Handle, FRenameNode.ItemID);
- SetWindowText(TreeView_GetEditControl(Self.Handle), PChar(FLastRenameName));
- FRenameNode := nil;
- end;
- end; {WMUserRename}
- function TDriveView.CanExpand(Node: TTreeNode): Boolean;
- var
- SubNode: TTreeNode;
- Drive: string;
- SaveCursor: TCursor;
- begin
- Result := inherited CanExpand(Node);
- Drive := GetDriveToNode(Node);
- if Node.HasChildren then
- begin
- if (not Assigned(Node.Parent)) and
- (not GetDriveStatus(Drive).Scanned) and
- DriveInfo.IsFixedDrive(Drive) then
- begin
- SubNode := Node.GetFirstChild;
- if not Assigned(SubNode) then
- begin
- ScanDrive(Drive);
- SubNode := Node.GetFirstChild;
- Node.HasChildren := Assigned(SubNode);
- Result := Node.HasChildren;
- if not Assigned(GetDriveStatus(Drive).DiscMonitor) then
- CreateWatchThread(Drive);
- end;
- end
- else
- begin
- SaveCursor := Screen.Cursor;
- Screen.Cursor := crHourGlass;
- try
- if (not TNodeData(Node.Data).Scanned) and DoScanDir(Node) then
- begin
- ReadSubDirs(Node);
- end;
- finally
- Screen.Cursor := SaveCursor;
- end;
- end;
- end;
- end; {CanExpand}
- procedure TDriveView.GetImageIndex(Node: TTreeNode);
- begin
- if TNodeData(Node.Data).IconEmpty then
- SetImageIndex(Node);
- inherited;
- end; {GetImageIndex}
- procedure TDriveView.Loaded;
- begin
- inherited;
- {Create the drive nodes:}
- RefreshRootNodes(True);
- {Set the initial directory:}
- if (Length(FDirectory) > 0) and DirectoryExists(ApiPath(FDirectory)) then
- Directory := FDirectory;
- FCreating := False;
- end; {Loaded}
- function TDriveView.CreateNode: TTreeNode;
- begin
- Result := TDriveTreeNode.Create(Items);
- end;
- procedure TDriveView.Delete(Node: TTreeNode);
- var
- NodeData: TNodeData;
- begin
- if Node = FPrevSelected then
- FPrevSelected := nil;
- NodeData := nil;
- if Assigned(Node) and Assigned(Node.Data) then
- NodeData := TNodeData(Node.Data);
- Node.Data := nil;
- inherited;
- if Assigned(NodeData) and not (csRecreating in ControlState) then
- begin
- FSubDirReaderThread.Delete(Node);
- if Assigned(NodeData.DelayedExclude) then
- begin
- CancelDelayedNode(Node);
- FDelayedNodes.Delete(FDelayedNodes.IndexOfObject(Node));
- UpdateDelayedNodeTimer;
- end;
- NodeData.Destroy;
- end;
- end; {OnDelete}
- procedure TDriveView.KeyPress(var Key: Char);
- begin
- inherited;
- if Assigned(Selected) then
- begin
- if Pos(Key, coInvalidDosChars) <> 0 then
- begin
- Beep;
- Key := #0;
- end;
- end;
- end; {KeyPress}
- function TDriveView.CanChange(Node: TTreeNode): Boolean;
- var
- Path: string;
- Drive: string;
- begin
- Result := inherited CanChange(Node);
- if not Reading and not (csRecreating in ControlState) then
- begin
- if Result and Assigned(Node) then
- begin
- Path := NodePathName(Node);
- if Path <> FLastDir then
- begin
- Drive := DriveInfo.GetDriveKey(Path);
- DriveInfo.ReadDriveStatus(Drive, dsSize or dsImageIndex);
- if not DriveInfo.Get(Drive).DriveReady then
- begin
- MessageDlg(Format(SDriveNotReady, [Drive]), mtError, [mbOK], 0);
- Result := False;
- end
- else
- try
- CheckCanOpenDirectory(Path);
- except
- Application.HandleException(Self);
- Result := False;
- end;
- end;
- end;
- if Result and (csDestroying in ComponentState) then
- begin
- Result := False;
- end;
- if Result and
- (not FCanChange) and
- Assigned(Node) and
- Assigned(Node.Data) and
- Assigned(Selected) and
- Assigned(Selected.Data) then
- begin
- DropTarget := Node;
- Result := False;
- end
- else
- begin
- DropTarget := nil;
- end;
- end;
- end; {CanChange}
- procedure TDriveView.Change(Node: TTreeNode);
- var
- Drive: string;
- OldSerial: DWORD;
- NewDir: string;
- PrevDrive: string;
- begin
- if not Reading and not (csRecreating in ControlState) then
- begin
- if Assigned(Node) then
- begin
- NewDir := NodePathName(Node);
- if NewDir <> FLastDir then
- begin
- Drive := DriveInfo.GetDriveKey(NewDir);
- if Length(FLastDir) > 0 then
- PrevDrive := DriveInfo.GetDriveKey(FLastDir)
- else
- PrevDrive := '';
- FChangeFlag := True;
- FLastDir := NewDir;
- OldSerial := DriveInfo.Get(Drive).DriveSerial;
- DriveInfo.ReadDriveStatus(Drive, dsSize or dsImageIndex);
- with DriveInfo.Get(Drive) do
- begin
- if Assigned(FDirView) and (FDirView.Path <> NewDir) then
- FDirView.Path := NewDir;
- if DriveReady then
- begin
- if not DirectoryExists(ApiPath(NewDir)) then
- begin
- ValidateDirectory(GetDriveStatus(Drive).RootNode);
- Exit;
- end;
- GetDriveStatus(Drive).DefaultDir := IncludeTrailingBackslash(NewDir);
- if PrevDrive <> Drive then
- begin
- if (PrevDrive <> '') and
- (DriveInfo.Get(PrevDrive).DriveType = DRIVE_REMOVABLE) then
- begin
- TerminateWatchThread(PrevDrive);
- end;
- {Drive serial has changed or is missing: allways reread the drive:}
- if (DriveSerial <> OldSerial) or (DriveSerial = 0) then
- begin
- if TNodeData(GetDriveStatus(Drive).RootNode.Data).Scanned then
- ScanDrive(Drive);
- end;
- end;
- StartWatchThread;
- end
- else {Drive not ready:}
- begin
- GetDriveStatus(Drive).RootNode.DeleteChildren;
- GetDriveStatus(Drive).DefaultDir := EmptyStr;
- end;
- end;
- end;
- if (not Assigned(FPrevSelected)) or (not FPrevSelected.HasAsParent(Node)) then
- Node.Expand(False);
- FPrevSelected := Node;
- ValidateCurrentDirectoryIfNotMonitoring;
- end;
- end;
- inherited;
- end; {Change}
- procedure TDriveView.SetImageIndex(Node: TTreeNode);
- var
- FileInfo: TShFileInfo;
- Drive, NodePath: string;
- begin
- if Assigned(Node) and TNodeData(Node.Data).IconEmpty then
- begin
- NodePath := NodePathName(Node);
- Drive := DriveInfo.GetDriveKey(NodePath);
- if not Assigned(Node.Parent) then
- begin
- with DriveInfo.Get(Drive) do
- begin
- if ImageIndex = 0 then
- begin
- DriveInfo.ReadDriveStatus(Drive, dsImageIndex);
- Node.ImageIndex := DriveInfo.Get(Drive).ImageIndex;
- end
- else Node.ImageIndex := ImageIndex;
- Node.SelectedIndex := Node.ImageIndex;
- end;
- end
- else
- begin
- if DriveInfo.Get(Drive).DriveType = DRIVE_REMOTE then
- begin
- Node.ImageIndex := StdDirIcon;
- Node.SelectedIndex := StdDirSelIcon;
- end
- else
- begin
- try
- SHGetFileInfo(PChar(NodePath), 0, FileInfo, SizeOf(FileInfo),
- SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
- if (FileInfo.iIcon < Images.Count) and (FileInfo.iIcon > 0) then
- begin
- Node.ImageIndex := FileInfo.iIcon;
- SHGetFileInfo(PChar(NodePath), 0, FileInfo, SizeOf(FileInfo),
- SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_OPENICON);
- Node.SelectedIndex := FileInfo.iIcon;
- end
- else
- begin
- Node.ImageIndex := StdDirIcon;
- Node.SelectedIndex := StdDirSelIcon;
- end;
- except
- Node.ImageIndex := StdDirIcon;
- Node.SelectedIndex := StdDirSelIcon;
- end;
- end;
- end;
- end; {IconEmpty}
- TNodeData(Node.Data).IconEmpty := False;
- end; {SetImageIndex}
- function TDriveView.GetDriveText(Drive: string): string;
- begin
- if FShowVolLabel and (Length(DriveInfo.GetPrettyName(Drive)) > 0) then
- begin
- case FVolDisplayStyle of
- doPrettyName: Result := DriveInfo.GetPrettyName(Drive);
- doDisplayName: Result := DriveInfo.GetDisplayName(Drive);
- end; {Case}
- end
- else
- begin
- Result := DriveInfo.GetSimpleName(Drive);
- end;
- end; {GetDriveText}
- function CompareDrive(List: TStringList; Index1, Index2: Integer): Integer;
- var
- Drive1, Drive2: string;
- RealDrive1, RealDrive2: Boolean;
- begin
- Drive1 := List[Index1];
- Drive2 := List[Index2];
- RealDrive1 := DriveInfo.IsRealDrive(Drive1);
- RealDrive2 := DriveInfo.IsRealDrive(Drive2);
- if RealDrive1 = RealDrive2 then
- begin
- Result := CompareText(Drive1, Drive2);
- end
- else
- if RealDrive1 and (not RealDrive2) then
- begin
- Result := -1;
- end
- else
- begin
- Result := 1;
- end;
- end;
- function TDriveView.GetDrives: TStrings;
- var
- DriveStatusPair: TDriveStatusPair;
- Drives: TStringList;
- begin
- Drives := TStringList.Create;
- { We could iterate only .Keys here, but that crashes IDE for some reason }
- for DriveStatusPair in FDriveStatus do
- begin
- Drives.Add(DriveStatusPair.Key);
- end;
- Drives.CustomSort(CompareDrive);
- Result := Drives;
- end;
- procedure TDriveView.DriveRemoved(Drive: string);
- var
- NewDrive: Char;
- begin
- if (Directory <> '') and (Directory[1] = Drive) then
- begin
- if DriveInfo.IsRealDrive(Drive) then NewDrive := Drive[1]
- else NewDrive := SystemDrive;
- repeat
- if NewDrive < SystemDrive then NewDrive := SystemDrive
- else
- if NewDrive = SystemDrive then NewDrive := LastDrive
- else Dec(NewDrive);
- DriveInfo.ReadDriveStatus(NewDrive, dsSize or dsImageIndex);
- if NewDrive = Drive then
- begin
- Break;
- end;
- if DriveInfo.Get(NewDrive).Valid and DriveInfo.Get(NewDrive).DriveReady and Assigned(GetDriveStatus(NewDrive).RootNode) then
- begin
- Directory := NodePathName(GetDriveStatus(NewDrive).RootNode);
- break;
- end;
- until False;
- if not Assigned(Selected) then
- begin
- Directory := NodePathName(GetDriveStatus(SystemDrive).RootNode);
- end;
- end;
- end;
- procedure TDriveView.DriveNotification(Notification: TDriveNotification; Drive: string);
- begin
- case Notification of
- dnRefresh:
- RefreshRootNodes;
- dnRemoving:
- // Lame way to reduce rick, we modify the treee while it's being updated already.
- // It might be better to post an update message to the message loop.
- if WatchThreadActive(Drive) then
- DriveRemoving(Drive);
- end;
- end;
- procedure TDriveView.RefreshRootNodes(Floppy: Boolean);
- var
- Drives: TStrings;
- NewText: string;
- SaveCursor: TCursor;
- WasValid: Boolean;
- NodeData: TNodeData;
- DriveStatus: TDriveStatus;
- NextDriveNode: TTreeNode;
- Index: Integer;
- Drive: string;
- begin
- SaveCursor := Screen.Cursor;
- Screen.Cursor := crHourGlass;
- Drives := nil;
- try
- Drives := GetDrives;
- NextDriveNode := nil;
- for Index := Drives.Count - 1 downto 0 do
- begin
- Drive := Drives[Index];
- DriveStatus := GetDriveStatus(Drive);
- if Floppy or DriveInfo.IsFixedDrive(Drive) then
- begin
- with DriveInfo.Get(Drive) do
- begin
- WasValid := Assigned(DriveStatus.RootNode);
- end;
- with DriveInfo.Get(Drive), DriveStatus do
- begin
- if Valid then
- begin
- if not WasValid then
- {New drive has arrived: insert new rootnode:}
- begin
- { Create root directory node }
- NodeData := TNodeData.Create;
- NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
- if Assigned(NextDriveNode) then
- RootNode := Items.InsertObject(NextDriveNode, '', NodeData)
- else
- RootNode := Items.AddObject(nil, '', NodeData);
- RootNode.Text := GetDisplayName(RootNode);
- RootNode.HasChildren := True;
- Scanned := False;
- Verified := False;
- end
- else
- if RootNode.ImageIndex <> DriveInfo.Get(Drive).ImageIndex then
- begin {WasValid = True}
- RootNode.ImageIndex := DriveInfo.Get(Drive).ImageIndex;
- RootNode.SelectedIndex := DriveInfo.Get(Drive).ImageIndex;
- end;
- if Assigned(RootNode) then
- begin
- NewText := GetDisplayName(RootNode);
- if RootNode.Text <> NewText then
- RootNode.Text := NewText;
- end;
- end
- else
- if WasValid then
- {Drive has been removed => delete rootnode:}
- begin
- DriveRemoved(Drive);
- Scanned := False;
- Verified := False;
- RootNode.Delete;
- RootNode := nil;
- end;
- end;
- end;
- if Assigned(DriveStatus.RootNode) then
- NextDriveNode := DriveStatus.RootNode;
- end;
- finally
- Screen.Cursor := SaveCursor;
- Drives.Free;
- end;
- end; {RefreshRootNodes}
- procedure TDriveView.AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec);
- var
- NewNode: TTreeNode;
- NodeData: TNodeData;
- begin
- NodeData := TNodeData.Create;
- NodeData.Attr := SRec.Attr;
- NodeData.DirName := SRec.Name;
- NodeData.FIsRecycleBin :=
- (SRec.Attr and faSysFile <> 0) and
- (not Assigned(ParentNode.Parent)) and
- (SameText(SRec.Name, 'RECYCLED') or
- SameText(SRec.Name, 'RECYCLER') or
- SameText(SRec.Name, '$RECYCLE.BIN'));
- NodeData.Scanned := False;
- NewNode := Self.Items.AddChildObject(ParentNode, '', NodeData);
- NewNode.Text := GetDisplayName(NewNode);
- NewNode.HasChildren := True;
- if GetDriveTypeToNode(ParentNode) <> DRIVE_REMOTE then
- FSubDirReaderThread.Add(NewNode, IncludeTrailingBackslash(ParentPath) + SRec.Name);
- end; {AddChildNode}
- function TDriveView.GetDriveStatus(Drive: string): TDriveStatus;
- begin
- if not FDriveStatus.TryGetValue(Drive, Result) then
- begin
- Result := CreateDriveStatus;
- FDriveStatus.Add(Drive, Result);
- end;
- end; {GetDriveStatus}
- function TDriveView.DoScanDir(FromNode: TTreeNode): Boolean;
- begin
- Result := not TNodeData(FromNode.Data).IsRecycleBin;
- end; {DoScanDir}
- function TDriveView.DirAttrMask: Integer;
- begin
- Result := faDirectory or faSysFile;
- if ShowHiddenDirs then
- Result := Result or faHidden;
- end;
- procedure TDriveView.ScanDrive(Drive: string);
- begin {ScanDrive}
- with Self.Items do
- begin
- ValidateDirectory(FindNodeToPath(DriveInfo.GetDriveRoot(Drive)));
- GetDriveStatus(Drive).Scanned := True;
- GetDriveStatus(Drive).Verified := False;
- end;
- end; {ScanDrive}
- function TDriveView.DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTreeNode;
- var
- SelectionHierarchy: array of TTreeNode;
- SelectionHierarchyHeight: Integer;
- function SearchSubDirs(ParentNode: TTreeNode; Path: string; Level: Integer): TTreeNode; forward;
- function ExtractFirstName(S: string): string;
- var
- I: Integer;
- begin
- I := Pos('\', S);
- if I = 0 then
- I := Length(S);
- Result := System.Copy(S, 1, I);
- end;
- function DoSearchSubDirs(ParentNode: TTreeNode; Path: string; Level: Integer): TTreeNode;
- var
- Node: TTreeNode;
- Dir: string;
- begin
- {Extract first directory from path:}
- Dir := ExtractFirstName(Path);
- System.Delete(Path, 1, Length(Dir));
- if Dir[Length(Dir)] = '\' then
- SetLength(Dir, Pred(Length(Dir)));
- // Optimization. Avoid iterating possibly thousands of nodes,
- // when the node we are looking for is the selected node or its ancestor.
- // This is often the case, when navigating under node that has lot of siblings.
- // Typically, when navigating in user's profile folder, and there are many [thousands] other user profile folders.
- if (SelectionHierarchyHeight > 0) and
- // Change of selection might indicate that the tree was rebuilt meanwhile and
- // the references in SelectionHierarchy might not be valid anymore
- (Selected = SelectionHierarchy[SelectionHierarchyHeight - 1]) and
- (Level < SelectionHierarchyHeight) and
- (Uppercase(GetDirName(SelectionHierarchy[Level])) = Dir) then
- begin
- Result := SelectionHierarchy[Level];
- end
- else
- begin
- // Paths have diverted
- SelectionHierarchyHeight := 0;
- Node := ParentNode.GetFirstChild;
- if (not Assigned(Node)) and (not ExistingOnly) then
- begin
- ValidateDirectoryEx(ParentNode, rsRecursiveExisting, True);
- Node := ParentNode.GetFirstChild;
- end;
- Result := nil;
- while (not Assigned(Result)) and Assigned(Node) do
- begin
- if UpperCase(GetDirName(Node)) = Dir then
- begin
- Result := Node;
- end
- else
- begin
- Node := ParentNode.GetNextChild(Node);
- end;
- end;
- end;
- if Assigned(Result) and (Length(Path) > 0) then
- begin
- Result := SearchSubDirs(Result, Path, Level + 1);
- end;
- end;
- function SearchSubDirs(ParentNode: TTreeNode; Path: string; Level: Integer): TTreeNode;
- var
- ParentPath, SubPath: string;
- SRec: TSearchRec;
- ParentNodeData: TNodeData;
- begin
- Result := nil;
- if Length(Path) > 0 then
- begin
- ParentNodeData := TNodeData(ParentNode.Data);
- if (not ParentNodeData.Scanned) and (not ExistingOnly) then
- begin
- ReadSubDirs(ParentNode);
- end;
- // Factored out of DoSearchSubDirs is remnant of Bug 956 superceded by Bug 1320
- Result := DoSearchSubDirs(ParentNode, Path, Level);
- if (not Assigned(Result)) and (not ExistingOnly) then
- begin
- ParentPath := NodePath(ParentNode);
- if DirectoryExists(ApiPath(IncludeTrailingBackslash(ParentPath) + Path)) then
- begin
- SubPath := IncludeTrailingBackslash(ParentPath) + ExcludeTrailingBackslash(ExtractFirstName(Path));
- if FindFirstSubDir(SubPath, SRec) then
- begin
- AddChildNode(ParentNode, ParentPath, SRec);
- if Assigned(ParentNodeData.DelayedExclude) then
- ParentNodeData.DelayedExclude.Add(SRec.Name);
- SortChildren(ParentNode, False);
- FindClose(SRec);
- end;
- Result := DoSearchSubDirs(ParentNode, Path, Level);
- end;
- end;
- end;
- end; {SearchSubDirs}
- var
- Drive: string;
- P, I: Integer;
- RootNode, Node: TTreeNode;
- begin {FindNodeToPath}
- Result := nil;
- if Length(Path) < 3 then
- Exit;
- // Particularly when used by TDirView to delegate browsing to
- // hidden drive view, the handle may not be created
- HandleNeeded;
- Drive := DriveInfo.GetDriveKey(Path);
- // Likely a network drive
- if (not Assigned(GetDriveStatus(Drive).RootNode)) and
- // Side effect of this is drive refresh that adds the network drive to the trees
- DriveInfo.Get(Drive).ValidButHiddenByDrivePolicy then
- begin
- // This refreshes the drives again
- DriveInfo.OverrideDrivePolicy(Drive);
- end;
- // if not assigned now, it must be that the drive already existed in DriveInfo, but it didn't make it to this view
- // (possible a network drive opened in another panel before)
- if not Assigned(GetDriveStatus(Drive).RootNode) then
- begin
- // Refresh the view drives to add the new drive and also explorer's drive drop down.
- // Overkill, as we know exactly what drive to add (so not need to check all drives)
- DriveInfo.DriveRefresh;
- end;
- if Assigned(GetDriveStatus(Drive).RootNode) then
- begin
- if DriveInfo.IsRealDrive(Drive) then
- begin
- System.Delete(Path, 1, 3);
- end
- else
- if IsUncPath(Path) then
- begin
- System.Delete(Path, 1, 2);
- P := Pos('\', Path);
- if P = 0 then
- begin
- Path := '';
- end
- else
- begin
- System.Delete(Path, 1, P);
- P := Pos('\', Path);
- if P = 0 then
- begin
- Path := '';
- end
- else
- begin
- System.Delete(Path, 1, P);
- end;
- end;
- end
- else
- begin
- raise EConvertError.Create(Format(ErrorInvalidDrive, [Path]))
- end;
- if Length(Path) > 0 then
- begin
- if (not GetDriveStatus(Drive).Scanned) and (not ExistingOnly) then
- begin
- ScanDrive(Drive);
- end;
- Node := Selected;
- RootNode := GetDriveStatus(Drive).RootNode;
- if not Assigned(Node) then
- begin
- SelectionHierarchyHeight := 0;
- end
- else
- begin
- SelectionHierarchyHeight := Node.Level + 1;
- SetLength(SelectionHierarchy, SelectionHierarchyHeight);
- for I := SelectionHierarchyHeight - 1 downto 0 do
- begin
- SelectionHierarchy[I] := Node;
- Node := Node.Parent;
- end;
- Assert(Selected = SelectionHierarchy[SelectionHierarchyHeight - 1]);
- // Different drive - nothing to optimize
- if RootNode <> SelectionHierarchy[0] then
- SelectionHierarchyHeight := 0;
- end;
- Result := SearchSubDirs(RootNode, UpperCase(Path), 1);
- end
- else Result := GetDriveStatus(Drive).RootNode;
- end;
- end; {FindNodetoPath}
- function TDriveView.FindNodeToPath(Path: string): TTreeNode;
- begin
- Result := DoFindNodeToPath(Path, False);
- end;
- function TDriveView.TryFindNodeToPath(Path: string): TTreeNode;
- begin
- Result := DoFindNodeToPath(Path, True);
- end;
- function TDriveView.GetSubDir(var SRec: TSearchRec): Boolean;
- begin
- Result := True;
- while Result and
- ((SRec.Name = '.' ) or
- (SRec.Name = '..') or
- ((SRec.Attr and faDirectory) = 0)) do
- begin
- if FindNext(SRec) <> 0 then
- begin
- Result := False;
- end;
- end;
- end;
- function TDriveView.FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
- begin
- Result := (FindFirstEx(ApiPath(Path), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS, FindExSearchLimitToDirectories) = 0);
- if Result then
- begin
- Result := GetSubDir(SRec);
- if not Result then FindClose(SRec);
- end;
- end;
- function TDriveView.FindNextSubDir(var SRec: TSearchRec): Boolean;
- begin
- Result := (FindNext(SRec) = 0) and GetSubDir(SRec);
- end;
- function TDriveView.ReadSubDirsBatch(Node: TTreeNode; var SRec: TSearchRec; CheckInterval, Limit: Integer): Boolean;
- var
- Start: TDateTime;
- Cont: Boolean;
- Path: string;
- Count: Integer;
- DelayedExclude: TStringList;
- begin
- Start := Now;
- Path := NodePath(Node);
- Result := True;
- Count := 0;
- DelayedExclude := TNodeData(Node.Data).DelayedExclude;
- // At least from SetDirectory > DoFindNodeToPath and CanExpand, this is not called within BeginUpdate/EndUpdate block.
- // But in any case, adding it here makes expanding (which calls CanExpand) noticeably slower, when there are lot of nodes,
- // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
- repeat
- if (not Assigned(DelayedExclude)) or
- (DelayedExclude.IndexOf(SRec.Name) < 0) then
- begin
- AddChildNode(Node, Path, SRec);
- Inc(Count);
- end;
- Cont := FindNextSubDir(SRec);
- // There are two other directory reading loops, where this is not checked
- if Cont and
- ((Count mod CheckInterval) = 0) and
- (Limit > 0) and
- (MilliSecondsBetween(Now, Start) > Limit) then
- begin
- Result := False;
- Cont := False;
- end
- until not Cont;
- if Result then
- FindClose(Srec);
- end;
- procedure TDriveView.DelayedNodeTimer(Sender: TObject);
- var
- Node: TTreeNode;
- NodeData: TNodeData;
- begin
- Assert(FDelayedNodes.Count > 0);
- if FDelayedNodes.Count > 0 then
- begin
- // Control was recreated
- if not Assigned(FDelayedNodes.Objects[0]) then
- begin
- FDelayedNodes.Objects[0] := TryFindNodeToPath(FDelayedNodes.Strings[0]);
- end;
- Node := TTreeNode(FDelayedNodes.Objects[0]);
- if not Assigned(Node) then
- begin
- FDelayedNodes.Delete(0);
- end
- else
- begin
- NodeData := TNodeData(Node.Data);
- if ReadSubDirsBatch(Node, NodeData.DelayedSrec, 10, 50) then
- begin
- FreeAndNil(NodeData.DelayedExclude);
- FDelayedNodes.Delete(0);
- SortChildren(Node, False);
- end;
- end;
- end;
- UpdateDelayedNodeTimer;
- end;
- procedure TDriveView.UpdateDelayedNodeTimer;
- begin
- FDelayedNodeTimer.Enabled := HandleAllocated and (FDelayedNodes.Count > 0);
- end;
- procedure TDriveView.ReadSubDirs(Node: TTreeNode);
- var
- SRec: TSearchRec;
- NodeData: TNodeData;
- Path: string;
- CheckInterval, Limit: Integer;
- begin
- NodeData := TNodeData(Node.Data);
- Path := NodePath(Node);
- if not FindFirstSubDir(IncludeTrailingBackslash(Path) + '*.*', SRec) then
- begin
- Node.HasChildren := False;
- end
- else
- begin
- CheckInterval := 100;
- Limit := DriveViewLoadingTooLongLimit * 1000;
- if not Showing then
- begin
- Limit := Limit div 10;
- CheckInterval := CheckInterval div 10;
- end;
- if not ReadSubDirsBatch(Node, SRec, CheckInterval, Limit) then
- begin
- NodeData.DelayedSrec := SRec;
- NodeData.DelayedExclude := TStringList.Create;
- NodeData.DelayedExclude.CaseSensitive := False;
- NodeData.DelayedExclude.Sorted := True;
- FDelayedNodes.AddObject(Path, Node);
- Assert(FDelayedNodes.Count < 20); // if more, something went likely wrong
- UpdateDelayedNodeTimer;
- end;
- SortChildren(Node, False);
- end;
- NodeData.Scanned := True;
- Application.ProcessMessages;
- end; {ReadSubDirs}
- procedure TDriveView.CancelDelayedNode(Node: TTreeNode);
- var
- NodeData: TNodeData;
- begin
- NodeData := TNodeData(Node.Data);
- FindClose(NodeData.DelayedSrec);
- FreeAndNil(NodeData.DelayedExclude);
- end;
- procedure TDriveView.DeleteNode(Node: TTreeNode);
- var
- ValidNode: TTreeNode;
- begin
- if Assigned(Selected) and Assigned(Node.Parent) and
- ((Selected = Node) or Selected.HasAsParent(Node)) then
- begin
- ValidNode := Node.Parent;
- while (not NodePathExists(ValidNode)) and Assigned(ValidNode.Parent) do
- ValidNode := ValidNode.Parent;
- Selected := ValidNode;
- end;
- if DropTarget = Node then
- DropTarget := nil;
- Node.Delete;
- end;
- function TDriveView.CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boolean;
- var
- WorkNode: TTreeNode;
- DelNode: TTreeNode;
- SRec: TSearchRec;
- SrecList: TStringList;
- SubDirList: TStringList;
- R: Boolean;
- Index: Integer;
- NewDirFound: Boolean;
- ParentDir: string;
- NodeData: TNodeData;
- ScanDirInfo: PScanDirInfo;
- begin {CallBackValidateDir}
- Result := True;
- if (not Assigned(Node)) or (not Assigned(Node.Data)) then
- Exit;
- NewDirFound := False;
- ScanDirInfo := PScanDirInfo(Data);
- {Check, if directory still exists: (but not with root directory) }
- if Assigned(Node.Parent) and (ScanDirInfo^.StartNode = Node) and
- (not NodePathExists(Node)) then
- begin
- DeleteNode(Node);
- Node := nil;
- Exit;
- end;
- WorkNode := Node.GetFirstChild;
- NodeData := TNodeData(Node.Data);
- if NodeData.Scanned and Assigned(WorkNode) then
- {if node was already scanned: check wether the existing subnodes are still alive
- and add all new subdirectories as subnodes:}
- begin
- if DoScanDir(Node) then
- begin
- ParentDir := IncludeTrailingBackslash(NodePath(Node));
- {Build list of existing subnodes:}
- SubDirList := TStringList.Create;
- SubDirList.CaseSensitive := True; // We want to reflect changes in subfolder name case
- while Assigned(WorkNode) do
- begin
- SubDirList.Add(TNodeData(WorkNode.Data).DirName);
- WorkNode := Node.GetNextChild(WorkNode);
- end;
- // Nodes are sorted using natural sorting, while TStringList.Find uses simple sorting
- SubDirList.Sort;
- SRecList := TStringList.Create;
- SRecList.CaseSensitive := True;
- R := FindFirstSubDir(ParentDir + '*.*', SRec);
- while R do
- begin
- SrecList.Add(Srec.Name);
- if not SubDirList.Find(Srec.Name, Index) then
- {Subnode does not exists: add it:}
- begin
- AddChildNode(Node, ParentDir, SRec);
- NewDirFound := True;
- end;
- R := FindNextSubDir(Srec);
- end;
- FindClose(Srec);
- Sreclist.Sort;
- {Remove not existing subnodes:}
- WorkNode := Node.GetFirstChild;
- while Assigned(WorkNode) do
- begin
- if not Assigned(WorkNode.Data) or
- not SrecList.Find(TNodeData(WorkNode.Data).DirName, Index) then
- begin
- DelNode := WorkNode;
- WorkNode := Node.GetNextChild(WorkNode);
- DeleteNode(DelNode);
- end
- else
- begin
- if (SrecList[Index] <> TNodeData(WorkNode.Data).DirName) then
- begin
- {Case of directory letters has changed:}
- TNodeData(WorkNode.Data).DirName := SrecList[Index];
- WorkNode.Text := SrecList[Index];
- end;
- WorkNode := Node.GetNextChild(WorkNode);
- end;
- end;
- SrecList.Free;
- SubDirList.Free;
- {Sort subnodes:}
- if NewDirFound then
- SortChildren(Node, False);
- end;
- end
- else
- {Node was not already scanned:}
- if (ScanDirInfo^.SearchNewDirs or
- NodeData.Scanned or
- (Node = ScanDirInfo^.StartNode)) and
- DoScanDir(Node) then
- begin
- ReadSubDirs(Node);
- end;
- end; {CallBackValidateDir}
- procedure TDriveView.RebuildTree;
- var
- Drive: string;
- begin
- for Drive in FDriveStatus.Keys do
- with GetDriveStatus(Drive) do
- if Assigned(RootNode) and Scanned then
- ValidateDirectory(RootNode);
- end;
- procedure TDriveView.ValidateCurrentDirectoryIfNotMonitoring;
- begin
- if Assigned(Selected) and
- not Assigned(GetDriveStatus(GetDriveToNode(Selected)).DiscMonitor) then
- begin
- ValidateDirectory(Selected);
- end;
- end;
- procedure TDriveView.ValidateDirectoryEx(Node: TTreeNode; Recurse: TRecursiveScan;
- NewDirs: Boolean);
- var
- Info: PScanDirInfo;
- SelDir: string;
- SaveCursor: TCursor;
- RestartWatchThread: Boolean;
- SaveCanChange: Boolean;
- CurrentPath: string;
- Drive: string;
- begin
- if Assigned(Node) and Assigned(Node.Data) and
- (not FValidateFlag) and DoScanDir(Node) then
- begin
- SelDir := Directory;
- SaveCursor := Screen.Cursor;
- if Self.Focused and (Screen.Cursor <> crHourGlass) then
- Screen.Cursor := crHourGlass;
- CurrentPath := NodePath(Node);
- Drive := DriveInfo.GetDriveKey(CurrentPath);
- if not Assigned(Node.Parent) then
- GetDriveStatus(Drive).ChangeTimer.Enabled := False;
- RestartWatchThread := WatchThreadActive;
- try
- if WatchThreadActive then
- StopWatchThread;
- FValidateFlag := True;
- FSysColorChangePending := False;
- New(Info);
- Info^.StartNode := Node;
- Info^.SearchNewDirs := NewDirs;
- Info^.DriveType := DriveInfo.Get(Drive).DriveType;
- SaveCanChange := FCanChange;
- FCanChange := True;
- FChangeFlag := False;
- Items.BeginUpdate;
- try
- IterateSubTree(Node, CallBackValidateDir, Recurse, coScanStartNode, Info);
- finally
- Items.EndUpdate;
- end;
- FValidateFlag := False;
- if (not Assigned(Selected)) and (Length(SelDir) > 0) then
- Directory := ExtractFileDrive(SelDir);
- if (SelDir <> Directory) and (not FChangeFlag) then
- Change(Selected);
- FCanChange := SaveCanChange;
- Dispose(Info);
- finally
- if RestartWatchThread and FWatchDirectory and not WatchThreadActive then
- StartWatchThread;
- if Screen.Cursor <> SaveCursor then
- Screen.Cursor := SaveCursor;
- if FSysColorChangePending then
- begin
- FSysColorChangePending := False;
- if HandleAllocated then Perform(CM_SYSCOLORCHANGE, 0, 0);
- end;
- end;
- end;
- end; {ValidateDirectoryEx}
- function TDriveView.GetDriveTypeToNode(Node: TTreeNode): Integer;
- begin
- Assert(Assigned(Node));
- Result := DriveInfo.Get(GetDriveToNode(Node)).DriveType;
- end; {GetDriveTypeToNode}
- procedure TDriveView.CreateWatchThread(Drive: string);
- begin
- if csDesigning in ComponentState then
- Exit;
- if (not Assigned(GetDriveStatus(Drive).DiscMonitor)) and
- FWatchDirectory and
- (DriveInfo.Get(Drive).DriveType <> DRIVE_REMOTE) then
- begin
- with GetDriveStatus(Drive) do
- begin
- DiscMonitor := TDiscMonitor.Create(Self);
- DiscMonitor.ChangeDelay := msThreadChangeDelay;
- DiscMonitor.SubTree := True;
- DiscMonitor.Filters := [moDirName];
- DiscMonitor.OnChange := ChangeDetected;
- DiscMonitor.OnInvalid := ChangeInvalid;
- DiscMonitor.SetDirectory(DriveInfo.GetDriveRoot(Drive));
- DiscMonitor.Open;
- end;
- SubscribeDriveNotifications(Drive);
- end;
- end; {CreateWatchThread}
- procedure TDriveView.SetWatchDirectory(Value: Boolean);
- begin
- if FWatchDirectory <> Value then
- begin
- FWatchDirectory := Value;
- if (not (csDesigning in ComponentState)) and Value then
- StartAllWatchThreads
- else
- StopAllWatchThreads;
- end;
- end; {SetAutoScan}
- procedure TDriveView.SetDirView(Value: TDirView);
- begin
- if Assigned(FDirView) then
- FDirView.DriveView := nil;
- FDirView := Value;
- if Assigned(FDirView) then
- FDirView.DriveView := Self;
- end; {SetDirView}
- function TDriveView.NodeWatched(Node: TTreeNode): Boolean;
- var
- Drive: string;
- begin
- Drive := GetDriveToNode(Node);
- Result := WatchThreadActive(Drive);
- end; {NodeWatched}
- procedure TDriveView.ChangeInvalid(Sender: TObject; const Directory: string;
- const ErrorStr: string);
- var
- Drive: string;
- begin
- Drive := DriveInfo.GetDriveKey((Sender as TDiscMonitor).Directories[0]);
- with GetDriveStatus(Drive) do
- begin
- DiscMonitor.Close;
- end;
- end; {DirWatchChangeInvalid}
- procedure TDriveView.ChangeDetected(Sender: TObject; const Directory: string;
- var SubdirsChanged: Boolean);
- var
- DirChanged: string;
- begin
- if Sender is TDiscMonitor then
- begin
- DirChanged := (Sender as TDiscMonitor).Directories[0];
- if Length(DirChanged) > 0 then
- begin
- with GetDriveStatus(DriveInfo.GetDriveKey(DirChanged)) do
- begin
- ChangeTimer.Interval := 0;
- ChangeTimer.Interval := FChangeInterval;
- ChangeTimer.Enabled := True;
- end;
- end;
- end;
- end; {DirWatchChangeDetected}
- procedure TDriveView.ChangeTimerOnTimer(Sender: TObject);
- var
- DriveStatusPair: TDriveStatusPair;
- begin
- if (FChangeTimerSuspended = 0) and (Sender is TTimer) then
- begin
- for DriveStatusPair in FDriveStatus do
- begin
- if DriveStatusPair.Value.ChangeTimer = Sender then
- begin
- // Messages are processed during ValidateDirectory, so we may detect another change while
- // updating the directory. Prevent the recursion.
- // But retry the update afterwards (by reenabling the timer in ChangeDetected)
- SuspendChangeTimer;
- try
- with DriveStatusPair.Value.ChangeTimer do
- begin
- Interval := 0;
- Enabled := False;
- end;
- if Assigned(DriveStatusPair.Value.RootNode) then
- begin
- {Check also collapsed (invisible) subdirectories:}
- ValidateDirectory(DriveStatusPair.Value.RootNode);
- end;
- finally
- ResumeChangeTimer;
- end;
- end;
- end;
- end;
- end; {ChangeTimerOnTimer}
- procedure TDriveView.SubscribeDriveNotifications(Drive: string);
- begin
- // As previously, subscribe drive notification only once the drive is at least once visited.
- // Shouldn't do much harm to subscribe always, but just in case.
- DriveInfo.SubscribeDriveNotifications(Drive);
- end;
- procedure TDriveView.StartWatchThread;
- var
- Drive: string;
- begin
- if (csDesigning in ComponentState) or
- not Assigned(Selected) or
- not fWatchDirectory then Exit;
- Drive := GetDriveToNode(Selected);
- with GetDriveStatus(Drive) do
- begin
- if not Assigned(DiscMonitor) then
- CreateWatchThread(Drive);
- if Assigned(DiscMonitor) and not DiscMonitor.Enabled then
- DiscMonitor.Enabled := True;
- end;
- SubscribeDriveNotifications(Drive);
- end; {StartWatchThread}
- procedure TDriveView.StopWatchThread;
- var
- Drive: string;
- begin
- if Assigned(Selected) then
- begin
- Drive := GetDriveToNode(Selected);
- with GetDriveStatus(Drive) do
- if Assigned(DiscMonitor) then
- DiscMonitor.Enabled := False;
- end;
- end; {StopWatchThread}
- procedure TDriveView.SuspendChangeTimer;
- begin
- Inc(FChangeTimerSuspended);
- end;
- procedure TDriveView.ResumeChangeTimer;
- begin
- Assert(FChangeTimerSuspended > 0);
- Dec(FChangeTimerSuspended);
- end;
- procedure TDriveView.TerminateWatchThread(Drive: string);
- begin
- with GetDriveStatus(Drive) do
- if Assigned(DiscMonitor) then
- begin
- DiscMonitor.Free;
- DiscMonitor := nil;
- end;
- end; {StopWatchThread}
- procedure TDriveView.StartAllWatchThreads;
- var
- DriveStatusPair: TDriveStatusPair;
- Drive: string;
- begin
- if (csDesigning in ComponentState) or (not FWatchDirectory) then
- Exit;
- for DriveStatusPair in FDriveStatus do
- with DriveStatusPair.Value do
- if Scanned then
- begin
- if not Assigned(DiscMonitor) then
- CreateWatchThread(DriveStatusPair.Key);
- if Assigned(DiscMonitor) and (not DiscMonitor.Active) then
- begin
- DiscMonitor.Open;
- SubscribeDriveNotifications(DriveStatusPair.Key);
- end;
- end;
- if Assigned(Selected) then
- begin
- Drive := GetDriveToNode(Selected);
- if not DriveInfo.IsFixedDrive(Drive) then
- begin
- StartWatchThread;
- end;
- end;
- end; {StartAllWatchThreads}
- procedure TDriveView.StopAllWatchThreads;
- var
- DriveStatusPair: TDriveStatusPair;
- begin
- if (csDesigning in ComponentState) or (not FWatchDirectory) then
- Exit;
- for DriveStatusPair in FDriveStatus do
- with DriveStatusPair.Value do
- begin
- if Assigned(DiscMonitor) then
- begin
- DiscMonitor.Close;
- end;
- end;
- end; {StopAllWatchThreads}
- function TDriveView.WatchThreadActive(Drive: string): Boolean;
- begin
- Result := FWatchDirectory and
- Assigned(GetDriveStatus(Drive).DiscMonitor) and
- GetDriveStatus(Drive).DiscMonitor.Active and
- GetDriveStatus(Drive).DiscMonitor.Enabled;
- end; {WatchThreadActive}
- function TDriveView.WatchThreadActive: Boolean;
- var
- Drive: string;
- begin
- if not Assigned(Selected) then
- begin
- Result := False;
- Exit;
- end;
- Drive := GetDriveToNode(Selected);
- Result := WatchThreadActive(Drive);
- end; {WatchThreadActive}
- function TDriveView.FindPathNode(Path: string): TTreeNode;
- var
- PossiblyHiddenPath: string;
- Attrs: Integer;
- begin
- if Assigned(FOnNeedHiddenDirectories) and
- (not ShowHiddenDirs) and
- DirectoryExistsFix(Path) then // do not even bother if the path does not exist
- begin
- PossiblyHiddenPath := ExcludeTrailingPathDelimiter(Path);
- while (PossiblyHiddenPath <> '') and
- (not IsRootPath(PossiblyHiddenPath)) do // Drives have hidden attribute
- begin
- Attrs := FileGetAttr(PossiblyHiddenPath, False);
- if (Attrs and faHidden) = faHidden then
- begin
- if Assigned(FOnNeedHiddenDirectories) then
- begin
- FOnNeedHiddenDirectories(Self);
- end;
- Break;
- end
- else
- begin
- PossiblyHiddenPath := ExtractFileDir(PossiblyHiddenPath);
- end;
- end;
- end;
- {Find existing path or parent path of not existing path:}
- repeat
- Result := FindNodeToPath(Path);
- if not Assigned(Result) then
- Path := ExtractFilePath(ExcludeTrailingBackslash(Path));
- until Assigned(Result) or (Length(Path) < 3);
- end;
- procedure TDriveView.SetDirectory(Value: string);
- begin
- Value := IncludeTrailingBackslash(Value);
- FDirectory := Value;
- inherited;
- if Assigned(Selected) and (not Assigned(Selected.Parent)) then
- begin
- if not GetDriveStatus(GetDriveToNode(Selected)).Scanned then
- ScanDrive(GetDriveToNode(Selected));
- end;
- end; {SetDirectory}
- function TDriveView.GetDirName(Node: TTreeNode): string;
- begin
- if Assigned(Node) and Assigned(Node.Data) then
- Result := TNodeData(Node.Data).DirName
- else
- Result := '';
- end; {GetDirName}
- {GetDrive: returns the drive of the Node.}
- function TDriveView.GetDriveToNode(Node: TTreeNode): string;
- var
- Path: string;
- begin
- if (not Assigned (Node)) or (not Assigned(Node.Data)) then
- raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDrive']));
- Path := NodePath(Node);
- Result := DriveInfo.GetDriveKey(Path);
- end; {GetDrive}
- {RootNode: returns the rootnode to the Node:}
- function TDriveView.RootNode(Node: TTreeNode): TTreeNode;
- begin
- Result := Node;
- if not Assigned(Node) then
- raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['RootNode']));
- while Assigned(Result.Parent) do
- Result := Result.Parent;
- end; {RootNode}
- function TDriveView.GetDisplayName(Node: TTreeNode): string;
- begin
- Result := '';
- if (not Assigned(Node)) or (not Assigned(Node.Data)) then
- raise ENodeNotAssigned.Create(Format(ErrorNodeNA, ['GetDisplayName']));
- if not Assigned(Node.Parent) then Result := GetDriveText(GetDriveToNode(Node))
- else
- begin
- Result := GetDirName(Node);
- end;
- end; {GetDisplayName}
- procedure TDriveView.SetShowVolLabel(ShowIt: Boolean);
- begin
- if ShowIt = FShowVolLabel then
- Exit;
- FShowVolLabel := ShowIt;
- RefreshRootNodes(True);
- end; {SetShowVolLabel}
- procedure TDriveView.DisplayContextMenu(Node: TTreeNode; Point: TPoint);
- var
- Verb: string;
- DirWatched: Boolean;
- begin
- DirWatched := NodeWatched(Node) and WatchThreadActive;
- Assert(Node <> nil);
- if Node <> Selected then
- DropTarget := Node;
- Verb := EmptyStr;
- if Assigned(FOnDisplayContextMenu) then
- FOnDisplayContextMenu(Self);
- ShellDisplayContextMenu(FParentForm.Handle, Point, NodePathName(Node),
- CanEdit(Node), Verb, False);
- if Verb = shcRename then Node.EditText
- else
- if Verb = shcCut then
- begin
- LastClipBoardOperation := cboCut;
- LastPathCut := NodePathName(Node);
- end
- else
- if Verb = shcCopy then LastClipBoardOperation := cboCopy
- else
- if Verb = shcPaste then
- PasteFromClipBoard(NodePathName(Node));
- DropTarget := nil;
- if not DirWatched then
- ValidateDirectory(Node);
- end; {DisplayContextMenu (2)}
- procedure TDriveView.DisplayPropertiesMenu(Node: TTreeNode);
- begin
- Assert(Assigned(Node));
- ShellExecuteContextCommand(FParentForm.Handle, shcProperties, NodePathName(Node));
- end; {ContextMenu}
- procedure TDriveView.SetSelected(Node: TTreeNode);
- begin
- if Node <> Selected then
- begin
- FChangeFlag := False;
- FCanChange := True;
- inherited Selected := Node;
- if not FChangeFlag then
- Change(Selected);
- end;
- end; {SetSelected}
- {Called by TFileDeleteThread, when a file deletion was detected by the D&D receiving application:}
- procedure TDriveView.SignalDirDelete(Sender: TObject; Files: TStringList);
- begin
- if Files.Count > 0 then
- ValidateDirectory(FindNodeToPath(Files[0]));
- end; {SignalDirDelete}
- function TDriveView.DDSourceEffects: TDropEffectSet;
- begin
- if not Assigned(FDragNode.Parent) then
- Result := [deLink]
- else
- Result := [deLink, deCopy, deMove];
- end;
- procedure TDriveView.DDChooseEffect(KeyState: Integer; var Effect: Integer; PreferredEffect: Integer);
- begin
- if DropTarget = nil then Effect := DROPEFFECT_NONE
- else
- if (KeyState and (MK_CONTROL or MK_SHIFT) = 0) and (PreferredEffect = 0) then
- begin
- if FDragDrive <> '' then
- begin
- if FExeDrag and DriveInfo.IsFixedDrive(GetDriveToNode(DropTarget)) and DriveInfo.IsFixedDrive(FDragDrive) then
- begin
- Effect := DROPEFFECT_LINK;
- end
- else
- if (Effect = DROPEFFECT_COPY) and
- (SameText(FDragDrive, GetDriveToNode(DropTarget)) and
- (FDragDropFilesEx.AvailableDropEffects and DROPEFFECT_MOVE <> 0)) then
- begin
- Effect := DROPEFFECT_MOVE;
- end;
- end;
- end;
- inherited;
- end;
- function TDriveView.DragCompleteFileList: Boolean;
- begin
- Result := (GetDriveTypeToNode(FDragNode) <> DRIVE_REMOVABLE);
- end;
- function TDriveView.DDExecute: TDragResult;
- var
- WatchThreadOK: Boolean;
- DragParentPath: string;
- DragPath: string;
- begin
- WatchThreadOK := WatchThreadActive;
- Result := FDragDropFilesEx.Execute(nil);
- if (Result = drMove) and (not WatchThreadOK) then
- begin
- DragPath := NodePathName(FDragNode);
- if Assigned(FDragNode.Parent) then
- DragParentPath := NodePathName(FDragNode.Parent)
- else
- DragParentPath := DragPath;
- if Assigned(FDragNode.Parent) or (DragParentPath <> NodePathName(Selected.Parent)) then
- begin
- FDragNode := FindNodeToPath(DragPath);
- if Assigned(FDragNode) then
- begin
- FDragFileList.Clear;
- FDragFileList.Add(DragPath);
- TFileDeleteThread.Create(FDragFileList, MaxWaitTimeOut, SignalDirDelete);
- end;
- end;
- end;
- end;
- procedure TDriveView.PerformDragDropFileOperation(Node: TTreeNode; Effect: Integer);
- var
- Index: Integer;
- SourcePath: string;
- SourceParentPath: string;
- SourceIsDirectory: Boolean;
- SaveCursor: TCursor;
- SourceNode, TargetNode: TTreeNode;
- TargetPath: string;
- IsRecycleBin: Boolean;
- begin
- TargetPath := NodePathName(Node);
- IsRecycleBin := NodeIsRecycleBin(Node);
- if FDragDropFilesEx.FileList.Count = 0 then
- Exit;
- SaveCursor := Screen.Cursor;
- Screen.Cursor := crHourGlass;
- SourcePath := EmptyStr;
- try
- if (Effect = DROPEFFECT_COPY) or (Effect = DROPEFFECT_MOVE) then
- begin
- StopAllWatchThreads;
- if Assigned(FDirView) then
- FDirView.StopWatchThread;
- if Assigned(DropSourceControl) and
- (DropSourceControl is TDirView) and
- (DropSourceControl <> FDirView) then
- begin
- TDirView(DropSourceControl).StopWatchThread;
- end;
- if DropFiles(
- DragDropFilesEx, Effect, FFileOperator, TargetPath, false, IsRecycleBin, ConfirmDelete, ConfirmOverwrite, False,
- Self, OnDDFileOperation, SourcePath, SourceIsDirectory) then
- begin
- if Assigned(FOnDDFileOperationExecuted) then
- FOnDDFileOperationExecuted(Self, Effect, SourcePath, TargetPath);
- end;
- ClearDragFileList(FDragDropFilesEx.FileList);
- // TDirView.PerformDragDropFileOperation validates the SourcePath and that actually seems correct
- SourceParentPath := ExtractFilePath(ExcludeTrailingBackslash(SourcePath));
- end
- else
- if Effect = DROPEFFECT_LINK then
- { Create Link requested: }
- begin
- for Index := 0 to FDragDropFilesEx.FileList.Count - 1 do
- begin
- if not DropLink(PFDDListItem(FDragDropFilesEx.FileList[Index]), TargetPath) then
- begin
- DDError(DDCreateShortCutError);
- end;
- end;
- end;
- if Effect = DROPEFFECT_MOVE then
- Items.BeginUpdate;
- {Update source directory, if move-operation was performed:}
- if ((Effect = DROPEFFECT_MOVE) or IsRecycleBin) then
- begin
- // See comment in corresponding operation in TDirView.PerformDragDropFileOperation
- SourceNode := TryFindNodeToPath(SourceParentPath);
- if Assigned(SourceNode) then
- ValidateDirectory(SourceNode);
- end;
- {Update subdirectories of target directory:}
- TargetNode := FindNodeToPath(TargetPath);
- if Assigned(TargetNode) then
- ValidateDirectory(TargetNode)
- else
- ValidateDirectory(GetDriveStatus(DriveInfo.GetDriveKey(TargetPath)).RootNode);
- if Effect = DROPEFFECT_MOVE then
- Items.EndUpdate;
- {Update linked component TDirView:}
- if Assigned(FDirView) and (not FDirView.WatchThreadActive) then
- begin
- case Effect of
- DROPEFFECT_COPY,
- DROPEFFECT_LINK:
- if (IncludeTrailingBackslash(TargetPath) = IncludeTrailingBackslash(DirView.Path)) then
- FDirView.Reload2;
- DROPEFFECT_MOVE:
- if (IncludeTrailingBackslash(TargetPath) = IncludeTrailingBackslash(DirView.Path)) or
- (IncludeTrailingBackslash(SourceParentPath) = IncludeTrailingBackslash(DirView.Path)) then
- begin
- if FDirView <> DropSourceControl then FDirView.Reload2;
- end;
- end; {Case}
- end;
- {Update the DropSource control, if files are moved and it is a TDirView:}
- if (Effect = DROPEFFECT_MOVE) and (DropSourceControl is TDirView) then
- begin
- TDirView(DropSourceControl).ValidateSelectedFiles;
- end;
- finally
- FFileOperator.OperandFrom.Clear;
- FFileOperator.OperandTo.Clear;
- StartAllWatchThreads;
- if Assigned(FDirView) and (not FDirView.WatchThreadActive) then
- FDirView.StartWatchThread;
- if Assigned(DropSourceControl) and (DropSourceControl is TDirView) and
- (not TDirView(DropSourceControl).WatchThreadActive) then
- TDirView(DropSourceControl).StartWatchThread;
- Screen.Cursor := SaveCursor;
- end;
- end; {PerformDragDropFileOperation}
- function TDriveView.GetCanUndoCopyMove: Boolean;
- begin
- Result := Assigned(FFileOperator) and FFileOperator.CanUndo;
- end; {CanUndoCopyMove}
- function TDriveView.UndoCopyMove: Boolean;
- var
- LastTarget: string;
- LastSource: string;
- begin
- Result := False;
- if FFileOperator.CanUndo then
- begin
- Lasttarget := FFileOperator.LastOperandTo[0];
- LastSource := FFileOperator.LastOperandFrom[0];
- StopAllWatchThreads;
- Result := FFileOperator.UndoExecute;
- ValidateDirectory(FindNodeToPath(ExtractFilePath(LastTarget)));
- ValidateDirectory(FindNodeToPath(ExtractFilePath(LastSource)));
- StartAllWatchThreads;
- if Assigned(FDirView) then
- with FDirView do
- if not WatchThreadActive then
- begin
- if (IncludeTrailingBackslash(ExtractFilePath(LastTarget)) = IncludeTrailingBackslash(Path)) or
- (IncludeTrailingBackslash(ExtractFilePath(LastSource)) = IncludeTrailingBackslash(Path)) then
- Reload2;
- end;
- end;
- end; {UndoCopyMove}
- {Clipboard operations:}
- procedure TDriveView.SetLastPathCut(Path: string);
- var
- Node: TTreeNode;
- begin
- if FLastPathCut <> Path then
- begin
- Node := FindNodeToPath(FLastPathCut);
- if Assigned(Node) then
- begin
- FLastPathCut := Path;
- Node.Cut := False;
- end;
- Node := FindNodeToPath(Path);
- if Assigned(Node) then
- begin
- FLastPathCut := Path;
- Node.Cut := True;
- end;
- end;
- end; {SetLastNodeCut}
- procedure TDriveView.EmptyClipboard;
- begin
- if Windows.OpenClipBoard(0) then
- begin
- Windows.EmptyClipBoard;
- Windows.CloseClipBoard;
- LastPathCut := '';
- LastClipBoardOperation := cboNone;
- if Assigned(FDirView) then
- FDirView.EmptyClipboard;
- end;
- end; {EmptyClipBoard}
- function TDriveView.CopyToClipBoard(Node: TTreeNode): Boolean;
- begin
- Result := Assigned(Selected);
- if Result then
- begin
- EmptyClipBoard;
- ClearDragFileList(FDragDropFilesEx.FileList);
- AddToDragFileList(FDragDropFilesEx.FileList, Selected);
- Result := FDragDropFilesEx.CopyToClipBoard;
- LastClipBoardOperation := cboCopy;
- end;
- end; {CopyToClipBoard}
- function TDriveView.CutToClipBoard(Node: TTreeNode): Boolean;
- begin
- Result := Assigned(Node) and Assigned(Node.Parent) and CopyToClipBoard(Node);
- if Result then
- begin
- LastPathCut := NodePathName(Node);
- LastClipBoardOperation := cboCut;
- end;
- end; {CutToClipBoard}
- function TDriveView.CanPasteFromClipBoard: Boolean;
- begin
- Result := False;
- if Assigned(Selected) and Windows.OpenClipboard(0) then
- begin
- Result := IsClipboardFormatAvailable(CF_HDROP);
- Windows.CloseClipBoard;
- end;
- end; {CanPasteFromClipBoard}
- function TDriveView.PasteFromClipBoard(TargetPath: String = ''): Boolean;
- begin
- ClearDragFileList(FDragDropFilesEx.FileList);
- Result := False;
- if CanPasteFromClipBoard and {MP}FDragDropFilesEx.GetFromClipBoard{/MP}
- then
- begin
- if TargetPath = '' then
- TargetPath := NodePathName(Selected);
- case LastClipBoardOperation of
- cboCopy,
- cboNone:
- begin
- PerformDragDropFileOperation(Selected, DROPEFFECT_COPY);
- if Assigned(FOnDDExecuted) then
- FOnDDExecuted(Self, DROPEFFECT_COPY);
- end;
- cboCut:
- begin
- PerformDragDropFileOperation(Selected, DROPEFFECT_MOVE);
- if Assigned(FOnDDExecuted) then
- FOnDDExecuted(Self, DROPEFFECT_MOVE);
- EmptyClipBoard;
- end;
- end;
- Result := True;
- end;
- end; {PasteFromClipBoard}
- procedure TDriveView.CMRecreateWnd(var Msg: TMessage);
- var
- ScheduledCount: Integer;
- begin
- ScheduledCount := FSubDirReaderThread.Detach;
- try
- inherited;
- finally
- FSubDirReaderThread.Reattach(ScheduledCount);
- end;
- end;
- procedure TDriveView.CMSysColorChange(var Message: TMessage);
- begin
- if not FValidateFlag then
- begin
- inherited;
- end
- else
- begin
- // Do not recreate the handle, if we are just iterating nodes, at that invalidates the node objects.
- // This is not perfect, as the handle can be recreated for other reasons.
- // But system color change is by far the most common case.
- FSysColorChangePending := True;
- end;
- end;
- end.
|