{
   Double Commander
   -------------------------------------------------------------------------
   This unit contains DC actions of the main form

   Copyright (C) 2008  Dmitry Kolomiets (B4rr4cuda@rambler.ru)
   Copyright (C) 2008-2023 Alexander Koblov (alexx2000@mail.ru)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see <http://www.gnu.org/licenses/>.
}

unit uMainCommands;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, ActnList, uFileView, uFileViewNotebook, uFileSourceOperation,
  uGlobs, uFileFunctions, uFormCommands, uFileSorting, uShellContextMenu, Menus, ufavoritetabs,ufile;

type

  TCopyFileNamesToClipboard = (cfntcPathAndFileNames, cfntcJustFileNames, cfntcJustPathWithSeparator, cfntcPathWithoutSeparator);

  { TProcedureDoingActionOnMultipleTabs }
  TProcedureDoingActionOnMultipleTabs = procedure(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer) of object;

  { TMainCommands }

  TMainCommands = class(TComponent{$IF FPC_FULLVERSION >= 020501}, IFormCommands{$ENDIF})
  private
   FCommands: TFormCommands;
   FOriginalNumberOfTabs: integer;
   FTabsMenu: TPopupMenu;

   // Helper routines
   procedure TryGetParentDir(FileView: TFileView; var SelectedFiles: TFiles);

   // Filters out commands.
   function CommandsFilter(Command: String): Boolean;
   procedure OnCopyOutStateChanged(Operation: TFileSourceOperation;
                                   State: TFileSourceOperationState);
   procedure OnEditCopyOutStateChanged(Operation: TFileSourceOperation;
                                       State: TFileSourceOperationState);
   procedure OnCalcStatisticsStateChanged(Operation: TFileSourceOperation;
                                          State: TFileSourceOperationState);
   procedure OnCalcChecksumStateChanged(Operation: TFileSourceOperation;
                                        State: TFileSourceOperationState);

  public
   constructor Create(TheOwner: TComponent; ActionList: TActionList = nil); reintroduce;

   property Commands: TFormCommands read FCommands{$IF FPC_FULLVERSION >= 020501} implements IFormCommands{$ENDIF};

   //---------------------
   // The Do... functions are cm_... functions' counterparts which are to be
   // executed directly from the code with specific - especially non-string
   // - arguments (instead of calling cm_... functions, in which case
   // parameters would have to be converted to and from strings).
   //
   procedure DoOpenVirtualFileSystemList(Panel: TFileView);
   procedure DoPanelsSplitterPerPos(SplitPos: Integer);
   procedure DoUpdateFileView(AFileView: TFileView; {%H-}UserData: Pointer);
   procedure DoCloseTab(Notebook: TFileViewNotebook; PageIndex: Integer);
   procedure DoCopySelectedFileNamesToClipboard(FileView: TFileView; TypeOfCopy: TCopyFileNamesToClipboard; const Params: array of string);
   procedure DoNewTab(Notebook: TFileViewNotebook);
   procedure DoRenameTab(Page: TFileViewPage);
   procedure DoTabMenuClick(Sender: TObject);
   procedure DoContextMenu(Panel: TFileView; X, Y: Integer; Background: Boolean; UserWishForContextMenu:TUserWishForContextMenu = uwcmComplete);
   procedure DoTransferPath(SourceFrame: TFileView; TargetNotebook: TFileViewNotebook); overload;
   procedure DoTransferPath(SourcePage: TFileViewPage; TargetPage: TFileViewPage; FromActivePanel: Boolean);
   procedure DoSortByFunctions(View: TFileView; FileFunctions: TFileFunctions);
   procedure DoShowMainMenu(bShow: Boolean);
   procedure DoShowCmdLineHistory(bNextCmdLine: Boolean);
   procedure DoChangeDirToRoot(FileView: TFileView);
   procedure GetAndSetMultitabsActionFromParams(const Params: array of string; var APanelSide:TTabsConfigLocation; var ActionOnLocked:boolean; var AskForLocked:integer);
   procedure DoActionOnMultipleTabs(const Params: array of string; ProcedureDoingActionOnMultipleTabs: TProcedureDoingActionOnMultipleTabs);
   procedure DoCloseAllTabs(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
   procedure DoCloseDuplicateTabs(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
   procedure DoSetAllTabsOptionNormal(ANotebook: TFileViewNotebook; var {%H-}bAbort: boolean; {%H-}bDoLocked: boolean; var {%H-}iAskForLocked: integer);
   procedure DoSetAllTabsOptionPathLocked(ANotebook: TFileViewNotebook; var {%H-}bAbort: boolean; {%H-}bDoLocked: boolean; var {%H-}iAskForLocked: integer);
   procedure DoAllTabsOptionPathResets(ANotebook: TFileViewNotebook; var {%H-}bAbort: boolean; {%H-}bDoLocked: boolean; var {%H-}iAskForLocked: integer);
   procedure DoSetAllTabsOptionDirsInNewTab(ANotebook: TFileViewNotebook; var {%H-}bAbort: boolean; {%H-}bDoLocked: boolean; var {%H-}iAskForLocked: integer);
   procedure DoOnClickMenuJobFavoriteTabs(Sender: TObject);
   procedure DoCopyAllTabsToOppositeSide(ANotebook: TFileViewNotebook; var {%H-}bAbort: boolean; {%H-}bDoLocked: boolean; var {%H-}iAskForLocked: integer);
   procedure DoShowFavoriteTabsOptions;
   procedure DoParseParametersForPossibleTreeViewMenu(const Params: array of string; gDefaultConfigWithCommand, gDefaultConfigWithDoubleClick:boolean; var bUseTreeViewMenu:boolean; var bUsePanel:boolean; var p: TPoint);
   procedure DoComputeSizeAndPosForWindowInMiddle(var iPosX:integer; var iPosY:integer; var iWidth:integer; var iHeight:integer);
   procedure DoActualMarkApplyOnAll(const maoaDispatcher: TMarkApplyOnAllDispatcher; const Params: array of string);
   procedure DoActualMarkUnMark(const Params: array of string; bSelect: boolean);
   procedure DoActualAddToCommandLine(const Params: array of string; sAddedString:string; bAddSpaceAtTheEnd:boolean);

   //---------------------

 published
   //--------------------------------------------------------------------------
   // All commands can be split into three groups:
   // 1. Commands intended for the application (cm_VisitHomePage,
   //    cm_About, cm_Exit, ...).
   //
   // 2. Commands intended for file views (cm_QuickSearch, cm_EditPath, etc.).
   //    Those commands are simply redirected to the currently active file view by calling:
   //       frmMain.ActiveFrame.ExecuteCommand(CommandName, Param);
   //    If they are supported by the given file view they are executed there.
   //
   //    If in future there will be a need to pass specific parameters to the
   //    commands, i.e. not string, they should be implemented by creating
   //    an interface for each command, and each file view implementing those
   //    interfaces which commands it supports.
   //
   // 3. Commands intended for file sources (cm_Copy, cm_Rename, cm_MakeDir).
   //    The file operations will mostly work only for non-virtual file sources.
   //
   //--------------------------------------------------------------------------
   // RECIPE TO ADD A "cm_" COMMAND:
   //--------------------------------------------------------------------------
   // In this recipe, we use as an exemple the command "cm_SrcOpenDrives"
   // 1.  In "fMain" we add the action in the "actionLst".
   // 2.  Make sure we add it in the appropriate category.
   // 3.  The action name must start with "act" and have the exact same name as the "cm_" we want to add.
   // 4.  So if we want "cm_SrcOpenDrives", we name the action "actSrcOpenDrives".
   // 5.  By the way, "KEEP THE SAME SPELLING EVERYWHERE!".
   // 6.  The order in which the "cm_SrcOpenDrives" will appear, is the same as its position in the "actionLst".
   // 7.  So command is "cm_SrcOpenDrives", so keep writing "cm_SrcOpenDrives" and not "cm_srcopendrives" for example.
   // 8.  The only single place to have lowercases is for the icon name which will be "cm_srcopendrives" but it's the only one case.
   // 9.  Give an appropriate "caption" name for the command, so for our example "Open drive list"
   // 10. Set the "Tag" to the same number as the other command of the same category.
   // 11. In the "uMainCommands", for the type "TMainCommands", add the code for the command.
   // 12. The command name must start with "cm_" and ends with the same name as what we added for the "act".
   // 13. So with our example we add "cm_SrcOpenDrives".
   // 14. Create an icon for the command.
   // 15. Make a 24-bits with alpha .PNG file.
   // 16. Name the file with he same name of the "cm_" command.
   // 17. But write the name all in lower case so here "cm_srcopendrives".
   // 18. Store the file here: to path "pixmaps\dctheme\32x32\actions\".
   // 19. If command is a compatible on with TC, add it in unit "uTotalCommander".
   // 20. So with this example we add: "(TCCommand: 'cm_SrcOpenDrives'; TCIcon: -1; DCCommand: 'cm_SrcOpenDrives')".
   // 21. If command needs to have a shortcut, go in unit "uGlobs", go to routine "LoadDefaultHotkeyBindings"(more detailed - read instructions in head of "LoadDefaultHotkeyBindings") and add the appropriate "AddIfNotExists".
   // 22. Don't abuse on adding keyboard shortcut! We must let some user's keys for user!
   // 23. For this example, we won't add a keyboard shortcut. TC does'nt have neither.
   // 24. Edit the file "doc\en\cmds.html" to add help for the command.
   // 25. For the languages we know, translate the caption of the action added.
   // 26. For example in our example, it will be "tfrmmain.actsrcopendrives.caption" that will need to be change.
   // 27. It's important to * T E S T * the "cm_" command you add.
   // 28. Add a single button in the toolbar to test it works.
   // 29. Make sure we see the expected icon and the expected tooltip.
   // 30. Make sure the actual button you added do the expected task.
   // 31. If command is using parameters, make sure you test the most cases of parameters.
   // 32. If you added keyboard shortcut, make sure keyboard shortcut works.
   // 33. With the "cm_DoAnyCmCommand", go find in the "Internal Command Selector" the command you added.
   // 34. Make sure it's present there, under the appropriate category, sorted at the classic logical place.
   // 35. Make sure we see the shortcut if any and that the description is correct.
   // 36. Test the help for the command from there to make sure it links to the correct place in the help file.
   procedure cm_AddPathToCmdLine(const {%H-}Params: array of string);
   procedure cm_AddFilenameToCmdLine(const {%H-}Params: array of string);
   procedure cm_AddPathAndFilenameToCmdLine(const {%H-}Params: array of string);
   procedure cm_CmdLineNext(const {%H-}Params: array of string);
   procedure cm_CmdLinePrev(const {%H-}Params: array of string);
   procedure cm_ContextMenu(const Params: array of string);
   procedure cm_CopyFullNamesToClip(const {%H-}Params: array of string);
   procedure cm_CopyFileDetailsToClip(const {%H-}Params: array of string);
   procedure cm_SaveFileDetailsToFile(const {%H-}Params: array of string);
   procedure cm_Exchange(const {%H-}Params: array of string);
   procedure cm_FlatView(const {%H-}Params: array of string);
   procedure cm_FlatViewSel(const {%H-}Params: array of string);
   procedure cm_LeftFlatView(const {%H-}Params: array of string);
   procedure cm_RightFlatView(const {%H-}Params: array of string);
   procedure cm_OpenArchive(const {%H-}Params: array of string);
   procedure cm_TestArchive(const {%H-}Params: array of string);
   procedure cm_OpenDirInNewTab(const {%H-}Params: array of string);
   procedure cm_Open(const {%H-}Params: array of string);
   procedure cm_ShellExecute(const Params: array of string);
   procedure cm_OpenVirtualFileSystemList(const {%H-}Params: array of string);
   procedure cm_TargetEqualSource(const {%H-}Params: array of string);
   procedure cm_LeftEqualRight(const {%H-}Params: array of string);
   procedure cm_RightEqualLeft(const {%H-}Params: array of string);
   procedure cm_PackFiles(const Params: array of string);
   procedure cm_ExtractFiles(const Params: array of string);
   procedure cm_QuickSearch(const Params: array of string);
   procedure cm_QuickFilter(const Params: array of string);
   procedure cm_SrcOpenDrives(const {%H-}Params: array of string);
   procedure cm_LeftOpenDrives(const {%H-}Params: array of string);
   procedure cm_RightOpenDrives(const {%H-}Params: array of string);
   procedure cm_OpenBar(const {%H-}Params: array of string);
   procedure cm_ShowButtonMenu(const Params: array of string);
   procedure cm_TransferLeft(const {%H-}Params: array of string);
   procedure cm_TransferRight(const {%H-}Params: array of string);
   procedure cm_GoToFirstEntry(const {%H-}Params: array of string);
   procedure cm_GoToLastEntry(const {%H-}Params: array of string);
   procedure cm_GoToFirstFile(const {%H-}Params: array of string);
   procedure cm_GoToNextEntry(const {%H-}Params: array of string);
   procedure cm_GoToPrevEntry(const {%H-}Params: array of string);
   procedure cm_GoToLastFile(const {%H-}Params: array of string);
   procedure cm_Minimize(const {%H-}Params: array of string);
   procedure cm_Wipe(const {%H-}Params: array of string);
   procedure cm_Exit(const {%H-}Params: array of string);
   procedure cm_NewTab(const {%H-}Params: array of string);
   procedure cm_RenameTab(const {%H-}Params: array of string);
   procedure cm_CloseTab(const {%H-}Params: array of string);
   procedure cm_CloseAllTabs(const Params: array of string);
   procedure cm_CloseDuplicateTabs(const Params: array of string);
   procedure cm_NextTab(const Params: array of string);
   procedure cm_PrevTab(const Params: array of string);
   procedure cm_MoveTabLeft(const Params: array of string);
   procedure cm_MoveTabRight(const Params: array of string);
   procedure cm_ShowTabsList(const Params: array of string);
   procedure cm_ActivateTabByIndex(const Params: array of string);
   procedure cm_SaveTabs(const Params: array of string);
   procedure cm_LoadTabs(const Params: array of string);
   procedure cm_SetTabOptionNormal(const Params: array of string);
   procedure cm_SetTabOptionPathLocked(const Params: array of string);
   procedure cm_SetTabOptionPathResets(const Params: array of string);
   procedure cm_SetTabOptionDirsInNewTab(const Params: array of string);
   procedure cm_Copy(const Params: array of string);
   procedure cm_CopyNoAsk(const Params: array of string);
   procedure cm_Delete(const Params: array of string);
   procedure cm_CheckSumCalc(const Params: array of string);
   procedure cm_CheckSumVerify(const Params: array of string);
   procedure cm_Edit(const Params: array of string);
   procedure cm_EditPath(const Params: array of string);
   procedure cm_MakeDir(const Params: array of string);
   procedure cm_Rename(const Params: array of string);
   procedure cm_RenameNoAsk(const Params: array of string);
   procedure cm_View(const Params: array of string);
   procedure cm_QuickView(const Params: array of string);
   procedure cm_BriefView(const Params: array of string);
   procedure cm_LeftBriefView(const Params: array of string);
   procedure cm_RightBriefView(const Params: array of string);
   procedure cm_ColumnsView(const Params: array of string);
   procedure cm_LeftColumnsView(const Params: array of string);
   procedure cm_RightColumnsView(const Params: array of string);
   procedure cm_ThumbnailsView(const Params: array of string);
   procedure cm_LeftThumbView(const Params: array of string);
   procedure cm_RightThumbView(const Params: array of string);
   procedure cm_TreeView(const Params: array of string);
   procedure cm_CopyNamesToClip(const {%H-}Params: array of string);
   procedure cm_FocusTreeView(const {%H-}Params: array of string);
   procedure cm_FocusCmdLine(const {%H-}Params: array of string);
   procedure cm_FileAssoc(const {%H-}Params: array of string);
   procedure cm_HelpIndex(const {%H-}Params: array of string);
   procedure cm_Keyboard(const {%H-}Params: array of string);
   procedure cm_VisitHomePage(const {%H-}Params: array of string);
   procedure cm_About(const {%H-}Params: array of string);
   procedure cm_ShowSysFiles(const {%H-}Params: array of string);
   procedure cm_SwitchIgnoreList(const Params: array of string);
   procedure cm_Options(const Params: array of string);
   procedure cm_CompareContents(const Params: array of string);
   procedure cm_Refresh(const {%H-}Params: array of string);
   procedure cm_ShowMainMenu(const Params: array of string);
   procedure cm_DirHotList(const Params: array of string);
   procedure cm_ConfigDirHotList(const {%H-}Params: array of string);
   procedure cm_WorkWithDirectoryHotlist(const Params: array of string);
   procedure cm_MarkInvert(const Params: array of string);
   procedure cm_MarkMarkAll(const Params: array of string);
   procedure cm_MarkUnmarkAll(const Params: array of string);
   procedure cm_MarkPlus(const Params: array of string);
   procedure cm_MarkMinus(const Params: array of string);
   procedure cm_MarkCurrentName(const Params: array of string);
   procedure cm_UnmarkCurrentName(const Params: array of string);
   procedure cm_MarkCurrentNameExt(const Params: array of string);
   procedure cm_UnmarkCurrentNameExt(const Params: array of string);
   procedure cm_MarkCurrentExtension(const Params: array of string);
   procedure cm_UnmarkCurrentExtension(const Params: array of string);
   procedure cm_MarkCurrentPath(const Params: array of string);
   procedure cm_UnmarkCurrentPath(const Params: array of string);
   procedure cm_SaveSelection(const Params: array of string);
   procedure cm_RestoreSelection(const Params: array of string);
   procedure cm_SaveSelectionToFile(const Params: array of string);
   procedure cm_LoadSelectionFromFile(const Params: array of string);
   procedure cm_LoadSelectionFromClip(const Params: array of string);
   procedure cm_SyncDirs(const Params: array of string);
   procedure cm_Search(const Params: array of string);
   procedure cm_HardLink(const Params: array of string);
   procedure cm_MultiRename(const Params: array of string);
   procedure cm_ReverseOrder(const Params: array of string);
   procedure cm_LeftReverseOrder(const Params: array of string);
   procedure cm_RightReverseOrder(const Params: array of string);
   procedure cm_UniversalSingleDirectSort(const Params: array of string);
   procedure cm_SortByName(const Params: array of string);
   procedure cm_SortByExt(const Params: array of string);
   procedure cm_SortByDate(const Params: array of string);
   procedure cm_SortBySize(const Params: array of string);
   procedure cm_SortByAttr(const Params: array of string);
   procedure cm_LeftSortByName(const Params: array of string);
   procedure cm_LeftSortByExt(const Params: array of string);
   procedure cm_LeftSortByDate(const Params: array of string);
   procedure cm_LeftSortBySize(const Params: array of string);
   procedure cm_LeftSortByAttr(const Params: array of string);
   procedure cm_RightSortByName(const Params: array of string);
   procedure cm_RightSortByExt(const Params: array of string);
   procedure cm_RightSortByDate(const Params: array of string);
   procedure cm_RightSortBySize(const Params: array of string);
   procedure cm_RightSortByAttr(const Params: array of string);
   procedure cm_SymLink(const Params: array of string);
   procedure cm_CopySamePanel(const Params: array of string);
   procedure cm_DirHistory(const Params: array of string);
   procedure cm_ViewHistory(const Params: array of string);
   procedure cm_ViewHistoryPrev(const {%H-}Params: array of string);
   procedure cm_ViewHistoryNext(const {%H-}Params: array of string);
   procedure cm_EditNew(const Params: array of string);
   procedure cm_RenameOnly(const Params: array of string);
   procedure cm_RunTerm(const Params: array of string);
   procedure cm_ShowCmdLineHistory(const Params: array of string);
   procedure cm_ToggleFullscreenConsole(const Params: array of string);
   procedure cm_CalculateSpace(const Params: array of string);
   procedure cm_CountDirContent(const Params: array of string);
   procedure cm_SetFileProperties(const Params: array of string);
   procedure cm_FileProperties(const Params: array of string);
   procedure cm_FileLinker(const Params: array of string);
   procedure cm_FileSpliter(const Params: array of string);
   procedure cm_PanelsSplitterPerPos(const Params: array of string);
   procedure cm_EditComment(const Params: array of string);
   procedure cm_CopyToClipboard(const Params: array of string);
   procedure cm_CutToClipboard(const Params: array of string);
   procedure cm_PasteFromClipboard(const Params: array of string);
   procedure cm_SyncChangeDir(const Params: array of string);
   procedure cm_ChangeDirToRoot(const Params: array of string);
   procedure cm_ChangeDirToHome(const Params: array of string);
   procedure cm_ChangeDirToParent(const Params: array of string);
   procedure cm_ChangeDir(const Params: array of string);
   procedure cm_ClearLogWindow(const Params: array of string);
   procedure cm_ClearLogFile(const Params: array of string);
   procedure cm_NetworkConnect(const Params: array of string);
   procedure cm_NetworkDisconnect(const Params: array of string);
   procedure cm_CopyNetNamesToClip(const Params: array of string);
   procedure cm_HorizontalFilePanels(const Params: array of string);
   procedure cm_OperationsViewer(const Params: array of string);
   procedure cm_CompareDirectories(const Params: array of string);
   procedure cm_ViewLogFile(const Params: array of string);
   procedure cm_ConfigToolbars(const Params: array of string);
   procedure cm_DebugShowCommandParameters(const Params: array of string);
   procedure cm_CopyPathOfFilesToClip(const Params: array of string);
   procedure cm_CopyPathNoSepOfFilesToClip(const Params: array of string);
   procedure cm_DoAnyCmCommand(const Params: array of string);
   procedure cm_SetAllTabsOptionNormal(const Params: array of string);
   procedure cm_SetAllTabsOptionPathLocked(const Params: array of string);
   procedure cm_SetAllTabsOptionPathResets(const Params: array of string);
   procedure cm_SetAllTabsOptionDirsInNewTab(const Params: array of string);
   procedure cm_ConfigFolderTabs(const {%H-}Params: array of string);
   procedure cm_ConfigFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_LoadFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_SaveFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_ReloadFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_PreviousFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_NextFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_ResaveFavoriteTabs(const {%H-}Params: array of string);
   procedure cm_CopyAllTabsToOpposite(const {%H-}Params: array of string);
   procedure cm_ConfigTreeViewMenus(const {%H-}Params: array of string);
   procedure cm_ConfigTreeViewMenusColors(const {%H-}Params: array of string);
   procedure cm_ConfigSavePos(const {%H-}Params: array of string);
   procedure cm_ConfigSaveSettings(const {%H-}Params: array of string);
   procedure cm_AddNewSearch(const Params: array of string);
   procedure cm_ViewSearches(const {%H-}Params: array of string);
   procedure cm_DeleteSearches(const {%H-}Params: array of string);
   procedure cm_ConfigSearches(const {%H-}Params: array of string);
   procedure cm_ConfigHotKeys(const {%H-}Params: array of string);
   procedure cm_ExecuteScript(const {%H-}Params: array of string);
   procedure cm_FocusSwap(const {%H-}Params: array of string);
   procedure cm_Benchmark(const {%H-}Params: array of string);
   procedure cm_ConfigArchivers(const {%H-}Params: array of string);
   procedure cm_ConfigTooltips(const {%H-}Params: array of string);
   procedure cm_ConfigPlugins(const {%H-}Params: array of string);
   procedure cm_OpenDriveByIndex(const Params: array of string);
   procedure cm_AddPlugin(const Params: array of string);
   procedure cm_LoadList(const Params: array of string);

   // Internal commands
   procedure cm_ExecuteToolbarItem(const Params: array of string);
  end;

implementation

uses fOptionsPluginsBase, fOptionsPluginsDSX, fOptionsPluginsWCX,
     fOptionsPluginsWDX, fOptionsPluginsWFX, fOptionsPluginsWLX, uFlatViewFileSource,
     uFindFiles, Forms, Controls, Dialogs, Clipbrd, strutils, LCLProc, HelpIntfs, DCStringHashListUtf8,
     dmHelpManager, typinfo, fMain, fPackDlg, fMkDir, DCDateTimeUtils, KASToolBar, KASToolItems,
     fExtractDlg, fAbout, fOptions, fDiffer, fFindDlg, fSymLink, fHardLink, fMultiRename,
     fLinker, fSplitter, fDescrEdit, fCheckSumVerify, fCheckSumCalc, fSetFileProperties,
     uLng, uLog, uShowMsg, uOSForms, uOSUtils, uDCUtils, uBriefFileView, fSelectDuplicates,
     uShowForm, uShellExecute, uClipboard, uHash, uDisplayFile, uLuaPas, uSysFolders,
     uFilePanelSelect, uFileSystemFileSource, uQuickViewPanel, Math, fViewer,
     uOperationsManager, uFileSourceOperationTypes, uWfxPluginFileSource,
     uFileSystemDeleteOperation, uFileSourceExecuteOperation, uSearchResultFileSource,
     uFileSourceOperationMessageBoxesUI, uFileSourceCalcChecksumOperation,
     uFileSourceCalcStatisticsOperation, uFileSource, uFileSourceProperty,
     uVfsFileSource, uFileSourceUtil, uArchiveFileSourceUtil, uThumbFileView,
     uTempFileSystemFileSource, uFileProperty, uFileSourceSetFilePropertyOperation,
     uTrash, uFileSystemCopyOperation, fOptionsFileAssoc, fDeleteDlg,
     fViewOperations, uVfsModule, uMultiListFileSource, uExceptions, uFileProcs,
     DCOSUtils, DCStrUtils, DCBasicTypes, uFileSourceCopyOperation, fSyncDirsDlg,
     uHotDir, DCXmlConfig, dmCommonData, fOptionsFrame, foptionsDirectoryHotlist,
     fMainCommandsDlg, uConnectionManager, fOptionsFavoriteTabs, fTreeViewMenu,
     uArchiveFileSource, fOptionsHotKeys, fBenchmark, uAdministrator, uWcxArchiveFileSource,
     uColumnsFileView
     ;

resourcestring
  rsFavoriteTabs_SetupNotExist = 'No setup named "%s"';

procedure ReadCopyRenameParams(
  const Params: array of string;
  var Confirmation: Boolean;
  out HasQueueId: Boolean;
  out QueueIdentifier: TOperationsManagerQueueIdentifier);
var
  Param, sQueueId: String;
  BoolValue: Boolean;
  iQueueId: Integer;
begin
  HasQueueId := False;
  for Param in Params do
  begin
    if GetParamBoolValue(Param, 'confirmation', BoolValue) then
      Confirmation := BoolValue
    else if GetParamValue(Param, 'queueid', sQueueId) then
    begin
      HasQueueId := TryStrToInt(sQueueId, iQueueId);
      if HasQueueId then
        QueueIdentifier := iQueueId;
    end;
  end;
end;
{ TMainCommands }

constructor TMainCommands.Create(TheOwner: TComponent; ActionList: TActionList = nil);
begin
  inherited Create(TheOwner);
  FCommands := TFormCommands.Create(Self, ActionList);
  FCommands.FilterFunc := @CommandsFilter;
end;

function TMainCommands.CommandsFilter(Command: String): Boolean;
begin
  Result := Command = 'cm_ExecuteToolbarItem';
end;

//------------------------------------------------------
procedure TMainCommands.TryGetParentDir(FileView: TFileView; var SelectedFiles: TFiles);
var
  activeFile : TFile;
  tempPath : String;
begin
  activeFile := FileView.CloneActiveFile;
  if assigned(activeFile) then begin
    if activeFile.Name = '..' then
      begin
        tempPath := activeFile.FullPath;
        activeFile.Name := ExtractFileName(ExcludeTrailingPathDelimiter(activeFile.Path));
        activeFile.Path := ExpandFileName(tempPath);
        SelectedFiles.Add(activeFile);
      end
      else
        FreeAndNil(activeFile);
    end;
end;

procedure TMainCommands.OnCopyOutStateChanged(Operation: TFileSourceOperation;
                                              State: TFileSourceOperationState);
var
  I: Integer;
  aFile: TFile;
  aFileList: TStringList;
  aFileSource: ITempFileSystemFileSource;
  aCopyOutOperation: TFileSourceCopyOperation;
  sCmd: string = '';
  sParams: string = '';
  sStartPath: string = '';
begin
  if (State = fsosStopped) and (Operation.Result = fsorFinished) then
  begin
    aFileList := TStringList.Create;
    try
      aCopyOutOperation := Operation as TFileSourceCopyOperation;
      aFileSource := aCopyOutOperation.TargetFileSource as ITempFileSystemFileSource;
      ChangeFileListRoot(aFileSource.FileSystemRoot, aCopyOutOperation.SourceFiles);

      try
        for I := 0 to aCopyOutOperation.SourceFiles.Count - 1 do
        begin
          aFile := aCopyOutOperation.SourceFiles[I];

          if not (aFile.IsDirectory or aFile.IsLinkToDirectory) then
          begin
            // Try to find 'view' command in internal associations
            if not gExts.GetExtActionCmd(aFile, 'view', sCmd, sParams, sStartPath) then
              aFileList.Add(aFile.FullPath)
            else
              begin
                if sStartPath='' then
                  sStartPath:=aCopyOutOperation.SourceFiles.Path;
                ProcessExtCommandFork(sCmd, sParams, aCopyOutOperation.SourceFiles.Path, aFile);
                // TODO:
                // If TempFileSource is used, create a wait thread that will
                // keep the TempFileSource alive until the command is finished.
                aCopyOutOperation.SourceFileSource.AddChild(aFileSource);
              end;
          end; // if selected
        end; // for

        // if aFileList has files then view it
        if aFileList.Count > 0 then
          ShowViewerByGlobList(aFileList, aFileSource);
      except
        on e: EInvalidCommandLine do
          MessageDlg(rsToolErrorOpeningViewer,
            rsMsgInvalidCommandLine + ' (' + rsToolViewer + '):' + LineEnding + e.Message,
            mtError, [mbOK], 0);
      end;

    finally
      FreeAndNil(aFileList);
    end;
  end;
end;

procedure TMainCommands.OnEditCopyOutStateChanged(Operation: TFileSourceOperation;
                                                  State: TFileSourceOperationState);
var
  WaitData: TEditorWaitData;
begin
  if (State = fsosStopped) and (Operation.Result = fsorFinished) then
  begin
    try
      WaitData := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
      try
        ShowEditorByGlob(WaitData);
      except
        WaitData.Free;
      end;
    except
      on e: EInvalidCommandLine do
        MessageDlg(rsToolErrorOpeningEditor,
          rsMsgInvalidCommandLine + ' (' + rsToolEditor + '):' + LineEnding + e.Message,
          mtError, [mbOK], 0);
    end;
  end;
end;

procedure TMainCommands.OnCalcStatisticsStateChanged(Operation: TFileSourceOperation;
                                             State: TFileSourceOperationState);
var
  CalcStatisticsOperation: TFileSourceCalcStatisticsOperation;
  CalcStatisticsOperationStatistics: TFileSourceCalcStatisticsOperationStatistics;
begin
  if (State = fsosStopped) and (Operation.Result = fsorFinished) then
  begin
    CalcStatisticsOperation := Operation as TFileSourceCalcStatisticsOperation;
    CalcStatisticsOperationStatistics := CalcStatisticsOperation.RetrieveStatistics;
    with CalcStatisticsOperationStatistics do
    begin
      if Size < 0 then
        msgOK(Format(rsSpaceMsg, [Files, Directories, '???', '???']))
      else begin
        msgOK(Format(rsSpaceMsg, [Files, Directories, cnvFormatFileSize(Size), IntToStrTS(Size)]));
      end;
    end;
  end;
end;

procedure TMainCommands.OnCalcChecksumStateChanged(Operation: TFileSourceOperation;
                                           State: TFileSourceOperationState);
var
  CalcChecksumOperation: TFileSourceCalcChecksumOperation;
begin
  if (State = fsosStopped) and (Operation.Result = fsorFinished) then
  begin
    CalcChecksumOperation := Operation as TFileSourceCalcChecksumOperation;
    if CalcChecksumOperation.Mode = checksum_verify then
      ShowVerifyCheckSum(CalcChecksumOperation.Result);
  end;
end;

//------------------------------------------------------

procedure TMainCommands.DoCloseTab(Notebook: TFileViewNotebook; PageIndex: Integer);
begin
  with frmMain do
  begin
    RemovePage(Notebook, PageIndex);
    ActiveFrame.SetFocus;
  end;
end;

procedure TMainCommands.DoShowCmdLineHistory(bNextCmdLine: Boolean);
begin
  with frmMain do
  begin
    if edtCommand.Visible then
    begin
      ShowCommandLine(True);

      if bNextCmdLine then
      begin
        if edtCommand.ItemIndex > 0 then
          edtCommand.ItemIndex := edtCommand.ItemIndex - 1;
      end
      else
      begin
        if edtCommand.ItemIndex < edtCommand.Items.Count - 1 then
          edtCommand.ItemIndex := edtCommand.ItemIndex + 1;
      end;
    end;
  end;
end;

procedure TMainCommands.DoChangeDirToRoot(FileView: TFileView);
var
  Page: TFileViewPage;
begin
  with FileView do
  begin
    Page := TFileViewPage(NotebookPage);
    if Assigned(Page) then
    begin
      if Page.LockState = tlsPathResets then
        ChooseFileSource(FileView, Page.LockPath)
      else begin
        CurrentPath := FileSource.GetRootDir(CurrentPath);
      end;
    end;
  end;
end;

procedure TMainCommands.DoCopySelectedFileNamesToClipboard(FileView: TFileView; TypeOfCopy: TCopyFileNamesToClipboard; const Params : Array of string);
var
  I: Integer;
  sl: TStringList = nil;
  SelectedFiles: TFiles = nil;
  PathToAdd, FileNameToAdd: String;
  separator : String;
begin
  SelectedFiles := FileView.CloneSelectedOrActiveFiles;
  if (SelectedFiles.Count = 0) then
    TryGetParentDir(FileView, SelectedFiles);
  try
    if SelectedFiles.Count > 0 then
    begin
      sl := TStringList.Create;
      for I := 0 to SelectedFiles.Count - 1 do
      begin
        PathToAdd:='';
        FileNameToAdd:='';

        //Let's set the "PathToAdd" according to type of copy.
        case TypeOfCopy of
          cfntcPathAndFileNames, cfntcJustPathWithSeparator, cfntcPathWithoutSeparator:
            begin
              PathToAdd:= SelectedFiles[I].Path;

              // Workaround for not fully implemented TMultiListFileSource.
              if (Pos(FileView.CurrentAddress, PathToAdd) <> 1) and
                 (not FileView.FileSource.IsClass(TMultiListFileSource)) then
              begin
                PathToAdd := FileView.CurrentAddress + PathToAdd;
              end;

              if TypeOfCopy=cfntcPathWithoutSeparator then PathToAdd:=ExcludeTrailingPathDelimiter(PathToAdd);
            end;
        end;

        //Let's set the "FilenameToAdd" according to type of copy.
        case TypeOfCopy of
          cfntcPathAndFileNames, cfntcJustFileNames: FileNameToAdd:=SelectedFiles[I].Name;
        end;
        if ((GetParamValue(Params, 'separator', separator)) and (separator.length > 0)) then
           sl.Add(ReplaceDirectorySeparator(PathToAdd + FileNameToAdd, separator[1]))
        else sl.Add(PathToAdd + FileNameToAdd);
      end;

      Clipboard.Clear;   // prevent multiple formats in Clipboard (specially synedit)
      ClipboardSetText(TrimRightLineEnding(sl.Text, sl.TextLineBreakStyle));
    end;

  finally
    if Assigned(sl) then
      FreeAndNil(sl);
    if Assigned(SelectedFiles) then
      FreeAndNil(SelectedFiles);
  end;
end;

procedure TMainCommands.DoNewTab(Notebook: TFileViewNotebook);
var
  NewPage: TFileViewPage;
begin
  NewPage := Notebook.NewPage(Notebook.ActiveView);
  NewPage.MakeActive;
end;

procedure TMainCommands.DoRenameTab(Page: TFileViewPage);
var
  sCaption: String;
begin
  sCaption := Page.CurrentTitle;
  if InputQuery(rsMsgTabRenameCaption, rsMsgTabRenamePrompt, sCaption) then
    Page.PermanentTitle := sCaption;
end;

procedure TMainCommands.DoTabMenuClick(Sender: TObject);
var
  MenuItem: TMenuItem absolute Sender;
begin
  TFileViewNotebook(FTabsMenu.PopupComponent).ActivateTabByIndex(MenuItem.Tag);
end;

procedure TMainCommands.DoOpenVirtualFileSystemList(Panel: TFileView);
var
  FileSource: IFileSource;
begin
  FileSource:= TVfsFileSource.Create(gWFXPlugins);
  if Assigned(FileSource) then
  begin
    Panel.AddFileSource(FileSource, FileSource.GetRootDir);
    frmMain.ActiveFrame.SetFocus;
  end;
end;

procedure TMainCommands.DoPanelsSplitterPerPos(SplitPos: Integer);
begin
  with frmMain do
  begin
    if (SplitPos >= 0) and (SplitPos <= 100) then
    begin
      // Update splitter position
      MainSplitterPos:= SplitPos;
      pnlNotebooksResize(pnlNotebooks);
    end;
  end;
end;

procedure TMainCommands.DoUpdateFileView(AFileView: TFileView; UserData: Pointer);
begin
  AFileView.UpdateView;
end;

procedure TMainCommands.DoContextMenu(Panel: TFileView; X, Y: Integer; Background: Boolean; UserWishForContextMenu:TUserWishForContextMenu);
var
  Index: Integer;
  AMenu: TPopupMenu;
  aFile: TFile = nil;
  aFiles: TFiles = nil;
  sPath, sName: String;
  OperationsTypes: TFileSourceOperationTypes;
begin
  with frmMain do
  begin
    if not (fspDirectAccess in Panel.FileSource.Properties) then
    begin
      if not Background then
      begin
        AMenu:= pmContextMenu;
        if (fspContextMenu in Panel.FileSource.Properties) then
        begin
          aFiles:= Panel.CloneSelectedOrActiveFiles;
          try
            Panel.FileSource.QueryContextMenu(aFiles, AMenu);
          finally
            FreeAndNil(aFiles);
          end;
        end;
        OperationsTypes:= Panel.FileSource.GetOperationsTypes;
        mnuContextDelete.Visible:= fsoDelete in OperationsTypes;
        mnuContextRenameOnly.Visible:= fsoSetFileProperty in OperationsTypes;
        AMenu.PopUp(X, Y);
      end;
      Exit;
    end;

    if not Panel.HasSelectedFiles then
    begin
      aFile:= Panel.CloneActiveFile;
      if not Assigned(aFile) then
        Background:= True
      else begin
        sName:= aFile.Name;
        FreeAndNil(aFile);
      end;
    end;

    if (Background = True) or (sName = '..') then
      begin
        sName:= ExcludeTrailingPathDelimiter(Panel.CurrentPath);
        sPath:= ExtractFilePath(sName);
        aFiles:= TFiles.Create(sPath);
        aFile:= Panel.FileSource.CreateFileObject(sPath);
        aFile.Attributes:= faFolder;
        aFile.Name:= ExtractFileName(sName);
        aFiles.Add(aFile);
      end
    else
      begin
        aFiles:= Panel.CloneSelectedOrActiveFiles;
      end;

    if Assigned(aFiles) then
    try
      if aFiles.Count > 0 then
      try
        if fspLinksToLocalFiles in Panel.FileSource.Properties then
        begin
          for Index:= 0 to aFiles.Count - 1 do
          begin
            aFile:= aFiles[Index];
            Panel.FileSource.GetLocalName(aFile);
          end;
        end;
        ShowContextMenu(frmMain, aFiles, X, Y, Background, nil, UserWishForContextMenu);
      except
        on e: EContextMenuException do
          ShowException(e);
      end;

    finally
      if Assigned(aFiles) then
        FreeAndNil(aFiles);
    end;
  end;
end;

procedure TMainCommands.DoTransferPath(SourceFrame: TFileView;
  TargetNotebook: TFileViewNotebook);
begin
  if TargetNotebook.ActivePage.LockState = tlsPathLocked then
    Exit;
  if TargetNotebook.ActivePage.LockState = tlsDirsInNewTab then
  begin
    TargetNotebook.NewPage(SourceFrame).MakeActive;
    TargetNotebook.ActivePage.LockState := tlsNormal;
  end
  else
  begin
    TargetNotebook.ActivePage.FileView := nil;
    SourceFrame.Clone(TargetNotebook.ActivePage);
  end;
end;

procedure TMainCommands.DoTransferPath(SourcePage: TFileViewPage; TargetPage: TFileViewPage; FromActivePanel: Boolean);
var
  aFile: TFile;
  NewPath: String;
begin
  if FromActivePanel then
  begin
    aFile := SourcePage.FileView.CloneActiveFile;
    if Assigned(aFile) then
    try
      if (fspLinksToLocalFiles in SourcePage.FileView.FileSource.GetProperties) and
         (SourcePage.FileView.FileSource.GetLocalName(aFile)) then
        begin
          if aFile.IsDirectory then
            ChooseFileSource(TargetPage.FileView, aFile.FullPath)
          else if not ChooseFileSource(TargetPage.FileView, TargetPage.FileView.FileSource, aFile) then
            begin
              ChooseFileSource(TargetPage.FileView, aFile.Path);
              TargetPage.FileView.SetActiveFile(aFile.Name);
            end;
        end
      else if aFile.IsDirectory then
      begin
        if aFile.Name = '..' then
        begin
          NewPath := GetParentDir(SourcePage.FileView.CurrentPath);
        end
        else
        begin
          // Change to a subdirectory.
          NewPath := aFile.FullPath;
        end;

        if NewPath <> EmptyStr then
          TargetPage.FileView.AddFileSource(SourcePage.FileView.FileSource, NewPath);
      end
      else
      begin
        // Change file source, if the file under cursor can be opened as another file source.
        try
          if not ChooseFileSource(TargetPage.FileView, SourcePage.FileView.FileSource, aFile) then
          begin
            if SourcePage.FileView.FileSource.IsClass(TArchiveFileSource) then
            begin
              NewPath:= ExtractFilePath(SourcePage.FileView.FileSource.CurrentAddress);
              if not mbCompareFileNames(TargetPage.FileView.CurrentPath, NewPath) then
              begin
                TargetPage.FileView.AddHistory(TFileSystemFileSource.GetFileSource, NewPath);
              end;
            end;
            TargetPage.FileView.AddFileSource(SourcePage.FileView.FileSource, aFile.Path);
          end;
          TargetPage.FileView.SetActiveFile(aFile.Name);
        except
          on e: EFileSourceException do
            MessageDlg('Error', e.Message, mtError, [mbOK], 0);
        end;
      end;
    finally
      FreeAndNil(aFile);
    end;
  end
  else
  begin
    TargetPage.FileView.AddFileSource(SourcePage.FileView.FileSource, SourcePage.FileView.CurrentPath);
  end;
end;

procedure TMainCommands.DoSortByFunctions(View: TFileView; FileFunctions: TFileFunctions);
var
  NewSorting: TFileSortings = nil;
  CurrentSorting: TFileSortings;
  SortDirection: TSortDirection = sdNone;
  i: Integer;
begin
  if Length(FileFunctions) = 0 then
    Exit;

  CurrentSorting := View.Sorting;

  // Check if there is already sorting by one of the functions.
  // If it is then reverse direction of sorting.
  for i := 0 to Length(FileFunctions) - 1 do
  begin
    SortDirection := GetSortDirection(CurrentSorting, FileFunctions[i]);
    if SortDirection <> sdNone then
    begin
      SortDirection := ReverseSortDirection(SortDirection);
      Break;
    end;
  end;

  //If there is no direction currently, sort "sdDescending" for size and date
  if SortDirection = sdNone then
  begin
    case FileFunctions[0] of
      fsfSize, fsfModificationTime, fsfCreationTime, fsfLastAccessTime: SortDirection:=sdDescending;
      else SortDirection:=sdAscending;
    end;
  end;

  SetLength(NewSorting, 1);
  SetLength(NewSorting[0].SortFunctions, 1);
  NewSorting[0].SortFunctions[0] := FileFunctions[0]; // Sort by single function.
  NewSorting[0].SortDirection := SortDirection;

  View.Sorting := NewSorting;
end;

procedure TMainCommands.DoShowMainMenu(bShow: Boolean);
begin
  gMainMenu := bShow;

  with frmMain do
  begin
    if bShow then
    begin
      Menu := mnuMain;
    end
    else if Assigned(Menu) then
    begin
      Menu := nil;
      {$IFDEF MSWINDOWS}
      // Workaround: on Windows need to recreate window to properly recalculate children sizes.
      RecreateWnd(frmMain);
      {$ENDIF}
    end;
  end;
end;

//------------------------------------------------------
//Published methods
//------------------------------------------------------

{ TMainCommands.DoActualAddToCommandLine }
procedure TMainCommands.DoActualAddToCommandLine(const Params: array of string; sAddedString:string; bAddSpaceAtTheEnd:boolean);
type
  tQuoteMode = (tqmSmartQuote,tqmForceQuote,tqmNeverQuote);
var
  OldPosition: Integer;
  sParamValue: String;
  QuoteMode: tQuoteMode = tqmSmartQuote;
  DefaultButton: TMyMsgButton;
  Answer: TMyMsgResult;
begin
  if Length(Params)>0 then
  begin
    if GetParamValue(Params[0], 'mode', sParamValue) then
    begin
      if sParamValue='smartquote' then QuoteMode:=tqmSmartQuote else
        if sParamValue='forcequote' then QuoteMode:=tqmForceQuote else
          if sParamValue='neverquote' then QuoteMode:=tqmNeverQuote else
            if sParamValue='prompt' then
            begin
              if sAddedString = QuoteFilenameIfNecessary(sAddedString) then DefaultButton:=msmbNo else DefaultButton:=msmbYes;
              Answer:=MsgBox(rsMsgAskQuoteOrNot,[msmbYes, msmbNo], DefaultButton, DefaultButton);
              case Answer of
                mmrYes:QuoteMode:=tqmForceQuote;
                mmrNo:QuoteMode:=tqmNeverQuote;
              end;
            end;
    end;
  end;

  case QuoteMode of
    tqmSmartQuote : sAddedString := QuoteFilenameIfNecessary(sAddedString);
    tqmForceQuote : sAddedString := QuoteStr(sAddedString);
    tqmNeverQuote : sAddedString := sAddedString;
    else sAddedString := QuoteFilenameIfNecessary(sAddedString);
  end;

  if bAddSpaceAtTheEnd then sAddedString:=sAddedString+' ';

  OldPosition := frmMain.edtCommand.SelStart;
  frmMain.edtCommand.Text := frmMain.edtCommand.Text + sAddedString;
  frmMain.edtCommand.SelStart := OldPosition + Length(sAddedString);

  frmMain.ShowCommandLine(False);
end;

{ TMainCommands.cm_AddPathToCmdLine }
procedure TMainCommands.cm_AddPathToCmdLine(const Params: array of string);
begin
  DoActualAddToCommandLine(Params, frmMain.ActiveFrame.CurrentPath, False);
end;

{ TMainCommands.cm_AddFilenameToCmdLine }
procedure TMainCommands.cm_AddFilenameToCmdLine(const Params: array of string);
var
  aFile: TFile;
begin
  aFile := frmMain.ActiveFrame.CloneActiveFile;
  if Assigned(aFile) then
  try
    DoActualAddToCommandLine(Params, aFile.Name, True);
  finally
    FreeAndNil(aFile);
  end;
end;

{ TMainCommands.cm_AddPathAndFilenameToCmdLine }
procedure TMainCommands.cm_AddPathAndFilenameToCmdLine(const Params: array of string);
var
  aFile: TFile;
begin
  aFile := frmMain.ActiveFrame.CloneActiveFile;
  if Assigned(aFile) then
  try
    if aFile.Name = '..' then
      DoActualAddToCommandLine(Params, frmMain.ActiveFrame.CurrentPath, True)
    else
      DoActualAddToCommandLine(Params, aFile.FullPath, True);
  finally
    FreeAndNil(aFile);
  end;
end;

procedure TMainCommands.cm_ContextMenu(const Params: array of string);
begin
  // Let file view handle displaying context menu at appropriate position.
  frmMain.ActiveFrame.ExecuteCommand('cm_ContextMenu', Params);
end;

procedure TMainCommands.cm_SaveFileDetailsToFile(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_SaveFileDetailsToFile', []);
end;

procedure TMainCommands.cm_CopyFullNamesToClip(const Params: array of string);
begin
  DoCopySelectedFileNamesToClipboard(frmMain.ActiveFrame, cfntcPathAndFileNames, Params);
end;

procedure TMainCommands.cm_CopyFileDetailsToClip(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_CopyFileDetailsToClip', []);
end;

procedure TMainCommands.cm_CopyNamesToClip(const Params: array of string);
begin
  DoCopySelectedFileNamesToClipboard(frmMain.ActiveFrame, cfntcJustFileNames, Params);
end;

procedure TMainCommands.cm_FocusTreeView(const Params: array of string);
begin
  with frmMain do
  begin
    if gSeparateTree then
    begin
      if ActiveFrame.Focused then
        ShellTreeView.SetFocus
      else
        ActiveFrame.SetFocus;
    end;
  end;
end;

//------------------------------------------------------
procedure TMainCommands.cm_Exchange(const Params: array of string);
var
  AFileView: TFileView;
  NFileView: TFileView;
  AFree, NFree: Boolean;
begin
  with frmMain do
  begin
    if (ActiveNotebook.ActivePage.LockState = tlsPathLocked) or
       (NotActiveNotebook.ActivePage.LockState = tlsPathLocked) then
      Exit;

    AFileView:= ActiveFrame;
    NFileView:= NotActiveFrame;

    if Assigned(QuickViewPanel) then QuickViewClose;

    AFree:= ActiveNotebook.ActivePage.LockState <> tlsDirsInNewTab;
    if AFree then ActiveNotebook.ActivePage.RemoveComponent(AFileView);

    DoTransferPath(NFileView, ActiveNotebook);

    NFree:= NotActiveNotebook.ActivePage.LockState <> tlsDirsInNewTab;
    if NFree then NotActiveNotebook.ActivePage.RemoveComponent(NFileView);

    DoTransferPath(AFileView, NotActiveNotebook);

    if AFree then AFileView.Free;
    if NFree then NFileView.Free;

    ActiveFrame.SetFocus;

    UpdateSelectedDrive(NotActiveNotebook);
  end;
end;

procedure TMainCommands.cm_ExecuteToolbarItem(const Params: array of string);
var
  ToolItemID, ToolBarID: String;
begin
  if GetParamValue(Params, 'ToolItemID', ToolItemID) then
  begin
    if not GetParamValue(Params, 'ToolBarID', ToolBarID) then
      frmMain.MainToolBar.ClickItem(ToolItemID)
    else begin
      if (ToolBarID = 'TfrmOptionsToolbar') then
        frmMain.MainToolBar.ClickItem(ToolItemID)
      else if (ToolBarID = 'TfrmOptionsToolbarMiddle') then
        frmMain.MiddleToolBar.ClickItem(ToolItemID);
    end;
  end;
end;

procedure TMainCommands.cm_FlatView(const Params: array of string);
var
  AFile: TFile;
  AFileView: TFileView;
  AValue, Param: String;
begin
  with frmMain do
  begin
    AFileView:= ActiveFrame;

    for Param in Params do
    begin
      if GetParamValue(Param, 'side', AValue) then
      begin
        if AValue = 'left' then AFileView:= FrameLeft
        else if AValue = 'right' then AFileView:= FrameRight
        else if AValue = 'inactive' then AFileView:= NotActiveFrame;
      end
    end;

    if not (fspListFlatView in AFileView.FileSource.GetProperties) then
    begin
      msgWarning(rsMsgErrNotSupported);
    end
    else begin
      AFileView.FlatView:= not AFileView.FlatView;
      if not AFileView.FlatView then
      begin
        AFile:= AFileView.CloneActiveFile;
        if Assigned(AFile) and AFile.IsNameValid then
        begin
          if not mbCompareFileNames(AFileView.CurrentPath, AFile.Path) then
          begin
            AFileView.CurrentPath:= AFile.Path;
            AFileView.SetActiveFile(AFile.Name);
          end;
        end;
        AFile.Free;
      end;
      AFileView.Reload;
    end;
  end;
end;

procedure TMainCommands.cm_FlatViewSel(const Params: array of string);
var
  AFileList: TFileTree;
  AFileSource: IFileSource;

  procedure ScanDir(const Dir: String);
  var
    I: Integer;
    AFile: TFile;
    AFiles: TFiles;
  begin
    AFiles := AFileSource.GetFiles(Dir);
    try
      for I := 0 to AFiles.Count - 1 do
      begin
        AFile := AFiles[I];
        if not AFile.IsDirectory then
          AFileList.AddSubNode(AFile.Clone)
        else if AFile.IsNameValid then
          ScanDir(AFile.FullPath);
      end;
    finally
      AFiles.Free;
    end;
  end;

var
  J: Integer;
  AFile: TFile;
  AFiles: TFiles;
  AFileView: TFileView;
  AFlatView: ISearchResultFileSource;
begin
  AFileView:= frmMain.ActiveFrame;
  AFileSource:= AFileView.FileSource;
  if AFileView.FlatView then
  begin
    AFileView.FlatView := False;
    if AFileSource.IsInterface(ISearchResultFileSource) then
      AFileView.ChangePathToParent(True)
    else
      AFileView.Reload;
    Exit;
  end;
  AFileList := TFileTree.Create;
  AFiles := AFileView.CloneSelectedFiles;
  for J := 0 to AFiles.Count - 1 do
  begin
    AFile := AFiles[J];
    if not AFile.IsDirectory then
      AFileList.AddSubNode(AFile.Clone)
    else if AFile.IsNameValid then
      ScanDir(AFile.FullPath);
  end;
  AFiles.Free;

  // Create search result file source.
  AFlatView := TFlatViewFileSource.Create;
  AFlatView.AddList(AFileList, AFileSource);

  AFileView.AddFileSource(AFlatView, AFileView.CurrentPath);
  AFileView.FlatView := True;
end;

procedure TMainCommands.cm_LeftFlatView(const Params: array of string);
begin
  cm_FlatView(['side=left']);
end;

procedure TMainCommands.cm_RightFlatView(const Params: array of string);
begin
  cm_FlatView(['side=right']);
end;

procedure TMainCommands.cm_OpenDirInNewTab(const Params: array of string);

  function OpenTab(const aFullPath: string): TFileViewPage;
  begin
    Result := FrmMain.ActiveNotebook.NewPage(FrmMain.ActiveFrame);
    // Workaround for Search Result File Source
    if Result.FileView.FileSource is TSearchResultFileSource then
      SetFileSystemPath(Result.FileView, aFullPath)
    else
      Result.FileView.CurrentPath := aFullPath;
  end;

  function OpenArchive(const aFile: TFile): TFileViewPage;
  begin
    Result := FrmMain.ActiveNotebook.NewPage(FrmMain.ActiveFrame);
    ChooseArchive(Result.FileView, Result.FileView.FileSource, aFile);
  end;

  function OpenParent: TFileViewPage;
  begin
    Result := FrmMain.ActiveNotebook.NewPage(FrmMain.ActiveFrame);
    Result.FileView.ChangePathToParent(True);
  end;

var
  aFile: TFile;
  NewPage: TFileViewPage;
begin
  aFile := FrmMain.ActiveFrame.CloneActiveFile;
  if not Assigned(aFile) then
    NewPage := OpenTab(FrmMain.ActiveFrame.CurrentPath)
  else try
    if not aFile.IsNameValid then
      NewPage := OpenParent
    else if (aFile.IsDirectory or aFile.IsLinkToDirectory) then
      NewPage := OpenTab(aFile.FullPath)
    else if FileIsArchive(aFile.FullPath) then
      NewPage := OpenArchive(aFile)
    else begin
      NewPage := OpenTab(aFile.Path);
      NewPage.FileView.SetActiveFile(aFile.Name);
    end;
  finally
    FreeAndNil(aFile);
  end;

  if tb_open_new_in_foreground in gDirTabOptions then
    NewPage.MakeActive;
end;

procedure TMainCommands.cm_TargetEqualSource(const Params: array of string);
begin
  with frmMain do
  begin
    DoTransferPath(ActiveFrame, NotActiveNotebook);
  end;
end;

procedure TMainCommands.cm_LeftEqualRight(const Params: array of string);
begin
  with frmMain do
  begin
    DoTransferPath(FrameRight, LeftTabs);

    // Destroying active view may have caused losing focus. Restore it if needed.
    if SelectedPanel = fpLeft then
      FrameLeft.SetFocus;
  end;
end;

procedure TMainCommands.cm_RightEqualLeft(const Params: array of string);
begin
  with frmMain do
  begin
    DoTransferPath(FrameLeft, RightTabs);

    // Destroying active view may have caused losing focus. Restore it if needed.
    if SelectedPanel = fpRight then
      FrameRight.SetFocus;
  end;
end;

procedure TMainCommands.cm_OpenArchive(const Params: array of string);
var
  aFile: TFile;
begin
  with frmMain.ActiveFrame do
  begin
    aFile := CloneActiveFile;
    if Assigned(aFile) then
    try
      if aFile.IsNameValid then
      begin
        if aFile.IsDirectory or aFile.IsLinkToDirectory then
          ChangePathToChild(aFile)
        else
          ChooseArchive(frmMain.ActiveFrame, FileSource, aFile, True);
      end;
    finally
      FreeAndNil(aFile);
    end;
  end;
end;

procedure TMainCommands.cm_TestArchive(const Params: array of string);
var
  Param: String;
  BoolValue: Boolean;
  SelectedFiles: TFiles;
  bConfirmation, HasConfirmationParam: Boolean;
  QueueId: TOperationsManagerQueueIdentifier = FreeOperationsQueueId;
begin
  with frmMain do
  begin
    HasConfirmationParam := False;

    for Param in Params do
    begin
      if GetParamBoolValue(Param, 'confirmation', BoolValue) then
      begin
        HasConfirmationParam := True;
        bConfirmation := BoolValue;
      end;
    end;
    if not HasConfirmationParam then begin
      bConfirmation := focTestArchive in gFileOperationsConfirmations;
    end;

    if (bConfirmation = False) or (ShowDeleteDialog(rsMsgTestArchive, ActiveFrame.FileSource, QueueId)) then
    begin
      SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
      try
        TestArchive(ActiveFrame, SelectedFiles, QueueId);
      finally
        FreeAndNil(SelectedFiles);
      end;
    end;
  end;
end;

procedure TMainCommands.cm_Open(const Params: array of string);
begin
  frmMain.ActiveFrame.OpenActiveFile;
end;

procedure TMainCommands.cm_ShellExecute(const Params: array of string);
var
  aFile: TFile;
begin
  if Length(Params) > 0 then
    with frmMain do
    ShellExecute(PrepareParameter(Params[0]))
  else
    with frmMain.ActiveFrame do
    begin
      aFile := CloneActiveFile;
      if Assigned(aFile) then
      try
        if aFile.IsNameValid then
          ShellExecute(aFile.FullPath)
        else if aFile.Name = '..' then
          ShellExecute(aFile.Path);
      finally
        FreeAndNil(aFile);
      end;
  end;
end;

procedure TMainCommands.cm_OpenVirtualFileSystemList(const Params: array of string);
begin
  DoOpenVirtualFileSystemList(frmMain.ActiveFrame);
end;

//------------------------------------------------------
(* Pack files in archive by creating a new archive *)
procedure TMainCommands.cm_PackFiles(const Params: array of string);
var
  Param: String;
  TargetPath: String;
  SelectedFiles: TFiles;
  TargetFileSource: IFileSource;
begin
  with frmMain do
  begin
    Param := GetDefaultParam(Params);
    if Param = 'PackHere' then
    begin
      TargetPath:= ActiveFrame.CurrentPath;
      TargetFileSource:= ActiveFrame.FileSource;
    end
    else begin
      TargetPath:= NotActiveFrame.CurrentPath;
      TargetFileSource:= NotActiveFrame.FileSource;
    end;
    if not (fspDirectAccess in TargetFileSource.Properties) then
      msgError(rsMsgErrNotSupported)
    else begin
      SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
      try
        if SelectedFiles.Count = 0 then
          msgWarning(rsMsgNoFilesSelected)
        else begin
          ShowPackDlg(frmMain,
                      ActiveFrame.FileSource,
                      nil, // No specific target (create new)
                      SelectedFiles,
                      TargetPath,
                      PathDelim { Copy to root of archive } {NotActiveFrame.FileSource.GetRootString}
                     );
        end;
      finally
        FreeAndNil(SelectedFiles);
      end;
    end;
  end;
end;

// This command is needed for extracting whole archive by Alt+F9 (without opening it).
procedure TMainCommands.cm_ExtractFiles(const Params: array of string);
var
  Param: String;
  TargetPath: String;
  SelectedFiles: TFiles;
  TargetFileSource: IFileSource;
begin
  with frmMain do
  begin
    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    if Assigned(SelectedFiles) then
    try
      if SelectedFiles.Count = 0 then
        msgWarning(rsMsgNoFilesSelected)
      else begin
        Param := GetDefaultParam(Params);
        if Param = 'ExtractHere' then
        begin
          TargetPath:= ActiveFrame.CurrentPath;
          TargetFileSource:= ActiveFrame.FileSource;
        end
        else begin
          TargetPath:= NotActiveFrame.CurrentPath;
          TargetFileSource:= NotActiveFrame.FileSource;
        end;
        ShowExtractDlg(frmMain, ActiveFrame.FileSource, SelectedFiles,
                       TargetFileSource, TargetPath);
      end;
    finally
      FreeAndNil(SelectedFiles);
    end;
  end;
end;

procedure TMainCommands.cm_QuickSearch(const Params: array of string);
begin
  FrmMain.ActiveFrame.ExecuteCommand('cm_QuickSearch', Params);
end;

procedure TMainCommands.cm_QuickFilter(const Params: array of string);
begin
  FrmMain.ActiveFrame.ExecuteCommand('cm_QuickFilter', Params);
end;

procedure TMainCommands.cm_SrcOpenDrives(const Params: array of string);
begin
  frmMain.ShowDrivesList(frmMain.SelectedPanel);
end;

procedure TMainCommands.cm_LeftOpenDrives(const Params: array of string);
begin
  frmMain.ShowDrivesList(fpLeft);
end;

procedure TMainCommands.cm_RightOpenDrives(const Params: array of string);
begin
  frmMain.ShowDrivesList(fpRight);
end;

procedure TMainCommands.cm_OpenBar(const Params: array of string);
begin
  // Deprecated.
end;

{ TMainCommands.DoComputeSizeAndPosForWindowInMiddle }
procedure TMainCommands.DoComputeSizeAndPosForWindowInMiddle(var iPosX:integer; var iPosY:integer; var iWidth:integer; var iHeight:integer);
var
  pl,pr: TPoint;
begin
  pl := frmMain.FrameLeft.ClientToScreen(Classes.Point(0,0));
  pr := frmMain.FrameRight.ClientToScreen(Classes.Point(0,0));
  iWidth := (((pr.x+frmMain.FrameRight.Width)- pl.x) * 68) div 100;
  iHeight := frmMain.FrameLeft.Height;
  iPosX := pl.x + (((frmMain.FrameLeft.Width+frmMain.FrameRight.Width) - iWidth) div 2);
  iPosY := pl.y;
end;

{ TMainCommands.cm_ShowButtonMenu }
procedure TMainCommands.cm_ShowButtonMenu(const Params: array of string);
var
  WantedButtonMenu, BoolValue: boolean;
  bWantedTreeViewButtonMenu : boolean = False;
  Param : string;
  iWantedPosX: integer = 0;
  iWantedPosY: integer = 0;
  iWantedWidth: integer = 800;
  iWantedHeight: integer = 600;
  APointer: Pointer;
  iTypeDispatcher: integer = 0;
  maybeKASToolButton: TKASToolButton;
  maybeKASToolItem: TKASToolItem;
begin
  WantedButtonMenu := gButtonBar;
  if Length(Params) > 0 then
  begin
    for Param in Params do
      if GetParamBoolValue(Param, 'toolbar', BoolValue) then WantedButtonMenu := BoolValue
      else if GetParamBoolValue(Param, 'treeview', BoolValue) then bWantedTreeViewButtonMenu := BoolValue
      else WantedButtonMenu := not WantedButtonMenu;
  end
  else
  begin
    WantedButtonMenu := not WantedButtonMenu;
  end;
  if not bWantedTreeViewButtonMenu then
  begin
    if WantedButtonMenu <> gButtonBar then
    begin
      gButtonBar := WantedButtonMenu;
      frmMain.UpdateWindowView;
    end;
  end
  else
  begin
    DoComputeSizeAndPosForWindowInMiddle(iWantedPosX, iWantedPosY, iWantedWidth, iWantedHeight);
    APointer := GetUserChoiceFromKASToolBar(frmMain.MainToolBar, tvmcKASToolBar, iWantedPosX, iWantedPosY, iWantedWidth, iWantedHeight, iTypeDispatcher);
    if APointer<>nil then
    begin
      case iTypeDispatcher of
        1:
        begin
          maybeKASToolButton := TKASToolButton(APointer);
          maybeKASToolButton.OnClick(maybeKASToolButton);
        end;
        2:
        begin
          maybeKASToolItem := TKASToolItem(APointer);
          frmMain.MainToolBar.PublicExecuteToolItem(maybeKASToolItem);
        end;
      end;
    end;
  end;
end;

procedure TMainCommands.cm_TransferLeft(const Params: array of string);
begin
  DoTransferPath(frmMain.RightTabs.ActivePage,
                 frmMain.LeftTabs.ActivePage,
                 frmMain.SelectedPanel = fpRight);
end;

procedure TMainCommands.cm_TransferRight(const Params: array of string);
begin
  DoTransferPath(frmMain.LeftTabs.ActivePage,
                 frmMain.RightTabs.ActivePage,
                 frmMain.SelectedPanel = fpLeft);
end;

procedure TMainCommands.cm_GoToFirstEntry(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToFirstEntry', []);
end;

procedure TMainCommands.cm_GoToLastEntry(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToLastEntry', []);
end;

procedure TMainCommands.cm_GoToFirstFile(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToFirstFile', []);
end;

procedure TMainCommands.cm_GoToNextEntry(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToNextEntry', []);
end;

procedure TMainCommands.cm_GoToPrevEntry(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToPrevEntry', []);
end;

procedure TMainCommands.cm_GoToLastFile(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_GoToLastFile', []);
end;

procedure TMainCommands.cm_Minimize(const Params: array of string);
begin
  FrmMain.MinimizeWindow;
end;

procedure TMainCommands.cm_Wipe(const Params: array of string);
var
  Message: String;
  theFilesToWipe: TFiles;
  Operation: TFileSourceOperation;
  QueueId: TOperationsManagerQueueIdentifier;
begin
  with frmMain.ActiveFrame do
  begin
    if not (fsoWipe in FileSource.GetOperationsTypes) then
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
    end;

    // ------------------------------------------------------

    theFilesToWipe := CloneSelectedOrActiveFiles; // free at Thread end by thread

    if Assigned(theFilesToWipe) then
    try
      if theFilesToWipe.Count = 0 then
        Exit;

      Message:= frmMain.GetFileDlgStr(rsMsgWipeSel, rsMsgWipeFlDr, theFilesToWipe);
      if not ShowDeleteDialog(Message, FileSource, QueueId) then
        Exit;

      Operation := FileSource.CreateWipeOperation(theFilesToWipe);

      if Assigned(Operation) then
      begin
        // Start operation.
        OperationsManager.AddOperation(Operation, QueueId, False);
      end
      else
      begin
        msgWarning(rsMsgNotImplemented);
      end;

    finally
      if Assigned(theFilesToWipe) then
        FreeAndNil(theFilesToWipe);
    end;
  end;
end;

procedure TMainCommands.cm_Exit(const Params: array of string);
begin
  frmMain.Close; // application.Terminate not save settings.
end;

procedure TMainCommands.cm_NewTab(const Params: array of string);
begin
  DoNewTab(frmMain.ActiveNotebook);
end;

procedure TMainCommands.cm_RenameTab(const Params: array of string);
begin
  DoRenameTab(frmMain.ActiveNotebook.ActivePage);
end;

procedure TMainCommands.cm_CloseTab(const Params: array of string);
begin
  with frmMain do
    DoCloseTab(ActiveNotebook, ActiveNotebook.PageIndex);
end;

{ TMainCommands.cm_CloseAllTabs }
procedure TMainCommands.cm_CloseAllTabs(const Params: array of string);
begin
  with frmMain do
  begin
    if (tb_confirm_close_all in gDirTabOptions) then
      if not msgYesNo(rsMsgCloseAllInActiveTabs) then Exit;

    DoActionOnMultipleTabs(Params,@DoCloseAllTabs);
  end;
end;

{ TMainCommands.cm_CloseDuplicateTabs }
procedure TMainCommands.cm_CloseDuplicateTabs(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params,@DoCloseDuplicateTabs);
end;

procedure TMainCommands.cm_NextTab(const Params: array of string);
begin
  frmMain.ActiveNotebook.ActivateNextTab;
end;

procedure TMainCommands.cm_PrevTab(const Params: array of string);
begin
  frmMain.ActiveNotebook.ActivatePrevTab;
end;

procedure TMainCommands.cm_MoveTabLeft(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    if PageIndex > 0 then PageIndex:= PageIndex - 1;
end;

procedure TMainCommands.cm_MoveTabRight(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    PageIndex:= PageIndex + 1;
end;

procedure TMainCommands.cm_ShowTabsList(const Params: array of string);
var
  ARect: TRect;
  Param: String;
  Index: Integer;
  AValue: String;
  APoint: TPoint;
  MenuItem: TMenuItem;
  ANotebook: TFileViewNotebook;
begin
  ANotebook:= frmMain.ActiveNotebook;
  for Param in Params do
  begin
    if GetParamValue(Param, 'side', AValue) then
    begin
      if AValue = 'left' then ANotebook:= frmMain.LeftTabs
      else if AValue = 'right' then ANotebook:= frmMain.RightTabs
      else if AValue = 'inactive' then ANotebook:= frmMain.NotActiveNotebook;
    end
  end;
  if (FTabsMenu = nil) then
  begin
    FTabsMenu:= TPopupMenu.Create(Self);
  end;
  FTabsMenu.Items.Clear;
  FTabsMenu.PopupComponent:= ANotebook;
  for Index:= 0 to ANotebook.PageCount - 1 do
  begin
    MenuItem:= TMenuItem.Create(FTabsMenu);
    MenuItem.Tag:= Index;
    MenuItem.Caption:= ANotebook.Page[Index].Caption;
    if (ANotebook.Page[Index].LockState in [tlsPathLocked, tlsPathResets, tlsDirsInNewTab]) and
       (tb_show_asterisk_for_locked in gDirTabOptions) then
    begin
      MenuItem.Caption:= Copy(MenuItem.Caption, 2, MaxInt);
    end;
    MenuItem.OnClick:= @DoTabMenuClick;
    FTabsMenu.Items.Add(MenuItem);
  end;
  ARect:= ANotebook.TabRect(ANotebook.PageIndex);
  APoint:= Classes.Point(ARect.Left, ARect.Bottom);
  APoint:= ANotebook.ClientToScreen(APoint);
  FTabsMenu.PopUp(APoint.X, APoint.Y);
end;

procedure TMainCommands.cm_ActivateTabByIndex(const Params: array of string);
var
  Param: String;
  Index: Integer;
  AValue: String;
  ANotebook: TFileViewNotebook;
begin
  if Length(Params) <> 0 then
  begin
    ANotebook:= frmMain.ActiveNotebook;
    for Param in Params do
    begin
      if GetParamValue(Param, 'index', AValue) then
      begin
        Index:= StrToIntDef(AValue, 1);
      end
      else if GetParamValue(Param, 'side', AValue) then
      begin
        if AValue = 'left' then ANotebook:= frmMain.LeftTabs
        else if AValue = 'right' then ANotebook:= frmMain.RightTabs
        else if AValue = 'inactive' then ANotebook:= frmMain.NotActiveNotebook;
      end
    end;
    if Index = -1 then
      ANotebook.ActivateTabByIndex(Index)
    else
      ANotebook.ActivateTabByIndex(Index - 1);
  end;
end;

{ TMainCommands.cm_SaveTabs }
// To respect legacy, we can invoke "cm_SaveTabs" with a single parameter and it will be a "DefaultParam", which means without any equal sign, directly the filename.
// With the following code, we may have more descriptive parameters like the following:
//         filename= : The giving parameter will be the output filename to save the tabs. If no "filename=" is specified, we will prompt user.
//   savedirhistory= : We indicate if we want to save dir history or not.
procedure TMainCommands.cm_SaveTabs(const Params: array of string);
var
  Config: TXmlConfig;
  Param, sValue: string;
  boolValue: boolean;
  bSaveDirHistory: boolean;
  sOutputTabsFilename: string = '';
begin
  // 1. We setup our default options.
  bSaveDirHistory := gSaveDirHistory;

  // 2. Let's parse the parameter to get the wanted ones. The default wanted parameter have been set in the "VAR" section
  //    We need to respect legacy of this command where *before* it was possible to simply and directly have the wanted output filename.
  //    Let's assume that if we have an "=" sign, it's can be a legacy usage but one with actual parameters.
  if (length(Params)>0) then
  begin
    sOutputTabsFilename := GetDefaultParam(Params);
    if pos('=',sOutputTabsFilename)<>0 then
    begin
      sOutputTabsFilename := '';
      for Param in Params do
      begin
        if GetParamValue(Param, 'filename', sValue) then sOutputTabsFilename := sValue
        else if GetParamBoolValue(Param, 'savedirhistory', boolValue) then bSaveDirHistory := boolValue;
      end;
    end;
  end;

  // 3. If no output filename has been specified so far, let's request an output filename.
  if sOutputTabsFilename='' then
  begin
    dmComData.SaveDialog.DefaultExt := 'tab';
    dmComData.SaveDialog.Filter := '*.tab|*.tab';
    if dmComData.SaveDialog.Execute then sOutputTabsFilename := dmComData.SaveDialog.FileName;
  end;

  // 4. If we get here with "sOutputTabsFilename<>''", we know what to save and where to save it.
  if sOutputTabsFilename<>'' then
  begin
    try
      Config := TXmlConfig.Create(sOutputTabsFilename);
      try
        frmMain.SaveTabsXml(Config, 'Tabs/OpenedTabs/', frmMain.LeftTabs, bSaveDirHistory);
        frmMain.SaveTabsXml(Config, 'Tabs/OpenedTabs/', frmMain.RightTabs, bSaveDirHistory);
        Config.Save;
      finally
        Config.Free;
      end;

    except
      on E: Exception do msgError(E.Message);
    end;
  end;
end;

{ TMainCommands.cm_LoadTabs }
// To respect legacy, invoking "cm_LoadTabs" with no parameter will attempt to load tabs for both panels and prompt the user for a filename.
// Still to respect lefacy, we can invoke "cm_LoadTabs" with a single parameter and it will be a "DefaultParam", which means without any equal sign, directly the filename.
// With the following code, we may have more descriptive parameters like the following:
//   filename   = The giving parameter will be the input filename to load the tabs from. If no "filename=" is specified, we will prompt user.
//   loadleftto = Indicate where to load what was saved for left panel. It could be left to be like before but also now right, active, inactive, both and none.
//   loadrightto= Indicate where to load what was saved for right panel. It could be right to be like before but also now left, active, inactive, both and none.
//   keep       = This indicates if in the target notebook where tabs will be loaded if we remove first the target present or not. When keep is "false", which is the default, we flush them first. If "keep" is "true", we add the loaded tab to the existing ones.
procedure TMainCommands.cm_LoadTabs(const Params: array of string);
var
  originalFilePanel:TFilePanelSelect;
  sInputTabsFilename: string = '';
  param, sValue: string;
  Config: TXmlConfig;
  TargetDestinationForLeft : TTabsConfigLocation = tclLeft;
  TargetDestinationForRight : TTabsConfigLocation = tclRight;
  DestinationToKeep : TTabsConfigLocation = tclNone;
  TabsAlreadyDestroyedFlags: TTabsFlagsAlreadyDestroyed = [];

  function EvaluateSideResult(sParamValue:string; DefaultValue:TTabsConfigLocation):TTabsConfigLocation;
  begin
    result:=DefaultValue;
    if sParamValue='left' then result := tclLeft else
      if sParamValue='right' then result := tclRight else
        if sParamValue='active' then result := tclActive else
          if sParamValue='inactive' then result := tclInactive else
            if sParamValue='both' then result := tclBoth else
              if sParamValue='none' then result := tclNone;
  end;
begin
  // 1. Note that most variable have been set with their default value in declaration.
  originalFilePanel := frmMain.SelectedPanel;

  // 2. Let's parse the parameter to get the wanted ones
  //    We need to respect legacy of this command where *before* it was possible to simply and directly have the wanted input filename.
  //    Let's assume that if we have an "=" sign, it's can't be a legacy usage but one with actual parameters.
  if (length(Params)>0) then
  begin
    sInputTabsFilename:=GetDefaultParam(Params);
    if pos('=',sInputTabsFilename)<>0 then
    begin
      sInputTabsFilename:='';
      for Param in Params do
      begin
        if GetParamValue(Param, 'filename', sValue) then sInputTabsFilename := sValue
        else if GetParamValue(Param, 'loadleftto', sValue) then TargetDestinationForLeft:=EvaluateSideResult(sValue,TargetDestinationForLeft)
        else if GetParamValue(Param, 'loadrightto', sValue) then TargetDestinationForRight:=EvaluateSideResult(sValue,TargetDestinationForRight)
        else if GetParamValue(Param, 'keep', sValue) then DestinationToKeep:=EvaluateSideResult(sValue,DestinationToKeep);
      end;
    end;
  end;

  // 3. If variable "sInputTabsFilename", we''ll request the user to provide an input filename.
  if sInputTabsFilename='' then
  begin
    dmComData.OpenDialog.Filter:= '*.tab|*.tab';
    dmComData.OpenDialog.FileName:= GetDefaultParam(Params);
    if dmComData.OpenDialog.Execute then
      sInputTabsFilename:=dmComData.OpenDialog.FileName;
  end;

  // 4. If we get here with "sInputTabsFilename<>''", we know what to load and from what to load it!
  if sInputTabsFilename<>'' then
  begin
    gFavoriteTabsList.SaveCurrentFavoriteTabsIfAnyPriorToChange;
    try
      Config := TXmlConfig.Create(sInputTabsFilename, True);
      try
        frmMain.LoadTheseTabsWithThisConfig(Config, 'Tabs/OpenedTabs/', tclLeft, TargetDestinationForLeft, DestinationToKeep, TabsAlreadyDestroyedFlags);
        frmMain.LoadTheseTabsWithThisConfig(Config, 'Tabs/OpenedTabs/', tclRight, TargetDestinationForRight, DestinationToKeep, TabsAlreadyDestroyedFlags);
      finally
        Config.Free;
      end;
    except
      on E: Exception do msgError(E.Message);
    end;
  end;

  frmMain.SelectedPanel := originalFilePanel;
  frmMain.ActiveFrame.SetFocus;
end;

procedure TMainCommands.cm_SetTabOptionNormal(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    LockState := tlsNormal;
end;

procedure TMainCommands.cm_SetTabOptionPathLocked(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    LockState := tlsPathLocked;
end;

procedure TMainCommands.cm_SetTabOptionPathResets(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    LockState := tlsPathResets;
end;

procedure TMainCommands.cm_SetTabOptionDirsInNewTab(const Params: array of string);
begin
  with frmMain.ActiveNotebook.ActivePage do
    LockState := tlsDirsInNewTab;
end;

//------------------------------------------------------

procedure TMainCommands.cm_View(const Params: array of string);
var
  aFile: TFile;
  i, n: Integer;
  IsFile: Boolean;
  sCmd: String = '';
  AMode: Integer = 0;
  sParams: String = '';
  Param, AValue: String;
  sl: TStringList = nil;
  AllFiles: TFiles = nil;
  sStartPath: String = '';
  ActiveFile: TFile = nil;
  aFileSource: IFileSource;
  ACursor: Boolean = False;
  SelectedFiles: TFiles = nil;
  LinksResolveNeeded: Boolean;
begin
  with frmMain do
  try
    ActiveFile := ActiveFrame.CloneActiveFile;

    if (Length(Params) > 0) then
    begin
      if GetParamValue(Params, 'cursor', AValue) then
        GetBoolValue(AValue, ACursor);
    end;

    if not ACursor then
      SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles
    else begin
      SelectedFiles:= TFiles.Create(ActiveFrame.CurrentPath);
      if ActiveFile.IsNameValid then SelectedFiles.Add(ActiveFile.Clone);
    end;

    if SelectedFiles.Count = 0 then
    begin
      msgWarning(rsMsgNoFilesSelected);
      Exit;
    end;

    aFile:= SelectedFiles[0];
    IsFile:= not (aFile.IsDirectory or aFile.IsLinkToDirectory);

    if (SelectedFiles.Count = 1) and (IsFile) and (Length(Params) > 0) then
    begin
      for Param in Params do
      begin
        if GetParamValue(Param, 'mode', AValue) then
        begin
          case LowerCase(AValue) of
          'text': AMode:= 1;
          'bin':  AMode:= 2;
          'hex':  AMode:= 3;
          'dec':  AMode:= 6;
          end;
          Break;
        end;
      end;
      if (AMode > 0) then
      begin
        with TViewerModeData.Create(AMode) do
        begin
          if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnCopyOutStateChanged) = pdrInCallback then
          begin
            Exit;
          end;
          Free;
        end;
        sl := TStringList.Create;
        sl.Add(aFile.FullPath);
        ShowViewer(sl, AMode);
        Exit;
      end;
    end;

    // Default to using the file source directly.
    aFileSource := ActiveFrame.FileSource;

    if not (fspDirectAccess in aFileSource.Properties) and
       not (fspLinksToLocalFiles in aFileSource.Properties) then
    begin
      for I := SelectedFiles.Count - 1 downto 0 do
      begin
        with SelectedFiles[I] do
        begin
          if IsDirectory or IsLinkToDirectory then
            SelectedFiles.Delete(I);
        end;
      end;
      if (SelectedFiles.Count = 0) then
      begin
        msgWarning(rsMsgNoFilesSelected);
        Exit;
      end;
    end;

    if PrepareData(aFileSource, SelectedFiles, @OnCopyOutStateChanged) <> pdrSynchronous then
      Exit;

    try
      // Try to find 'view' command in internal associations
      if gExts.GetExtActionCmd(aFile, 'view', sCmd, sParams, sStartPath) then
      begin
        ProcessExtCommandFork(sCmd, sParams, ActiveFrame.CurrentPath);
        Exit;
      end;

      sl := TStringList.Create;
      for I := 0 to SelectedFiles.Count - 1 do
      begin
        sl.Add(SelectedFiles[I].FullPath);
      end; // for

      // If only one file was selected then add all files in panel to the list.
      // Works only for directly accessible files and only when using internal viewer.
      if (sl.Count = 1) and (IsFile) and
         (not ACursor) and (not gExternalTools[etViewer].Enabled) and
         ([fspDirectAccess, fspLinksToLocalFiles] * ActiveFrame.FileSource.Properties <> []) then
        begin
          AllFiles := ActiveFrame.CloneFiles;

          LinksResolveNeeded := fspLinksToLocalFiles in ActiveFrame.FileSource.Properties;

          n := -1;
          for i := 0 to AllFiles.Count - 1 do
            begin
              aFile := AllFiles[i];
              if not (aFile.IsDirectory or aFile.IsLinkToDirectory) then
              begin
                if aFile.Name = ActiveFile.Name then
                  n := i;
                if LinksResolveNeeded then
                  ActiveFrame.FileSource.GetLocalName(aFile);
                if (n <> -1) and (i <> n) then
                  sl.Add(aFile.FullPath);
              end;
            end;

          for i:=0 to n-1 do
            begin
              aFile := AllFiles[i];
              if not (aFile.IsDirectory or aFile.IsLinkToDirectory) then
                sl.Add(aFile.FullPath);
            end;
        end;

      // if sl has files then view it
      if sl.Count > 0 then
        ShowViewerByGlobList(sl, aFileSource);
    except
      on e: EInvalidCommandLine do
        MessageDlg(rsToolErrorOpeningViewer,
          rsMsgInvalidCommandLine + ' (' + rsToolViewer + '):' + LineEnding + e.Message,
          mtError, [mbOK], 0);
    end;

  finally
    FreeAndNil(sl);
    FreeAndNil(AllFiles);
    FreeAndNil(SelectedFiles);
    FreeAndNil(ActiveFile);
  end;
end;

procedure TMainCommands.cm_QuickView(const Params: array of string);
var
  Param: String;
begin
  with frmMain do
  begin
    Param := GetDefaultParam(Params);
    if Assigned(QuickViewPanel) then
      begin
        QuickViewClose;
      end
    else if (param <> 'Close') then
      begin
        QuickViewShow(NotActiveNotebook.ActivePage, ActiveFrame);
      end;
  end;
end;

procedure TMainCommands.cm_BriefView(const Params: array of string);
var
  aFileView: TFileView;
begin
  with frmMain do
  begin
    aFileView:= TBriefFileView.Create(ActiveNotebook.ActivePage, ActiveFrame);
    ActiveNotebook.ActivePage.FileView:= aFileView;
    ActiveFrame.SetFocus;
  end;
end;

procedure TMainCommands.cm_LeftBriefView(const Params: array of string);
var
  aFileView: TFileView;
begin
  with frmMain do
  begin
    aFileView:= TBriefFileView.Create(LeftTabs.ActivePage, FrameLeft);
    LeftTabs.ActivePage.FileView:= aFileView;
  end;
end;

procedure TMainCommands.cm_RightBriefView(const Params: array of string);
var
  aFileView: TFileView;
begin
  with frmMain do
  begin
    aFileView:= TBriefFileView.Create(RightTabs.ActivePage, FrameRight);
    RightTabs.ActivePage.FileView:= aFileView;
  end;
end;

procedure TMainCommands.cm_ColumnsView(const Params: array of string);
var
  AParam: String;
  aFileView: TFileView;
begin
  with frmMain do
  begin
    GetParamValue(Params, 'columnset', AParam);
    if (ActiveFrame is TColumnsFileView) then
      TColumnsFileView(ActiveFrame).SetColumnSet(AParam)
    else begin
      aFileView:= TColumnsFileView.Create(ActiveNotebook.ActivePage, ActiveFrame, AParam);
      ActiveNotebook.ActivePage.FileView:= aFileView;
      ActiveFrame.SetFocus;
    end;
  end;
end;

procedure TMainCommands.cm_LeftColumnsView(const Params: array of string);
var
  AParam: String;
  aFileView: TFileView;
begin
  with frmMain do
  begin
    GetParamValue(Params, 'columnset', AParam);
    if (FrameLeft is TColumnsFileView) then
      TColumnsFileView(FrameLeft).SetColumnSet(AParam)
    else begin
      aFileView:= TColumnsFileView.Create(LeftTabs.ActivePage, FrameLeft, AParam);
      LeftTabs.ActivePage.FileView:= aFileView;
    end;
  end;
end;

procedure TMainCommands.cm_RightColumnsView(const Params: array of string);
var
  AParam: String;
  aFileView: TFileView;
begin
  with frmMain do
  begin
    GetParamValue(Params, 'columnset', AParam);
    if (FrameRight is TColumnsFileView) then
      TColumnsFileView(FrameRight).SetColumnSet(AParam)
    else begin
      aFileView:= TColumnsFileView.Create(RightTabs.ActivePage, FrameRight, AParam);
      RightTabs.ActivePage.FileView:= aFileView;
    end;
  end;
end;

procedure ToggleOrNotToOrFromThumbnailsView(WorkingFileView: TFileView; WorkingNotebook: TFileViewNotebook);
var
  aFileView: TFileView;
begin
  if WorkingFileView.ClassType <> TThumbFileView then
  begin
    // Save current file view type
    WorkingNotebook.ActivePage.BackupViewClass := TFileViewClass(WorkingFileView.ClassType);
    // Save current columns set name
    if (WorkingFileView is TColumnsFileView) then begin
      WorkingNotebook.ActivePage.BackupColumnSet:= TColumnsFileView(WorkingFileView).ActiveColm;
    end;
    // Create thumbnails view
    aFileView:= TThumbFileView.Create(WorkingNotebook.ActivePage, WorkingFileView);
  end
  else
  begin
    // Restore previous file view type
    if WorkingNotebook.ActivePage.BackupViewClass <> TColumnsFileView then
      aFileView:= WorkingNotebook.ActivePage.BackupViewClass.Create(WorkingNotebook.ActivePage, WorkingFileView)
    else
      aFileView:= TColumnsFileView.Create(WorkingNotebook.ActivePage, WorkingFileView, WorkingNotebook.ActivePage.BackupColumnSet);
  end;
  WorkingNotebook.ActivePage.FileView:= aFileView;
end;

procedure TMainCommands.cm_ThumbnailsView(const Params: array of string);
begin
  case frmMain.SelectedPanel of
    fpLeft: ToggleOrNotToOrFromThumbnailsView(frmMain.FrameLeft, frmMain.LeftTabs);
    fpRight: ToggleOrNotToOrFromThumbnailsView(frmMain.FrameRight, frmMain.RightTabs);
  end;
  frmMain.ActiveFrame.SetFocus;
end;

procedure TMainCommands.cm_LeftThumbView(const Params: array of string);
begin
  ToggleOrNotToOrFromThumbnailsView(frmMain.FrameLeft, frmMain.LeftTabs);
  frmMain.ActiveFrame.SetFocus;
end;

procedure TMainCommands.cm_RightThumbView(const Params: array of string);
begin
  ToggleOrNotToOrFromThumbnailsView(frmMain.FrameRight, frmMain.RightTabs);
  frmMain.ActiveFrame.SetFocus;
end;

procedure TMainCommands.cm_TreeView(const Params: array of string);
begin
  gSeparateTree := not gSeparateTree;
  with frmMain do
  begin
    DisableAutoSizing;
    try
      UpdateShellTreeView;
      UpdateTreeViewPath;
      MainSplitterPos:= MainSplitterPos;
    finally
      EnableAutoSizing;
    end;
  end;
end;

procedure TMainCommands.cm_Edit(const Params: array of string);
var
  I: Integer;
  aFile: TFile;
  sCmd: String = '';
  sParams: String = '';
  Param, AValue: String;
  sStartPath: String = '';
  ACursor: Boolean = False;
  SelectedFiles: TFiles = nil;
begin
  with frmMain do
  try
    if (Length(Params) > 0) then
    begin
      if GetParamValue(Params, 'cursor', AValue) then
        GetBoolValue(AValue, ACursor);
    end;

    if not ACursor then
      SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles
    else begin
      SelectedFiles:= TFiles.Create(ActiveFrame.CurrentPath);
      aFile:= ActiveFrame.CloneActiveFile;
      if aFile.IsNameValid then
        SelectedFiles.Add(aFile)
      else begin
        aFile.Free;
      end;
    end;

    for I := SelectedFiles.Count - 1 downto 0 do
    begin
      aFile := SelectedFiles[I];
      if aFile.IsDirectory or aFile.IsLinkToDirectory then
        SelectedFiles.Delete(I);
    end;

    if SelectedFiles.Count = 0 then
    begin
      msgWarning(rsMsgNoFilesSelected);
      Exit;
    end;

    if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnEditCopyOutStateChanged) <> pdrSynchronous then
      Exit;

    try

      // For now we only process one file.
      aFile := SelectedFiles[0];

      //now test if exists "EDIT" command in "extassoc.xml" :)
      if gExts.GetExtActionCmd(aFile, 'edit', sCmd, sParams, sStartPath) then
        ProcessExtCommandFork(sCmd, sParams, aFile.Path)
      else
        ShowEditorByGlob(aFile.FullPath);

    except
      on e: EInvalidCommandLine do
        MessageDlg(rsToolErrorOpeningEditor,
          rsMsgInvalidCommandLine + ' (' + rsToolEditor + '):' + LineEnding + e.Message,
          mtError, [mbOK], 0);
    end;

  finally
    FreeAndNil(SelectedFiles);
  end;
end;

procedure TMainCommands.cm_EditPath(const Params: array of string);
begin
  if gCurDir then frmMain.ActiveFrame.ExecuteCommand('cm_EditPath', Params);
end;

// Parameters:
// confirmation=
//   1/true            - show confirmation
//   0/false           - don't show confirmation
// queueid=            - by default put to this queue
//   <queue_identifier>
procedure TMainCommands.cm_Copy(const Params: array of string);
var
  bConfirmation, HasQueueId: Boolean;
  QueueIdentifier: TOperationsManagerQueueIdentifier;
begin
  bConfirmation := focCopy in gFileOperationsConfirmations;
  ReadCopyRenameParams(Params, bConfirmation, HasQueueId, QueueIdentifier);

  if HasQueueId then
    frmMain.CopyFiles(frmMain.NotActiveFrame.CurrentPath, bConfirmation, QueueIdentifier)
  else
    frmMain.CopyFiles(frmMain.NotActiveFrame.CurrentPath, bConfirmation);
end;

procedure TMainCommands.cm_CopyNoAsk(const Params: array of string);
begin
  frmMain.CopyFiles(frmMain.NotActiveFrame.CurrentPath, False);
end;

// Parameters:
// confirmation=
//   1/true            - show confirmation
//   0/false           - don't show confirmation
// queueid=            - by default put to this queue
//   <queue_identifier>
procedure TMainCommands.cm_Rename(const Params: array of string);
var
  bConfirmation, HasQueueId: Boolean;
  QueueIdentifier: TOperationsManagerQueueIdentifier;
begin
  bConfirmation := focMove in gFileOperationsConfirmations;
  ReadCopyRenameParams(Params, bConfirmation, HasQueueId, QueueIdentifier);

  if HasQueueId then
    frmMain.MoveFiles(frmMain.NotActiveFrame.CurrentPath, bConfirmation, QueueIdentifier)
  else
    frmMain.MoveFiles(frmMain.NotActiveFrame.CurrentPath, bConfirmation);
end;

procedure TMainCommands.cm_RenameNoAsk(const Params: array of string);
begin
  frmMain.MoveFiles(frmMain.NotActiveFrame.CurrentPath, False);
end;

procedure TMainCommands.cm_MakeDir(const Params: array of string);
var
  sPath: String;
  Files: TFiles;
  Directory: String;
  ActiveFile: TFile = nil;
  bMakeViaCopy: Boolean = False;
  Operation: TFileSourceOperation = nil;
  UI: TFileSourceOperationMessageBoxesUI = nil;
begin
  with frmMain do
  try
    if not (fsoCreateDirectory in ActiveFrame.FileSource.GetOperationsTypes) then
    begin
      if (fsoCopyIn in ActiveFrame.FileSource.GetOperationsTypes) then
        bMakeViaCopy := True
      else begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;
    end;

    ActiveFile := ActiveFrame.CloneActiveFile;
    if Assigned(ActiveFile) and ActiveFile.IsNameValid then
    begin
      if ActiveFile.IsDirectory or ActiveFile.IsLinkToDirectory then
        sPath := ActiveFile.Name
      else begin
        sPath := ActiveFile.NameNoExt;
      end;
    end
    else
      sPath := EmptyStr;

    if not ShowMkDir(frmMain, sPath) then Exit;   // show makedir dialog
    if (sPath = EmptyStr) then Exit;

    if bMakeViaCopy then
    begin
      Directory := GetTempName(GetTempFolderDeletableAtTheEnd, EmptyStr);
      if not mbForceDirectory(IncludeTrailingBackslash(Directory) + sPath) then
      begin
        MessageDlg(mbSysErrorMessage(GetLastOSError), mtError, [mbOK], 0);
        Exit;
      end;
      Files := TFiles.Create(Directory);
      sPath := IncludeTrailingBackslash(Directory) + ExtractWord(1, sPath, [PathDelim]);
      Files.Add(TFileSystemFileSource.CreateFileFromFile(sPath));
      Operation := ActiveFrame.FileSource.CreateCopyInOperation(TFileSystemFileSource.GetFileSource, Files, ActiveFrame.CurrentPath);
      if Assigned(Operation) then
      begin
        OperationsManager.AddOperation(Operation);
        Operation := nil;
      end;
      Exit;
    end;

    Operation := ActiveFrame.FileSource.CreateCreateDirectoryOperation(ActiveFrame.CurrentPath, sPath);
    if Assigned(Operation) then
    begin
      // Call directly - not through operations manager.
      UI := TFileSourceOperationMessageBoxesUI.Create;
      Operation.AddUserInterface(UI);
      Operation.Execute;

      sPath := ExtractFileName(ExcludeTrailingPathDelimiter(sPath));
      ActiveFrame.SetActiveFile(sPath);
    end;
  finally
    FreeAndNil(Operation);
    FreeAndNil(UI);
    FreeAndNil(ActiveFile);
  end;
end;

// Parameters:
// trashcan=
//   1/true            - delete to trash can
//   0/false           - delete directly
//   setting           - if gUseTrash then delete to trash, otherwise delete directly
//   reversesetting    - if gUseTrash then delete directly, otherwise delete to trash
// confirmation=
//   1/true            - show confirmation
//   0/false           - don't show confirmation
//
// Deprecated:
// "recycle"           - delete to trash can
// "norecycle"         - delete directly
// "recyclesetting"    - if gUseTrash then delete to trash, otherwise delete directly
// "recyclesettingrev" - if gUseTrash then delete directly, otherwise delete to trash
procedure TMainCommands.cm_Delete(const Params: array of string);
var
  I: Integer;
  Message: String;
  theFilesToDelete: TFiles;
  // 12.05.2009 - if delete to trash, then show another messages
  MsgDelSel, MsgDelFlDr : string;
  Operation: TFileSourceOperation;
  bRecycle: Boolean;
  bConfirmation, HasConfirmationParam: Boolean;
  Param, ParamTrashCan: String;
  BoolValue: Boolean;
  QueueId: TOperationsManagerQueueIdentifier = FreeOperationsQueueId;
begin
  with frmMain.ActiveFrame do
  begin
    if not (fsoDelete in FileSource.GetOperationsTypes) then
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
    end;

    bRecycle := gUseTrash;
    HasConfirmationParam := False;

    for Param in Params do
    begin
      if Param = 'recycle' then
        bRecycle := True
      else if Param = 'norecycle' then
        bRecycle := False
      else if Param = 'recyclesetting' then
        bRecycle := gUseTrash
      else if Param = 'recyclesettingrev' then
        bRecycle := not gUseTrash
      else if GetParamValue(Param, 'trashcan', ParamTrashCan) then
      begin
        if ParamTrashCan = 'setting' then
          bRecycle := gUseTrash
        else if ParamTrashCan = 'reversesetting' then
          bRecycle := not gUseTrash
        else if GetBoolValue(ParamTrashCan, BoolValue) then
          bRecycle := BoolValue;
      end
      else if GetParamBoolValue(Param, 'confirmation', BoolValue) then
      begin
        HasConfirmationParam := True;
        bConfirmation := BoolValue;
      end;
    end;

    // Save parameter for later use
    BoolValue := bRecycle;

    if bRecycle then
      bRecycle := FileSource.IsClass(TFileSystemFileSource) and
                  mbCheckTrash(CurrentPath);

    if not HasConfirmationParam then
    begin
      if not bRecycle then
        bConfirmation := focDelete in gFileOperationsConfirmations
      else
        bConfirmation := focDeleteToTrash in gFileOperationsConfirmations;
    end;

    // Showing delete dialog: to trash or to /dev/null :)
    If bRecycle then
     begin
      MsgDelSel := rsMsgDelSelT;
      MsgDelFlDr := rsMsgDelFlDrT;
     end
    else
     begin
      MsgDelSel := rsMsgDelSel;
      MsgDelFlDr := rsMsgDelFlDr;
     end;

    // Special case for fspLinksToLocalFiles
    if (fspLinksToLocalFiles in FileSource.Properties) then
      bRecycle := BoolValue;

    // ------------------------------------------------------

    theFilesToDelete := CloneSelectedOrActiveFiles; // free at Thread end by thread

    if Assigned(theFilesToDelete) then
    try
      if (theFilesToDelete.Count = 0) then Exit;
      if (theFilesToDelete.Count = 1) then
        Message:= Format(MsgDelSel, [theFilesToDelete[0].Name])
      else begin
         Message:= Format(MsgDelFlDr, [theFilesToDelete.Count]) + LineEnding;
         for I:= 0 to Min(4, theFilesToDelete.Count - 1) do
         begin
           Message+= LineEnding + theFilesToDelete[I].Name;
         end;
         if theFilesToDelete.Count > 5 then Message+= LineEnding + '...';
      end;
      if (bConfirmation = False) or (ShowDeleteDialog(Message, FileSource, QueueId)) then
      begin
        if FileSource.IsClass(TFileSystemFileSource) then
        begin
          if frmMain.NotActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
          begin
            for I:= 0 to theFilesToDelete.Count - 1 do
            begin
              if (theFilesToDelete[I].IsDirectory or theFilesToDelete[I].IsLinkToDirectory) and
                 IsInPath(theFilesToDelete[I].FullPath, frmMain.NotActiveFrame.CurrentPath, True, True) then
              begin
                frmMain.NotActiveFrame.CurrentPath:= theFilesToDelete.Path;
                Break;
              end;
            end;
          end
          else if frmMain.NotActiveFrame.FileSource.IsClass(TArchiveFileSource) then
          begin
            Message:= (frmMain.NotActiveFrame.FileSource as TArchiveFileSource).ArchiveFileName;
            for I:= 0 to theFilesToDelete.Count - 1 do
            begin
              if IsInPath(theFilesToDelete[I].FullPath, Message, True, True) then
              begin
                SetFileSystemPath(frmMain.NotActiveFrame, theFilesToDelete.Path);
                Break;
              end;
            end;
          end;
        end;

        Operation := FileSource.CreateDeleteOperation(theFilesToDelete);

        if Assigned(Operation) then
        begin
          // Special case for filesystem - 'recycle' parameter.
          if Operation is TFileSystemDeleteOperation then
            with Operation as TFileSystemDeleteOperation do
            begin
              // 30.04.2009 - передаем параметр корзины в поток.
              Recycle := bRecycle;
            end;

          // Start operation.
          OperationsManager.AddOperation(Operation, QueueId, False);
        end
        else
        begin
          msgWarning(rsMsgNotImplemented);
        end;
      end;

    finally
      FreeAndNil(theFilesToDelete);
    end;
  end;
end;

procedure TMainCommands.cm_CheckSumCalc(const Params: array of string);
var
  I: Integer;
  sFileName: String;
  SelectedFiles: TFiles;
  HashAlgorithm: THashAlgorithm;
  TextLineBreakStyle: TTextLineBreakStyle;
  QueueId: TOperationsManagerQueueIdentifier;
  Operation: TFileSourceCalcChecksumOperation;
  bSeparateFile, bOpenFileAfterJobCompleted: Boolean;
begin
  // This will work only for filesystem.
  // For other file sources use temp file system when it's done.

  with frmMain do
  begin
    if not (fsoCalcChecksum in ActiveFrame.FileSource.GetOperationsTypes) then
    begin
      msgWarning(rsMsgNotImplemented);
      Exit;
      // Create temp file source.
      // CopyOut ActiveFrame.FileSource to TempFileSource.
      // Do command on TempFileSource and later delete it (or leave cached on disk?)
    end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      if SelectedFiles.Count = 0 then
      begin
        msgWarning(rsMsgNoFilesSelected);
        Exit;
      end;

      bSeparateFile:= False;
      bOpenFileAfterJobCompleted:= False;
      for I := 0 to SelectedFiles.Count - 1 do // find files in selection
        if not SelectedFiles[I].IsDirectory then
          begin
            bSeparateFile:= True;
            Break;
          end;

      if SelectedFiles.Count > 1 then
        sFileName:= ActiveFrame.CurrentPath + MakeFileName(ActiveFrame.CurrentPath, 'checksum')
      else
        sFileName:= ActiveFrame.CurrentPath + SelectedFiles[0].Name;

      if ShowCalcCheckSum(sFileName, bSeparateFile, HashAlgorithm, bOpenFileAfterJobCompleted, TextLineBreakStyle, QueueId) then
      begin
        Operation := ActiveFrame.FileSource.CreateCalcChecksumOperation(
                       SelectedFiles, ActiveFrame.CurrentPath, sFileName) as TFileSourceCalcChecksumOperation;

        if Assigned(Operation) then
        begin
          Operation.Mode := checksum_calc;
          Operation.OneFile := not bSeparateFile;
          Operation.TextLineBreakStyle:= TextLineBreakStyle;
          Operation.OpenFileAfterOperationCompleted := bOpenFileAfterJobCompleted;
          Operation.Algorithm := HashAlgorithm;

          // Start operation.
          OperationsManager.AddOperation(Operation, QueueId, False);
        end
        else
        begin
          msgWarning(rsMsgNotImplemented);
        end;
      end;

    finally
      if Assigned(SelectedFiles) then
        FreeAndNil(SelectedFiles);
    end;
  end;
end;

procedure TMainCommands.cm_CheckSumVerify(const Params: array of string);
var
  I: Integer;
  Hash: String;
  Param: String;
  BoolValue: Boolean;
  SelectedFiles: TFiles;
  Algorithm: THashAlgorithm;
  Operation: TFileSourceCalcChecksumOperation;
  bConfirmation, HasConfirmationParam: Boolean;
  QueueId: TOperationsManagerQueueIdentifier = FreeOperationsQueueId;
begin
  // This will work only for filesystem.
  // For other file sources use temp file system when it's done.

  with frmMain do
  begin
    if not (fsoCalcChecksum in ActiveFrame.FileSource.GetOperationsTypes) then
    begin
      msgWarning(rsMsgNotImplemented);
      Exit;
      // Create temp file source.
      // CopyOut ActiveFrame.FileSource to TempFileSource.
      // Do command on TempFileSource and later delete it (or leave cached on disk?)
    end;

    HasConfirmationParam := False;

    for Param in Params do
    begin
      if GetParamBoolValue(Param, 'confirmation', BoolValue) then
      begin
        HasConfirmationParam := True;
        bConfirmation := BoolValue;
      end;
    end;
    if not HasConfirmationParam then begin
      bConfirmation := focVerifyChecksum in gFileOperationsConfirmations;
    end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      if SelectedFiles.Count = 0 then
      begin
        msgWarning(rsMsgNoFilesSelected);
        Exit;
      end;

      for I := 0 to SelectedFiles.Count - 1 do // find files in selection
        if not FileExtIsHash(SelectedFiles[I].Extension) then
        begin
          if (SelectedFiles.Count > 1) or (SelectedFiles[I].IsDirectory) or
             (SelectedFiles[I].IsLinkToDirectory) then
          begin
            msgError(rsMsgSelectOnlyCheckSumFiles);
            Exit;
          end
          else begin
            if not ShowCalcVerifyCheckSum(Hash, Algorithm, QueueId) then
              Exit;
            bConfirmation:= False;
          end;
        end;

      if (bConfirmation = False) or (ShowDeleteDialog(rsMsgVerifyChecksum, ActiveFrame.FileSource, QueueId)) then
      begin
        Operation := ActiveFrame.FileSource.CreateCalcChecksumOperation(
                       SelectedFiles, Hash, '') as TFileSourceCalcChecksumOperation;

        if Assigned(Operation) then
        begin
          Operation.Algorithm := Algorithm;
          Operation.AddStateChangedListener([fsosStopped], @OnCalcChecksumStateChanged);
          Operation.Mode := checksum_verify;

          // Start operation.
          OperationsManager.AddOperation(Operation, QueueId, False);
        end
        else
        begin
          msgWarning(rsMsgNotImplemented);
        end;
      end;
    finally
      if Assigned(SelectedFiles) then
        FreeAndNil(SelectedFiles);
    end;
  end;
end;

procedure TMainCommands.cm_FocusCmdLine(const Params: array of string);
begin
  frmMain.ShowCommandLine(True);
end;

{ TMainCommands.cm_FileAssoc }
procedure TMainCommands.cm_FileAssoc(const Params: array of string);
var
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
begin
  Options := ShowOptions(TfrmOptionsFileAssoc);
  Application.ProcessMessages;
  Editor := Options.GetEditor(TfrmOptionsFileAssoc);
  if Editor.CanFocus then Editor.SetFocus;
  TfrmOptionsFileAssoc(Editor).MakeUsInPositionToWorkWithActiveFile;
end;

procedure TMainCommands.cm_HelpIndex(const Params: array of string);
begin
  ShowHelpOrErrorForKeyword('', '/index.html');
end;

procedure TMainCommands.cm_Keyboard(const Params: array of string);
begin
  ShowHelpOrErrorForKeyword('', '/shortcuts.html');
end;

procedure TMainCommands.cm_VisitHomePage(const Params: array of string);
var
  ErrMsg: String = '';
begin
  dmHelpMgr.HTMLHelpDatabase.ShowURL('https://doublecmd.sourceforge.io','Double Commander Web Site', ErrMsg);
end;

procedure TMainCommands.cm_About(const Params: array of string);
begin
  ShowAboutBox(frmMain);
end;

procedure TMainCommands.cm_ShowSysFiles(const Params: array of string);
begin
  with frmMain do
  begin
    uGlobs.gShowSystemFiles:= not uGlobs.gShowSystemFiles;
    actShowSysFiles.Checked:= uGlobs.gShowSystemFiles;
    UpdateTreeView;
    // Update all tabs
    ForEachView(@DoUpdateFileView, nil);
  end;
end;

procedure TMainCommands.cm_SwitchIgnoreList(const Params: array of string);
{$OPTIMIZATION OFF}
var
  WantedIgnoreList, BoolValue:boolean;
begin
  WantedIgnoreList:=gIgnoreListFileEnabled;
  with frmMain do
  begin
    if Length(Params)>0 then
    begin
      if GetParamBoolValue(Params[0], 'ignorelist', BoolValue) then
        WantedIgnoreList:=BoolValue
      else
        WantedIgnoreList := not WantedIgnoreList;
    end
    else
    begin
      WantedIgnoreList := not WantedIgnoreList;
    end;

    if WantedIgnoreList<>gIgnoreListFileEnabled then
    begin
      gIgnoreListFileEnabled:=WantedIgnoreList;
      actSwitchIgnoreList.Checked:= gIgnoreListFileEnabled;
      //repaint both panels
      FrameLeft.Reload;
      FrameRight.Reload;
    end;
  end;
end;
{$OPTIMIZATION DEFAULT}

// Parameter is name of TOptionsEditorClass.
procedure TMainCommands.cm_Options(const Params: array of string);
begin
  ShowOptions(GetDefaultParam(Params));
end;

procedure TMainCommands.cm_CompareContents(const Params: array of string);
var
  FilesNumber: Integer = 0;
  DirsNumber: Integer = 0;

  procedure CountFiles(const Files: TFiles);
  var I: Integer;
  begin
    if Assigned(Files) then
      for I := 0 to Files.Count - 1 do
        if Files[I].IsDirectory then
          Inc(DirsNumber)
        else
          Inc(FilesNumber);
  end;

var
  I : Integer;
  Param: String;
  ActiveSelectedFiles: TFiles = nil;
  NotActiveSelectedFiles: TFiles = nil;
  FirstFileSource: IFileSource = nil;
  FirstFileSourceFiles: TFiles = nil;
  SecondFileSource: IFileSource = nil;
  SecondFileSourceFiles: TFiles = nil;
begin
  with frmMain do
  begin
    Param := GetDefaultParam(Params);

    if Param = 'dir' then
    begin
      if gExternalTools[etDiffer].Enabled then
        ShowDifferByGlob(FrameLeft.CurrentPath, FrameRight.CurrentPath)
      else
        MsgWarning(rsMsgNotImplemented);
      Exit;
    end;

    try
      ActiveSelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;

      if ActiveSelectedFiles.Count = 1 then
      begin
        // If no files selected in the opposite panel and panels have
        // different path then try to get file with the same name.
        if (not NotActiveFrame.HasSelectedFiles) and (not mbCompareFileNames(NotActiveFrame.CurrentPath, ActiveFrame.CurrentPath)) then
        begin
          for I := 0 to NotActiveFrame.DisplayFiles.Count - 1 do
            if mbCompareFileNames(NotActiveFrame.DisplayFiles[I].FSFile.Name, ActiveSelectedFiles[0].Name) then
            begin
              NotActiveSelectedFiles := TFiles.Create(NotActiveFrame.CurrentPath);
              NotActiveSelectedFiles.Add(NotActiveFrame.DisplayFiles[I].FSFile.Clone);
              Break;
            end;
        end;

        if not Assigned(NotActiveSelectedFiles) then
          NotActiveSelectedFiles := NotActiveFrame.CloneSelectedOrActiveFiles;

        if NotActiveSelectedFiles.Count <> 1 then
        begin
          // Only one file selected in active panel.
          MsgWarning(rsMsgInvalidSelection);
          Exit;
        end;

        { compare single selected files in both panels }

        case gResultingFramePositionAfterCompare of
          rfpacActiveOnLeft:
            begin
              FirstFileSource := ActiveFrame.FileSource;
              FirstFileSourceFiles := ActiveSelectedFiles;
              SecondFileSource := NotActiveFrame.FileSource;
              SecondFileSourceFiles := NotActiveSelectedFiles;
            end;
          rfpacLeftOnLeft:
            begin
              if ActiveFrame = FrameLeft then
              begin
                FirstFileSource := ActiveFrame.FileSource;
                FirstFileSourceFiles := ActiveSelectedFiles;
                SecondFileSource := NotActiveFrame.FileSource;
                SecondFileSourceFiles := NotActiveSelectedFiles;
              end
              else begin
                FirstFileSource := NotActiveFrame.FileSource;
                FirstFileSourceFiles := NotActiveSelectedFiles;
                SecondFileSource := ActiveFrame.FileSource;
                SecondFileSourceFiles := ActiveSelectedFiles;
              end;
            end;
        end;
      end
      else if ActiveSelectedFiles.Count > 1 then
      begin
        { compare all selected files in active frame }

        FirstFileSource := ActiveFrame.FileSource;
        FirstFileSourceFiles := ActiveSelectedFiles;
      end;

      CountFiles(FirstFileSourceFiles);
      CountFiles(SecondFileSourceFiles);

      if ((FilesNumber > 0) and (DirsNumber > 0))
      or ((FilesNumber = 1) or (DirsNumber = 1)) then
        // Either files or directories must be selected and more than one.
        MsgWarning(rsMsgInvalidSelection)
      else if (FilesNumber = 0) and (DirsNumber = 0) then
        MsgWarning(rsMsgNoFilesSelected)
      else if (FilesNumber > 2) and not gExternalTools[etDiffer].Enabled then
        MsgWarning(rsMsgTooManyFilesSelected)
      else if (DirsNumber > 0) and not gExternalTools[etDiffer].Enabled then
        MsgWarning(rsMsgNotImplemented)
      else
      begin
        if not Assigned(SecondFileSource) then
          PrepareToolData(FirstFileSource, FirstFileSourceFiles,
                          @ShowDifferByGlobList)
        else
          PrepareToolData(FirstFileSource, FirstFileSourceFiles,
                          SecondFileSource, SecondFileSourceFiles,
                          @ShowDifferByGlobList);
      end;

    finally
      ActiveSelectedFiles.Free;
      NotActiveSelectedFiles.Free;
    end;
  end;
end;

{ TMainCommands.cm_ShowMainMenu }
procedure TMainCommands.cm_ShowMainMenu(const Params: array of string);
{$OPTIMIZATION OFF}
var
  WantedMainMenu, BoolValue: boolean;
  bWantedTreeViewMenu: boolean = False;
  Param: string;
  sMaybeMenuItem: TMenuItem;
  iWantedPosX: integer = 0;
  iWantedPosY: integer = 0;
  iWantedWidth: integer = 800;
  iWantedHeight: integer = 600;
begin
  WantedMainMenu:=gMainMenu;

  if Length(Params)>0 then
  begin
    for Param in Params do
      if GetParamBoolValue(Param, 'menu', BoolValue) then WantedMainMenu := BoolValue
      else if GetParamBoolValue(Param, 'treeview', BoolValue) then bWantedTreeViewMenu := BoolValue
      else WantedMainMenu := not WantedMainMenu;
  end
  else
  begin
    WantedMainMenu := not WantedMainMenu;
  end;

  if not bWantedTreeViewMenu then
  begin
  if WantedMainMenu<>gMainMenu then
  begin
    gMainMenu:=WantedMainMenu;
    DoShowMainMenu(gMainMenu);
    end;
  end
  else
  begin
    DoComputeSizeAndPosForWindowInMiddle(iWantedPosX, iWantedPosY, iWantedWidth, iWantedHeight);
    sMaybeMenuItem := GetUserChoiceFromTreeViewMenuLoadedFromPopupMenu(frmMain.mnuMain, tvmcMainMenu, iWantedPosX, iWantedPosY, iWantedWidth, iWantedHeight);
    if sMaybeMenuItem <> nil then
    begin
      if sMaybeMenuItem.Action <> nil then
      begin
        if sMaybeMenuItem.Action.OnExecute<>nil then
          sMaybeMenuItem.Action.OnExecute(sMaybeMenuItem.Action)
      end
      else
        if sMaybeMenuItem.OnClick<>nil then
          sMaybeMenuItem.OnClick(sMaybeMenuItem);
    end;
  end;
end;
{$OPTIMIZATION DEFAULT}

procedure TMainCommands.cm_Refresh(const Params: array of string);
begin
  with frmMain do
  begin
    ActiveFrame.FileSource.Reload(ActiveFrame.CurrentPath);
    ActiveFrame.Reload(True);
    if ActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
    begin
      UpdateDiskCount;
      UpdateSelectedDrives;
    end;
  end;
end;

//------------------------------------------------------

{ TMainCommands.DoActualMarkUnMark }
procedure TMainCommands.DoActualMarkUnMark(const Params: array of string; bSelect: boolean);
var
  iParameter: integer;
  sWantedMask, sParamValue: string;
  sAttribute: string = '';
  bWantedCaseSensitive, bWantedIgnoreAccents, bWantedWindowsInterpretation: boolean;
  pbWantedCaseSensitive, pbWantedIgnoreAccents, pbWantedWindowsInterpretation: PBoolean;
  psAttribute: pString = nil;
  MarkSearchTemplateRec: TSearchTemplateRec;
  MarkFileChecks: TFindFileChecks;
begin
   if frmMain.ActiveFrame is TColumnsFileView then
   begin
     if TColumnsFileView(frmMain.ActiveFrame).isSlave then
     begin
      ShowSelectDuplicates(frmMain, frmMain.ActiveFrame);
      Exit;
     end;
   end;

  sWantedMask := '';
  pbWantedCaseSensitive := nil;
  pbWantedIgnoreAccents := nil;
  pbWantedWindowsInterpretation := nil;

  for iParameter:=0 to pred(Length(Params)) do
  begin
    if GetParamValue(Params[iParameter], 'mask', sParamValue) then sWantedMask := sParamValue
    else if GetParamBoolValue(Params[iParameter], 'casesensitive', bWantedCaseSensitive) then pbWantedCaseSensitive := @bWantedCaseSensitive
    else if GetParamBoolValue(Params[iParameter], 'ignoreaccents', bWantedIgnoreAccents) then pbWantedIgnoreAccents := @bWantedIgnoreAccents
    else if GetParamBoolValue(Params[iParameter], 'windowsinterpretation', bWantedWindowsInterpretation) then pbWantedWindowsInterpretation := @bWantedWindowsInterpretation
    else if GetParamValue(Params[iParameter], 'attr', sAttribute) then psAttribute := @sAttribute;
  end;

  // When mask is specified, we don't prompt the user
  if sWantedMask<>'' then
  begin
    if psAttribute <> nil then MarkSearchTemplateRec.AttributesPattern := psAttribute^ else MarkSearchTemplateRec.AttributesPattern := gMarkDefaultWantedAttribute;
    AttrsPatternOptionsToChecks(MarkSearchTemplateRec, MarkFileChecks);
    frmMain.ActiveFrame.MarkGroup(sWantedMask, bSelect, pbWantedCaseSensitive, pbWantedIgnoreAccents, pbWantedWindowsInterpretation, @MarkFileChecks)
  end
  else
  begin
    frmMain.ActiveFrame.MarkGroup(bSelect, pbWantedCaseSensitive, pbWantedIgnoreAccents, pbWantedWindowsInterpretation, psAttribute)
  end;
end;

{ TMainCommands.DoActualMarkApplyOnAll }
procedure TMainCommands.DoActualMarkApplyOnAll(const maoaDispatcher: TMarkApplyOnAllDispatcher; const Params: array of string);
var
  iParameter: integer;
  sAttribute, sParam: string;
  MarkSearchTemplateRec: TSearchTemplateRec;
  MarkFileChecks: TFindFileChecks;
begin
  sAttribute := gMarkDefaultWantedAttribute;
  for iParameter:=0 to pred(Length(Params)) do
    if GetParamValue(Params[iParameter], 'attr', sParam) then sAttribute := sParam;
  MarkSearchTemplateRec.AttributesPattern := sAttribute;
  AttrsPatternOptionsToChecks(MarkSearchTemplateRec, MarkFileChecks);
  frmMain.ActiveFrame.MarkApplyOnAllFiles(maoaDispatcher, MarkFileChecks);
end;

{ TMainCommands.cm_MarkMarkAll }
procedure TMainCommands.cm_MarkMarkAll(const Params: array of string);
begin
  DoActualMarkApplyOnAll(tmaoa_Mark, Params);
end;

{ TMainCommands.cm_MarkUnmarkAll }
procedure TMainCommands.cm_MarkUnmarkAll(const Params: array of string);
begin
  DoActualMarkApplyOnAll(tmaoa_UnMark, Params);
end;

{ TMainCommands.cm_MarkInvert }
procedure TMainCommands.cm_MarkInvert(const Params: array of string);
begin
  DoActualMarkApplyOnAll(tmaoa_InvertMark, Params);
end;

{ TMainCommands.cm_MarkPlus }
procedure TMainCommands.cm_MarkPlus(const Params: array of string);
begin
  DoActualMarkUnMark(Params, True);
end;

{ TMainCommands.cm_MarkMinus }
procedure TMainCommands.cm_MarkMinus(const Params: array of string);
begin
  DoActualMarkUnMark(Params, False);
end;

procedure TMainCommands.cm_MarkCurrentName(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentName(True);
end;

procedure TMainCommands.cm_UnmarkCurrentName(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentName(False);
end;

procedure TMainCommands.cm_MarkCurrentNameExt(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentNameExt(True);
end;

procedure TMainCommands.cm_UnmarkCurrentNameExt(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentNameExt(False);
end;

procedure TMainCommands.cm_MarkCurrentExtension(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentExtension(True);
end;

procedure TMainCommands.cm_UnmarkCurrentExtension(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentExtension(False);
end;

procedure TMainCommands.cm_MarkCurrentPath(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentPath(True);
end;

procedure TMainCommands.cm_UnmarkCurrentPath(const Params: array of string);
begin
  frmMain.ActiveFrame.MarkCurrentPath(False);
end;

procedure TMainCommands.cm_SaveSelection(const Params: array of string);
begin
  frmMain.ActiveFrame.SaveSelection;
end;

procedure TMainCommands.cm_RestoreSelection(const Params: array of string);
begin
  frmMain.ActiveFrame.RestoreSelection;
end;

procedure TMainCommands.cm_SaveSelectionToFile(const Params: array of string);
begin
  frmMain.ActiveFrame.SaveSelectionToFile(GetDefaultParam(Params));
end;

procedure TMainCommands.cm_LoadSelectionFromFile(const Params: array of string);
begin
  frmMain.ActiveFrame.LoadSelectionFromFile(GetDefaultParam(Params));
end;

procedure TMainCommands.cm_LoadSelectionFromClip(const Params: array of string);
begin
  frmMain.ActiveFrame.LoadSelectionFromClipboard;
end;

{ TMainCommands.DoParseParametersForPossibleTreeViewMenu }
procedure TMainCommands.DoParseParametersForPossibleTreeViewMenu(const Params: array of string; gDefaultConfigWithCommand, gDefaultConfigWithDoubleClick:boolean; var bUseTreeViewMenu:boolean; var bUsePanel:boolean; var p: TPoint);
var
  Param, sValue: string;
  bSpecifiedPopup: boolean = false;
  bSpecifiedTreeView: boolean = false;
  bSpecifiedPanel: boolean = false;
  bSpecifiedMouse: boolean = false;
begin
  for Param in Params do
  begin
    if GetParamValue(Param, 'menutype', sValue) then
    begin
      if (sValue = 'popup') OR (sValue = 'combobox') then bSpecifiedPopup := True else
      if sValue = 'treeview' then bSpecifiedTreeView := True;
    end
    else if GetParamValue(Param, 'position', sValue) then
    begin
      if sValue = 'panel' then bSpecifiedPanel:=true else
      if sValue = 'cursor' then bSpecifiedMouse:=true;
    end;
  end;

  if (not bSpecifiedPopup) AND (bSpecifiedTreeView OR (not bSpecifiedMouse AND gDefaultConfigWithCommand) OR (bSpecifiedMouse AND gDefaultConfigWithDoubleClick)) then
    bUseTreeViewMenu:=True;
  if bSpecifiedPanel OR (not bSpecifiedMouse AND bUsePanel) then
  begin
    p := frmMain.ActiveFrame.ClientToScreen(Classes.Point(0, 0));
    bUsePanel := True;
  end
  else
  begin
    p := Mouse.CursorPos;
    bUsePanel := False;
  end;
end;

{ TMainCommands.cm_DirHotList }
// Command to SHOW the Directory Hotlist popup menu
// The directory popup hotlist is run-time continously regenerated each time command is invoken.
// If any param is provided, it is assume the popup menu as to be shown where the mouse cursor is which is friendly with user since it minimize mouse travel.
//
procedure TMainCommands.cm_DirHotList(const Params: array of string);
var
  bUseTreeViewMenu: boolean = false;
  bUsePanel: boolean = true;
  p: TPoint = (x:0; y:0);
  iWantedWidth: integer = 0;
  iWantedHeight: integer = 0;
  sMaybeMenuItem: TMenuItem = nil;
begin
  // 1. Let's parse our parameters.
  DoParseParametersForPossibleTreeViewMenu(Params, gUseTreeViewMenuWithDirectoryHotlistFromMenuCommand, gUseTreeViewMenuWithDirectoryHotlistFromDoubleClick, bUseTreeViewMenu, bUsePanel, p);

  // 2. No matter what, we need to fill in the popup menu structure.
  gDirectoryHotlist.PopulateMenuWithHotDir(frmMain.pmHotList,@frmMain.HotDirSelected,@frmMain.miHotAddOrConfigClick,mpHOTDIRSWITHCONFIG,0); // TODO: i thing in future this must call on create or change
    Application.ProcessMessages; //TODO: Same thing as with "cm_DirHotList", in Windows, Not sure why, but on all system I tried, this eliminate a "beep" when the popup is shown.

  // 3. Show the appropriate menu.
  if bUseTreeViewMenu then
  begin
    if not bUsePanel then
      iWantedHeight := 0
    else
    begin
      iWantedWidth := frmMain.ActiveFrame.Width;
      iWantedHeight := frmMain.ActiveFrame.Height;
    end;

    sMaybeMenuItem := GetUserChoiceFromTreeViewMenuLoadedFromPopupMenu(frmMain.pmHotList, tvmcHotDirectory, p.X, p.Y, iWantedWidth, iWantedHeight);
    if sMaybeMenuItem <> nil then sMaybeMenuItem.OnClick(sMaybeMenuItem);
  end
  else
  begin
    frmMain.pmHotList.Popup(p.X,p.Y);
  end;
end;

{ TMainCommands.cm_ConfigDirHotList }
// Mainly present for backward compatibility since "cm_ConfigDirHotList" existed before.
//
procedure TMainCommands.cm_ConfigDirHotList(const Params: array of string);
begin
  cm_WorkWithDirectoryHotlist(['action=config', 'source='+QuoteStr(frmMain.ActiveFrame.CurrentLocation), 'target='+QuoteStr(frmMain.NotActiveFrame.CurrentLocation), 'index=0']);
end;

{ TMainCommands.cm_WorkWithDirectoryHotlist }
// The parameter 0, in text, indicate the job to do to generic "SubmitToAddOrConfigToHotDirDlg" routine.
// This way, "SubmitToAddOrConfigToHotDirDlg" is to entry point to attempt to do anything in the Directory Hotlist conifguration screen.
//
procedure TMainCommands.cm_WorkWithDirectoryHotlist(const Params: array of string);
var
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
  SearchingIndex, WantedAction, WantedIndexToEdit: integer;
  WantedSourcePath, WantedTargetPath : string;
  Param, sValue: String;
begin
  //1o) Let's set our default values
  WantedAction := ACTION_INVALID;
  WantedSourcePath := frmMain.ActiveFrame.CurrentPath;
  WantedTargetPath := frmMain.NotActiveFrame.CurrentPath;
  WantedIndexToEdit := 0;

  //2o) Let's parse the parameter to get the wanted ones
  for Param in Params do
  begin
    if GetParamValue(Param, 'action', sValue) then
    begin
      SearchingIndex:=1;
      while ( (SearchingIndex<=length(HOTLISTMAGICWORDS)) AND (WantedAction = ACTION_INVALID) ) do
        if sValue=HOTLISTMAGICWORDS[SearchingIndex] then WantedAction:=SearchingIndex else inc(SearchingIndex);
    end
    else if GetParamValue(Param, 'source', sValue) then
    begin
      sValue:=RemoveQuotation(PrepareParameter(sValue));
      if (sValue<>'') and (not HasPathInvalidCharacters(sValue)) then WantedSourcePath:=sValue;
    end
    else if GetParamValue(Param, 'target', sValue) then
    begin
      sValue:=RemoveQuotation(PrepareParameter(sValue));
      if (sValue<>'') and (not HasPathInvalidCharacters(sValue)) then WantedTargetPath:=sValue;
    end
    else if GetParamValue(Param, 'index', sValue) then
    begin
      WantedIndexToEdit:=(strtointdef(sValue,0));
    end;
  end;
  if WantedAction=ACTION_INVALID then WantedAction:=ACTION_JUSTSHOWCONFIGHOTLIST;

  //3o) Let's do the sorting job now!
  Options := ShowOptions(TfrmOptionsDirectoryHotlist);
  Editor := Options.GetEditor(TfrmOptionsDirectoryHotlist);
  Application.ProcessMessages;
  if Editor.CanFocus then  Editor.SetFocus;
  TfrmOptionsDirectoryHotlist(Editor).SubmitToAddOrConfigToHotDirDlg(WantedAction, WantedSourcePath, WantedTargetPath, WantedIndexToEdit);
end;

procedure TMainCommands.cm_Search(const Params: array of string);
var
  TemplateName: String;
begin
  if not (frmMain.ActiveFrame.FileSource.IsClass(TFileSystemFileSource) or
    frmMain.ActiveFrame.FileSource.IsClass(TWcxArchiveFileSource)) then
  begin
    msgError(rsMsgErrNotSupported)
  end
  else begin
    if Length(Params) > 0 then
      TemplateName:= Params[0]
    else begin
      TemplateName:= gSearchDefaultTemplate;
    end;
    ShowFindDlg(frmMain.ActiveFrame, TemplateName);
  end;
end;

procedure TMainCommands.cm_SyncDirs(const Params: array of string);
var
  OperationType: TFileSourceOperationType;
begin
  with frmMain do
  begin
    if GetCopyOperationType(FrameLeft.FileSource, FrameRight.FileSource, OperationType) or
       GetCopyOperationType(FrameRight.FileSource, FrameLeft.FileSource, OperationType) then
    begin
      ShowSyncDirsDlg(FrameLeft, FrameRight);
    end
    else begin
      msgWarning(rsMsgErrNotSupported);
    end;
  end;
end;

//------------------------------------------------------

procedure TMainCommands.cm_SymLink(const Params: array of string);
var
  sExistingFile, sLinkToCreate: String;
  SelectedFiles: TFiles;
begin
  with frmMain do
  begin
    // Symlinks work only for file system.
    if not (ActiveFrame.FileSource.IsClass(TFileSystemFileSource)) then
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
      // Or create a symlink in temp filesystem and CopyIn to target file source.
    end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      if SelectedFiles.Count > 1 then
        msgWarning(rsMsgTooManyFilesSelected)
      else if SelectedFiles.Count = 0 then
        msgWarning(rsMsgNoFilesSelected)
      else
      begin
        sExistingFile := SelectedFiles[0].Path + SelectedFiles[0].Name;

        if Length(Params) > 0 then
          sLinkToCreate := Params[0]
        else
        begin
          if NotActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            sLinkToCreate := NotActiveFrame.CurrentPath
          else
            sLinkToCreate := ActiveFrame.CurrentPath;
        end;

        sLinkToCreate := sLinkToCreate + SelectedFiles[0].Name;

        if ShowSymLinkForm(frmMain, sExistingFile, sLinkToCreate, ActiveFrame.CurrentPath) then
        begin
          ActiveFrame.Reload;
          if NotActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            NotActiveFrame.Reload;
        end;
      end;

    finally
      FreeAndNil(SelectedFiles);
    end;
  end;
end;

procedure TMainCommands.cm_HardLink(const Params: array of string);
var
  sExistingFile, sLinkToCreate: String;
  SelectedFiles: TFiles;
begin
  with frmMain do
  begin
    // Hard links work only for file system.
    if not (ActiveFrame.FileSource.IsClass(TFileSystemFileSource)) then
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
    end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      if SelectedFiles.Count > 1 then
        msgWarning(rsMsgTooManyFilesSelected)
      else if SelectedFiles.Count = 0 then
        msgWarning(rsMsgNoFilesSelected)
      else
      begin
        sExistingFile := SelectedFiles[0].Path + SelectedFiles[0].Name;

        if Length(Params) > 0 then
          sLinkToCreate := Params[0]
        else
        begin
          if NotActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            sLinkToCreate := NotActiveFrame.CurrentPath
          else
            sLinkToCreate := ActiveFrame.CurrentPath;
        end;

        sLinkToCreate := sLinkToCreate + SelectedFiles[0].Name;

        if ShowHardLinkForm(frmMain, sExistingFile, sLinkToCreate, ActiveFrame.CurrentPath) then
        begin
          ActiveFrame.Reload;
          if NotActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            NotActiveFrame.Reload;
        end;
      end;

    finally
      FreeAndNil(SelectedFiles);
    end;
  end;
end;

// Uses to change sort direction when columns header is disabled
procedure TMainCommands.cm_ReverseOrder(const Params: array of string);
begin
  with frmMain.ActiveFrame do
    Sorting := ReverseSortDirection(Sorting);
end;

procedure TMainCommands.cm_LeftReverseOrder(const Params: array of string);
begin
  with frmMain.FrameLeft do
    Sorting := ReverseSortDirection(Sorting);
end;

procedure TMainCommands.cm_RightReverseOrder(const Params: array of string);
begin
  with frmMain.FrameRight do
    Sorting := ReverseSortDirection(Sorting);
end;

procedure TMainCommands.cm_SortByName(const Params: array of string);
begin
  DoSortByFunctions(frmMain.ActiveFrame, [fsfNameNoExtension]);
end;

procedure TMainCommands.cm_SortByExt(const Params: array of string);
begin
  DoSortByFunctions(frmMain.ActiveFrame, [fsfExtension]);
end;

procedure TMainCommands.cm_SortBySize(const Params: array of string);
begin
  DoSortByFunctions(frmMain.ActiveFrame, [fsfSize]);
end;

procedure TMainCommands.cm_SortByDate(const Params: array of string);
begin
  DoSortByFunctions(frmMain.ActiveFrame, [fsfModificationTime]);
end;

procedure TMainCommands.cm_SortByAttr(const Params: array of string);
begin
  DoSortByFunctions(frmMain.ActiveFrame, [fsfAttr]);
end;

procedure TMainCommands.cm_LeftSortByName(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameLeft, [fsfNameNoExtension]);
end;

procedure TMainCommands.cm_LeftSortByExt(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameLeft, [fsfExtension]);
end;

procedure TMainCommands.cm_LeftSortBySize(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameLeft, [fsfSize]);
end;

procedure TMainCommands.cm_LeftSortByDate(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameLeft, [fsfModificationTime]);
end;

procedure TMainCommands.cm_LeftSortByAttr(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameLeft, [fsfAttr]);
end;

procedure TMainCommands.cm_RightSortByName(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameRight, [fsfNameNoExtension]);
end;

procedure TMainCommands.cm_RightSortByExt(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameRight, [fsfExtension]);
end;

procedure TMainCommands.cm_RightSortBySize(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameRight, [fsfSize]);
end;

procedure TMainCommands.cm_RightSortByDate(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameRight, [fsfModificationTime]);
end;

procedure TMainCommands.cm_RightSortByAttr(const Params: array of string);
begin
  DoSortByFunctions(frmMain.FrameRight, [fsfAttr]);
end;

{ Command to request to sort a frame with a column with a defined order.
  This command may be user by the user via the toolbar,
  but it is definitively a nice-to-have for the "uHotDir" unit who may specify the order to be in when switching to a hotdir.}
procedure TMainCommands.cm_UniversalSingleDirectSort(const Params: array of string);
var
  Param: String;
  sValue: String;
  WantedFileView: TFileView;
  WantedSortFunction: TFileFunction;
  WantedSortDirection: TSortDirection;
  FileFunctions: TFileFunctions = nil;
  NewSorting: TFileSortings = nil;
begin
  //1o) Let's set our default values
  WantedFileView:=frmMain.ActiveFrame;
  WantedSortFunction:=fsfName;
  WantedSortDirection:=sdAscending;

  //2o) Let's parse the parameter to get the wanted ones
  for Param in Params do
  begin
    if GetParamValue(Param, 'panel', sValue) then
    begin
      if sValue='inactive' then WantedFileView:=frmMain.NotActiveFrame else
        if sValue='left' then WantedFileView:=frmMain.FrameLeft else
          if sValue='right' then WantedFileView:=frmMain.FrameRight;
    end
    else if GetParamValue(Param, 'column', sValue) then
    begin
      if sValue='ext' then WantedSortFunction:=fsfExtension else
        if sValue='size' then WantedSortFunction:=fsfSize else
          if sValue='datetime' then WantedSortFunction:=fsfModificationTime;
    end
    else if GetParamValue(Param, 'order', sValue) then
    begin
      if sValue='descending' then WantedSortDirection:=sdDescending;
    end;
  end;

  //3o) Let's do the sorting job now!
  AddSortFunction(FileFunctions, WantedSortFunction);
  SetLength(NewSorting, 1);
  SetLength(NewSorting[0].SortFunctions, 1);
  NewSorting[0].SortFunctions[0] := FileFunctions[0];
  NewSorting[0].SortDirection := WantedSortDirection;
  WantedFileView.Sorting := NewSorting;
end;

procedure TMainCommands.cm_MultiRename(const Params: array of string);
var
  aFiles: TFiles;
  sValue, Param: string;
  sPresetToLoad: string = '';
begin
  with frmMain do
  begin
    if not (fsoSetFileProperty in ActiveFrame.FileSource.GetOperationsTypes) then
      begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;

    aFiles:= ActiveFrame.CloneSelectedOrActiveFiles;
    if Assigned(aFiles) then
      try
        if aFiles.Count > 0 then
        begin
          for Param in Params do
            if GetParamValue(Param, 'preset', sValue) then
              sPresetToLoad := sValue;

          ShowMultiRenameForm(ActiveFrame.FileSource, aFiles, sPresetToLoad)
        end
        else
          msgWarning(rsMsgNoFilesSelected);
      finally
        FreeAndNil(aFiles);
      end;
  end;
end;

//------------------------------------------------------

procedure TMainCommands.cm_CopySamePanel(const Params: array of string);
begin
  frmMain.CopyFiles('', True);
end;

procedure TMainCommands.cm_RenameOnly(const Params: array of string);
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_RenameOnly', Params);
end;

procedure TMainCommands.cm_EditNew(const Params: array of string);
var
  sNewFile: String;
  hFile: System.THandle = 0;
  aFile: TFile;
  Attrs: TFileAttrs;
  sCmd: string = '';
  sParams: string = '';
  sStartPath: string = '';
  AElevate: TDuplicates = dupIgnore;
begin
  frmMain.ActiveFrame.ExecuteCommand('cm_EditNew', Params);

  // For now only works for FileSystem.

  with frmMain do
  if ActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
  begin
    aFile := ActiveFrame.CloneActiveFile;
    if Assigned(aFile) then
    try
      if aFile.IsNameValid then
        sNewFile:= aFile.Name
      else
        sNewFile:= rsEditNewFile;
    finally
      FreeAndNil(aFile);
    end;

    if not InputQuery(rsEditNewOpen, rsEditNewFileName, sNewFile) then Exit;

    // If user entered only a filename prepend it with current directory.
    if ExtractFilePath(sNewFile) = '' then
      sNewFile:= ActiveFrame.CurrentPath + sNewFile;

    PushPop(AElevate);
    try
      sNewFile := TrimPath(sNewFile);
      Attrs := FileGetAttrUAC(sNewFile);
      if Attrs = faInvalidAttributes then
      begin
        hFile := FileCreateUAC(sNewFile, fmShareDenyWrite);
        if hFile = feInvalidHandle then
        begin
          MessageDlg(rsMsgErrECreate, mbSysErrorMessage(GetLastOSError), mtWarning, [mbOK], 0);
          Exit;
        end;
        FileClose(hFile);
        ActiveFrame.FileSource.Reload(ExtractFilePath(sNewFile));
        ActiveFrame.SetActiveFile(sNewFile);
      end
      else if FPS_ISDIR(Attrs) then
      begin
        MessageDlg(rsMsgErrECreate, Format(rsMsgErrCreateFileDirectoryExists,
          [ExtractFileName(sNewFile)]), mtWarning, [mbOK], 0);
        Exit;
      end;
    finally
      PushPop(AElevate);
    end;

    aFile := TFileSystemFileSource.CreateFileFromFile(sNewFile);
    try
      // Try to find Edit command in "extassoc.xml"
      if not gExts.GetExtActionCmd(aFile, 'edit', sCmd, sParams, sStartPath) then
        ShowEditorByGlob(aFile.FullPath) // If command not found then use default editor
      else
        begin
          ProcessExtCommandFork(sCmd, sParams, aFile.Path, aFile);
        end;
    finally
      FreeAndNil(aFile);
    end;
  end
  else
    msgWarning(rsMsgNotImplemented);
end;

{ TMainCommands.cm_DirHistory }
// Shows recently visited directories (global).
procedure TMainCommands.cm_DirHistory(const Params: array of string);
var
  bUseTreeViewMenu: boolean = false;
  bUsePanel: boolean = true;
  p: TPoint = (x:0; y:0);
  iWantedWidth: integer = 0;
  iWantedHeight: integer = 0;
  sMaybeMenuItem: TMenuItem = nil;
begin
  // 1. Let's parse our parameters.
  DoParseParametersForPossibleTreeViewMenu(Params, gUseTreeViewMenuWithDirHistory, gUseTreeViewMenuWithDirHistory, bUseTreeViewMenu, bUsePanel, p);

  frmMain.CreatePopUpDirHistory(bUseTreeViewMenu, 0);
  Application.ProcessMessages; //TODO: In Windows, Not sure why, but on all systems tried, this eliminate a "beep" when the popup is shown.

  if bUseTreeViewMenu then
  begin
    if not bUsePanel then
      iWantedHeight := 0
    else
    begin
      iWantedWidth := frmMain.ActiveFrame.Width;
      iWantedHeight := frmMain.ActiveFrame.Height;
    end;
    sMaybeMenuItem := GetUserChoiceFromTreeViewMenuLoadedFromPopupMenu(frmMain.pmDirHistory, tvmcDirHistory, p.X, p.Y, iWantedWidth, iWantedHeight);
    if sMaybeMenuItem <> nil then sMaybeMenuItem.OnClick(sMaybeMenuItem);
  end
  else
  begin
    frmMain.pmDirHistory.Popup(p.X,p.Y);
  end;
end;

// Shows browser-like history for active file view.
procedure TMainCommands.cm_ViewHistory(const Params: array of string);
begin
  frmMain.ShowFileViewHistory(Params);
end;

procedure TMainCommands.cm_ViewHistoryPrev(const Params: array of string);
begin
  with frmMain do
  begin
    ActiveFrame.GoToPrevHistory;
  end;
end;

procedure TMainCommands.cm_ViewHistoryNext(const Params: array of string);
begin
  with frmMain do
  begin
    ActiveFrame.GoToNextHistory;
  end;
end;

{ TMainCommands.cm_ShowCmdLineHistory }
procedure TMainCommands.cm_ShowCmdLineHistory(const Params: array of string);
var
  p: TPoint = (x:0; y:0);
  sUserChoice:string;
  bUseTreeViewMenu: boolean = false;
  bUsePanel: boolean = true;
  iWantedWidth: integer = 0;
  iWantedHeight: integer = 0;
begin
  with frmMain do
  begin
    if IsCommandLineVisible then
    begin
      // 1. Let's parse our parameters.
      DoParseParametersForPossibleTreeViewMenu(Params, gUseTreeViewMenuWithCommandLineHistory, gUseTreeViewMenuWithCommandLineHistory, bUseTreeViewMenu, bUsePanel, p);

      // 2. No matter what, we need to fill in the popup menu structure.
      gFavoriteTabsList.PopulateMenuWithFavoriteTabs(frmMain.pmFavoriteTabs, @DoOnClickMenuJobFavoriteTabs, ftmp_FAVTABSWITHCONFIG);
      Application.ProcessMessages;

      // 3. Show the appropriate menu.
      if bUseTreeViewMenu then
      begin
        iWantedWidth := frmMain.edtCommand.Width;
        iWantedHeight := frmMain.ActiveFrame.Height;
        p := frmMain.edtCommand.ClientToScreen(Classes.Point(0, 0));
        p.y := p.y - iWantedHeight;

        sUserChoice := GetUserChoiceFromTStrings(edtCommand.Items, tvmcCommandLineHistory, p.x, p.y, iWantedWidth, iWantedHeight);
        if sUserChoice<>'' then
        begin
          edtCommand.ItemIndex:=edtCommand.Items.IndexOf(sUserChoice);
          edtCommand.SetFocus;
        end;
      end
      else
      begin
        edtCommand.SetFocus;
        if edtCommand.Items.Count>0 then
          edtCommand.DroppedDown:=True;
      end;
    end;

  end;
end;

procedure TMainCommands.cm_ToggleFullscreenConsole(const Params: array of string);
begin
  frmMain.ToggleFullscreenConsole;
end;

procedure TMainCommands.cm_RunTerm(const Params: array of string);
begin
  with frmMain do
  if not edtCommand.Focused then
  try
    ProcessExtCommandFork(gRunTermCmd, gRunTermParams, ActiveFrame.CurrentPath);
  except
    on e: EInvalidCommandLine do
      MessageDlg(rsToolErrorOpeningTerminal,
            rsMsgInvalidCommandLine + ' (' + rsToolTerminal + '):' + LineEnding + e.Message,
            mtError, [mbOK], 0);
  end;
end;

procedure TMainCommands.cm_CalculateSpace(const Params: array of string);
var
  SelectedFiles: TFiles;
  Operation: TFileSourceOperation;
begin
  with frmMain do
  begin
    if not (fsoCalcStatistics in ActiveFrame.FileSource.GetOperationsTypes) then
      begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      Operation := ActiveFrame.FileSource.CreateCalcStatisticsOperation(SelectedFiles);
      if not Assigned(Operation) then
        msgWarning(rsMsgErrNotSupported)
      else
        begin
          Operation.AddStateChangedListener([fsosStopped], @OnCalcStatisticsStateChanged);
          OperationsManager.AddOperation(Operation);
        end;
    finally
      if Assigned(SelectedFiles) then
        FreeAndNil(SelectedFiles);
    end;
  end;
end;

procedure TMainCommands.cm_CountDirContent(const Params: array of string);
begin
  frmMain.ActiveFrame.CalculateSpaceOfAllDirectories;
end;

procedure TMainCommands.cm_SetFileProperties(const Params: array of string);
var
  ActiveFile: TFile = nil;
  SelectedFiles: TFiles = nil;
  aFileProperties: TFileProperties;
  CreationTime: DCBasicTypes.TFileTimeEx;
  LastAccessTime : DCBasicTypes.TFileTimeEx;
  ModificationTime: DCBasicTypes.TFileTimeEx;
  Operation: TFileSourceSetFilePropertyOperation = nil;
begin
  with frmMain do
  try
    if not (fsoSetFileProperty in ActiveFrame.FileSource.GetOperationsTypes) then
      begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;

    SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    ActiveFile := ActiveFrame.CloneActiveFile;
    if Assigned(ActiveFile) and (SelectedFiles.Count > 0) then
      begin
        if fspDirectAccess in ActiveFrame.FileSource.Properties then
        begin
          if mbFileGetTime(ActiveFile.FullPath, ModificationTime, CreationTime, LastAccessTime) then
          begin
            if fpModificationTime in ActiveFile.SupportedProperties then
              ActiveFile.ModificationTime:= FileTimeToDateTimeEx(ModificationTime);
            if fpCreationTime in ActiveFile.SupportedProperties then
              ActiveFile.CreationTime:= FileTimeToDateTimeEx(CreationTime);
            if fpLastAccessTime in ActiveFile.SupportedProperties then
              ActiveFile.LastAccessTime:= FileTimeToDateTimeEx(LastAccessTime);
          end;
        end;
        FillByte(aFileProperties, SizeOf(aFileProperties), 0);
        if fpAttributes in ActiveFile.SupportedProperties then
          aFileProperties[fpAttributes]:= ActiveFile.Properties[fpAttributes].Clone;
        if fpModificationTime in ActiveFile.SupportedProperties then
          aFileProperties[fpModificationTime]:= ActiveFile.Properties[fpModificationTime].Clone;
        if fpCreationTime in ActiveFile.SupportedProperties then
          aFileProperties[fpCreationTime]:= ActiveFile.Properties[fpCreationTime].Clone;
        if fpLastAccessTime in ActiveFile.SupportedProperties then
          aFileProperties[fpLastAccessTime]:= ActiveFile.Properties[fpLastAccessTime].Clone;

        Operation:= ActiveFrame.FileSource.CreateSetFilePropertyOperation(
                        SelectedFiles,
                        aFileProperties) as TFileSourceSetFilePropertyOperation;

        if Assigned(Operation) then
        begin
          if (Operation.SupportedProperties * [fpModificationTime, fpCreationTime,
                                               fpLastAccessTime, fpAttributes] = []) then
          begin
            msgWarning(rsMsgErrNotSupported);
            Exit;
          end;

          if ShowChangeFilePropertiesDialog(Operation) then
          begin
            OperationsManager.AddOperation(Operation);
            Operation := nil; // So it doesn't get destroyed below.
          end;
        end;
      end;
  finally
    FreeAndNil(SelectedFiles);
    FreeAndNil(ActiveFile);
    FreeAndNil(Operation);
  end;
end;

procedure TMainCommands.cm_FileProperties(const Params: array of string);
var
  SelectedFiles: TFiles;
  Operation: TFileSourceExecuteOperation;
  aFile: TFile;
begin
  with frmMain do
  begin
    if ActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
      begin
        SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
        if Assigned(SelectedFiles) then
        try
          if SelectedFiles.Count > 0 then
          try
            ShowFilePropertiesDialog(ActiveFrame.FileSource, SelectedFiles);
          except
            on e: EContextMenuException do
              ShowException(e);
          end;
        finally
          FreeAndNil(SelectedFiles);
        end;
      end
    else if (fsoExecute in ActiveFrame.FileSource.GetOperationsTypes) then
      begin
        aFile:= ActiveFrame.CloneActiveFile;
        if Assigned(aFile) then
          try
            Operation:= ActiveFrame.FileSource.CreateExecuteOperation(
                            aFile,
                            ActiveFrame.CurrentPath,
                            'properties') as TFileSourceExecuteOperation;
            if Assigned(Operation) then
              Operation.Execute;
          finally
            FreeAndNil(Operation);
            FreeAndNil(aFile);
          end;
      end;
  end;
end;

procedure TMainCommands.cm_FileLinker(const Params: array of string);
var
  I: Integer;
  aSelectedFiles: TFiles = nil;
  aFile: TFile;
  aFirstFilenameOfSeries: String;
begin
  with frmMain, frmMain.ActiveFrame do
  begin
    if not (fsoCombine in FileSource.GetOperationsTypes) then
      begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;
    try
      aSelectedFiles := CloneSelectedOrActiveFiles;

      for I := 0 to aSelectedFiles.Count - 1 do
        begin
          aFile := aSelectedFiles[I];
          if (aFile.IsDirectory or aFile.IsLinkToDirectory) then
          begin
            msgWarning(rsMsgInvalidSelection);
            Exit;
          end;
        end;

      if aSelectedFiles.Count > 1 then
      begin
        ShowLinkerFilesForm(frmMain, FileSource, aSelectedFiles, NotActiveFrame.CurrentPath);
      end
      else
      begin
        if aSelectedFiles.Count = 1 then
        begin
          try
            if StrToInt(aSelectedFiles[0].Extension)>0 then
            begin
              aFirstFilenameOfSeries:='1';
              while length(aFirstFilenameOfSeries)<length(aSelectedFiles[0].Extension) do aFirstFilenameOfSeries:='0'+aFirstFilenameOfSeries;
              aFirstFilenameOfSeries:=aSelectedFiles[0].Path + aSelectedFiles[0].NameNoExt + ExtensionSeparator + aFirstFilenameOfSeries;
              DoDynamicFilesLinking(FileSource, aSelectedFiles, NotActiveFrame.CurrentPath, aFirstFilenameOfSeries);
            end
            else
            begin
              msgWarning(rsMsgInvalidSelection);
            end;
          except
            msgWarning(rsMsgInvalidSelection);
          end;
        end
        else
        begin
          msgWarning(rsMsgInvalidSelection);
        end;
      end;
    finally
      FreeAndNil(aSelectedFiles);
    end; // try
  end; // with
end;

procedure TMainCommands.cm_FileSpliter(const Params: array of string);
var
  aFile: TFile = nil;
begin
  with frmMain, frmMain.ActiveFrame do
  begin
    if not (fsoSplit in FileSource.GetOperationsTypes) then
      begin
        msgWarning(rsMsgErrNotSupported);
        Exit;
      end;

    try
      aFile := CloneActiveFile;
      if (not Assigned(aFile)) or (aFile.IsDirectory or aFile.IsLinkToDirectory) then
        msgWarning(rsMsgInvalidSelection)
      else
        ShowSplitterFileForm(frmMain, FileSource, aFile, NotActiveFrame.CurrentPath);
    finally
      FreeAndNil(aFile);
    end; // try
  end; // with
end;

procedure TMainCommands.cm_PanelsSplitterPerPos(const Params: array of string);
var
  Split: Integer = 50;
  Param, SplitPct: String;
begin
  for Param in Params do
  begin
    if GetParamValue(Param, 'splitpct', SplitPct) then
      Split := StrToIntDef(SplitPct, Split);
  end;
  DoPanelsSplitterPerPos(Split);
end;

procedure TMainCommands.cm_EditComment(const Params: array of string);
var
  aFile: TFile;
begin
  with frmMain.ActiveFrame do
  begin
    if (fspDirectAccess in FileSource.GetProperties) then
      begin
        aFile:= CloneActiveFile;
        if Assigned(aFile) then
          try
            if aFile.IsNameValid then
              ShowDescrEditDlg(aFile.FullPath, frmMain.ActiveFrame)
            else
              msgWarning(rsMsgNoFilesSelected);
          finally
            FreeAndNil(aFile);
          end;
      end
    else if (fspLinksToLocalFiles in FileSource.GetProperties) then
      begin
        aFile:= CloneActiveFile;
        if Assigned(aFile) then
          try
            if aFile.IsNameValid then
              begin
                if FileSource.GetLocalName(aFile) then
                  ShowDescrEditDlg(aFile.FullPath, frmMain.ActiveFrame)
                else
                  msgWarning(rsMsgErrNotSupported);
              end
            else
              begin
                msgWarning(rsMsgNoFilesSelected);
              end;
          finally
            FreeAndNil(aFile);
          end;
      end
    else
      msgWarning(rsMsgErrNotSupported);
  end;
end;

function SendToClipboard(ClipboardMode: uClipboard.TClipboardOperation):Boolean;
var
  sl: TStringList = nil;
  i : Integer;
  theSelectedFiles: TFiles = nil;
begin
  // Only works for file system.

  Result := False;

  with frmMain.ActiveFrame do
  if (fspDirectAccess in FileSource.Properties) then
  begin
    sl := TStringList.Create;
    try
      theSelectedFiles := CloneSelectedOrActiveFiles;

      for i := 0 to theSelectedFiles.Count - 1 do
        sl.Add(theSelectedFiles[i].FullPath);

      case ClipboardMode of
        uClipboard.ClipboardCut:
            Result := uClipboard.CutToClipboard(sl);

        uClipboard.ClipboardCopy:
            Result := uClipboard.CopyToClipboard(sl);
      end;

    finally
      if Assigned(sl) then
        FreeAndNil(sl);
      if Assigned(theSelectedFiles) then
        FreeAndNil(theSelectedFiles);
    end;
  end
  else
    msgWarning(rsMsgErrNotSupported);
end;

procedure TMainCommands.cm_CopyToClipboard(const Params: array of string);
begin
  SendToClipboard(ClipboardCopy);
end;

procedure TMainCommands.cm_CutToClipboard(const Params: array of string);
begin
  SendToClipboard(ClipboardCut);
end;

procedure TMainCommands.cm_PasteFromClipboard(const Params: array of string);
var
  ClipboardOp: TClipboardOperation;
  filenamesList: TStringList;
  Files: TFiles = nil;
  Operation: TFileSourceOperation = nil;
  SourceFileSource: IFileSource = nil;
begin
  with frmMain do
  begin
    if PasteFromClipboard(ClipboardOp, filenamesList) = True then
    try
      // fill file list with files
      Files := TFileSystemFileSource.CreateFilesFromFileList(
          ExtractFilePath(filenamesList[0]), fileNamesList, True);

      if Files.Count > 0 then
      begin
        case ClipboardOp of

          uClipboard.ClipboardCut:
          begin
            SourceFileSource := TFileSystemFileSource.GetFileSource;

            if ActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            begin
              if not (fsoMove in ActiveFrame.FileSource.GetOperationsTypes) then
              begin
                msgWarning(rsMsgErrNotSupported);
                Exit;
              end;

              Operation := SourceFileSource.CreateMoveOperation(
                             Files, ActiveFrame.CurrentPath);
            end
            else
            begin
              if (not (fsoCopyIn in ActiveFrame.FileSource.GetOperationsTypes)) or
                 (not (fsoDelete in SourceFileSource.GetOperationsTypes)) then
              begin
                msgWarning(rsMsgErrNotSupported);
                Exit;
              end;
  {
              // Meta-operation: CopyIn + Delete

              Operation := ActiveFrame.FileSource.CreateCopyInOperation(
                             SourceFileSource, Files,
                             ActiveFrame.CurrentPath);
  }
            end;
          end;

          uClipboard.ClipboardCopy:
          begin
            if not (fsoCopyIn in ActiveFrame.FileSource.GetOperationsTypes) then
            begin
              msgWarning(rsMsgErrNotSupported);
              Exit;
            end;

            SourceFileSource := TFileSystemFileSource.GetFileSource;

            if ActiveFrame.FileSource.IsClass(TFileSystemFileSource) then
            begin
              Operation := SourceFileSource.CreateCopyOutOperation(
                             ActiveFrame.FileSource, Files,
                             ActiveFrame.CurrentPath);
            end
            else
            begin
              Operation := ActiveFrame.FileSource.CreateCopyInOperation(
                             SourceFileSource, Files,
                             ActiveFrame.CurrentPath);
            end;
          end;

          else
            // Invalid clipboard operation.
            Exit;

        end;

        if Assigned(Operation) then
        begin
          if Operation is TFileSystemCopyOperation then
            (Operation as TFileSystemCopyOperation).AutoRenameItSelf:= True;
          OperationsManager.AddOperation(Operation);

          // Files have been moved so clear the clipboard because
          // the files location in the clipboard is invalid now.
          if ClipboardOp = uClipboard.ClipboardCut then
            uClipboard.ClearClipboard;
        end
        else
          msgWarning(rsMsgNotImplemented);
      end;

    finally
      FreeAndNil(fileNamesList);
      if Assigned(Files) then
        FreeAndNil(Files);
    end;
  end;
end;

procedure TMainCommands.cm_SyncChangeDir(const Params: array of string);
begin
  with frmMain do
  begin
    actSyncChangeDir.Checked:= not actSyncChangeDir.Checked;
    if actSyncChangeDir.Checked then
      SyncChangeDir:= ExcludeTrailingBackslash(ActiveFrame.CurrentPath);
  end;
end;

procedure TMainCommands.cm_ChangeDirToRoot(const Params: array of string);
begin
  DoChangeDirToRoot(frmMain.ActiveFrame);
end;

procedure TMainCommands.cm_ChangeDirToHome(const Params: array of string);
begin
  SetFileSystemPath(frmMain.ActiveFrame, GetHomeDir);
end;

procedure TMainCommands.cm_ChangeDirToParent(const Params: array of string);
begin
  frmMain.ActiveFrame.ChangePathToParent(True);
end;

// Parameters:
// Full path to a directory.
procedure TMainCommands.cm_ChangeDir(const Params: array of string);
var
  Param, WantedPath: string;
begin
  //1o) Let's set our default values
  WantedPath := frmMain.ActiveFrame.CurrentPath;

  //2o) Let's parse the parameter to get the wanted ones
  for Param in Params do
  begin
    if GetParamValue(Param, 'activepath', WantedPath) then
    begin
      WantedPath:= PrepareParameter(WantedPath);
      ChooseFileSource(frmMain.ActiveFrame, RemoveQuotation(WantedPath));
    end
    else
    if GetParamValue(Param, 'inactivepath', WantedPath) then
    begin
      WantedPath:= PrepareParameter(WantedPath);
      ChooseFileSource(frmMain.NotActiveFrame, RemoveQuotation(WantedPath));
    end
    else
    if GetParamValue(Param, 'leftpath', WantedPath) then
    begin
      WantedPath:= PrepareParameter(WantedPath);
      ChooseFileSource(frmMain.FrameLeft, RemoveQuotation(WantedPath));
    end
    else
    if GetParamValue(Param, 'rightpath', WantedPath) then
    begin
      WantedPath:=PrepareParameter(WantedPath);
      ChooseFileSource(frmMain.FrameRight, RemoveQuotation(WantedPath));
    end;
  end;

  //3o) Let's support the DC legacy way of working of the command
  if Length(Params)=1 then
  begin
    if (not GetParamValue(Params[0], 'activepath', WantedPath)) AND (not GetParamValue(Params[0], 'inactivepath', WantedPath)) AND (not GetParamValue(Params[0], 'leftpath', WantedPath)) AND (not GetParamValue(Params[0], 'rightpath', WantedPath)) then
      ChooseFileSource(frmMain.ActiveFrame, RemoveQuotation(ReplaceEnvVars(Params[0])));
  end;
end;

procedure TMainCommands.cm_ClearLogWindow(const Params: array of string);
begin
  frmMain.seLogWindow.Lines.Clear;
end;

procedure TMainCommands.cm_CmdLineNext(const Params: array of string);
begin
  DoShowCmdLineHistory(True);
end;

procedure TMainCommands.cm_CmdLinePrev(const Params: array of string);
begin
  DoShowCmdLineHistory(False);
end;

procedure TMainCommands.cm_ViewLogFile(const Params: array of string);
begin
  ShowViewerByGlob(GetActualLogFilename);
end;

procedure TMainCommands.cm_ClearLogFile(const Params: array of string);
begin
  if MsgBox(Format(rsMsgPopUpHotDelete,['log file ('+GetActualLogFilename+')']),[msmbYes, msmbNo], msmbNo, msmbNo ) = mmrYes then
  begin
    mbDeleteFile(GetActualLogFilename);
  end;
end;

procedure TMainCommands.cm_NetworkConnect(const Params: array of string);
begin
  DoOpenVirtualFileSystemList(frmMain.ActiveFrame);
end;

procedure TMainCommands.cm_NetworkDisconnect(const Params: array of string);
begin
  CloseNetworkConnection();
end;

procedure TMainCommands.cm_CopyNetNamesToClip(const Params: array of string);
begin
  CopyNetNamesToClip;
end;

procedure TMainCommands.cm_HorizontalFilePanels(const Params: array of string);
var
  sParamValue:string;
  WantedHorizontalFilePanels:boolean;
begin
  WantedHorizontalFilePanels:=gHorizontalFilePanels;

  if Length(Params)>0 then
  begin
    if GetParamValue(Params[0], 'mode', sParamValue) then
    begin
      if sParamValue='legacy' then WantedHorizontalFilePanels := not WantedHorizontalFilePanels else
        if sParamValue='vertical' then WantedHorizontalFilePanels:=FALSE else
          if sParamValue='horizontal' then WantedHorizontalFilePanels:=TRUE;
    end;
  end
  else
  begin
    WantedHorizontalFilePanels := not WantedHorizontalFilePanels;
  end;

  if WantedHorizontalFilePanels<>gHorizontalFilePanels then
  begin
    gHorizontalFilePanels:=WantedHorizontalFilePanels;
    frmMain.actHorizontalFilePanels.Checked := gHorizontalFilePanels;
    frmMain.UpdateWindowView;
  end;
end;

procedure TMainCommands.cm_OperationsViewer(const Params: array of string);
begin
  ShowOperationsViewer;
end;

procedure TMainCommands.cm_CompareDirectories(const Params: array of string);
var
  I: LongWord;
  Param: String;
  BoolValue: Boolean;
  NtfsShift: Boolean;
  SourceFile: TDisplayFile;
  TargetFile: TDisplayFile;
  AFiles, AFolders: Boolean;
  SourceList: TStringHashListUtf8;
  SourceFiles: TDisplayFiles = nil;
  TargetFiles: TDisplayFiles = nil;
begin
  AFiles := True;
  AFolders := False;
  for Param in Params do
  begin
    if GetParamBoolValue(Param, 'files', BoolValue) then
      AFiles := BoolValue
    else if GetParamBoolValue(Param, 'directories', BoolValue) then
    begin
      AFolders := BoolValue
    end;
  end;
  if (AFiles = False) and (AFolders = False) then AFiles := True;

  SourceList:= TStringHashListUtf8.Create(FileNameCaseSensitive);
  with frmMain do
  try
    NtfsShift:= gNtfsHourTimeDelay and NtfsHourTimeDelay(ActiveFrame.CurrentPath, NotActiveFrame.CurrentPath);
    SourceFiles:= ActiveFrame.DisplayFiles;
    TargetFiles:= NotActiveFrame.DisplayFiles;
    for I:= 0 to SourceFiles.Count - 1 do
    begin
      SourceFile:= SourceFiles[I];
      if SourceFile.FSFile.IsDirectory or SourceFile.FSFile.IsLinkToDirectory then
      begin
        if not AFolders then Continue;
      end
      else begin
        if not AFiles then Continue;
      end;
      ActiveFrame.MarkFile(SourceFile, True);
      SourceList.Add(SourceFile.FSFile.Name, SourceFile);
    end;
    for I:= 0 to TargetFiles.Count - 1 do
    begin
      TargetFile:= TargetFiles[I];
      if TargetFile.FSFile.IsDirectory or TargetFile.FSFile.IsLinkToDirectory then
      begin
        if not AFolders then Continue;
      end
      else begin
        if not AFiles then Continue;
      end;
      SourceFile:= TDisplayFile(SourceList.Data[TargetFile.FSFile.Name]);
      if (SourceFile = nil) then
        NotActiveFrame.MarkFile(TargetFile, True)
      else
        case FileTimeCompare(SourceFile.FSFile.ModificationTime, TargetFile.FSFile.ModificationTime, NtfsShift) of
          0:
            ActiveFrame.MarkFile(SourceFile, False);
          +1:
            NotActiveFrame.MarkFile(TargetFile, False);
          -1:
            begin
              ActiveFrame.MarkFile(SourceFile, False);
              NotActiveFrame.MarkFile(TargetFile, True);
            end;
        end;
    end;
  finally
    SourceList.Free;
    ActiveFrame.Repaint;
    NotActiveFrame.Repaint;
  end;
end;

{ TMainCommands.cm_ConfigToolbars }
procedure TMainCommands.cm_ConfigToolbars(const Params: array of string);
begin
  cm_Options(['TfrmOptionsToolbar']);
end;

{ TMainCommands.cm_DebugShowCommandParameters }
procedure TMainCommands.cm_DebugShowCommandParameters(const Params: array of string);
var
  sMessageToshow:string;
  indexParameter:integer;
begin
  sMessageToshow:='Number of parameters: '+IntToStr(Length(Params));
  if Length(Params)>0 then
  begin
    sMessageToshow:=sMessageToshow+#$0A;
    for indexParameter:=0 to pred(Length(Params)) do
    begin
      sMessageToshow:=sMessageToshow+#$0A+'Parameter #'+IntToStr(indexParameter)+': '+Params[indexParameter]+' ==> '+PrepareParameter(Params[indexParameter]);
    end;
  end;
  msgOK(sMessageToshow);
end;

{ TMainCommands.cm_CopyPathOfFilesToClip }
procedure TMainCommands.cm_CopyPathOfFilesToClip(const Params: array of string);
begin
  DoCopySelectedFileNamesToClipboard(frmMain.ActiveFrame, cfntcJustPathWithSeparator, Params);
end;

{ TMainCommands.cm_CopyPathNoSepOfFilesToClip }
procedure TMainCommands.cm_CopyPathNoSepOfFilesToClip(const Params: array of string);
begin
  DoCopySelectedFileNamesToClipboard(frmMain.ActiveFrame, cfntcPathWithoutSeparator, Params);
end;

{ TMainCommands.cm_DoAnyCmCommand }
procedure TMainCommands.cm_DoAnyCmCommand(const Params: array of string);
var
  CommandReturnedToExecute:string='';
begin
  if ShowMainCommandDlgForm(gLastDoAnyCommand,CommandReturnedToExecute) then
  begin
    gLastDoAnyCommand := CommandReturnedToExecute;
    frmMain.Commands.Commands.ExecuteCommand(CommandReturnedToExecute, []);
  end;
end;

{ TMainCommands.DoCloseAllTabs }
procedure TMainCommands.DoCloseAllTabs(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
begin
  for iPage := ANotebook.PageCount - 1 downto 0 do
    if (not bAbort) AND (iPage <> ANotebook.PageIndex) then
      case frmMain.RemovePage(ANotebook, iPage, bDoLocked, iAskForLocked, True) of
        1: Continue; // skip tab
        2: bAbort := True; // cancel operation
        3: iAskForLocked := 2; // user answered to delete them all, we won't ask anymore during the rest of this command
      end;
end;

{ TMainCommands.DoCloseDuplicateTabs }
// Close tabs pointing to same dirs so at the end of action, only one tab for each dir is kept.
// Tabs that are kept follow these rules of priority:
//   -All the locked tabs are kept without asking question *except* if "bDoLocked" is set, which means we want also to elimit double lock tab.
//   -The one that has been user renamed by the user are eliminate IF a equivalent locked tab exist.
//   -If a user rename tab point the same directoy as another tab but not renamed, no matter the order, we keep the renamed tab and eliminate the other.
//   -A locked renamed tabs is stronger than a non-renamed tab locked so we eliminate the second one, the one not renamed.
//   -If two equals importance identical exist, we keep the one on left and elimitate the one on right.
// At the end of the process, we stay in a tab that has the same path as where we were initally.
procedure TMainCommands.DoCloseDuplicateTabs(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  sOriginalPath: String;
  iTabIndex, jTabIndex, jScore, tScore: Integer;
  bFlagDeleted: boolean;
begin
  // 1. We save to restore later the original directory of the active tab.
  sOriginalPath := ANoteBook.Page[ANotebook.PageIndex].FileView.CurrentPath;

  // 2. We do the actual job.
  jTabIndex := pred(ANotebook.PageCount);
  while (not bAbort) AND (jTabIndex>0) do
  begin
    jScore:=$0;
    if (ANoteBook.Page[jTabIndex].PermanentTitle <> '') then jScore := (jScore OR $01);
    if (ANoteBook.Page[jTabIndex].LockState <> tlsNormal) then jScore := (jScore OR $02);

    iTabIndex := (jTabIndex-1);
    bFlagDeleted := FALSE;
    while (not bAbort) AND (iTabIndex>=0) AND (bFlagDeleted=FALSE) do
    begin
      if mbCompareFileNames(ANoteBook.Page[iTabIndex].FileView.CurrentPath, ANoteBook.Page[jTabIndex].FileView.CurrentPath) then
      begin
        tScore:=jScore;
        if (ANoteBook.Page[iTabIndex].PermanentTitle <> '') then tScore := (tScore OR $04);
        if (ANoteBook.Page[iTabIndex].LockState <> tlsNormal) then tScore := (tScore OR $08);

        case tScore of
          $00, $04, $05, $08, $09, $0C, $0D: // We eliminate the one on right.
            begin
              frmMain.RemovePage(ANotebook, jTabIndex, False);
              bFlagDeleted:=TRUE;
            end;

          $01, $02, $03, $06, $07: // We eliminate the one on left.
            begin
              frmMain.RemovePage(ANotebook, iTabIndex, False);
              dec(jTabIndex); // If we eliminate one on left, the right tab now moved one position lower, we must take this in account.
            end;

          $0A, $0E, $0F: // We eliminate the one on right, EVEN if it is locked if specified.
            begin
              if bDoLocked then
              begin
                case frmMain.RemovePage(ANotebook, jTabIndex, bDoLocked, iAskForLocked, True) of
                  0: bFlagDeleted:=True; // Standard Removed.
                  1: begin end; // Skip tab, we keep going.
                  2: bAbort := True; // Cancel operation!
                  3:
                    begin
                      iAskForLocked := 2; // user answered to delete them all, we won't ask anymore during the rest of this command
                      bFlagDeleted:=True;
                    end;
                end;


              end;
            end;

          $0B: // We eliminate the one on left, EVEN if it is locked, if specified.
            begin
              if bDoLocked then
              begin
                case frmMain.RemovePage(ANotebook, iTabIndex, bDoLocked, iAskForLocked, True) of
                  0: dec(jTabIndex); // If we eliminate one on left, the right tab now moved one position lower, we must take this in account.
                  1: begin end; // Skip tab, we keep going.
                  2: bAbort := True; // Cancel operation!
                  3:
                    begin
                      iAskForLocked := 2; // user answered to delete them all, we won't ask anymore during the rest of this command
                      dec(jTabIndex);
                    end;
                end;
              end;
            end;
        end; // case tScore
      end;
      dec(iTabIndex);
    end;
    dec(jTabIndex);
  end;

  // 3. We attempt to select a tab with the actual original path from where we were.
  if not mbCompareFileNames(ANoteBook.Page[ANotebook.PageIndex].FileView.CurrentPath , sOriginalPath) then
  begin
    iTabIndex:=0;
    while (iTabIndex<ANotebook.PageCount) do
      if mbCompareFileNames(ANoteBook.Page[iTabIndex].FileView.CurrentPath , sOriginalPath) then
      begin
        ANotebook.PageIndex:=iTabIndex;
        iTabIndex:=ANotebook.PageCount;
      end
      else
        inc(iTabIndex);
  end;
end;

{ TMainCommands.DoSetAllTabsOptionNormal }
procedure TMainCommands.DoSetAllTabsOptionNormal(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
begin
  for iPage:=0 to pred(ANoteBook.PageCount) do
    ANoteBook.Page[iPage].LockState:=tlsNormal;
end;

{ TMainCommands.DoSetAllTabsOptionPathLocked }
procedure TMainCommands.DoSetAllTabsOptionPathLocked(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
begin
  for iPage:=0 to pred(ANoteBook.PageCount) do
    ANoteBook.Page[iPage].LockState:=tlsPathLocked;
end;

{ TMainCommands.DoAllTabsOptionPathResets }
procedure TMainCommands.DoAllTabsOptionPathResets(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
begin
  for iPage:=0 to pred(ANoteBook.PageCount) do
    ANoteBook.Page[iPage].LockState:=tlsPathResets;
end;

{ TMainCommands.DoSetAllTabsOptionDirsInNewTab }
procedure TMainCommands.DoSetAllTabsOptionDirsInNewTab(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
begin
  for iPage:=0 to pred(ANoteBook.PageCount) do
    ANoteBook.Page[iPage].LockState:=tlsDirsInNewTab;
end;

{ TMainCommands.cm_SetAllTabsOptionNormal }
procedure TMainCommands.cm_SetAllTabsOptionNormal(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params,@DoSetAllTabsOptionNormal);
end;

{ TMainCommands.cm_SetAllTabsOptionPathLocked }
procedure TMainCommands.cm_SetAllTabsOptionPathLocked(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params,@DoSetAllTabsOptionPathLocked);
end;

{ TMainCommands.cm_SetAllTabsOptionPathResets }
procedure TMainCommands.cm_SetAllTabsOptionPathResets(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params,@DoAllTabsOptionPathResets);
end;

{ TMainCommands.cm_SetAllTabsOptionDirsInNewTab }
procedure TMainCommands.cm_SetAllTabsOptionDirsInNewTab(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params,@DoSetAllTabsOptionDirsInNewTab);
end;

{ TMainCommands.DoActionOnMultipleTabs }
procedure TMainCommands.DoActionOnMultipleTabs(const Params: array of string; ProcedureDoingActionOnMultipleTabs: TProcedureDoingActionOnMultipleTabs);
var
  originalFilePanel:TFilePanelSelect;
  SideOfAction : TTabsConfigLocation = tclActive;
  bAbort: boolean = False;
  bDoLocked: boolean = False;
  iAskForLocked: integer = 0;
  iCurrentPageCount: integer;
begin
  FOriginalNumberOfTabs := -1;
  originalFilePanel := frmMain.SelectedPanel;
  GetAndSetMultitabsActionFromParams(Params, SideOfAction, bDoLocked, iAskForLocked);

  case SideOfAction of
    tclLeft: ProcedureDoingActionOnMultipleTabs(frmMain.LeftTabs, bAbort, bDoLocked, iAskForLocked);
    tclRight: ProcedureDoingActionOnMultipleTabs(frmMain.RightTabs, bAbort, bDoLocked, iAskForLocked);
    tclActive: ProcedureDoingActionOnMultipleTabs(frmMain.ActiveNotebook, bAbort, bDoLocked, iAskForLocked);
    tclInactive: ProcedureDoingActionOnMultipleTabs(frmMain.NotActiveNotebook, bAbort, bDoLocked, iAskForLocked);
    tclBoth:
      begin
        iCurrentPageCount := frmMain.RightTabs.PageCount;
        ProcedureDoingActionOnMultipleTabs(frmMain.LeftTabs, bAbort, bDoLocked, iAskForLocked);
        FOriginalNumberOfTabs := iCurrentPageCount;
        ProcedureDoingActionOnMultipleTabs(frmMain.RightTabs, bAbort, bDoLocked, iAskForLocked);
      end;
  end;

  frmMain.SelectedPanel := originalFilePanel;
  frmMain.ActiveFrame.SetFocus;
end;

{ TMainCommands.GetAndSetMultitabsActionFromParams }
procedure TMainCommands.GetAndSetMultitabsActionFromParams(const Params: array of string; var APanelSide:TTabsConfigLocation; var ActionOnLocked:boolean; var AskForLocked:integer);
var
 Param, sValue: String;
 boolValue: boolean;
begin
  Param := GetDefaultParam(Params);
  ActionOnLocked := False;
  AskForLocked := 0;

  // 1. Evaluate if we're running from legacy parameter style.
  if pos('=',Param)=0 then
  begin
    // 1.a. If yes, just watch for the magic word
    if Param = 'LeftTabs' then APanelSide := tclLeft
    else if Param = 'RightTabs' then APanelSide := tclRight
    else if Param = 'ActiveTabs' then APanelSide := tclActive
    else if Param = 'InactiveTabs' then APanelSide := tclInactive
    else if Param = 'BothTabs' then APanelSide := tclBoth
    else APanelSide := tclActive; // Legacy default is to close from Active Notebook.
  end
  else
  begin
    // 1.b. If no, let's parse it.

    // 2. Let's set our default values.
    APanelSide := tclActive;

    // 3. Parsing may now begin!
    for Param in Params do
    begin
      if GetParamValue(Param, 'side', sValue) then
      begin
        if sValue = 'left' then APanelSide := tclLeft
        else if sValue = 'right' then APanelSide := tclRight
        else if sValue = 'active' then APanelSide := tclActive
        else if sValue = 'inactive' then APanelSide := tclInactive
        else if sValue = 'both' then APanelSide := tclBoth;
      end
      else if GetParamBoolValue(Param, 'dolocked', boolValue) then ActionOnLocked := boolValue
      else if GetParamBoolValue(Param, 'confirmlocked', boolValue) then
      begin
        if boolValue then AskForLocked:=1 else AskForLocked:=2;
      end;
    end;
  end;
end;

{ TMainCommands.cm_ConfigFolderTabs }
procedure TMainCommands.cm_ConfigFolderTabs(const Params: array of string);
begin
  cm_Options(['TfrmOptionsTabs']);
end;

{ TMainCommands.DoShowFavoriteTabsOptions }
procedure TMainCommands.DoShowFavoriteTabsOptions;
var
  Options: IOptionsDialog;
  Editor: TOptionsEditor;
begin
  Options := ShowOptions(TfrmOptionsFavoriteTabs);
  Editor := Options.GetEditor(TfrmOptionsFavoriteTabs);
  Application.ProcessMessages;
  if Editor.CanFocus then  Editor.SetFocus;
  TfrmOptionsFavoriteTabs(Editor).MakeUsInPositionToWorkWithActiveFavoriteTabs;
end;

{ TMainCommands.cm_ConfigFavoriteTabs }
procedure TMainCommands.cm_ConfigFavoriteTabs(const Params: array of string);
begin
  DoShowFavoriteTabsOptions;
end;

{ TMainCommands.cm_ResaveFavoriteTabs }
procedure TMainCommands.cm_ResaveFavoriteTabs(const Params: array of string);
begin
  if gFavoriteTabsList.ReSaveTabsToXMLEntry(gFavoriteTabsList.GetIndexLastFavoriteTabsLoaded) then
    if gFavoriteTabsGoToConfigAfterReSave then
      DoShowFavoriteTabsOptions;
end;

{ TMainCommands.cm_SaveFavoriteTabs }
procedure TMainCommands.cm_SaveFavoriteTabs(const Params: array of string);
var
  sFavoriteTabsEntryName: string = '';
begin
  if gFavoriteTabsList.GetSuggestedParamsForFavoriteTabs(frmMain.ActiveNotebook.ActivePage.CurrentTitle, sFavoriteTabsEntryName) then
    if gFavoriteTabsList.SaveNewEntryFavoriteTabs(sFavoriteTabsEntryName) then
      if gFavoriteTabsGoToConfigAfterSave then
        DoShowFavoriteTabsOptions;
end;

{ TMainCommands.DoOnClickMenuJobFavoriteTabs }
//  We're supposed to jump here when we get called by an item from a favorite tabs menu item.
//  The value of "tag" will indicate us what to do.
//  -A value below TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING ($10000) indicates we want to load tabs with what was saved in our favorite tabs list at the index of "tag".
//  -A value equal or higher than TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING ($10000) indicates we want to save our current tabs OVER the existing entry in our favorite tabs list at the index of "tag MOD TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING".
procedure TMainCommands.DoOnClickMenuJobFavoriteTabs(Sender: TObject);
begin
  with Sender as TComponent do
  begin
    case tag of
      0 .. pred(TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING):  // We want to adjust our current tab from one in the favorite tabs.
      begin
        gFavoriteTabsList.SaveCurrentFavoriteTabsIfAnyPriorToChange;
        gFavoriteTabsList.LoadTabsFromXmlEntry(tag);
      end;

      TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING .. pred(TAGOFFSET_FAVTABS_SOMETHINGELSE): // We want to save our current tabs an existing favorite tabs entry.
        if gFavoriteTabsList.ReSaveTabsToXMLEntry(tag mod TAGOFFSET_FAVTABS_FORSAVEOVEREXISTING) then
          if gFavoriteTabsGoToConfigAfterReSave then
            DoShowFavoriteTabsOptions;
    end; //case iDispatcher of ...
  end;
end;

{ TMainCommands.cm_ReloadFavoriteTabs }
procedure TMainCommands.cm_ReloadFavoriteTabs(const Params: array of string);
begin
  // Here we won't call "gFavoriteTabsList.SaveCurrentFavoriteTabsIfAnyPriorToChange;" because if user wants to reload it, it's because he does not want to save what he has right now...
  // Otherwise, it would be a useless action. :-/
  gFavoriteTabsList.LoadTabsFromXmlEntry(gFavoriteTabsList.GetIndexLastFavoriteTabsLoaded);
end;

{ TMainCommands.cm_PreviousFavoriteTabs }
procedure TMainCommands.cm_PreviousFavoriteTabs(const Params: array of string);
begin
  gFavoriteTabsList.SaveCurrentFavoriteTabsIfAnyPriorToChange;
  gFavoriteTabsList.LoadTabsFromXmlEntry(gFavoriteTabsList.GetIndexPreviousLastFavoriteTabsLoaded);
end;

{ TMainCommands.cm_NextFavoriteTabs }
procedure TMainCommands.cm_NextFavoriteTabs(const Params: array of string);
begin
  gFavoriteTabsList.SaveCurrentFavoriteTabsIfAnyPriorToChange;
  gFavoriteTabsList.LoadTabsFromXmlEntry(gFavoriteTabsList.GetIndexNextLastFavoriteTabsLoaded);
end;

{ TMainCommands.cm_LoadFavoriteTabs }
procedure TMainCommands.cm_LoadFavoriteTabs(const Params: array of string);
const
  sNOSETUPPARAMLEGACY = 'd93525d5-e711-4b9d-ad2c-54d815354819';
var
  bUseTreeViewMenu: boolean = false;
  bUsePanel: boolean = true;
  p: TPoint = (x:0; y:0);
  iWantedWidth: integer = 0;
  iWantedHeight: integer = 0;
  sMaybeMenuItem: TMenuItem = nil;
  sParam, sMaybeValue, sSearchedFavoriteTabsName: string;
  iMaybeIndex: integer;
begin
  // 1. Check if we have the parameter "setup".
  sSearchedFavoriteTabsName := sNOSETUPPARAMLEGACY;
  for sParam in Params do
    if GetParamValue(sParam, 'setup', sMaybeValue) then
      sSearchedFavoriteTabsName := Trim(sMaybeValue);

  // 2. Check if we've seen the 'setup' parameter.
  if sSearchedFavoriteTabsName = sNOSETUPPARAMLEGACY then
  begin
    // If we not see the 'setup' parameter, we do thing as legacy, which means showing the menu to select the favorite tabs so user select it.
    DoParseParametersForPossibleTreeViewMenu(Params, gUseTreeViewMenuWithFavoriteTabsFromMenuCommand, gUseTreeViewMenuWithFavoriteTabsFromDoubleClick, bUseTreeViewMenu, bUsePanel, p);

    // We fill in the popup menu structure.
    gFavoriteTabsList.PopulateMenuWithFavoriteTabs(frmMain.pmFavoriteTabs, @DoOnClickMenuJobFavoriteTabs, ftmp_FAVTABSWITHCONFIG);
    Application.ProcessMessages;

    // Show the appropriate menu.
    if bUseTreeViewMenu then
    begin
      if not bUsePanel then
        iWantedHeight := 0
      else
      begin
        iWantedWidth := frmMain.ActiveFrame.Width;
        iWantedHeight := frmMain.ActiveFrame.Height;
      end;

      sMaybeMenuItem := GetUserChoiceFromTreeViewMenuLoadedFromPopupMenu(frmMain.pmFavoriteTabs, tvmcFavoriteTabs, p.X, p.Y, iWantedWidth, iWantedHeight);
      if sMaybeMenuItem <> nil then sMaybeMenuItem.Click;
    end
    else
    begin
      frmMain.pmFavoriteTabs.Popup(p.X, p.Y);
    end;
  end
  else
  begin
    // If we've seen the 'setup' parameter, let's see if user provided a name or not.
    if sSearchedFavoriteTabsName<>'' then
    begin
      // If we got a name, let's attempt to load a setup with that name.
      iMaybeIndex := gFavoriteTabsList.GetIndexForSuchFavoriteTabsName(sSearchedFavoriteTabsName);
      if iMaybeIndex <> -1 then
        gFavoriteTabsList.LoadTabsFromXmlEntry(iMaybeIndex)
      else
        if gToolbarReportErrorWithCommands then
          msgError(Format(rsFavoriteTabs_SetupNotExist,[sSearchedFavoriteTabsName]));
    end
    else
    begin
      // If no name provided, it means user want to unselect current setup.
      gFavoriteTabsList.LastFavoriteTabsLoadedUniqueId := DCGetNewGUID;
    end;
  end;
end;

{ TMainCommands.DoCopyAllTabsToOppositeSide }
procedure TMainCommands.DoCopyAllTabsToOppositeSide(ANotebook: TFileViewNotebook; var bAbort: boolean; bDoLocked: boolean; var iAskForLocked: integer);
var
  iPage: integer;
  localFileViewPage: TFileViewPage;
  localPath: string;
  TargetNotebook: TFileViewNotebook;
  iPageCountLimit: integer;
begin
  if FOriginalNumberOfTabs <> -1 then iPageCountLimit := FOriginalNumberOfTabs else iPageCountLimit := ANotebook.PageCount;
  if ANotebook = FrmMain.LeftTabs then TargetNotebook := FrmMain.RightTabs else TargetNotebook := FrmMain.LeftTabs;
  for iPage := 0 to pred(iPageCountLimit) do
  begin
    localPath := ANotebook.Page[iPage].FileView.CurrentPath;

    localFileViewPage := TargetNotebook.NewPage(ANotebook.Page[iPage].FileView);
    // Workaround for Search Result File Source
    if localFileViewPage.FileView.FileSource is TSearchResultFileSource then
      SetFileSystemPath(localFileViewPage.FileView, localPath)
    else
      localFileViewPage.FileView.CurrentPath := localPath;
  end;
end;

{ TMainCommands.cm_CopyAllTabsToOpposite }
procedure TMainCommands.cm_CopyAllTabsToOpposite(const Params: array of string);
begin
  DoActionOnMultipleTabs(Params, @DoCopyAllTabsToOppositeSide);
end;

{ TMainCommands.cm_ConfigTreeViewMenus }
procedure TMainCommands.cm_ConfigTreeViewMenus(const {%H-}Params: array of string);
begin
  cm_Options(['TfrmOptionsTreeViewMenu']);
end;

{ TMainCommands.cm_ConfigTreeViewMenusColors }
procedure TMainCommands.cm_ConfigTreeViewMenusColors(const {%H-}Params: array of string);
begin
  cm_Options(['TfrmOptionsTreeViewMenuColor']);
end;

procedure TMainCommands.cm_ConfigSavePos(const Params: array of string);
begin
  frmMain.SaveWindowState;
  try
    gConfig.Save;
  except
    on E: Exception do msgError(E.Message);
  end;
end;

{ TMainCommands.cm_ConfigSaveSettings }
procedure TMainCommands.cm_ConfigSaveSettings(const Params: array of string);
begin
  frmMain.ConfigSaveSettings(True);
end;

{ TMainCommands.cm_ExecuteScript }
procedure TMainCommands.cm_ExecuteScript(const Params: array of string);
var
  FileName, sErrorMessage: String;
  Index, Count: Integer;
  Args: array of String;
begin
  if Length(Params) > 0 then
  begin
    // Get script file name
    FileName:= PrepareParameter(Params[0]);
    if not mbFileExists(FileName) then
    begin
      msgError(Format(rsMsgFileNotFound, [Filename]));
      Exit;
    end;

    // Get script arguments
    Count:= Length(Params) - 1;
    if (Count > 0) then
    begin
      SetLength(Args, Count);
      for Index := 1 to Count do begin
        Args[Index - 1]:= PrepareParameter(Params[Index]);
      end;
    end;

    // Execute script
    if not ExecuteScript(FileName, Args, sErrorMessage) then
      if sErrorMessage <> '' then
        if msgYesNo(sErrorMessage + #$0A + rsMsgWantToConfigureLibraryLocation) then
          cm_Options(['TfrmOptionsPluginsGroup']);
  end;
end;

procedure TMainCommands.cm_FocusSwap(const Params: array of string);
var
  AParam, AValue: String;
begin
  with frmMain do
  begin
    // Select opposite panel
    if Length(Params) = 0 then
    begin
      case SelectedPanel of
        fpLeft: SetActiveFrame(fpRight);
        fpRight: SetActiveFrame(fpLeft);
      end;
    end
    else begin
      AParam:= GetDefaultParam(Params);
      if GetParamValue(AParam, 'side', AValue) then
      begin
        if AValue = 'left' then
          SetActiveFrame(fpLeft)
        else if AValue = 'right' then
          SetActiveFrame(fpRight);
      end;
    end;
  end;
end;

procedure TMainCommands.cm_Benchmark(const Params: array of string);
begin
  OperationsManager.AddOperation(TBenchmarkOperation.Create(frmMain));
end;

{ TMainCommands.cm_ConfigArchivers }
procedure TMainCommands.cm_ConfigArchivers(const {%H-}Params: array of string);
begin
  cm_Options(['TfrmOptionsArchivers']);
end;

{ TMainCommands.cm_ConfigTooltip }
procedure TMainCommands.cm_ConfigTooltips(const {%H-}Params: array of string);
begin
  cm_Options(['TfrmOptionsToolTips']);
end;

procedure TMainCommands.cm_OpenDriveByIndex(const Params: array of string);
var
  Param: String;
  Index: Integer;
  AValue: String;
  SelectedPanel: TFilePanelSelect;
begin
  if Length(Params) > 0 then
  begin
    SelectedPanel:= frmMain.SelectedPanel;

    for Param in Params do
    begin
      if GetParamValue(Param, 'index', AValue) then
      begin
        Index:= StrToIntDef(AValue, 1) - 1;
      end
      else if GetParamValue(Param, 'side', AValue) then
      begin
        if AValue = 'left' then SelectedPanel:= fpLeft
        else if AValue = 'right' then SelectedPanel:= fpRight
        else if AValue = 'inactive' then
        begin
          if frmMain.SelectedPanel = fpLeft then
            SelectedPanel:= fpRight
          else if frmMain.SelectedPanel = fpRight then
            SelectedPanel:= fpLeft;
        end;
      end
    end;

    if (Index >= 0) and (Index < frmMain.Drives.Count) then
    begin
      frmMain.SetPanelDrive(SelectedPanel, frmMain.Drives.Items[Index], True);
    end;
  end;
end;

{ TMainCommands.cm_ConfigPlugins }
procedure TMainCommands.cm_ConfigPlugins(const {%H-}Params: array of string);
begin
  cm_Options(['TfrmOptionsPluginsGroup']);
end;

{ TMainCommands.cm_AddNewSearch }
procedure TMainCommands.cm_AddNewSearch(const Params: array of string);
var
  TemplateName: String;
begin
  if Length(Params) > 0 then
    TemplateName:= Params[0]
  else begin
    TemplateName:= gSearchDefaultTemplate;
  end;
  ShowFindDlg(frmMain.ActiveFrame, TemplateName, True);
end;

{ TMainCommands.cm_ViewSearches }
procedure TMainCommands.cm_ViewSearches(const {%H-}Params: array of string);
var
  iIndex,iCurrentPage:integer;
  iSelectedWindow: integer = -1;
  slWindowTitleToOffer:TStringList;
  sTitleSelected:string='';
begin
  if ListOffrmFindDlgInstance.Count>0 then
  begin
  slWindowTitleToOffer:=TStringList.Create;

  try
    for iIndex:=0 to pred(ListOffrmFindDlgInstance.count) do
      slWindowTitleToOffer.Add(ListOffrmFindDlgInstance.frmFindDlgInstance[iIndex].Caption);

    if ShowInputListBox(rsListOfFindFilesWindows, rsSelectYouFindFilesWindow, slWindowTitleToOffer,sTitleSelected,iSelectedWindow) then
    begin
      if (iSelectedWindow>-1) AND (iSelectedWindow<ListOffrmFindDlgInstance.Count) then
      begin
        iCurrentPage:=ListOffrmFindDlgInstance.frmFindDlgInstance[iSelectedWindow].pgcSearch.ActivePageIndex;
        ListOffrmFindDlgInstance.frmFindDlgInstance[iSelectedWindow].ShowOnTop;
        ListOffrmFindDlgInstance.frmFindDlgInstance[iSelectedWindow].pgcSearch.ActivePageIndex:=iCurrentPage;
      end;
    end;
  finally
    FreeAndNil(slWindowTitleToOffer);
  end;
  end
  else
  begin
    msgOK(rsNoFindFilesWindowYet);
  end;
end;

{ TMainCommands.cm_DeleteSearches }
procedure TMainCommands.cm_DeleteSearches(const Params: array of string);
var
  iIndex:integer;
begin
  if ListOffrmFindDlgInstance.Count>0 then
  begin
    for iIndex := pred(ListOffrmFindDlgInstance.count) downto 0 do
      ListOffrmFindDlgInstance.frmFindDlgInstance[iIndex].CancelCloseAndFreeMem;
  end
  else
  begin
    msgOK(rsNoFindFilesWindowYet);
  end;
end;

{ TMainCommands.cm_ConfigSearches }
procedure TMainCommands.cm_ConfigSearches(const Params: array of string);
begin
  cm_Options(['TfrmOptionsFileSearch']);
end;

{ TMainCommands.cm_ConfigHotKeys }
procedure TMainCommands.cm_ConfigHotKeys(const Params: array of string);
var
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
  Param, sCategoryName:string;
begin
  sCategoryName:='';
  Options := ShowOptions(TfrmOptionsHotkeys);
  Editor := Options.GetEditor(TfrmOptionsHotkeys);
  Application.ProcessMessages;
  for Param in Params do
    GetParamValue(Param, 'category', sCategoryName);
  TfrmOptionsHotkeys(Editor).TryToSelectThatCategory(sCategoryName);
  if Editor.CanFocus then  Editor.SetFocus;
end;

{ TMainCommands.cm_AddPlugin }
procedure TMainCommands.cm_AddPlugin(const Params: array of string);
const
  sPLUGIN_FAMILY = 'DSX|WCX|WDX|WFX|WLX|';
  sPLUGIN64_FAMILY = 'DSX64|WCX64|WDX64|WFX64|WLX64|';
var
  Param, sValue, sMaybeFilename, sPluginFilename: string;
  PluginType: TPluginType;
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
  sPluginSuffix: string;
  iPluginDispatcher: integer = -1;

  procedure SetPluginTypeBasedOnThisString(sSubString:string);
  begin
    sSubString := UpperCase(StringReplace(sSubString, '.', '', [rfReplaceAll]));
    if pos((sSubString+'|'), sPLUGIN_FAMILY) <> 0 then
    begin
      sPluginSuffix := sSubString;
      iPluginDispatcher := ((pos(sPluginSuffix, sPLUGIN_FAMILY) - 1) div 4);
    end
    else
    begin
      if pos((sSubString+'|'), sPLUGIN64_FAMILY) <> 0 then
      begin
        sPluginSuffix := LeftStr(sSubString, 3);
        iPluginDispatcher := ((pos(sPluginSuffix, sPLUGIN64_FAMILY) - 1) div 6);
      end;
    end;
  end;

begin
  //1. We initialize our seeking variables.
  sPluginSuffix := '';
  sPluginFilename := '';

  //2. Let's parse the parameter to get the wanted ones.
  for Param in Params do
  begin
    if GetParamValue(Param, 'type', sValue) then
      SetPluginTypeBasedOnThisString(sValue)
    else if GetParamValue(Param, 'file', sValue) then
      sPluginFilename := RemoveQuotation(PrepareParameter(sValue));
  end;

  //3. If user provided no parameter, let's launch the file requester to have user point a file.
  if Length(Params) = 0 then
  begin
    dmComData.OpenDialog.Filter:= ParseLineToFileFilter([rsFilterPluginFiles, '*.dsx;*.wcx;*.wdx;*.wfx;*.wlx;*.dsx64;*.wcx64;*.wdx64;*.wfx64;*.wlx64', rsFilterAnyFiles, AllFilesMask]);
    dmComData.OpenDialog.InitialDir := frmMain.ActiveNotebook.ActivePage.FileView.CurrentPath;
    if dmComData.OpenDialog.Execute then
      sPluginFilename := dmComData.OpenDialog.FileName;
  end;

  //3. If user provided just the filename, let's guess the plugin type based on file's extension.
  if (sPluginSuffix = '') AND (sPluginFilename <> '') then
    SetPluginTypeBasedOnThisString(ExtractFileExt(sPluginFilename));

  //4. If user provided something but did not specify clear parematers, let's assume it's simply directly a filename.
  if (sPluginSuffix = '') AND (sPluginFilename = '') and (Length(Params) > 0) then
  begin
    sMaybeFilename := RemoveQuotation(PrepareParameter(Params[0]));
    if FileExists(sMaybeFilename) then
    begin
      sPluginFilename := sMaybeFilename;
      SetPluginTypeBasedOnThisString(ExtractFileExt(sPluginFilename));
    end;
  end;

  //5. At this point, if we have a filename and have determine plugin type, let's attempt to add the plugin.
  if (sPluginSuffix <> '') AND (sPluginFilename <> '') then
  begin
    if FileExists(sPluginFilename) then
    begin
      Options := ShowOptions('TfrmOptionsPlugins' + sPluginSuffix);
      Application.ProcessMessages;
      case iPluginDispatcher of
        0: Editor := Options.GetEditor(TfrmOptionsPluginsDSX);
        1: Editor := Options.GetEditor(TfrmOptionsPluginsWCX);
        2: Editor := Options.GetEditor(TfrmOptionsPluginsWDX);
        3: Editor := Options.GetEditor(TfrmOptionsPluginsWFX);
        4: Editor := Options.GetEditor(TfrmOptionsPluginsWLX);
        else exit;
      end;

      if Editor.CanFocus then Editor.SetFocus;
      TfrmOptionsPluginsBase(Editor).ActualAddPlugin(sPluginFilename);
    end;
  end
  else if (Length(sMaybeFilename) > 0) then
  begin
    InstallPlugin(sMaybeFilename);
  end;
end;

procedure TMainCommands.cm_LoadList(const Params: array of string);
var
  aFile: TFile;
  sValue: String;
  AParam: String;
  Index: Integer;
  AFileName: String;
  FileList: TFileTree;
  NewPage: TFileViewPage;
  StringList: TStringListUAC;
  Notebook: TFileViewNotebook;
  SearchResultFS: ISearchResultFileSource;
begin
  with frmMain do
  begin
    AFileName:= EmptyStr;
    Notebook := ActiveNotebook;
    for AParam in Params do
    begin
      if GetParamValue(AParam, 'filename', sValue) then
        AFileName := sValue
      else if GetParamValue(AParam, 'side', sValue) then
      begin
        if sValue = 'left' then Notebook := LeftTabs
        else if sValue = 'right' then Notebook := RightTabs
        else if sValue = 'active' then Notebook := ActiveNotebook
        else if sValue = 'inactive' then Notebook := NotActiveNotebook;
      end;
    end;
    if (Length(AFileName) = 0) then
    begin
      msgError(rsMsgInvalidFilename);
      Exit;
    end;
    StringList:= TStringListUAC.Create;
    try
      StringList.LoadFromFile(AFileName);

      FileList := TFileTree.Create;

      for Index := 0 to StringList.Count - 1 do
      begin
        try
          aFile := TFileSystemFileSource.CreateFileFromFile(StringList[Index]);
          FileList.AddSubNode(aFile);
        except
          on EFileNotFound do ;
        end;
      end;
      SearchResultFS := TSearchResultFileSource.Create;
      SearchResultFS.AddList(FileList, TFileSystemFileSource.GetFileSource);

      NewPage := Notebook.ActivePage;
      NewPage.FileView.AddFileSource(SearchResultFS, SearchResultFS.GetRootDir);
      NewPage.FileView.FlatView := True;
      NewPage.MakeActive;
    except
      on E: Exception do msgError(E.Message);
    end;
    StringList.Free;
  end;
end;

end.

