//--------------------------------------------------------------------------- #pragma warn -pch // WORKAROUND (see My.cpp) #include #pragma hdrstop #include #include "UnixDriveView.h" #ifndef DESIGN_ONLY #include #include #include #endif #pragma package(smart_init) //--------------------------------------------------------------------------- static inline void ValidCtrCheck(TUnixDriveView *) { new TUnixDriveView(NULL); } //--------------------------------------------------------------------------- namespace Unixdriveview { void __fastcall PACKAGE Register() { TComponentClass classes[1] = {__classid(TUnixDriveView)}; RegisterComponents(L"Scp", classes, 0); } } //--------------------------------------------------------------------------- __fastcall TUnixDriveView::TUnixDriveView(TComponent * Owner) : TCustomUnixDriveView(Owner) { } //--------------------------------------------------------------------------- struct TNodeData { TRemoteFileList * FileList; TRemoteFile * File; UnicodeString Directory; }; //--------------------------------------------------------------------------- __fastcall TCustomUnixDriveView::TCustomUnixDriveView(TComponent* Owner) : TCustomDriveView(Owner) { FTerminal = NULL; FRootName = Customunixdirview_SUnixDefaultRootName; FIgnoreChange = false; FPrevSelected = NULL; FDDAllowMove = false; FShowInaccesibleDirectories = true; FDummyDragFile = NULL; FPendingDelete = new TList(); FDragDropFilesEx->PreferCopy = true; FChangingDirectory = false; } //--------------------------------------------------------------------------- __fastcall TCustomUnixDriveView::~TCustomUnixDriveView() { Terminal = NULL; if (FDummyDragFile != NULL) { #ifndef DESIGN_ONLY SAFE_DESTROY(FDummyDragFile); #endif } SAFE_DESTROY(FPendingDelete); } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::CreateWnd() { TCustomDriveView::CreateWnd(); FDragDropFilesEx->TargetEffects = TDropEffectSet() << deCopy << deMove; // in case the items were recreated FPrevSelected = Selected; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::DestroyWnd() { // in case we are recreating (TCustomTreeView.DestroyWnd deletes items) FPrevSelected = NULL; TCustomDriveView::DestroyWnd(); } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::SetTerminal(TTerminal * value) { #ifndef DESIGN_ONLY if ((FTerminal != value) || ((FTerminal != NULL) && !FTerminal->Active)) // Abused by TCustomScpExplorerForm::DisconnectSession { FTerminal = value; Items->Clear(); // If terminal is not active initially, we will never load fixed paths, when it becomes active. // But actually terminal is not active here, only when we are replacing an abandoned terminal // with a dummy one (which will never become active) if ((FTerminal != NULL) && FTerminal->Active) { TStrings * FixedPaths = FTerminal->FixedPaths; if (FixedPaths != NULL) { for (int i = 0; i < FixedPaths->Count; i++) { LoadPath(FixedPaths->Strings[i]); } } } } #else DebugUsedParam(value); #endif } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::SetDirView(TUnixDirView * Value) { if (FDirView != Value) { if (FDirView != NULL) { FDirView->DriveView = NULL; } FDirView = Value; if (FDirView != NULL) { FDirView->DriveView = this; } } } //--------------------------------------------------------------------------- TCustomDirView * __fastcall TCustomUnixDriveView::GetCustomDirView() { return FDirView; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::SetCustomDirView(TCustomDirView * Value) { SetDirView(dynamic_cast(Value)); } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::IsRootNameStored() { return (FRootName != Customunixdirview_SUnixDefaultRootName); } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::NodeIsHidden(const TTreeNode * Node) { #ifndef DESIGN_ONLY TNodeData * Data = NodeData(Node); TRemoteFile * File = Data->File; return ((File != NULL) && File->IsHidden) || ((File == NULL) && IsUnixHiddenFile(UnixExtractFileName(Data->Directory))); #else return false; #endif } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::NodeTryDelete(TTreeNode * Node, bool RememberIfFails) { bool Result = (Selected == NULL) || ((Selected != Node) && !Selected->HasAsParent(Node)); if (Result) { Node->Delete(); } else { if (RememberIfFails) { int I = FPendingDelete->IndexOf(Node); if (I < 0) { FPendingDelete->Add(Node); } } } return Result; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::UpdatePath(TTreeNode * Node, bool Force, bool CanLoad) { #ifndef DESIGN_ONLY TNodeData * Data = NodeData(Node); UnicodeString Path = Data->Directory; TDateTime Timestamp = (Data->FileList != NULL) ? Data->FileList->Timestamp : TDateTime(); TRemoteFileList * FileList = FTerminal->DirectoryFileList(Path, Timestamp, CanLoad); if ((FileList != NULL) || ((Data->FileList != NULL) && Force)) { TStringList * ChildrenDirs = new TStringList(); TRemoteFileList * OldFileList = NULL; try { if (FileList != NULL) { OldFileList = Data->FileList; Data->FileList = FileList; } ChildrenDirs->Duplicates = Types::dupAccept; ChildrenDirs->CaseSensitive = true; bool FirstChild = true; TTreeNode * ChildNode = Node->getFirstChild(); while (ChildNode != NULL) { TNodeData * ChildData = NodeData(ChildNode); ChildData->File = NULL; ChildrenDirs->AddObject(UnixExtractFileName(ChildData->Directory), ChildNode); ChildNode = Node->GetNextChild(ChildNode); FirstChild = false; } // sort only after adding all items. // should be faster than keeping the list sorted since beginning ChildrenDirs->Sorted = true; for (int i = 0; i < Data->FileList->Count; i++) { TRemoteFile * File = Data->FileList->Files[i]; if (File->IsDirectory && IsRealFile(File->FileName) && (ShowHiddenDirs || !File->IsHidden) && (ShowInaccesibleDirectories || !File->IsInaccesibleDirectory)) { int ChildIndex = ChildrenDirs->IndexOf(File->FileName); if (ChildIndex >= 0) { TTreeNode * ChildNode = dynamic_cast(ChildrenDirs->Objects[ChildIndex]); TNodeData * ChildData = NodeData(ChildNode); ChildData->File = File; UpdatePath(ChildNode, Force); } else { UnicodeString ChildPath = UnixIncludeTrailingBackslash(Path) + File->FileName; DebugAssert(!IsUnixRootPath(ChildPath)); LoadPathEasy(Node, ChildPath, File); if (FirstChild) { LoadNodeState(Node, Path); FirstChild = false; } } } } for (int i = 0; i < ChildrenDirs->Count; i++) { TTreeNode * ChildNode = dynamic_cast(ChildrenDirs->Objects[i]); TNodeData * ChildData = NodeData(ChildNode); if (ChildData->File == NULL) { NodeTryDelete(ChildNode, true); } } Node->AlphaSort(false); } __finally { delete ChildrenDirs; // Release files only now, once they are no longer referenced in the tree delete OldFileList; // if not NULL } } else if (Force) { TTreeNode * ChildNode = Node->GetLastChild(); while (ChildNode != NULL) { TTreeNode * PrevChildNode = Node->GetPrevChild(ChildNode); TRemoteFile * File = NodeFile(ChildNode); if (((ShowHiddenDirs || !NodeIsHidden(ChildNode)) && (ShowInaccesibleDirectories || (File == NULL) || !File->IsInaccesibleDirectory)) || !NodeTryDelete(ChildNode, true)) { UpdatePath(ChildNode, true); } ChildNode = PrevChildNode; } } #else DebugUsedParam(Node); DebugUsedParam(Force); DebugUsedParam(CanLoad); #endif } //--------------------------------------------------------------------------- TTreeNode * __fastcall TCustomUnixDriveView::LoadPathEasy(TTreeNode * Parent, UnicodeString Path, TRemoteFile * File) { #ifndef DESIGN_ONLY DebugAssert(Path == UnixExcludeTrailingBackslash(Path)); UnicodeString DirName; if (IsUnixRootPath(Path)) { DirName = RootName; } else { DirName = UnixExtractFileName(Path); } TTreeNode * Node = Items->AddChild(Parent, DirName); TNodeData * Data = new TNodeData(); Node->Data = Data; Data->Directory = Path; Data->File = File; Data->FileList = NULL; UpdatePath(Node, true); return Node; #else DebugUsedParam(Parent); DebugUsedParam(File); return NULL; #endif } //--------------------------------------------------------------------------- TTreeNode * __fastcall TCustomUnixDriveView::LoadPath(UnicodeString Path) { #ifndef DESIGN_ONLY if (Path.IsEmpty()) { Path = ROOTDIRECTORY; } TTreeNode * Node = FindNodeToPath(Path); if (Node == NULL) { Path = UnixExcludeTrailingBackslash(Path); TTreeNode * Parent = NULL; TRemoteFile * File = NULL; UnicodeString ParentPath; if (!IsUnixRootPath(Path)) { ParentPath = UnixExtractFileDir(Path); Parent = LoadPath(ParentPath); } Node = FindNodeToPath(Path); if (Node == NULL) { // node still does not exist, this should happen only when // if the parent directory is not in cache if (Parent != NULL) { TRemoteFileList * ParentFileList = NodeFileList(Parent); if (ParentFileList != NULL) { File = ParentFileList->FindFile(UnixExtractFileName(Path)); } } Node = LoadPathEasy(Parent, Path, File); if (Parent != NULL) { LoadNodeState(Parent, ParentPath); Parent->AlphaSort(false); } } else { UpdatePath(Node, false); } } else { UpdatePath(Node, false); } return Node; #else return NULL; #endif } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::LoadDirectory() { #ifndef DESIGN_ONLY DebugAssert(!FIgnoreChange); FIgnoreChange = true; try { // WM_SETREDRAW does not prevent re-drawing of the scrollbar (with each added node) LockWindowUpdate(Handle); TTreeNode * NewSelected; try { NewSelected = LoadPath(FTerminal->Files->Directory); } __finally { LockWindowUpdate(NULL); } Selected = NewSelected; DebugAssert(Selected != NULL); FPrevSelected = Selected; } __finally { FIgnoreChange = false; FDirectoryLoaded = true; } #endif } //--------------------------------------------------------------------------- TNodeData * __fastcall TCustomUnixDriveView::NodeData(const TTreeNode * Node) { DebugAssert(Node->Data != NULL); return static_cast(Node->Data); } //--------------------------------------------------------------------------- TRemoteFileList * __fastcall TCustomUnixDriveView::NodeFileList(const TTreeNode * Node) { DebugAssert(Node->Data != NULL); return static_cast(Node->Data)->FileList; } //--------------------------------------------------------------------------- TRemoteFile * __fastcall TCustomUnixDriveView::NodeFile(const TTreeNode * Node) { DebugAssert(Node->Data != NULL); return static_cast(Node->Data)->File; } //--------------------------------------------------------------------------- TRemoteFile * __fastcall TCustomUnixDriveView::NodeFileForce(TTreeNode * Node) { TRemoteFile * File = NodeFile(Node); if (File == NULL) { #ifndef DESIGN_ONLY SAFE_DESTROY(FDummyDragFile); FDummyDragFile = new TRemoteDirectoryFile(); FDummyDragFile->FileName = Node->Text; FDummyDragFile->FullFileName = NodePathName(Node); File = FDummyDragFile; #endif } return File; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::Delete(TTreeNode * Node) { if (Node == FPrevSelected) { FPrevSelected = NULL; } // optimization check if (FPendingDelete->Count > 0) { int I = FPendingDelete->IndexOf(Node); if (I >= 0) { FPendingDelete->Delete(I); } } TNodeData * Data = NULL; if (Node != NULL) { Data = NodeData(Node); } TCustomDriveView::Delete(Node); // delete file list at last, when we are sure that no child nodes exist if (Data != NULL) { #ifndef DESIGN_ONLY delete Data->FileList; #endif delete Data; } } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::CanChange(TTreeNode * Node) { return TCustomDriveView::CanChange(Node) && !FChangingDirectory; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::Change(TTreeNode * Node) { #ifndef DESIGN_ONLY bool Expand = false; try { // During D&D Selected is set to NULL and then back to previous selection, // prevent actually changing directory in such case if (Reading || ControlState.Contains(csRecreating) || FRecreatingHandle || (Node == NULL) || (Node == FPrevSelected)) { TCustomDriveView::Change(Node); } else { // if previous node is child to newly selected one, do not expand it. // it is either already expanded and it is even being collapsed. Expand = (FPrevSelected == NULL) || !FPrevSelected->HasAsParent(Node); if (FIgnoreChange) { TCustomDriveView::Change(Node); } else { if (FDirView != NULL) { // remember current directory, so it gets selected if we move to parent // directory FDirView->ContinueSession(true); } FDirectoryLoaded = false; bool SetBusy = !ControlState.Contains(csRecreating); if (SetBusy) { StartBusy(); } try { { // Prevent further changes while loading the folder. // Particularly prevent user from trying to proceed with incremental search. TValueRestorer ChangingDirectoryRestorer(FChangingDirectory, true); Terminal->ChangeDirectory(NodePathName(Node)); } TCustomDriveView::Change(Node); } __finally { if (SetBusy) { EndBusy(); } if (!FDirectoryLoaded) { DebugAssert(!FIgnoreChange); Expand = false; FIgnoreChange = true; try { Selected = FPrevSelected; } __finally { FIgnoreChange = false; } } } } } } __finally { FPrevSelected = Selected; if (Expand) { Selected->Expand(false); } CheckPendingDeletes(); } #else TCustomDriveView::Change(Node); #endif } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::CheckPendingDeletes() { int Index = 0; while (Index < FPendingDelete->Count) { TTreeNode * Node = static_cast(FPendingDelete->Items[Index]); // this as well deletes node from FPendingDelete if (!NodeTryDelete(Node, false)) { Index++; } } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::PerformDragDropFileOperation( TTreeNode * Node, int Effect) { if (OnDDFileOperation) { // see a comment in TUnixDirView::PerformItemDragDropOperation if (DragDropFilesEx->FileList->Count > 0) { DebugAssert(Node != NULL); UnicodeString SourceDirectory; UnicodeString TargetDirectory; SourceDirectory = ExtractFilePath(DragDropFilesEx->FileList->Items[0]->Name); TargetDirectory = NodeData(Node)->Directory; bool DoFileOperation = true; OnDDFileOperation( this, Effect, SourceDirectory, TargetDirectory, false, DoFileOperation); } } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::DDChooseEffect(int KeyState, int & Effect, int PreferredEffect) { // if any drop effect is allowed at all (e.g. no drop to self and drop to parent) if (Effect != DROPEFFECT_NONE) { if (DropTarget != NULL) { if ((KeyState & (MK_CONTROL | MK_SHIFT)) == 0) { Effect = DROPEFFECT_COPY; } } else { Effect = DROPEFFECT_NONE; } } TCustomDriveView::DDChooseEffect(KeyState, Effect, PreferredEffect); } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::UpdateDropTarget() { // should never be NULL if (DropTarget != NULL) { UpdatePath(DropTarget, false, true); } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::UpdateDropSource() { // DragNode may be NULL if its parent directory was reloaded as result // of D&D operation and thus all child nodes are recreated if ((DragNode != NULL) && (DragNode->Parent != NULL)) { UpdatePath(DragNode->Parent, false, true); } } //--------------------------------------------------------------------------- TStrings * __fastcall TCustomUnixDriveView::DragFileList() { DebugAssert(DragNode != NULL); TStrings * FileList = new TStringList(); try { #ifndef DESIGN_ONLY FileList->AddObject(ExcludeTrailingBackslash(NodePathName(DragNode)), NodeFileForce(DragNode)); #endif } catch(...) { delete FileList; throw; } return FileList; } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::DragCompleteFileList() { return true; } //--------------------------------------------------------------------------- TDropEffectSet __fastcall TCustomUnixDriveView::DDSourceEffects() { TDropEffectSet Result; Result << deCopy; if (DDAllowMove) { Result << deMove; } return Result; } //--------------------------------------------------------------------------- UnicodeString __fastcall TCustomUnixDriveView::NodePathName(TTreeNode * Node) { // same as NodePath return NodeData(Node)->Directory; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::ClearDragFileList(TFileList * FileList) { #ifndef DESIGN_ONLY if (FDummyDragFile != NULL) { SAFE_DESTROY(FDummyDragFile); } #endif TCustomDriveView::ClearDragFileList(FileList); } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::AddToDragFileList(TFileList * FileList, TTreeNode * Node) { UnicodeString FileName = NodePathName(Node); TRemoteFile * File = NodeFileForce(Node); if (OnDDDragFileName != NULL) { OnDDDragFileName(this, File, FileName); } FileList->AddItem(NULL, FileName); } //--------------------------------------------------------------------------- UnicodeString __fastcall TCustomUnixDriveView::NodePath(TTreeNode * Node) { // same as NodePathName return NodeData(Node)->Directory; } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::NodeIsRecycleBin(TTreeNode * /*Node*/) { return false; } //--------------------------------------------------------------------------- bool __fastcall TCustomUnixDriveView::NodePathExists(TTreeNode * /*Node*/) { return true; } //--------------------------------------------------------------------------- TColor __fastcall TCustomUnixDriveView::NodeColor(TTreeNode * Node) { DebugAssert(Node != NULL); TColor Result = static_cast(clDefaultItemColor); #ifndef DESIGN_ONLY if (FDimmHiddenDirs && !Node->Selected) { if (NodeIsHidden(Node)) { Result = clGrayText; } } #else DebugUsedParam(Node); #endif return Result; } //--------------------------------------------------------------------------- Word __fastcall TCustomUnixDriveView::NodeOverlayIndexes(TTreeNode * Node) { #ifndef DESIGN_ONLY Word Result = oiNoOverlay; // Cannot query root node for file if (Node->Parent != NULL) { TRemoteFile * File = NodeFile(Node); if (File != NULL) { if (File->IsSymLink) { // broken link cannot probably happen anyway // as broken links are treated as files Result |= File->BrokenLink ? oiBrokenLink : oiLink; } if (File->IsEncrypted) { Result |= oiEncrypted; } } } return Result; #else DebugUsedParam(Node); return 0; #endif } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::GetImageIndex(TTreeNode * Node) { TCustomDriveView::GetImageIndex(Node); Node->ImageIndex = StdDirIcon; Node->SelectedIndex = StdDirSelIcon; } //--------------------------------------------------------------------------- TTreeNode * __fastcall TCustomUnixDriveView::FindNodeToPath(UnicodeString Path) { TTreeNode * Result; #ifndef DESIGN_ONLY if (IsUnixRootPath(Path)) { Result = Items->GetFirstNode(); } else { Result = NULL; Path = UnixExcludeTrailingBackslash(Path); TTreeNode * Parent = NULL; if (!IsUnixRootPath(Path)) { Parent = FindNodeToPath(UnixExtractFileDir(Path)); } if ((Parent != NULL) && (Parent->getFirstChild() != NULL)) { UnicodeString DirName = UnixExtractFileName(Path); int StartIndex = 0; int EndIndex = Parent->Count - 1; while (true) { int Index = (StartIndex + EndIndex) / 2; UnicodeString NodeDir = Parent->Item[Index]->Text; int C = DoCompareText(DirName, NodeDir); if (C == 0) { Result = Parent->Item[Index]; break; } else if (C < 0) { if (Index == StartIndex) { break; } EndIndex = Index - 1; } else { if (Index == EndIndex) { break; } StartIndex = Index + 1; } } } } #else Result = NULL; #endif return Result; } //--------------------------------------------------------------------------- TTreeNode * __fastcall TCustomUnixDriveView::FindPathNode(UnicodeString Path) { TTreeNode * Result = NULL; #ifndef DESIGN_ONLY if (Items->GetFirstNode() != NULL) { do { Result = FindNodeToPath(Path); if (Result == NULL) { DebugAssert(!IsUnixRootPath(Path)); Path = UnixExtractFileDir(UnixExcludeTrailingBackslash(Path)); } } while (Result == NULL); } #endif return Result; } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::ValidateDirectoryEx(TTreeNode * /*Node*/, TRecursiveScan /*Recurse*/, bool /*NewDirs*/) { // nothing } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::RebuildTree() { TTreeNode * First = Items->GetFirstNode(); if (First != NULL) { UpdatePath(First, true); } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::SetShowInaccesibleDirectories(bool value) { if (FShowInaccesibleDirectories != value) { FShowInaccesibleDirectories = value; RebuildTree(); } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::CMShowingChanged(TMessage & Message) { TCustomDriveView::Dispatch(&Message); if (Showing && (Terminal != NULL)) { LoadDirectory(); } } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::DisplayContextMenu(TTreeNode * /*Node*/, const TPoint & /*ScreenPos*/) { // TODO } //--------------------------------------------------------------------------- void __fastcall TCustomUnixDriveView::DisplayPropertiesMenu(TTreeNode * /*Node*/) { // TODO } //--------------------------------------------------------------------------- void TCustomUnixDriveView::LoadNodeState(TTreeNode * Node, const UnicodeString & Path) { if ((FDirView != NULL) && (FDirView->FAnnouncedDriveViewState != NULL)) { TStrings * AnnouncedDriveViewState = DebugNotNull(dynamic_cast(FDirView->FAnnouncedDriveViewState)); if (AnnouncedDriveViewState->IndexOf(Path) >= 0) { Node->Expand(false); } } } //--------------------------------------------------------------------------- TObject * TCustomUnixDriveView::SaveState() { #ifndef DESIGN_ONLY std::unique_ptr ExpandedNodes(CreateSortedStringList()); TTreeNode * Node = Items->GetFirstNode(); while (Node != NULL) { // Node->HasChildren would be false in extreme cases only (like while closing the window) if (Node->Expanded && Node->HasChildren) { ExpandedNodes->Add(NodePathName(Node)); } Node = Node->GetNext(); } return ExpandedNodes.release(); #else return NULL; #endif }