/* treelist.cpp Tree panel */ /* Copyright (c) 1996 Eugene Roshal Copyright (c) 2000 Far Group All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "headers.hpp" #pragma hdrstop #include "treelist.hpp" #include "flink.hpp" #include "keyboard.hpp" #include "colors.hpp" #include "lang.hpp" #include "keys.hpp" #include "filepanels.hpp" #include "filelist.hpp" #include "cmdline.hpp" #include "chgprior.hpp" #include "scantree.hpp" #include "copy.hpp" #include "qview.hpp" #include "savescr.hpp" #include "ctrlobj.hpp" #include "help.hpp" #include "lockscrn.hpp" #include "macroopcode.hpp" #include "RefreshFrameManager.hpp" #include "scrbuf.hpp" #include "TPreRedrawFunc.hpp" #include "TaskBar.hpp" #include "cddrv.hpp" #include "interf.hpp" #include "message.hpp" #include "clipboard.hpp" #include "config.hpp" #include "delete.hpp" #include "mkdir.hpp" #include "setattr.hpp" #include "execute.hpp" #include "ffolders.hpp" #include "dirmix.hpp" #include "pathmix.hpp" #include "processname.hpp" #include "constitle.hpp" #include "syslog.hpp" static int _cdecl SortList(const void *el1,const void *el2); static int _cdecl SortCacheList(const void *el1,const void *el2); static int StaticSortCaseSensitive; static int StaticSortNumeric; static int TreeCmp(const wchar_t *Str1,const wchar_t *Str2, int Numeric, int CaseSensitive); static clock_t TreeStartTime; static int LastScrX = -1; static int LastScrY = -1; static struct TreeListCache { string strTreeName; wchar_t **ListName; int TreeCount; int TreeSize; TreeListCache() { ListName=NULL; TreeCount=0; TreeSize=0; } void Resize() { if (TreeCount==TreeSize) { TreeSize+=TreeSize?TreeSize>>2:32; wchar_t **NewPtr=(wchar_t**)xf_realloc(ListName,sizeof(wchar_t*)*TreeSize); if (!NewPtr) return; ListName=NewPtr; } } void Add(const wchar_t* name) { Resize(); ListName[TreeCount++]=xf_wcsdup(name); } void Insert(int idx,const wchar_t* name) { Resize(); memmove(ListName+idx+1,ListName+idx,sizeof(wchar_t*)*(TreeCount-idx)); ListName[idx]=xf_wcsdup(name); TreeCount++; } void Delete(int idx) { if (ListName[idx]) xf_free(ListName[idx]); memmove(ListName+idx,ListName+idx+1,sizeof(wchar_t*)*(TreeCount-idx-1)); TreeCount--; } void Clean() { if (!TreeSize)return; for (int i=0; iClean(); for (int I=0; I < TreeCount; I++) Dest->Add(ListName[I]); } } TreeCache, tempTreeCache; TreeList::TreeList(int IsPanel) { Type=TREE_PANEL; ListData=NULL; SaveListData=NULL; TreeCount=0; WorkDir=CurFile=CurTopFile=0; GetSelPosition=0; // *Root=0; Flags.Set(FTREELIST_UPDATEREQUIRED); CaseSensitiveSort=FALSE; NumericSort=FALSE; PrevMacroMode = -1; Flags.Clear(FTREELIST_TREEISPREPARED); ExitCode=1; Flags.Change(FTREELIST_ISPANEL,IsPanel); } TreeList::~TreeList() { if (ListData) { for (long i=0; iGetType()==FILE_PANEL) { int RootCaseSensitive=((FileList *)RootPanel)->IsCaseSensitive(); int RootNumeric=RootPanel->GetNumericSort(); if (RootCaseSensitive!=CaseSensitiveSort || RootNumeric != NumericSort) { CaseSensitiveSort=RootCaseSensitive; NumericSort=RootNumeric; StaticSortCaseSensitive=CaseSensitiveSort; StaticSortNumeric=NumericSort; far_qsort(ListData,TreeCount,sizeof(*ListData),SortList); FillLastData(); SyncDir(); } } DisplayTree(FALSE); } Flags.Clear(FSCROBJ_ISREDRAWING); } string &TreeList::GetTitle(string &strTitle,int SubLen,int TruncSize) { strTitle.Format(L" %s ",ModalMode ? MSG(MFindFolderTitle):MSG(MTreeTitle)); TruncStr(strTitle,X2-X1-3); return strTitle; } void TreeList::DisplayTree(int Fast) { wchar_t TreeLineSymbol[4][3]= { {L' ', L' ', 0}, {BoxSymbols[BS_V1], L' ', 0}, {BoxSymbols[BS_LB_H1V1],BoxSymbols[BS_H1],0}, {BoxSymbols[BS_L_H1V1], BoxSymbols[BS_H1],0}, }; TreeItem *CurPtr; string strTitle; LockScreen *LckScreen=NULL; if (CtrlObject->Cp()->GetAnotherPanel(this)->GetType() == QVIEW_PANEL) LckScreen=new LockScreen; CorrectPosition(); if (TreeCount>0) strCurDir = ListData[CurFile]->strName; //BUGBUG // xstrncpy(CurDir,ListData[CurFile].Name,sizeof(CurDir)); if (!Fast) { Box(X1,Y1,X2,Y2,COL_PANELBOX,DOUBLE_BOX); DrawSeparator(Y2-2-(ModalMode!=0)); GetTitle(strTitle); if (!strTitle.IsEmpty()) { SetColor((Focus || ModalMode) ? COL_PANELSELECTEDTITLE:COL_PANELTITLE); GotoXY(X1+(X2-X1+1-(int)strTitle.GetLength())/2,Y1); Text(strTitle); } } for (int I=Y1+1,J=CurTopFile; IDepth-1 && WhereX()+3*iLast[i]?0:1]; } strOutStr+=TreeLineSymbol[CurPtr->Last[CurPtr->Depth-1]?2:3]; BoxText(strOutStr); const wchar_t *ChPtr=LastSlash(CurPtr->strName); if (ChPtr!=NULL) DisplayTreeName(ChPtr+1,J); } } SetColor(COL_PANELTEXT); if (WhereX()0) { GotoXY(X1+1,Y2-1); FS<strName; } UpdateViewPanel(); SetTitle(); // не забудим прорисовать заголовок if (LckScreen) delete LckScreen; } void TreeList::DisplayTreeName(const wchar_t *Name,int Pos) { if (WhereX()>X2-4) GotoXY(X2-4,WhereY()); if (Pos==CurFile) { GotoXY(WhereX()-1,WhereY()); if (Focus || ModalMode) { SetColor((Pos==WorkDir) ? COL_PANELSELECTEDCURSOR:COL_PANELCURSOR); FS<0 && ((Mode & UPDATE_KEEP_SELECTION)==0 || LastTreeCount!=TreeCount)) { SyncDir(); TreeItem *CurPtr=ListData[CurFile]; if (apiGetFileAttributes(CurPtr->strName)==INVALID_FILE_ATTRIBUTES) { DelTreeName(CurPtr->strName); Update(UPDATE_KEEP_SELECTION); Show(); } } else if (!RetFromReadTree) { Show(); if (!Flags.Check(FTREELIST_ISPANEL)) { Panel *AnotherPanel=CtrlObject->Cp()->GetAnotherPanel(this); AnotherPanel->Update(UPDATE_KEEP_SELECTION|UPDATE_SECONDARY); AnotherPanel->Redraw(); } } } int TreeList::ReadTree() { ChangePriority ChPriority(THREAD_PRIORITY_NORMAL); //SaveScreen SaveScr; TPreRedrawFuncGuard preRedrawFuncGuard(TreeList::PR_MsgReadTree); ScanTree ScTree(FALSE); FAR_FIND_DATA_EX fdata; string strFullName; SaveState(); FlushCache(); GetRoot(); if (ListData) { for (long i=0; iClear(); ListData[0]->strName = strRoot; SaveScreen SaveScrTree; UndoGlobalSaveScrPtr UndSaveScr(&SaveScrTree); /* Т.к. мы можем вызвать диалог подтверждения (который не перерисовывает панельки, а восстанавливает сохраненный образ экрана, то нарисуем чистую панель */ //Redraw(); TreeCount=1; int FirstCall=TRUE, AscAbort=FALSE; TreeStartTime = clock(); RefreshFrameManager frref(ScrX,ScrY,TreeStartTime,FALSE);//DontRedrawFrame); ScTree.SetFindPath(strRoot, L"*", 0); LastScrX = ScrX; LastScrY = ScrY; TaskBar TB; while (ScTree.GetNextName(&fdata,strFullName)) { // if(TreeCount > 3) TreeList::MsgReadTree(TreeCount,FirstCall); if (CheckForEscSilent()) { AscAbort=ConfirmAbortOp()!=0; FirstCall=TRUE; } if (AscAbort) break; if ((TreeCount & 255)==0) { TreeItem **TmpListData=(TreeItem **)xf_realloc(ListData,(TreeCount+256+1)*sizeof(TreeItem*)); if (!TmpListData) { AscAbort=TRUE; break; } ListData = TmpListData; } if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; ListData[TreeCount] = new TreeItem; ListData[TreeCount]->Clear(); ListData[TreeCount]->strName = strFullName; TreeCount++; } if (AscAbort && !Flags.Check(FTREELIST_ISPANEL)) { if (ListData) { for (long i=0; iCp()->GetAnotherPanel(this); AnotherPanel->Redraw(); } return TRUE; } //extern FILE *_wfopen(const wchar_t *filename, const wchar_t *mode); void TreeList::SaveTreeFile() { if (TreeCount<4) return; string strName; long I; size_t RootLength=strRoot.IsEmpty()?0:strRoot.GetLength()-1; MkTreeFileName(strRoot, strName); // получим и сразу сбросим атрибуты (если получится) DWORD FileAttributes=apiGetFileAttributes(strName); if (FileAttributes != INVALID_FILE_ATTRIBUTES) apiSetFileAttributes(strName,FILE_ATTRIBUTE_NORMAL); HANDLE hTreeFile=apiCreateFile(strName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL); if (hTreeFile==INVALID_HANDLE_VALUE) { /* $ 16.10.2000 tran если диск должен кешироваться, то и пытаться не стоит */ if (MustBeCached(strRoot)) if (!GetCacheTreeName(strRoot,strName,TRUE) || (hTreeFile=apiCreateFile(strName,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL))==INVALID_HANDLE_VALUE) return; /* tran $ */ } bool Success=true; for (I=0; I=ListData[I]->strName.GetLength()) { DWORD Size=2*sizeof(WCHAR); Success=(WriteFile(hTreeFile,L"\\\n",Size,&Written,NULL) && Written==Size); } else { DWORD Size=static_cast((ListData[I]->strName.GetLength()-RootLength)*sizeof(WCHAR)); Success=(WriteFile(hTreeFile,ListData[I]->strName+RootLength,Size,&Written,NULL) && Written==Size); Size=1*sizeof(WCHAR); Success=(WriteFile(hTreeFile,L"\n",Size,&Written,NULL) && Written==Size); } } CloseHandle(hTreeFile); if (!Success) { apiDeleteFile(strName); Message(MSG_WARNING|MSG_ERRORTYPE,1,MSG(MError),MSG(MCannotSaveTree),strName,MSG(MOk)); } else if (FileAttributes != INVALID_FILE_ATTRIBUTES) // вернем атрибуты (если получится :-) apiSetFileAttributes(strName,FileAttributes); } int TreeList::GetCacheTreeName(const wchar_t *Root, string &strName,int CreateDir) { string strVolumeName, strFileSystemName; DWORD dwVolumeSerialNumber; if (!apiGetVolumeInformation( Root, &strVolumeName, &dwVolumeSerialNumber, NULL, NULL, &strFileSystemName )) return(FALSE); string strFolderName; string strFarPath; MkTreeCacheFolderName(strFarPath, strFolderName); if (CreateDir) { apiCreateDirectory(strFolderName, NULL); apiSetFileAttributes(strFolderName,Opt.Tree.TreeFileAttr); } string strRemoteName; wchar_t *lpwszRemoteName; // char RemoteName[NM*3]; // *RemoteName=0; if (*Root == L'\\') strRemoteName = Root; else { wchar_t wszLocalName[]={*Root,L':',0}; apiWNetGetConnection(wszLocalName, strRemoteName); if (!strRemoteName.IsEmpty()) AddEndSlash(strRemoteName); } lpwszRemoteName = strRemoteName.GetBuffer(); for (int I=0; lpwszRemoteName[I] != 0; I++) if (lpwszRemoteName[I]==L'\\') lpwszRemoteName[I]=L'_'; strRemoteName.ReleaseBuffer(); strName.Format( L"%s\\%s.%x.%s.%s", (const wchar_t*)strFolderName, (const wchar_t*)strVolumeName, dwVolumeSerialNumber, (const wchar_t*)strFileSystemName, (const wchar_t*)strRemoteName ); return(TRUE); } void TreeList::GetRoot() { string strPanelDir; Panel *RootPanel=GetRootPanel(); RootPanel->GetCurDir(strPanelDir); strRoot = ExtractPathRoot(strPanelDir); } Panel* TreeList::GetRootPanel() { Panel *RootPanel; if (ModalMode) { if (ModalMode==MODALTREE_ACTIVE) RootPanel=CtrlObject->Cp()->ActivePanel; else if (ModalMode==MODALTREE_FREE) RootPanel=this; else { RootPanel=CtrlObject->Cp()->GetAnotherPanel(CtrlObject->Cp()->ActivePanel); if (!RootPanel->IsVisible()) RootPanel=CtrlObject->Cp()->ActivePanel; } } else RootPanel=CtrlObject->Cp()->GetAnotherPanel(this); return(RootPanel); } void TreeList::SyncDir() { string strPanelDir; Panel *AnotherPanel=GetRootPanel(); AnotherPanel->GetCurDir(strPanelDir); if (!strPanelDir.IsEmpty()) { if (AnotherPanel->GetType()==FILE_PANEL) { if (!SetDirPosition(strPanelDir)) { ReadSubTree(strPanelDir); ReadTreeFile(); SetDirPosition(strPanelDir); } } else SetDirPosition(strPanelDir); } } void TreeList::PR_MsgReadTree() { int FirstCall=1; PreRedrawItem preRedrawItem=PreRedraw.Peek(); TreeList::MsgReadTree(preRedrawItem.Param.Flags,FirstCall); } int TreeList::MsgReadTree(int TreeCount,int &FirstCall) { /* $ 24.09.2001 VVM ! Писать сообщение о чтении дерева только, если это заняло более 500 мсек. */ BOOL IsChangeConsole = LastScrX != ScrX || LastScrY != ScrY; if (IsChangeConsole) { LastScrX = ScrX; LastScrY = ScrY; } if (IsChangeConsole || (clock() - TreeStartTime) > 1000) { wchar_t NumStr[32]; _itow(TreeCount,NumStr,10); //BUGBUG Message((FirstCall ? 0:MSG_KEEPBACKGROUND),0,MSG(MTreeTitle), MSG(MReadingTree),NumStr); PreRedrawItem preRedrawItem=PreRedraw.Peek(); preRedrawItem.Param.Flags=TreeCount; PreRedraw.SetParam(preRedrawItem.Param); TreeStartTime = clock(); } return(1); } bool TreeList::FillLastData() { int Last,PathLength,SubDirPos,I,J; size_t Pos,Depth; size_t RootLength = strRoot.IsEmpty()?0:strRoot.GetLength()-1; for (I=1; IstrName.RPos(Pos,L'\\')) PathLength=(int)Pos+1; else PathLength=0; Depth=ListData[I]->Depth=CountSlash((const wchar_t*)ListData[I]->strName+RootLength); if (!Depth) return false; for (J=I+1,SubDirPos=I,Last=1; JstrName+RootLength)>Depth) { SubDirPos=J; continue; } else { if (StrCmpNI(ListData[I]->strName,ListData[J]->strName,PathLength)==0) Last=0; break; } } for (J=I; J<=SubDirPos; J++) { if (Depth>ListData[J]->LastCount) { ListData[J]->LastCount<<=1; ListData[J]->Last=static_cast(xf_realloc(ListData[J]->Last,ListData[J]->LastCount*sizeof(int))); } ListData[J]->Last[Depth-1]=Last; } } return true; } UINT TreeList::CountSlash(const wchar_t *Str) { UINT Count=0; for (; *Str; Str++) if (IsSlash(*Str)) Count++; return(Count); } __int64 TreeList::VMProcess(int OpCode,void *vParam,__int64 iParam) { switch (OpCode) { case MCODE_C_EMPTY: return TreeCount<=0; case MCODE_C_EOF: return CurFile==TreeCount-1; case MCODE_C_BOF: return CurFile==0; case MCODE_C_SELECTED: return 0; case MCODE_V_ITEMCOUNT: return TreeCount; case MCODE_V_CURPOS: return CurFile+1; } return 0; } int TreeList::ProcessKey(int Key) { if (!IsVisible()) return(FALSE); if (TreeCount==0 && Key!=KEY_CTRLR) return(FALSE); string strTemp; if (SaveFolderShortcut(Key,&strCurDir,&strTemp,&strTemp,&strTemp)) return(TRUE); if (ProcessShortcutFolder(Key,TRUE)) return(TRUE); switch (Key) { case KEY_F1: { Help Hlp(L"TreePanel"); return TRUE; } case KEY_SHIFTNUMENTER: case KEY_CTRLNUMENTER: case KEY_SHIFTENTER: case KEY_CTRLENTER: case KEY_CTRLF: case KEY_CTRLALTINS: case KEY_CTRLALTNUMPAD0: { string strQuotedName=ListData[CurFile]->strName; QuoteSpace(strQuotedName); if (Key==KEY_CTRLALTINS||Key==KEY_CTRLALTNUMPAD0) { CopyToClipboard(strQuotedName); } else { if (Key == KEY_SHIFTENTER||Key == KEY_SHIFTNUMENTER) { Execute(strQuotedName,FALSE,TRUE,TRUE); } else { strQuotedName+=L" "; CtrlObject->CmdLine->InsertString(strQuotedName); } } return TRUE; } case KEY_CTRLBACKSLASH: { CurFile=0; ProcessEnter(); return(TRUE); } case KEY_NUMENTER: case KEY_ENTER: { if (!ModalMode && CtrlObject->CmdLine->GetLength()>0) break; ProcessEnter(); return(TRUE); } case KEY_F4: case KEY_CTRLA: { if (SetCurPath()) ShellSetFileAttributes(this); return(TRUE); } case KEY_CTRLR: { ReadTree(); if (TreeCount>0) SyncDir(); Redraw(); break; } case KEY_SHIFTF5: case KEY_SHIFTF6: { if (SetCurPath()) { int ToPlugin=0; ShellCopy ShCopy(this,Key==KEY_SHIFTF6,FALSE,TRUE,TRUE,ToPlugin,NULL); } return(TRUE); } case KEY_F5: case KEY_DRAGCOPY: case KEY_F6: case KEY_ALTF6: case KEY_DRAGMOVE: { if (SetCurPath() && TreeCount>0) { Panel *AnotherPanel=CtrlObject->Cp()->GetAnotherPanel(this); int Ask=((Key!=KEY_DRAGCOPY && Key!=KEY_DRAGMOVE) || Opt.Confirm.Drag); int Move=(Key==KEY_F6 || Key==KEY_DRAGMOVE); int ToPlugin=AnotherPanel->GetMode()==PLUGIN_PANEL && AnotherPanel->IsVisible() && !CtrlObject->Plugins.UseFarCommand(AnotherPanel->GetPluginHandle(),PLUGIN_FARPUTFILES); int Link=(Key==KEY_ALTF6 && !ToPlugin); if (Key==KEY_ALTF6 && !Link) // молча отвалим :-) return TRUE; { ShellCopy ShCopy(this,Move,Link,FALSE,Ask,ToPlugin,NULL); } if (ToPlugin==1) { PluginPanelItem *ItemList=new PluginPanelItem[1]; int ItemNumber=1; HANDLE hAnotherPlugin=AnotherPanel->GetPluginHandle(); FileList::FileNameToPluginItem(ListData[CurFile]->strName,ItemList); int PutCode=CtrlObject->Plugins.PutFiles(hAnotherPlugin,ItemList,ItemNumber,Move,0); if (PutCode==1 || PutCode==2) AnotherPanel->SetPluginModified(); if (ItemList) xf_free(ItemList); if (Move) ReadSubTree(ListData[CurFile]->strName); Update(0); Redraw(); AnotherPanel->Update(UPDATE_KEEP_SELECTION); AnotherPanel->Redraw(); } } return(TRUE); } case KEY_F7: { if (SetCurPath()) ShellMakeDir(this); return(TRUE); } /* Удаление Shift-Del, Shift-F8, F8 Удаление файлов и папок. F8 и Shift-Del удаляют все выбранные файлы, Shift-F8 - только файл под курсором. Shift-Del всегда удаляет файлы, не используя Корзину (Recycle Bin). Использование Корзины командами F8 и Shift-F8 зависит от конфигурации. Уничтожение файлов и папок Alt-Del */ case KEY_F8: case KEY_SHIFTDEL: case KEY_SHIFTNUMDEL: case KEY_SHIFTDECIMAL: case KEY_ALTNUMDEL: case KEY_ALTDECIMAL: case KEY_ALTDEL: { if (SetCurPath()) { int SaveOpt=Opt.DeleteToRecycleBin; if (Key==KEY_SHIFTDEL||Key==KEY_SHIFTNUMDEL||Key==KEY_SHIFTDECIMAL) Opt.DeleteToRecycleBin=0; ShellDelete(this,Key==KEY_ALTDEL||Key==KEY_ALTNUMDEL||Key==KEY_ALTDECIMAL); // Надобно не забыть обновить противоположную панель... Panel *AnotherPanel=CtrlObject->Cp()->GetAnotherPanel(this); AnotherPanel->Update(UPDATE_KEEP_SELECTION); AnotherPanel->Redraw(); Opt.DeleteToRecycleBin=SaveOpt; if (Opt.Tree.AutoChangeFolder && !ModalMode) ProcessKey(KEY_ENTER); } return(TRUE); } case KEY_MSWHEEL_UP: case(KEY_MSWHEEL_UP | KEY_ALT): { Scroll(Key & KEY_ALT?-1:-Opt.MsWheelDelta); return(TRUE); } case KEY_MSWHEEL_DOWN: case(KEY_MSWHEEL_DOWN | KEY_ALT): { Scroll(Key & KEY_ALT?1:Opt.MsWheelDelta); return(TRUE); } case KEY_MSWHEEL_LEFT: case(KEY_MSWHEEL_LEFT | KEY_ALT): { int Roll = Key & KEY_ALT?1:Opt.MsHWheelDelta; for (int i=0; i=KEY_ALT_BASE+0x01 && Key<=KEY_ALT_BASE+255) || (Key>=KEY_ALTSHIFT_BASE+0x01 && Key<=KEY_ALTSHIFT_BASE+255)) { FastFind(Key); if (Opt.Tree.AutoChangeFolder && !ModalMode) ProcessKey(KEY_ENTER); } else break; return(TRUE); } return(FALSE); } int TreeList::GetNextNavPos() { int NextPos=CurFile; if (CurFile+1 < TreeCount) { int CurDepth=ListData[CurFile]->Depth; for (int I=CurFile+1; I < TreeCount; ++I) if (ListData[I]->Depth == CurDepth) { NextPos=I; break; } } return NextPos; } int TreeList::GetPrevNavPos() { int PrevPos=CurFile; if (CurFile-1 > 0) { int CurDepth=ListData[CurFile]->Depth; for (int I=CurFile-1; I > 0; --I) if (ListData[I]->Depth == CurDepth) { PrevPos=I; break; } } return PrevPos; } void TreeList::Up(int Count) { CurFile-=Count; DisplayTree(TRUE); } void TreeList::Down(int Count) { CurFile+=Count; DisplayTree(TRUE); } void TreeList::Scroll(int Count) { CurFile+=Count; CurTopFile+=Count; DisplayTree(TRUE); } void TreeList::CorrectPosition() { if (TreeCount==0) { CurFile=CurTopFile=0; return; } int Height=Y2-Y1-3-(ModalMode!=0); if (CurTopFile+Height>TreeCount) CurTopFile=TreeCount-Height; if (CurFile<0) CurFile=0; if (CurFile > TreeCount-1) CurFile=TreeCount-1; if (CurTopFile<0) CurTopFile=0; if (CurTopFile > TreeCount-1) CurTopFile=TreeCount-1; if (CurFileCurTopFile+Height-1) CurTopFile=CurFile-(Height-1); } BOOL TreeList::SetCurDir(const wchar_t *NewDir,int ClosePlugin) { if (TreeCount==0) Update(0); if (TreeCount>0 && !SetDirPosition(NewDir)) { Update(0); SetDirPosition(NewDir); } if (GetFocus()) { CtrlObject->CmdLine->SetCurDir(NewDir); CtrlObject->CmdLine->Show(); } return TRUE; //??? } int TreeList::SetDirPosition(const wchar_t *NewDir) { long I; for (I=0; IstrName)==0) { WorkDir=CurFile=I; CurTopFile=CurFile-(Y2-Y1-1)/2; CorrectPosition(); return(TRUE); } } return(FALSE); } int TreeList::GetCurDir(string &strCurDir) { if (TreeCount==0) { if (ModalMode==MODALTREE_FREE) strCurDir = strRoot; else strCurDir.Clear(); } else strCurDir = ListData[CurFile]->strName; //BUGBUG return (int)strCurDir.GetLength(); } int TreeList::ProcessMouse(MOUSE_EVENT_RECORD *MouseEvent) { int OldFile=CurFile; int RetCode; if (Opt.ShowPanelScrollbar && MouseX==X2 && (MouseEvent->dwButtonState & 1) && !IsDragging()) { int ScrollY=Y1+1; int Height=Y2-Y1-3; if (MouseY==ScrollY) { while (IsMouseButtonPressed()) ProcessKey(KEY_UP); if (!ModalMode) SetFocus(); return(TRUE); } if (MouseY==ScrollY+Height-1) { while (IsMouseButtonPressed()) ProcessKey(KEY_DOWN); if (!ModalMode) SetFocus(); return(TRUE); } if (MouseY>ScrollY && MouseY2) { CurFile=(TreeCount-1)*(MouseY-ScrollY)/(Height-2); DisplayTree(TRUE); if (!ModalMode) SetFocus(); return(TRUE); } } if (Panel::PanelProcessMouse(MouseEvent,RetCode)) return(RetCode); if (MouseEvent->dwMousePosition.Y>Y1 && MouseEvent->dwMousePosition.YdwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) && MouseEvent->dwEventFlags==DOUBLE_CLICK) || ((MouseEvent->dwButtonState & RIGHTMOST_BUTTON_PRESSED) && MouseEvent->dwEventFlags==0) || (OldFile!=CurFile && Opt.Tree.AutoChangeFolder && !ModalMode)) { ProcessEnter(); return(TRUE); } return(TRUE); } if (MouseEvent->dwMousePosition.Y<=Y1+1) { if (!ModalMode) SetFocus(); if (TreeCount==0) return(TRUE); while (IsMouseButtonPressed() && MouseY<=Y1+1) Up(1); if (Opt.Tree.AutoChangeFolder && !ModalMode) ProcessKey(KEY_ENTER); return(TRUE); } if (MouseEvent->dwMousePosition.Y>=Y2-2) { if (!ModalMode) SetFocus(); if (TreeCount==0) return(TRUE); while (IsMouseButtonPressed() && MouseY>=Y2-2) Down(1); if (Opt.Tree.AutoChangeFolder && !ModalMode) ProcessKey(KEY_ENTER); return(TRUE); } return(FALSE); } void TreeList::MoveToMouse(MOUSE_EVENT_RECORD *MouseEvent) { CurFile=CurTopFile+MouseEvent->dwMousePosition.Y-Y1-1; CorrectPosition(); } void TreeList::ProcessEnter() { TreeItem *CurPtr; DWORD Attr; CurPtr=ListData[CurFile]; if ((Attr=apiGetFileAttributes(CurPtr->strName))!=INVALID_FILE_ATTRIBUTES && (Attr & FILE_ATTRIBUTE_DIRECTORY)) { if (!ModalMode && FarChDir(CurPtr->strName)) { Panel *AnotherPanel=GetRootPanel(); SetCurDir(CurPtr->strName,TRUE); Show(); AnotherPanel->SetCurDir(CurPtr->strName,TRUE); AnotherPanel->Redraw(); } } else { DelTreeName(CurPtr->strName); Update(UPDATE_KEEP_SELECTION); Show(); } } int TreeList::ReadTreeFile() { FILE *TreeFile=NULL; size_t RootLength=strRoot.IsEmpty()?0:strRoot.GetLength()-1; string strName; //SaveState(); FlushCache(); MkTreeFileName(strRoot,strName); if (MustBeCached(strRoot) || (TreeFile=_wfopen(strName,L"rb"))==NULL) if (!GetCacheTreeName(strRoot,strName,FALSE) || (TreeFile=_wfopen(strName,L"rb"))==NULL) { //RestoreState(); return(FALSE); } if (ListData) { for (long i=0; i(NT_MAX_PATH-RootLength),TreeFile)!=NULL) { if (!IsSlash(*(DirName+RootLength)) || StrCmpI(DirName,strLastDirName)==0) continue; strLastDirName=DirName; wchar_t *ChPtr=wcschr(DirName,L'\n'); if (ChPtr) *ChPtr=0; if (RootLength>0 && DirName[RootLength-1]!=L':' && IsSlash(DirName[RootLength]) && DirName[RootLength+1]==0) DirName[RootLength]=0; if ((TreeCount & 255)==0) { TreeItem **TmpListData=(TreeItem **)xf_realloc(ListData,(TreeCount+256+1)*sizeof(TreeItem*)); if (!TmpListData) { if (ListData) { for (long i=0; iClear(); ListData[TreeCount]->strName = DirName; TreeCount++; } delete[] DirName; } fclose(TreeFile); if (TreeCount==0) return(FALSE); CaseSensitiveSort=FALSE; NumericSort=FALSE; far_qsort(TreeCache.ListName,TreeCache.TreeCount,sizeof(wchar_t*),SortCacheList); return FillLastData(); } int TreeList::FindPartName(const wchar_t *Name,int Next,int Direct,int ExcludeSets) { string strMask; strMask = Name; strMask += L"*"; if (ExcludeSets) { ReplaceStrings(strMask,L"[",L"<[%>",-1,1); ReplaceStrings(strMask,L"]",L"[]]",-1,1); ReplaceStrings(strMask,L"<[%>",L"[[]",-1,1); } for (int i=CurFile+(Next?Direct:0); i >= 0 && i < TreeCount; i+=Direct) { if (CmpName(strMask,ListData[i]->strName,true,(i==CurFile))) { CurFile=i; CurTopFile=CurFile-(Y2-Y1-1)/2; DisplayTree(TRUE); return(TRUE); } } for (int i=(Direct > 0)?0:TreeCount-1; (Direct > 0) ? i < CurFile:i > CurFile; i+=Direct) { if (CmpName(strMask,ListData[i]->strName,true)) { CurFile=i; CurTopFile=CurFile-(Y2-Y1-1)/2; DisplayTree(TRUE); return(TRUE); } } return(FALSE); } int TreeList::GetSelCount() { return(1); } int TreeList::GetSelName(string *strName,DWORD &FileAttr,string *strShortName,FAR_FIND_DATA_EX *fd) { if (strName==NULL) { GetSelPosition=0; return(TRUE); } if (GetSelPosition==0) { GetCurDir(*strName); if (strShortName != NULL) *strShortName = *strName; FileAttr=FILE_ATTRIBUTE_DIRECTORY; GetSelPosition++; return(TRUE); } GetSelPosition=0; return(FALSE); } int TreeList::GetCurName(string &strName, string &strShortName) { if (TreeCount==0) { strName.Clear(); strShortName.Clear(); return(FALSE); } strName = ListData[CurFile]->strName; strShortName = strName; return(TRUE); } void TreeList::AddTreeName(const wchar_t *Name) { if (*Name == 0) return; string strFullName; ConvertNameToFull(Name, strFullName); string strRoot = ExtractPathRoot(strFullName); Name = strFullName; Name += strRoot.GetLength() - 1; if (!LastSlash(Name)) return; ReadCache(strRoot); for (long CachePos = 0; CachePos < TreeCache.TreeCount; CachePos++) { int Result = StrCmpI(TreeCache.ListName[CachePos], Name); if (Result == 0) break; if (Result > 0) { TreeCache.Insert(CachePos, Name); break; } } } void TreeList::DelTreeName(const wchar_t *Name) { if (*Name==0) return; string strFullName; ConvertNameToFull(Name, strFullName); string strRoot = ExtractPathRoot(strFullName); Name = strFullName; Name += strRoot.GetLength() - 1; ReadCache(strRoot); for (long CachePos = 0; CachePos < TreeCache.TreeCount; CachePos++) { const wchar_t* wszDirName = TreeCache.ListName[CachePos]; int Length = StrLength(Name); int DirLength = StrLength(wszDirName); if (DirLength < Length) continue; if (StrCmpNI(Name, wszDirName, Length) == 0 && (wszDirName[Length] == 0 || IsSlash(wszDirName[Length]))) { TreeCache.Delete(CachePos); CachePos--; } } } void TreeList::RenTreeName(const wchar_t *SrcName,const wchar_t *DestName) { if (*SrcName==0 || *DestName==0) return; string SrcNameFull, DestNameFull; ConvertNameToFull(SrcName, SrcNameFull); ConvertNameToFull(DestName, DestNameFull); string strSrcRoot = ExtractPathRoot(SrcNameFull); string strDestRoot = ExtractPathRoot(DestNameFull); if (StrCmpI(strSrcRoot, strDestRoot) != 0) { DelTreeName(SrcName); ReadSubTree(SrcName); } SrcName += strSrcRoot.GetLength() - 1; DestName += strDestRoot.GetLength() - 1; ReadCache(strSrcRoot); int SrcLength = StrLength(SrcName); for (int CachePos = 0; CachePos < TreeCache.TreeCount; CachePos++) { const wchar_t* DirName = TreeCache.ListName[CachePos]; if (StrCmpNI(SrcName,DirName,SrcLength) == 0 && (DirName[SrcLength] == 0 || IsSlash(DirName[SrcLength]))) { string strNewName = DestName; strNewName += DirName + SrcLength; if (TreeCache.ListName[CachePos]) xf_free(TreeCache.ListName[CachePos]); TreeCache.ListName[CachePos] = xf_wcsdup(strNewName); } } } void TreeList::ReadSubTree(const wchar_t *Path) { ChangePriority ChPriority(THREAD_PRIORITY_NORMAL); //SaveScreen SaveScr; TPreRedrawFuncGuard preRedrawFuncGuard(TreeList::PR_MsgReadTree); ScanTree ScTree(FALSE); FAR_FIND_DATA_EX fdata; string strDirName; string strFullName; int Count=0; DWORD FileAttr; if ((FileAttr=apiGetFileAttributes(Path))==INVALID_FILE_ATTRIBUTES || (FileAttr & FILE_ATTRIBUTE_DIRECTORY)==0) return; ConvertNameToFull(Path, strDirName); AddTreeName(strDirName); int FirstCall=TRUE, AscAbort=FALSE; ScTree.SetFindPath(strDirName,L"*",0); LastScrX = ScrX; LastScrY = ScrY; while (ScTree.GetNextName(&fdata, strFullName)) { if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { TreeList::MsgReadTree(Count+1,FirstCall); if (CheckForEscSilent()) { AscAbort=ConfirmAbortOp()!=0; FirstCall=TRUE; } if (AscAbort) break; AddTreeName(strFullName); ++Count; } } } void TreeList::ClearCache(int EnableFreeMem) { TreeCache.Clean(); } void TreeList::ReadCache(const wchar_t *TreeRoot) { string strTreeName; FILE *TreeFile=NULL; if (StrCmp(MkTreeFileName(TreeRoot,strTreeName),TreeCache.strTreeName)==0) return; if (TreeCache.TreeCount!=0) FlushCache(); if (MustBeCached(TreeRoot) || (TreeFile=_wfopen(strTreeName,L"rb"))==NULL) if (!GetCacheTreeName(TreeRoot,strTreeName,FALSE) || (TreeFile=_wfopen(strTreeName,L"rb"))==NULL) { ClearCache(1); return; } TreeCache.strTreeName = strTreeName; wchar_t *DirName=new wchar_t[NT_MAX_PATH]; if (DirName) { while (fgetws(DirName,NT_MAX_PATH,TreeFile)!=NULL) { if (!IsSlash(*DirName)) continue; wchar_t *ChPtr=wcschr(DirName,L'\n'); if (ChPtr) *ChPtr=0; TreeCache.Add(DirName); } delete[] DirName; } fclose(TreeFile); } void TreeList::FlushCache() { FILE *TreeFile; if (!TreeCache.strTreeName.IsEmpty()) { DWORD FileAttributes=apiGetFileAttributes(TreeCache.strTreeName); if (FileAttributes != INVALID_FILE_ATTRIBUTES) apiSetFileAttributes(TreeCache.strTreeName,FILE_ATTRIBUTE_NORMAL); if ((TreeFile=_wfopen(TreeCache.strTreeName,L"wb"))==NULL) { ClearCache(1); return; } far_qsort(TreeCache.ListName,TreeCache.TreeCount,sizeof(wchar_t*),SortCacheList); for (int i=0; iGetType()==QVIEW_PANEL && SetCurPath()) ((QuickView *)AnotherPanel)->ShowFile(strCurName,FALSE,NULL); } } int TreeList::GoToFile(long idxItem) { if ((DWORD)idxItem < (DWORD)TreeCount) { CurFile=idxItem; CorrectPosition(); return TRUE; } return FALSE; } int TreeList::GoToFile(const wchar_t *Name,BOOL OnlyPartName) { return GoToFile(FindFile(Name,OnlyPartName)); } long TreeList::FindFile(const wchar_t *Name,BOOL OnlyPartName) { for (long I=0; IstrName):ListData[I]->strName.CPtr(); if (StrCmp(Name,CurPtrName)==0) return I; if (StrCmpI(Name,CurPtrName)==0) return I; } return -1; } long TreeList::FindFirst(const wchar_t *Name) { return FindNext(0,Name); } long TreeList::FindNext(int StartPos, const wchar_t *Name) { if ((DWORD)StartPos < (DWORD)TreeCount) { for (int I=StartPos; I < TreeCount; I++) { if (CmpName(Name,ListData[I]->strName,true)) if (!TestParentFolderName(ListData[I]->strName)) return I; } } return -1; } int TreeList::GetFileName(string &strName,int Pos,DWORD &FileAttr) { if (Pos < 0 || Pos >= TreeCount) return FALSE; strName = ListData[Pos]->strName; FileAttr=FILE_ATTRIBUTE_DIRECTORY|apiGetFileAttributes(ListData[Pos]->strName); return TRUE; } int _cdecl SortList(const void *el1,const void *el2) { return TreeCmp(((TreeItem **)el1)[0]->strName, ((TreeItem **)el2)[0]->strName, StaticSortNumeric, StaticSortCaseSensitive); } int _cdecl SortCacheList(const void *el1,const void *el2) { return TreeCmp(*(wchar_t **)el1, *(wchar_t **)el2, StaticSortNumeric, 0); } int TreeCmp(const wchar_t *Str1, const wchar_t *Str2, int Numeric, int CaseSensitive) { typedef int (__cdecl *CMPFUNC)(const wchar_t *, int, const wchar_t *, int); static CMPFUNC funcs[2][2] = { {StrCmpNN, StrCmpNNI}, {NumStrCmpN, NumStrCmpNI} }; CMPFUNC cmpfunc = funcs[Numeric?1:0][CaseSensitive?0:1]; if (*Str1 == L'\\' && *Str1 == *Str2) { Str1++; Str2++; } const wchar_t *s1 = wcschr(Str1,L'\\'); const wchar_t *s2 = wcschr(Str2,L'\\'); while (s1 && s2) { int r = cmpfunc(Str1,static_cast(s1-Str1),Str2,static_cast(s2-Str2)); if (r) return r; Str1 = s1 + 1; Str2 = s2 + 1; s1 = wcschr(Str1,L'\\'); s2 = wcschr(Str2,L'\\'); } if (s1 || s2) { int r = cmpfunc(Str1,s1?static_cast(s1-Str1):-1,Str2,s2?static_cast(s2-Str2):-1); if (r) return r; return s1 ? 1 : -1; } return cmpfunc(Str1, -1, Str2,-1); } /* $ 16.10.2000 tran функция, определяющаяя необходимость кеширования файла */ int TreeList::MustBeCached(const wchar_t *Root) { UINT type; type=FAR_GetDriveType(Root); if (type==DRIVE_UNKNOWN || type==DRIVE_NO_ROOT_DIR || type==DRIVE_REMOVABLE || IsDriveTypeCDROM(type) ) { if (type==DRIVE_REMOVABLE) { if (Upper(Root[0])==L'A' || Upper(Root[0])==L'B') return FALSE; // это дискеты } return TRUE; // кешируются CD, removable и неизвестно что :) } /* остались DRIVE_REMOTE DRIVE_RAMDISK DRIVE_FIXED */ return FALSE; } void TreeList::SetFocus() { Panel::SetFocus(); SetTitle(); SetMacroMode(FALSE); } void TreeList::KillFocus() { if (CurFilestrName)==INVALID_FILE_ATTRIBUTES) { DelTreeName(ListData[CurFile]->strName); Update(UPDATE_KEEP_SELECTION); } } Panel::KillFocus(); SetMacroMode(TRUE); } void TreeList::SetMacroMode(int Restore) { if (CtrlObject == NULL) return; if (PrevMacroMode == -1) PrevMacroMode = CtrlObject->Macro.GetMode(); CtrlObject->Macro.SetMode(Restore ? PrevMacroMode:MACRO_TREEPANEL); } BOOL TreeList::UpdateKeyBar() { KeyBar *KB = CtrlObject->MainKeyBar; KB->SetAllGroup(KBL_MAIN, MKBTreeF1, 12); KB->SetAllGroup(KBL_SHIFT, MKBTreeShiftF1, 12); KB->SetAllGroup(KBL_ALT, MKBTreeAltF1, 12); KB->SetAllGroup(KBL_CTRL, MKBTreeCtrlF1, 12); KB->SetAllGroup(KBL_CTRLSHIFT, MKBTreeCtrlShiftF1, 12); KB->SetAllGroup(KBL_CTRLALT, MKBTreeCtrlAltF1, 12); KB->SetAllGroup(KBL_ALTSHIFT, MKBTreeAltShiftF1, 12); KB->SetAllGroup(KBL_CTRLALTSHIFT, MKBTreeCtrlAltShiftF1, 12); DynamicUpdateKeyBar(); return TRUE; } void TreeList::DynamicUpdateKeyBar() { KeyBar *KB = CtrlObject->MainKeyBar; KB->ReadRegGroup(L"Tree",Opt.strLanguage); KB->SetAllRegGroup(); } void TreeList::SetTitle() { if (GetFocus()) { string strTitleDir(L"{"); const wchar_t *Ptr=ListData?ListData[CurFile]->strName:L""; if (*Ptr) { strTitleDir += Ptr; strTitleDir += L" - "; } strTitleDir += L"Tree}"; ConsoleTitle::SetFarTitle(strTitleDir); } } /* "Local AppData" = [HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders]/Local AppData "AppData" = [HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders]/AppData */ // TODO: Файлы "Tree.Far" для локальных дисков должны храниться в "Local AppData\Far" // TODO: Файлы "Tree.Far" для сетевых дисков должны храниться в "%HOMEDRIVE%\%HOMEPATH%", // если эти переменные среды не определены, то "%APPDATA%\Far" // хpаним "X.tree" (где 'X' - буква диска, если не сетевой путь) // хpаним "server.share.tree" - для сетевого диска без буквы string &TreeList::MkTreeFileName(const wchar_t *RootDir,string &strDest) { strDest = RootDir; AddEndSlash(strDest); strDest += L"tree2.far"; return strDest; } // TODO: этому каталогу (Tree.Cache) место не в FarPath, а в "Local AppData\Far\" string &TreeList::MkTreeCacheFolderName(const wchar_t *RootDir,string &strDest) { strDest = RootDir; AddEndSlash(strDest); strDest += L"tree2.cache"; return strDest; } /* Opt.Tree.LocalDisk Opt.Tree.NetDisk Opt.Tree.NetPath Opt.Tree.RemovableDisk Opt.Tree.CDROM Opt.Tree.SavedTreePath локальных дисков - "X.nnnnnnnn.tree" сетевых дисков - "X.nnnnnnnn.tree" сетевых путей - "Server.share.tree" сменных дисков(DRIVE_REMOVABLE) - "Far.nnnnnnnn.tree" сменных дисков(CD) - "Label.nnnnnnnn.tree" */ string &TreeList::CreateTreeFileName(const wchar_t *Path,string &strDest) { #if 0 char RootPath[NM]; RootPath = ExtractPathRoot(Path); UINT DriveType = FAR_GetDriveType(RootPath,NULL,FALSE); // получение инфы о томе char VolumeName[NM],FileSystemName[NM]; DWORD MaxNameLength,FileSystemFlags,VolumeNumber; if (!GetVolumeInformation(RootDir,VolumeName,sizeof(VolumeName),&VolumeNumber, &MaxNameLength,&FileSystemFlags, FileSystemName,sizeof(FileSystemName))) Opt.Tree.SavedTreePath #endif return strDest; } BOOL TreeList::GetItem(int Index,void *Dest) { if (Index == -1 || Index == -2) Index=GetCurrentPos(); if (Index >= (int)TreeCount) return FALSE; *((TreeItem *)Dest) = *ListData[Index]; return TRUE; } int TreeList::GetCurrentPos() { return CurFile; } bool TreeList::SaveState() { if (SaveListData) delete [] SaveListData; SaveListData=NULL; SaveTreeCount=SaveWorkDir=0; if (TreeCount > 0) { SaveListData= new TreeItem[TreeCount]; if (SaveListData) { for (int i=0; i 0 && (ListData=(TreeItem **)xf_realloc_nomove(ListData,SaveTreeCount*sizeof(TreeItem*))) != NULL) { for (int i=0; i