{
   Double Commander
   -------------------------------------------------------------------------
   Licence  : GNU GPL v 2.0
   Copyright (C) 2006-2023 Alexander Koblov (Alexx2000@mail.ru)

   Main Dialog window

   based on:

   Seksi Commander (radekc.regnet.cz)
   ----------------------------
   Licence  : GNU GPL v 2.0
   Author   : radek.cervinka@centrum.cz

   Main Dialog window and other stuff

   contributors:


   based on (heavy rewriten):

   main Unit of PFM : Peter's File Manager
   ---------------------------------------

   Copyright : Peter Cernoch 2002
   Contact   : pcernoch@volny.cz
   Licence   : GNU GPL v 2.0
   
   contributors:

   Copyright (C) 2008 Vitaly Zotov (vitalyzotov@mail.ru)

}

unit fMain;

{$mode objfpc}{$H+}

interface

uses
  ufavoritetabs, Graphics, Forms, Menus, Controls, StdCtrls, ExtCtrls, ActnList,
  Buttons, SysUtils, Classes, SynEdit, LCLType, ComCtrls, LResources,
  KASToolBar, KASComboBox, uFilePanelSelect, uBriefFileView, VTEmuCtl, VTEmuPty,
  uFileView, uFileSource, uFileViewNotebook, uFile, LCLVersion, KASToolPanel,
  uOperationsManager, uFileSourceOperation, uDrivesList, DCClassesUtf8,
  DCXmlConfig, uDrive, uDriveWatcher, uDCVersion, uMainCommands, uFormCommands,
  uOperationsPanel, KASToolItems, uKASToolItemsExtended, uCmdLineParams, uOSForms
  {$IF DEFINED(LCLQT)}
  , Qt4, QtWidgets
  {$ELSEIF DEFINED(LCLQT5)}
  , Qt5, QtWidgets
  {$ELSEIF DEFINED(LCLQT6)}
  , Qt6, QtWidgets
  {$ELSEIF DEFINED(LCLGTK2)}
  , Glib2, Gtk2
  {$ELSEIF DEFINED(DARWIN)}
  , uMyDarwin
  {$ENDIF}
  , Types, LMessages;

type

  TForEachViewFunction = procedure (AFileView: TFileView; UserData: Pointer) of object;

  { TfrmMain }

  TfrmMain = class(TAloneForm, IFormCommands)
    actAddPlugin: TAction;
    actShowTabsList: TAction;
    actSaveFileDetailsToFile: TAction;
    actLoadList: TAction;
    actExtractFiles: TAction;
    actAddPathToCmdLine: TAction;
    actFileAssoc: TAction;
    actFocusCmdLine: TAction;
    actContextMenu: TAction;
    actCopyNamesToClip: TAction;
    actCopyFullNamesToClip: TAction;
    actCutToClipboard: TAction;
    actCopyToClipboard: TAction;
    actSyncChangeDir: TAction;
    actChangeDirToRoot: TAction;
    actCountDirContent: TAction;
    actCheckSumVerify: TAction;
    actCheckSumCalc: TAction;
    actClearLogFile: TAction;
    actClearLogWindow: TAction;
    actChangeDir: TAction;
    actAddFilenameToCmdLine: TAction;
    actAddPathAndFilenameToCmdLine: TAction;
    actCopyNoAsk: TAction;
    actChangeDirToParent: TAction;
    actEditPath: TAction;
    actHorizontalFilePanels: TAction;
    actGoToFirstEntry: TAction;
    actGoToLastEntry: TAction;
    actGoToNextEntry: TAction;
    actGoToPrevEntry: TAction;
    actGoToFirstFile: TAction;
    actGoToLastFile: TAction;
    actCompareDirectories: TAction;
    actCmdLineNext: TAction;
    actCmdLinePrev: TAction;
    actBriefView: TAction;
    actColumnsView: TAction;
    actChangeDirToHome: TAction;
    actCopyFileDetailsToClip: TAction;
    actFlatView: TAction;
    actFlatViewSel: TAction;
    actConfigDirHotList: TAction;
    actCopyPathOfFilesToClip: TAction;
    actCopyPathNoSepOfFilesToClip: TAction;
    actDoAnyCmCommand: TAction;
    actCloseDuplicateTabs: TAction;
    actCopyAllTabsToOpposite: TAction;
    actConfigTreeViewMenus: TAction;
    actConfigTreeViewMenusColors: TAction;
    actConfigSavePos: TAction;
    actConfigSaveSettings: TAction;
    actExecuteScript: TAction;
    actFocusSwap: TAction;
    actConfigArchivers: TAction;
    actConfigTooltips: TAction;
    actConfigPlugins: TAction;
    actUnmarkCurrentNameExt: TAction;
    actMarkCurrentNameExt: TAction;
    actUnmarkCurrentName: TAction;
    actMarkCurrentName: TAction;
    actUnmarkCurrentPath: TAction;
    actMarkCurrentPath: TAction;
    actTreeView: TAction;
    actFocusTreeView: TAction;
    actToggleFullscreenConsole: TAction;
    actSrcOpenDrives: TAction;
    actRightReverseOrder: TAction;
    actLeftReverseOrder: TAction;
    actRightFlatView: TAction;
    actLeftFlatView: TAction;
    actRightSortByAttr: TAction;
    actRightSortByDate: TAction;
    actRightSortBySize: TAction;
    actRightSortByExt: TAction;
    actRightSortByName: TAction;
    actLeftSortByAttr: TAction;
    actLeftSortByDate: TAction;
    actLeftSortBySize: TAction;
    actLeftSortByExt: TAction;
    actLeftSortByName: TAction;
    actLeftThumbView: TAction;
    actRightThumbView: TAction;
    actRightColumnsView: TAction;
    actLeftColumnsView: TAction;
    actRightBriefView: TAction;
    actLeftBriefView: TAction;
    actWorkWithDirectoryHotlist: TAction;
    actUniversalSingleDirectSort: TAction;
    actViewLogFile: TAction;
    actLoadTabs: TAction;
    actSaveTabs: TAction;
    actSyncDirs: TAction;
    actThumbnailsView: TAction;
    actShellExecute: TAction;
    actRenameTab: TAction;
    actOperationsViewer: TAction;
    actCopyNetNamesToClip: TAction;
    actNetworkDisconnect: TAction;
    actNetworkQuickConnect: TAction;
    actNetworkConnect: TAction;
    actViewHistory: TAction;
    actViewHistoryPrev: TAction;
    actViewHistoryNext: TAction;
    actLoadSelectionFromClip: TAction;
    actLoadSelectionFromFile: TAction;
    actSaveSelectionToFile: TAction;
    actSaveSelection: TAction;
    actRestoreSelection: TAction;
    actSwitchIgnoreList: TAction;
    actTestArchive: TAction;
    actQuickView: TAction;
    actOpenBar: TAction;
    actSetFileProperties: TAction;
    actQuickFilter: TAction;
    actRenameNoAsk: TAction;
    actPanelsSplitterPerPos: TAction;
    actMinimize: TAction;
    actRightEqualLeft: TAction;
    actLeftEqualRight: TAction;
    actPasteFromClipboard: TAction;
    actExchange: TAction;
    actEditComment: TAction;
    actHelpIndex: TAction;
    actVisitHomePage: TAction;
    actKeyboard: TAction;
    actPrevTab: TAction;
    actNextTab: TAction;
    actMoveTabLeft: TAction;
    actMoveTabRight: TAction;
    actActivateTabByIndex: TAction;
    actCloseAllTabs: TAction;
    actSetTabOptionNormal: TAction;
    actSetTabOptionPathLocked: TAction;
    actSetTabOptionPathResets: TAction;
    actSetTabOptionDirsInNewTab: TAction;
    actUnmarkCurrentExtension: TAction;
    actMarkCurrentExtension: TAction;
    actWipe: TAction;
    actOpenDirInNewTab: TAction;
    actTargetEqualSource: TAction;
    actOpen: TAction;
    actQuickSearch: TAction;
    actShowButtonMenu: TAction;
    actOpenArchive: TAction;
    actTransferRight: TAction;
    actTransferLeft: TAction;
    actRightOpenDrives: TAction;
    actLeftOpenDrives: TAction;
    actOpenVirtualFileSystemList: TAction;
    actPackFiles: TAction;
    actCloseTab: TAction;
    actNewTab: TAction;
    actConfigToolbars: TAction;
    actDebugShowCommandParameters: TAction;
    actOpenDriveByIndex: TAction;
    btnF10: TSpeedButton;
    btnF3: TSpeedButton;
    btnF4: TSpeedButton;
    btnF5: TSpeedButton;
    btnF6: TSpeedButton;
    btnF7: TSpeedButton;
    btnF8: TSpeedButton;
    btnF9: TSpeedButton;
    btnLeftDirectoryHotlist: TSpeedButton;
    btnRightDirectoryHotlist: TSpeedButton;
    dskLeft: TKASToolBar;
    dskRight: TKASToolBar;
    edtCommand: TComboBoxWithDelItems;
    imgLstActions: TImageList;
    imgLstDirectoryHotlist: TImageList;
    lblRightDriveInfo: TLabel;
    lblLeftDriveInfo: TLabel;
    lblCommandPath: TLabel;
    mnuDoAnyCmCommand: TMenuItem;
    miConfigArchivers: TMenuItem;
    mnuConfigSavePos: TMenuItem;
    mnuConfigSaveSettings: TMenuItem;
    miLine55: TMenuItem;
    mnuConfigureFavoriteTabs: TMenuItem;
    mnuRewriteFavoriteTabs: TMenuItem;
    mnuCreateNewFavoriteTabs: TMenuItem;
    mnuReloadActiveFavoriteTabs: TMenuItem;
    mnuFavoriteTabs: TMenuItem;
    mnuCloseDuplicateTabs: TMenuItem;
    miCloseDuplicateTabs: TMenuItem;
    mnuTreeView: TMenuItem;
    mnuCmdConfigDirHotlist: TMenuItem;
    mnuLoadTabs: TMenuItem;
    mnuSaveTabs: TMenuItem;
    miLine38: TMenuItem;
    miFlatView: TMenuItem;
    miMakeDir: TMenuItem;
    miWipe: TMenuItem;
    miDelete: TMenuItem;
    miLine50: TMenuItem;
    miCopyFileDetailsToClip: TMenuItem;
    mnuCmdSyncDirs: TMenuItem;
    mnuContextRenameOnly: TMenuItem;
    mnuContextCopy: TMenuItem;
    mnuContextOpen: TMenuItem;
    mnuContextLine1: TMenuItem;
    mnuContextLine2: TMenuItem;
    mnuContextFileProperties: TMenuItem;
    mnuContextDelete: TMenuItem;
    mnuContextView: TMenuItem;
    mnuThumbnailsView: TMenuItem;
    mnuColumnsView: TMenuItem;
    mnuBriefView: TMenuItem;
    miLine33: TMenuItem;
    mnuAllOperStart: TMenuItem;
    mnuAllOperStop: TMenuItem;
    mnuAllOperPause: TMenuItem;
    mnuAllOperProgress: TMenuItem;
    miCompareDirectories: TMenuItem;
    miLine37: TMenuItem;
    miRenameTab: TMenuItem;
    pnlMain: TPanel;
    tbChangeDir: TMenuItem;
    mnuShowHorizontalFilePanels: TMenuItem;
    miLine20: TMenuItem;
    miNetworkDisconnect: TMenuItem;
    miNetworkQuickConnect: TMenuItem;
    miNetworkConnect: TMenuItem;
    mnuNetwork: TMenuItem;
    pnlDskLeft: TPanel;
    pnlDiskLeftInner: TKASToolPanel;
    pnlDskRight: TPanel;
    pnlDiskRightInner: TKASToolPanel;
    Timer: TTimer;
    PanelAllProgress: TPanel;
    pbxRightDrive: TPaintBox;
    pbxLeftDrive: TPaintBox;
    tbPaste: TMenuItem;
    tbCopy: TMenuItem;
    tbCut: TMenuItem;
    tbSeparator: TMenuItem;
    mnuLoadSelectionFromClip: TMenuItem;
    mnuLoadSelectionFromFile: TMenuItem;
    mnuSaveSelectionToFile: TMenuItem;
    mnuRestoreSelection: TMenuItem;
    mnuSaveSelection: TMenuItem;
    miLine47: TMenuItem;
    mnuTestArchive: TMenuItem;
    mnuQuickView: TMenuItem;
    miLine32: TMenuItem;
    miLine14: TMenuItem;
    mnuTabOptionNormal: TMenuItem;
    mnuTabOptionDirsInNewTabs: TMenuItem;
    mnuTabOptions: TMenuItem;
    miTabOptionPathResets: TMenuItem;
    miTabOptionDirsInNewTab: TMenuItem;
    miTabOptionPathLocked: TMenuItem;
    miTabOptionNormal: TMenuItem;
    miTabOptions: TMenuItem;
    miLine19: TMenuItem;
    mnuSetFileProperties: TMenuItem;
    mnuShowOperations: TMenuItem;
    miLine13: TMenuItem;
    miLogClear: TMenuItem;
    miLogHide: TMenuItem;
    miLine25: TMenuItem;
    miLogSelectAll: TMenuItem;
    miLogCopy: TMenuItem;
    miLine24: TMenuItem;
    miTrayIconRestore: TMenuItem;
    miLine8: TMenuItem;
    miTrayIconExit: TMenuItem;
    mnuCheckSumCalc: TMenuItem;
    mnuCheckSumVerify: TMenuItem;
    mnuCountDirContent: TMenuItem;
    miLine22: TMenuItem;
    miLine18: TMenuItem;
    mnuHelpIndex: TMenuItem;
    mnuHelpVisitHomePage: TMenuItem;
    mnuHelpKeyboard: TMenuItem;
    MenuItem2: TMenuItem;
    mnuPrevTab: TMenuItem;
    mnuNextTab: TMenuItem;
    miLine17: TMenuItem;
    miLine16: TMenuItem;
    mnuTabOptionPathLocked: TMenuItem;
    mnuTabOptionPathResets: TMenuItem;
    mnuCloseAllTabs: TMenuItem;
    mnuCloseTab: TMenuItem;
    miLine15: TMenuItem;
    mnuOpenDirInNewTab: TMenuItem;
    mnuNewTab: TMenuItem;
    miCloseAllTabs: TMenuItem;
    miCloseTab: TMenuItem;
    miNewTab: TMenuItem;
    miEditComment: TMenuItem;
    mnuMarkCurrentExtension: TMenuItem;
    mnuTabs: TMenuItem;
    mnuUnmarkCurrentExtension: TMenuItem;
    miSymLink: TMenuItem;
    miHardLink: TMenuItem;
    miCancel: TMenuItem;
    miLine12: TMenuItem;
    miCopy: TMenuItem;
    miMove: TMenuItem;
    mi8020: TMenuItem;
    mi7030: TMenuItem;
    mi6040: TMenuItem;
    mi5050: TMenuItem;
    mi4060: TMenuItem;
    mi3070: TMenuItem;
    mi2080: TMenuItem;
    miCopyFullNamesToClip: TMenuItem;
    miCopyNamesToClip: TMenuItem;
    mnuFileAssoc: TMenuItem;
    nbConsole: TPageControl;
    pgConsole: TTabSheet;
    pnlCmdLine: TPanel;
    MainSplitter: TPanel;
    MainToolBar: TKASToolBar;
    MiddleToolBar: TKASToolBar;
    mnuOpenVFSList: TMenuItem;
    mnuExtractFiles: TMenuItem;
    pmContextMenu: TPopupMenu;
    pmSplitterPercent: TPopupMenu;
    pnlCommand: TPanel;
    pnlKeys: TPanel;
    pnlLeftTools: TPanel;
    pnlRightTools: TPanel;
    pnlRight: TPanel;
    pnlLeft: TPanel;
    btnLeftDrive: TSpeedButton;
    btnLeftHome: TSpeedButton;
    btnLeftUp: TSpeedButton;
    btnLeftRoot: TSpeedButton;
    btnRightDrive: TSpeedButton;
    btnRightHome: TSpeedButton;
    btnRightUp: TSpeedButton;
    btnRightRoot: TSpeedButton;
    LogSplitter: TSplitter;
    pmColumnsMenu: TPopupMenu;
    pmDropMenu: TPopupMenu;
    pmTabMenu: TPopupMenu;
    pmTrayIconMenu: TPopupMenu;
    pmLogMenu: TPopupMenu;
    seLogWindow: TSynEdit;
    btnRightEqualLeft: TSpeedButton;
    btnLeftEqualRight: TSpeedButton;
    ConsoleSplitter: TSplitter;
    tbDelete: TMenuItem;
    tbEdit: TMenuItem;
    mnuMain: TMainMenu;
    pnlNotebooks: TPanel;
    pnlDisk: TKASToolPanel;
    mnuHelp: TMenuItem;
    mnuHelpAbout: TMenuItem;
    mnuShow: TMenuItem;
    mnuShowName: TMenuItem;
    mnuShowExtension: TMenuItem;
    mnuShowTime: TMenuItem;
    mnuShowSize: TMenuItem;
    mnuShowAttrib: TMenuItem;
    miLine7: TMenuItem;
    mnuShowReverse: TMenuItem;
    mnuShowReread: TMenuItem;
    mnuFiles: TMenuItem;
    mnuPackFiles : TMenuItem;
    mnuFilesSplit: TMenuItem;
    mnuFilesCombine: TMenuItem;
    mnuCmd: TMenuItem;
    mnuCmdDirHotlist: TMenuItem;
    miLine2: TMenuItem;
    mnuFilesSpace: TMenuItem;
    mnuFilesAttrib: TMenuItem;
    mnuFilesProperties: TMenuItem;
    miLine6: TMenuItem;
    mnuCmdSwapSourceTarget: TMenuItem;
    mnuCmdTargetIsSource: TMenuItem;
    miLine3: TMenuItem;
    mnuFilesShwSysFiles: TMenuItem;
    miLine1: TMenuItem;
    mnuFilesHardLink: TMenuItem;
    mnuFilesSymLink: TMenuItem;
    mnuConfig: TMenuItem;
    mnuConfigOptions: TMenuItem;
    mnuMark: TMenuItem;
    mnuMarkSGroup: TMenuItem;
    mnuMarkUGroup: TMenuItem;
    mnuMarkSAll: TMenuItem;
    mnuMarkUAll: TMenuItem;
    mnuMarkInvert: TMenuItem;
    miLine5: TMenuItem;
    mnuCmdSearch: TMenuItem;
    mnuCmdAddNewSearch:TMenuItem;
    mnuCmdViewSearches:TMenuItem;
    actionLst: TActionList;
    actExit: TAction;
    actView: TAction;
    actEdit: TAction;
    actCopy: TAction;
    actRename: TAction;
    actMakeDir: TAction;
    actDelete: TAction;
    actAbout: TAction;
    actShowSysFiles: TAction;
    actOptions: TAction;
    mnuFilesCmpCnt: TMenuItem;
    actCompareContents: TAction;
    actShowMainMenu: TAction;
    actRefresh: TAction;
    actSearch: TAction;
    actAddNewSearch: TAction;
    actViewSearches: TAction;
    actDeleteSearches: TAction;
    actConfigSearches: TAction;
    actConfigHotKeys: TAction;
    actDirHotList: TAction;
    actMarkMarkAll: TAction;
    actMarkInvert: TAction;
    actMarkUnmarkAll: TAction;
    pmHotList: TPopupMenu;
    actMarkPlus: TAction;
    actMarkMinus: TAction;
    actSymLink: TAction;
    actHardLink: TAction;
    actReverseOrder: TAction;
    actSortByName: TAction;
    actSortByExt: TAction;
    actSortBySize: TAction;
    actSortByDate: TAction;
    actSortByAttr: TAction;
    miLine4: TMenuItem;
    miExit: TMenuItem;
    actMultiRename: TAction;
    miMultiRename: TMenuItem;
    actCopySamePanel: TAction;
    actRenameOnly: TAction;
    actEditNew: TAction;
    actDirHistory: TAction;
    pmDirHistory: TPopupMenu;
    actShowCmdLineHistory: TAction;
    actRunTerm: TAction;
    miLine9: TMenuItem;
    miRunTerm: TMenuItem;
    actBenchmark: TAction;
    actCalculateSpace: TAction;
    actFileProperties:  TAction;
    actFileLinker: TAction;
    actFileSpliter: TAction;
    pmToolBar: TPopupMenu;
    MainTrayIcon: TTrayIcon;
    TreePanel: TPanel;
    TreeSplitter: TSplitter;
    ShellTreeView: TCustomTreeView;
    miLine10: TMenuItem;
    miLine11: TMenuItem;
    miLine21: TMenuItem;
    miLine23: TMenuItem;
    miLine26: TMenuItem;
    miLine39: TMenuItem;
    miLine40: TMenuItem;
    actSetAllTabsOptionNormal: TAction;
    actSetAllTabsOptionPathLocked: TAction;
    actSetAllTabsOptionPathResets: TAction;
    actSetAllTabsOptionDirsInNewTab: TAction;
    actConfigFolderTabs: TAction;
    actLoadFavoriteTabs: TAction;
    actConfigFavoriteTabs: TAction;
    actSaveFavoriteTabs: TAction;
    actReloadFavoriteTabs: TAction;
    actNextFavoriteTabs: TAction;
    actPreviousFavoriteTabs: TAction;
    pmFavoriteTabs: TPopupMenu;
    mnuRenameTab: TMenuItem;
    mnuConfigFolderTabs: TMenuItem;
    mnuConfigFavoriteTabs: TMenuItem;
    mnuConfigurationFavoriteTabs: TMenuItem;
    mnuSaveFavoriteTabs: TMenuItem;
    mnuLoadFavoriteTabs: TMenuItem;
    mnuConfigurationFolderTabs: TMenuItem;
    mnuSetAllTabsOptionNormal: TMenuItem;
    mnuSetAllTabsOptionPathLocked: TMenuItem;
    mnuSetAllTabsOptionPathResets: TMenuItem;
    mnuSetAllTabsOptionDirsInNewTab: TMenuItem;
    miConfigFolderTabs: TMenuItem;
    miConfigFavoriteTabs: TMenuItem;
    miNextTab: TMenuItem;
    miPrevTab: TMenuItem;
    miSaveTabs: TMenuItem;
    miLoadTabs: TMenuItem;
    miSaveFavoriteTabs: TMenuItem;
    miLoadFavoriteTabs: TMenuItem;
    miSetAllTabsOptionNormal: TMenuItem;
    miSetAllTabsOptionPathLocked: TMenuItem;
    miSetAllTabsOptionPathResets: TMenuItem;
    miSetAllTabsOptionDirsInNewTab: TMenuItem;
    miOpenDirInNewTab: TMenuItem;
    actResaveFavoriteTabs: TAction;
    procedure actExecute(Sender: TObject);
    procedure btnF3MouseWheelDown(Sender: TObject; Shift: TShiftState;
      {%H-}MousePos: TPoint; var {%H-}Handled: Boolean);
    procedure btnF3MouseWheelUp(Sender: TObject; Shift: TShiftState;
      {%H-}MousePos: TPoint; var {%H-}Handled: Boolean);
    procedure btnF8MouseDown(Sender: TObject; Button: TMouseButton;
      {%H-}Shift: TShiftState; X, Y: Integer);
    procedure ConsoleSplitterMoved(Sender: TObject);
    procedure dskToolButtonMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormKeyUp( Sender: TObject; var {%H-}Key: Word; Shift: TShiftState) ;
    procedure FormResize(Sender: TObject);
    procedure lblDriveInfoResize(Sender: TObject);
    function MainToolBarToolItemShortcutsHint(Sender: TObject; ToolItem: TKASNormalItem): String;
    procedure mnuAllOperStartClick(Sender: TObject);
    procedure mnuAllOperStopClick(Sender: TObject);
    procedure mnuAllOperPauseClick(Sender: TObject);
    procedure mnuAllOperProgressClick(Sender: TObject);
    procedure btnF8Click(Sender: TObject);
    procedure btnLeftClick(Sender: TObject);
    procedure btnLeftDirectoryHotlistClick(Sender: TObject);
    procedure btnRightClick(Sender: TObject);
    procedure btnRightDirectoryHotlistClick(Sender: TObject);
    procedure btnDriveMouseUp(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer);
    procedure ConsoleSplitterCanResize(Sender: TObject; var NewSize: Integer;
      var {%H-}Accept: Boolean);
    procedure dskLeftRightToolButtonDragDrop(Sender, {%H-}Source: TObject; {%H-}X, {%H-}Y: Integer);
    procedure dskToolButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure lblAllProgressPctClick(Sender: TObject);
    procedure MainToolBarToolButtonDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure MainToolBarToolButtonDragOver(Sender, Source: TObject; X,
      Y: Integer; State: TDragState; var Accept: Boolean;
      NumberOfButton: Integer);
    procedure MainToolBarToolButtonMouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure MainToolBarToolButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

    procedure miLogMenuClick(Sender: TObject);
    procedure miTrayIconExitClick(Sender: TObject);
    procedure miTrayIconRestoreClick(Sender: TObject);
    procedure PanelButtonClick(Button: TSpeedButton; FileView: TFileView);
    procedure ShellTreeViewSelect;
    procedure ShellTreeViewKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure ShellTreeViewMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure tbDeleteClick(Sender: TObject);
    procedure dskLeftToolButtonClick(Sender: TObject);
    procedure dskRightToolButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
    procedure FormUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
    procedure FormWindowStateChange(Sender: TObject);
    procedure MainSplitterDblClick(Sender: TObject);
    procedure MainSplitterMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure MainSplitterMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure MainSplitterMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);   
    procedure MainTrayIconClick(Sender: TObject);
    procedure lblDriveInfoDblClick(Sender: TObject);
    procedure MainToolBarDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure MainToolBarDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    function MainToolBarLoadButtonGlyph(ToolItem: TKASToolItem;
      iIconSize: Integer; clBackColor: TColor): TBitmap;
    function MainToolBarLoadButtonOverlay(ToolItem: TKASToolItem;
      iIconSize: Integer; clBackColor: TColor): TBitmap;
    procedure MainToolBarMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure frmMainClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure frmMainAfterShow(Sender: TObject);
    procedure frmMainShow(Sender: TObject);
    procedure mnuDropClick(Sender: TObject);
    procedure mnuSplitterPercentClick(Sender: TObject);
    procedure mnuTabMenuExecute(Sender: TObject);
    procedure mnuTabMenuClick(Sender: TObject);
    procedure nbPageAfterMouseDown(Data: PtrInt);
    procedure nbPageMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure nbPageChanged(Sender: TObject);
    procedure nbPageMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure NotebookDragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure NotebookDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure NotebookCloseTabClicked(Sender: TObject);
    procedure pmDropMenuClose(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure edtCommandKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);

    procedure FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure pmToolBarPopup(Sender: TObject);

    procedure ShellTreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView;
      Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
      var PaintImages, DefaultDraw: Boolean);

    procedure pnlLeftResize(Sender: TObject);
    procedure pnlLeftRightDblClick(Sender: TObject);
    procedure pnlNotebooksResize(Sender: TObject);
    procedure pnlRightResize(Sender: TObject);
    procedure sboxDrivePaint(Sender: TObject);
    procedure PaintDriveFreeBar(Sender: TObject; const bIndUseGradient: boolean;
      const pIndForeColor, pIndThresholdForeColor, pIndBackColor: TColor);
    procedure seLogWindowSpecialLineColors(Sender: TObject; Line: integer;
      var Special: boolean; var FG, BG: TColor);

    procedure FileViewFreeAsync(Data: PtrInt);
    function FileViewAutoSwitch(FileSource: IFileSource; var FileView: TFileView; Reason: TChangePathReason; const NewPath: String): Boolean;
    function FileViewBeforeChangePath(FileView: TFileView; NewFileSource: IFileSource; Reason: TChangePathReason; const NewPath : String): Boolean;
    procedure FileViewAfterChangePath(FileView: TFileView);
    procedure FileViewActivate(FileView: TFileView);
    procedure FileViewFilesChanged(FileView: TFileView);
    procedure edtCommandKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure edtCommandExit(Sender: TObject);
    procedure tbChangeDirClick(Sender: TObject);
    procedure tbCopyClick(Sender: TObject);
    procedure tbEditClick(Sender: TObject);
    procedure OnUniqueInstanceMessage(Sender: TObject; Params: TCommandLineParams);
    procedure tbPasteClick(Sender: TObject);
    procedure AllProgressOnUpdateTimer(Sender: TObject);
    procedure OperationManagerNotify(Item: TOperationsManagerItem;
                                     Event: TOperationManagerEvent);
{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
  private
    QEventHook: QObject_hookH;
    function QObjectEventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
{$ENDIF}
{$IF DEFINED(LCLGTK2)}
    procedure WindowStateUpdate(Data: PtrInt);
{$ENDIF}
  private
    { Private declarations }
    FMainSplitterPos: Double;
    PanelSelected: TFilePanelSelect;
    DrivesList : TDrivesList;
    MainSplitterHintWnd: THintWindow;
    HiddenToTray: Boolean;
    HidingTrayIcon: Boolean; // @true if the icon is in the process of hiding
    nbLeft, nbRight: TFileViewNotebook;
    cmdConsole: TVirtualTerminal;
    FCommands: TMainCommands;
    FInitializedView: Boolean;
    {en
       Used to pass drag&drop parameters to pmDropMenu. Single variable
       can be used, because the user can do only one menu popup at a time. }
    FDropParams: TDropParams;
    FDrivesListPopup: TDrivesListPopup;
    FOperationsPanel: TOperationsPanel;
    FSyncChangeParent: Boolean;
    FSyncChangeDir: String;
    sStaticTitleBarString: String;

    // frost_asm begin
    // mainsplitter
    MainSplitterLeftMouseBtnDown: Boolean;
    MainSplitterMouseDownX, MainSplitterMouseDownY: Integer;
    FResizingFilePanels: Boolean;
    // lastWindowState
    lastWindowState:TWindowState;
    // frost_asm end
    // for dragging buttons and etc
    NumberOfMoveButton, NumberOfNewMoveButton: integer;
    Draging : boolean;
    FUpdateDiskCount: Boolean;
    FModalOperationResult: Boolean;

    FRestoredLeft: Integer;
    FRestoredTop: Integer;
    FRestoredWidth: Integer;
    FRestoredHeight: Integer;
    FDelayedEventCtr: Integer;
    FDelayedWMMove, FDelayedWMSize: Boolean;

    procedure DelayedEvent(Data: PtrInt);

    procedure CheckCommandLine(ShiftEx: TShiftState; var Key: Word);
    function ExecuteCommandFromEdit(sCmd: String; bRunInTerm: Boolean): Boolean;
    procedure SetMainSplitterPos(AValue: Double);
    procedure SetPanelSelected(AValue: TFilePanelSelect);
    procedure UpdateActionIcons;
    procedure UpdateHotDirIcons;
    procedure TypeInCommandLine(Str: String);
    procedure AddSpecialButtons(dskPanel: TKASToolBar);
    procedure HideToTray;
    procedure RestoreFromTray;
    procedure ShowTrayIcon(bShow: Boolean);
    procedure HideTrayIconDelayed(Data: PtrInt);

    procedure PopupDragDropMenu(var DropParams: TDropParams);
    procedure CloseNotebook(ANotebook: TFileViewNotebook);

    procedure DriveListDriveSelected(Sender: TObject; ADriveIndex: Integer;
      APanel: TFilePanelSelect);
    procedure DriveListClose(Sender: TObject);
    function  FindMatchingDrive(Address, Path: String): Integer;
    procedure UpdateDriveToolbarSelection(DriveToolbar: TKAStoolBar; FileView: TFileView);
    procedure UpdateDriveButtonSelection(DriveButton: TSpeedButton; FileView: TFileView);
{$IF DEFINED(MSWINDOWS)}
    procedure OnDriveIconLoaded(Data: PtrInt);
{$ENDIF}
    procedure OnDriveGetFreeSpace(Data: PtrInt);
    procedure OnDriveWatcherEvent(EventType: TDriveWatcherEvent; const ADrive: PDrive);
    procedure AppActivate(Sender: TObject);
    procedure AppDeActivate(Sender: TObject);
    procedure AppEndSession(Sender: TObject);
    procedure AppThemeChange(Sender: TObject);
    procedure AppQueryEndSession(var Cancel: Boolean);
    procedure AppException(Sender: TObject; E: Exception);
    procedure AppShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo);
    {en
       Convert toolbar configuration from .bar file to global config.
    }
    procedure ConvertToolbarBarConfig(BarFileName: String);
    procedure ConvertIniToolbarItem(Loader: TKASToolBarIniLoader; var Item: TKASToolItem; const Shortcut: String);
    procedure CreateDefaultToolbar;
    procedure EditToolbarButton(Toolbar: TKASToolBar; Button: TKASToolButton);
    procedure ToolbarExecuteCommand(ToolItem: TKASToolItem);
    procedure ToolbarExecuteProgram(ToolItem: TKASToolItem);
    procedure LeftDriveBarExecuteDrive(ToolItem: TKASToolItem);
    procedure RightDriveBarExecuteDrive(ToolItem: TKASToolItem);
    procedure SetDragCursor(Shift: TShiftState);
    {$IFDEF DARWIN}
    procedure createDarwinAppMenu;
    procedure aboutOnClick(Sender: TObject);
    procedure optionsOnClick(Sender: TObject);
    {$ENDIF}

  protected
    procedure CreateWnd; override;
    procedure DoFirstShow; override;
    procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
                            const AXProportion, AYProportion: Double); override;

    procedure WMMove(var Message: TLMMove); message LM_MOVE;
    procedure WMSize(var message: TLMSize); message LM_Size;

  public
    constructor Create(TheOwner: TComponent); override;
    procedure AfterConstruction; override;
    Function ActiveFrame: TFileView;  // get Active frame
    Function NotActiveFrame: TFileView; // get NotActive frame :)
    function ActiveNotebook: TFileViewNotebook;
    function NotActiveNotebook: TFileViewNotebook;
    function FrameLeft: TFileView;
    function FrameRight: TFileView;
    procedure ForEachView(CallbackFunction: TForEachViewFunction; UserData: Pointer);
    procedure GetListOpenedPaths(const APaths:TStringList);
    //check selected count and generate correct msg, parameters is lng indexs
    Function GetFileDlgStr(sLngOne, sLngMulti : String; Files: TFiles):String;
    procedure HotDirSelected(Sender:TObject);
    procedure HotDirActualSwitchToDir(Index:longint);
    procedure HistorySelected(Sender:TObject);
    procedure HistorySomeSelected(Sender:TObject);
    procedure ViewHistorySelected(Sender:TObject);
    procedure ViewHistoryPrevSelected(Sender:TObject);
    procedure ViewHistoryNextSelected(Sender:TObject);
    procedure CreatePopUpDirHistory(UseTreeViewMenu: Boolean; FromPathIndex: Integer);
    procedure ShowFileViewHistory(const Params: array of string);
    procedure ShowFileViewHistory(const Params: array of string; FromFileSourceIndex, FromPathIndex, ToFileSourceIndex, ToPathIndex: Integer);
    procedure miHotAddOrConfigClick(Sender: TObject);
    procedure OnCopyOutTempStateChanged(Operation: TFileSourceOperation; State: TFileSourceOperationState);

    {en
       Returns @true if copy operation has been successfully started.
    }
    function CopyFiles(SourceFileSource, TargetFileSource: IFileSource;
                       var SourceFiles: TFiles; TargetPath: String;
                       bShowDialog: Boolean;
                       QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean; overload;
    {en
       Returns @true if move operation has been successfully started.
    }
    function MoveFiles(SourceFileSource, TargetFileSource: IFileSource;
                       var SourceFiles: TFiles; TargetPath: String;
                       bShowDialog: Boolean;
                       QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean; overload;
    function CopyFiles(sDestPath: String; bShowDialog: Boolean;
                       QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean; overload; //  this is for F5 and Shift+F5
    function MoveFiles(sDestPath: String; bShowDialog: Boolean;
                       QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean; overload;
    procedure GetDestinationPathAndMask(SourceFiles: TFiles;
                                        TargetFileSource: IFileSource;
                                        EnteredPath: String; BaseDir: String;
                                        out DestPath, DestMask: String); overload;
    procedure GetDestinationPathAndMask(SourceFiles: TFiles;
                                        SourceFileSource: IFileSource;
                                        var TargetFileSource: IFileSource;
                                        EnteredPath: String; BaseDir: String;
                                        out DestPath, DestMask: String); overload;
    procedure SetActiveFrame(panel: TFilePanelSelect);
    procedure SetActiveFrame(FileView: TFileView);
    procedure UpdateFileView;
    procedure UpdateShellTreeView;
    procedure UpdateTreeViewPath;
    procedure UpdateTreeView;
    procedure UpdateDiskCount;
    procedure UpdateSelectedDrives;
    procedure UpdateGUIFunctionKeys;
    procedure UpdateMainTitleBar;
    procedure CreateDiskPanel(dskPanel : TKASToolBar);
    procedure UpdateSelectedDrive(ANoteBook: TFileViewNotebook);
    procedure SetPanelDrive(aPanel: TFilePanelSelect; Drive: PDrive; ActivateIfNeeded: Boolean);
    function CreateFileView(sType: String; Page: TFileViewPage; AConfig: TXmlConfig; ANode: TXmlNode): TFileView;
    procedure AssignEvents(AFileView: TFileView);
    function RemovePage(ANoteBook: TFileViewNotebook; iPageIndex:Integer; CloseLocked: Boolean = True; ConfirmCloseLocked: integer = 0; ShowButtonAll: Boolean = False): LongInt;
    procedure LoadTabsXml(AConfig: TXmlConfig; ABranch:string; ANoteBook: TFileViewNotebook);
    procedure SaveTabsXml(AConfig: TXmlConfig; ABranch:string; ANoteBook: TFileViewNotebook; ASaveHistory: boolean);
    procedure LoadTheseTabsWithThisConfig(Config: TXmlConfig; ABranch:string; Source, Destination:TTabsConfigLocation; DestinationToKeep : TTabsConfigLocation; var TabsAlreadyDestroyedFlags:TTabsFlagsAlreadyDestroyed);
    procedure ToggleConsole;
    procedure UpdateWindowView;
    procedure MinimizeWindow;
    procedure RestoreWindow;
    procedure LoadTabs;
    procedure LoadTabsCommandLine(Params: TCommandLineParams);
    procedure AddTab(ANoteBook: TFileViewNotebook; aPath: String);
    {$IF DEFINED(DARWIN)}
    procedure OnNSServiceOpenWithNewTab( filenames:TStringList );
    function NSServiceMenuIsReady(): boolean;
    function NSServiceMenuGetFilenames(): TStringList;
    procedure NSThemeChangedHandler();
    {$ENDIF}
    procedure LoadWindowState;
    procedure SaveWindowState;

    procedure LoadToolbar(AToolBar: TKASToolBar);
    procedure SaveToolBar(AToolBar: TKASToolBar);

    procedure ShowLogWindow(Data: PtrInt);
    function  IsCommandLineVisible: Boolean;
    procedure ShowCommandLine(AFocus: Boolean);
    procedure ConfigSaveSettings(bForce: Boolean);
    procedure ShowDrivesList(APanel: TFilePanelSelect);
    procedure ExecuteCommandLine(bRunInTerm: Boolean);
    procedure UpdatePrompt;
    procedure UpdateFreeSpace(Panel: TFilePanelSelect; Clear: Boolean);
    procedure ReLoadTabs(ANoteBook: TFileViewNotebook);
    procedure ShowOptionsLayout(Data: PtrInt);
    procedure ToggleFullscreenConsole;

    {en
       This function is called from various points to handle dropping files
       into the panel. It converts drop effects available on the system
       into TDragDropOperation operations.
       Handles freeing DropParams. }
    procedure DropFiles(var DropParams: TDropParams);

    {en
       Performs all drag&drop actions.
       Frees DropParams.
    }
    procedure DoDragDropOperation(Operation: TDragDropOperation;
                                  var DropParams: TDropParams);


    property Drives: TDrivesList read DrivesList;
    property SyncChangeDir: String write FSyncChangeDir;
    property Commands: TMainCommands read FCommands implements IFormCommands;
    property SelectedPanel: TFilePanelSelect read PanelSelected write SetPanelSelected;
    property LeftTabs: TFileViewNotebook read nbLeft;
    property RightTabs: TFileViewNotebook read nbRight;
    property MainSplitterPos: Double read FMainSplitterPos write SetMainSplitterPos;
    property StaticTitle: String read sStaticTitleBarString write sStaticTitleBarString;
  end;

var
  frmMain: TfrmMain;
  Cons: TCustomPtyDevice = nil;

implementation

{$R *.lfm}

uses
  Themes, uFileProcs, uShellContextMenu, fTreeViewMenu, uSearchResultFileSource,
  Math, LCLIntf, Dialogs, uGlobs, uLng, uMasks, fCopyMoveDlg, uQuickViewPanel,
  uShowMsg, uDCUtils, uLog, uGlobsPaths, LCLProc, uOSUtils, uPixMapManager, LazUTF8,
  uDragDropEx, uKeyboard, uFileSystemFileSource, fViewOperations, uMultiListFileSource,
  uFileSourceOperationTypes, uFileSourceCopyOperation, uFileSourceMoveOperation,
  uFileSourceProperty, uFileSourceExecuteOperation, uArchiveFileSource, uThumbFileView,
  uShellExecute, fSymLink, fHardLink, uExceptions, uUniqueInstance, Clipbrd, ShellCtrls,
  uFileSourceOperationOptionsUI, uDebug, uHotkeyManager, uFileSourceUtil, uTempFileSystemFileSource,
  Laz2_XMLRead, DCOSUtils, DCStrUtils, fOptions, fOptionsFrame, fOptionsToolbar, uClassesEx,
  uHotDir, uFileSorting, DCBasicTypes, foptionsDirectoryHotlist, uConnectionManager,
  fOptionsToolbarBase, fOptionsToolbarMiddle, fEditor, uColumns, StrUtils, uSysFolders,
  uColumnsFileView, dmHigh, uFileSourceOperationMisc
{$IFDEF MSWINDOWS}
  , uShellFileSource, uNetworkThread
{$ENDIF}
  ;

const
  HotkeysCategory = 'Main';
  DCToolItemClipboardHeader = 'DOUBLECMD#TOOLBAR#XMLDATA';
  TCToolbarClipboardHeader  = 'TOTALCMD#BAR#DATA';
  DCToolbarClipboardHeader  = 'DOUBLECMD#BAR#DATA';

{$IF DEFINED(LCLGTK2) or DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
var
  LastActiveWindow: TCustomForm = nil;
{$ENDIF}

{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
var
  CloseQueryResult: Boolean = False;
{$ENDIF}

{$IFDEF LCLGTK2}
var
  MinimizedWindowButton: Boolean = False;
{$ENDIF}

var
  FunctionButtonsCaptions: array[0..7] of record
                             ACaption: String;
                             ACommand: String;
                           end =
     ((ACaption: ''; ACommand: 'cm_View'),
      (ACaption: ''; ACommand: 'cm_Edit'),
      (ACaption: ''; ACommand: 'cm_Copy'),
      (ACaption: ''; ACommand: 'cm_Rename'),
      (ACaption: ''; ACommand: 'cm_MakeDir'),
      (ACaption: ''; ACommand: 'cm_Delete'),
      (ACaption: ''; ACommand: 'cm_RunTerm'),
      (ACaption: ''; ACommand: 'cm_Exit'));

type

  { TFreeSpaceData }

  TFreeSpaceData = class
    Path: String;
    Result: Boolean;
    OnFinish: TDataEvent;
    Panel: TFilePanelSelect;
    FileSource: IFileSource;
    FreeSize, TotalSize : Int64;
    procedure GetFreeSpaceInThread;
  end;

  { TShellTreeView }

  TShellTreeView = class(ShellCtrls.TShellTreeView)
  protected
    function CanExpand(Node: TTreeNode): Boolean; override;
    function ShellTreeViewSort(Node1, Node2: TTreeNode): Integer;
  end;

function HistoryIndexesToTag(aFileSourceIndex, aPathIndex: Integer): Longint;
begin
  Result := (aFileSourceIndex << 16) or aPathIndex;
end;

procedure HistoryIndexesFromTag(aTag: Longint; out aFileSourceIndex, aPathIndex: Integer);
begin
  aFileSourceIndex := aTag >> 16;
  aPathIndex := aTag and ((1<<16) - 1);
end;

{ TFreeSpaceData }

procedure TFreeSpaceData.GetFreeSpaceInThread;
begin
  Result:= FileSource.GetFreeSpace(Path, FreeSize, TotalSize);
  if Assigned(Application) and not (AppDoNotCallAsyncQueue in Application.Flags) then
    Application.QueueAsyncCall(OnFinish, PtrInt(Self));
end;

{ TShellTreeView }

function TShellTreeView.CanExpand(Node: TTreeNode): Boolean;
begin
  Result:= inherited CanExpand(Node);
  if Result then Node.CustomSort(@ShellTreeViewSort);
end;

function TShellTreeView.ShellTreeViewSort(Node1, Node2: TTreeNode): Integer;
begin
  Result:= CompareStrings(Node1.Text, Node2.Text, gSortNatural, gSortSpecial, gSortCaseSensitivity);
end;

{ TfrmMain }

procedure TfrmMain.FormCreate(Sender: TObject);

  function CreateNotebook(aParent: TWinControl; aSide: TFilePanelSelect): TFileViewNotebook;
  begin
    Result := TFileViewNotebook.Create(aParent, aSide);
    Result.Align := alClient;
    Result.Options := [nboHidePageListPopup];
    {$if lcl_fullversion >= 1070000}
      Result.Options := Result.Options + [nboDoChangeOnSetIndex];
    {$endif}

    Result.OnCloseTabClicked := @NotebookCloseTabClicked;
    Result.OnMouseDown := @nbPageMouseDown;
    Result.OnMouseUp := @nbPageMouseUp;
    Result.OnChange := @nbPageChanged;
    Result.OnDblClick := @pnlLeftRightDblClick;
    Result.OnDragOver:= @NotebookDragOver;
    Result.OnDragDrop:= @NotebookDragDrop;
  end;

  function GenerateTitle(): String;
  var
    R: Integer;
    ARevision, AServerName: String;
  begin
    if Length(UniqueInstance.ServernameByUser) > 0 then
      AServerName := ' [' + UniqueInstance.ServernameByUser + ']'
    else begin
      AServerName := EmptyStr;
    end;

    if TryStrToInt(dcRevision, R) then
      ARevision:= '~' + dcRevision
    else begin
      ARevision:= EmptyStr;
    end;

    Result := Format('%s%s %s%s',
        ['Double Commander',
        AServerName,
        Copy2Space(dcVersion),
        ARevision]
    );
  end;

var
  HMMainForm: THMForm;
  I: Integer;
begin
  Application.OnException := @AppException;
  Application.OnActivate := @AppActivate;
  Application.OnDeActivate := @AppDeActivate;
  Application.OnShowHint := @AppShowHint;
  Application.OnEndSession := @AppEndSession;
  Application.OnQueryEndSession := @AppQueryEndSession;

  {$IF DEFINED(DARWIN)}
  // in LCL's DARWIN implements, there is no way but to Use LCL's method of dropping files
  // from external applications
  frmMain.OnDropFiles := @FormDropFiles;
  AllowDropFiles := true; // DARWIN support external DragDragSource only, not DragDragTarget
  {$ELSE}
  // Use LCL's method of dropping files from external
  // applications if we don't support it ourselves.
  if not IsExternalDraggingSupported then
    frmMain.OnDropFiles := @FormDropFiles;
  AllowDropFiles := not uDragDropEx.IsExternalDraggingSupported;
  {$ENDIF}

  {$IF DEFINED(DARWIN)}
  // MainForm receives in Mac OS closing events on system shortcut Command-Q
  // See details at http://doublecmd.sourceforge.net/mantisbt/view.php?id=712
  Application.MainForm.OnClose := @frmMainClose;
  Application.MainForm.OnCloseQuery := @FormCloseQuery;
  {$ENDIF}

  ConvertToolbarBarConfig(gpCfgDir + 'default.bar');
  CreateDefaultToolbar;
  sStaticTitleBarString := GenerateTitle();

  // Remove the initial caption of the button, which is just a text of the associated action.
  // The text would otherwise be briefly shown before the drive button was updated.
  btnLeftDrive.Caption := '';
  btnRightDrive.Caption := '';

  //Have the correct button label to indicate root
  btnLeftRoot.Caption:=DirectorySeparator;
  btnRightRoot.Caption:=DirectorySeparator;

  for I := 0 to pnlKeys.ControlCount - 1 do
    FunctionButtonsCaptions[I].ACaption := pnlKeys.Controls[I].Caption;

  {$IF DEFINED(LCLGTK2)}
  // Workaround: "Layout and line"
  // http://doublecmd.sourceforge.net/mantisbt/view.php?id=573
  TreePanel.Visible := False;
  pnlLeftTools.Visible := False;
  pnlRightTools.Visible := False;
  PanelAllProgress.Visible := False;
  {$ENDIF}

  InitPropStorage(Self);

  PanelSelected:=fpLeft;

  seLogWindow.FixDefaultKeystrokes;

  HMMainForm := HotMan.Register(Self, HotkeysCategory);
  HotMan.Register(edtCommand, 'Command Line');

  nbLeft := CreateNotebook(pnlLeft, fpLeft);
  nbRight := CreateNotebook(pnlRight, fpRight);

  FDrivesListPopup := TDrivesListPopup.Create(Self, Self);
  FDrivesListPopup.OnDriveSelected := @DriveListDriveSelected;
  FDrivesListPopup.OnClose := @DriveListClose;

  //NOTE: we don't check gOnlyOneAppInstance anymore, because cmdline option "--client" was implemented,
  //      so, we should always listen for the messages
  if Assigned(UniqueInstance) then
    UniqueInstance.OnMessage:= @OnUniqueInstanceMessage;

  MainFormCreate(Self);

  // Load command line history
  edtCommand.Items.Assign(glsCmdLineHistory);

  // Initialize actions.
  actShowSysFiles.Checked := uGlobs.gShowSystemFiles;
  actHorizontalFilePanels.Checked := gHorizontalFilePanels;

  MainToolBar.AddToolItemExecutor(TKASCommandItem, @ToolbarExecuteCommand);
  MainToolBar.AddToolItemExecutor(TKASProgramItem, @ToolbarExecuteProgram);

  MiddleToolBar.AddToolItemExecutor(TKASCommandItem, @ToolbarExecuteCommand);
  MiddleToolBar.AddToolItemExecutor(TKASProgramItem, @ToolbarExecuteProgram);

  // Use the same tooltips for some left and right panel butttons.
  btnRightDirectoryHotlist.Hint := btnLeftDirectoryHotlist.Hint;
  btnRightHome.Hint := btnLeftHome.Hint;
  btnRightRoot.Hint := btnLeftRoot.Hint;
  btnRightUp.Hint := btnLeftUp.Hint;

  { *HotKeys* }
  if (HotMan.Forms.Count = 0) or (HotMan.Version < hkVersion) then
    LoadDefaultHotkeyBindings;

  // Register action list for main form hotkeys.
  HMMainForm.RegisterActionList(actionlst);
  { *HotKeys* }

  UpdateActionIcons;

  {$IF DEFINED(LCLCOCOA)}
  // 1. TCustomTabControl.GetControlClassDefaultSize() return 200 for Default Width
  // 2. on Cocoa, it is likely to cause TCocoaTabControl not wide enough to
  //    accommodate all tabs loaded in LoadTabsXml() during startup.
  // 3. when setting PageIndex in LoadTabsXml(), it will cause an extra tab switch.
  // 4. and it will cause an extra directory to be monitored in FileView.
  // 5. the issue can be effectively avoided by setting a larger width.
  nbLeft.Width:= 2048;
  nbRight.Width:= 2048;
  {$ENDIF}

  LoadTabs;

  // Must be after LoadTabs
  TDriveWatcher.Initialize(GetWindowHandle(Application.MainForm));
  TDriveWatcher.AddObserver(@OnDriveWatcherEvent);

{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
  // Fixes bug - [0000033] "DC cancels shutdown in KDE"
  // http://doublecmd.sourceforge.net/mantisbt/view.php?id=33
  QEventHook:= QObject_hook_create(TQtWidget(Self.Handle).Widget);
  QObject_hook_hook_events(QEventHook, @QObjectEventFilter);
{$ENDIF}

  OperationsManager.AddEventsListener([omevOperationAdded, omevOperationRemoved],
                                      @OperationManagerNotify);

  UpdateWindowView;
  gFavoriteTabsList.AssociatedMainMenuItem := mnuFavoriteTabs;
  gFavoriteTabsList.RefreshAssociatedMainMenu;

  // Update selected drive and free space before main form is shown,
  // otherwise there is a bit of delay.
  UpdateTreeView;
  UpdateTreeViewPath;
  UpdateSelectedDrives;
  UpdateFreeSpace(fpLeft, True);
  UpdateFreeSpace(fpRight, True);

  ThemeServices.OnThemeChange:= @AppThemeChange;

{$IF DEFINED(DARWIN)}
  InitNSServiceProvider( @OnNSServiceOpenWithNewTab, @NSServiceMenuIsReady, @NSServiceMenuGetFilenames );
  InitNSThemeChangedObserver( @NSThemeChangedHandler );
  createDarwinAppMenu;
{$ENDIF}
end;

procedure TfrmMain.btnLeftClick(Sender: TObject);
begin
  PanelButtonClick(Sender as TSpeedButton, FrameLeft);
end;

procedure TfrmMain.actExecute(Sender: TObject);
var
  cmd: string;
begin
  cmd := (Sender as TAction).Name;
  cmd := 'cm_' + Copy(cmd, 4, Length(cmd) - 3);
  Commands.Commands.ExecuteCommand(cmd, []);
end;

procedure TfrmMain.btnF3MouseWheelDown(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin
  if (ssCtrl in Shift) and (gFonts[dcfFunctionButtons].Size > gFonts[dcfFunctionButtons].MinValue) then
  begin
    Dec(gFonts[dcfFunctionButtons].Size);
    UpdateGUIFunctionKeys;
  end;
end;

procedure TfrmMain.btnF3MouseWheelUp(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin
  if (ssCtrl in Shift) and (gFonts[dcfFunctionButtons].Size < gFonts[dcfFunctionButtons].MaxValue) then
  begin
    Inc(gFonts[dcfFunctionButtons].Size);
    UpdateGUIFunctionKeys;
  end;
end;

procedure TfrmMain.btnF8MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Point: TPoint;
begin
  if Button = mbRight then
  begin
    Point := (Sender as TControl).ClientToScreen(Classes.Point(X, Y));
    ShowTrashContextMenu(Self, Point.X, Point.Y, nil);
  end;
end;

procedure TfrmMain.ConsoleSplitterMoved(Sender: TObject);
var
  AHeight: Integer;
begin
  AHeight:= nbConsole.Height + nbConsole.Tag - pnlCommand.Height;
  if AHeight > 0 then
  begin
    nbConsole.Height := AHeight;
    cmdConsole.Visible:= True;
  end
  else begin
    cmdConsole.Hide;
    nbConsole.Height := 0;
  end;
end;

procedure TfrmMain.dskToolButtonMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  ANotebook: TFileViewNotebook;
begin
  if Button = mbMiddle then
  begin
    if Sender is TKASToolButton then
    begin
      if TKASToolButton(Sender).ToolBar = dskLeft then
      begin
        ANotebook:= nbLeft;
      end
      else if gDriveBar2 then
      begin
        ANotebook:= nbRight;
      end
      else begin
        ANotebook:= ActiveNotebook;
      end;
      Commands.DoNewTab(ANotebook);
      TKASToolButton(Sender).Click;
    end;
  end;
end;

procedure TfrmMain.mnuAllOperStopClick(Sender: TObject);
begin
  OperationsManager.StopAll;
end;

procedure TfrmMain.mnuAllOperPauseClick(Sender: TObject);
begin
  OperationsManager.PauseAll;
end;

procedure TfrmMain.mnuAllOperProgressClick(Sender: TObject);
begin
  ShowOperationsViewer;
end;

procedure TfrmMain.mnuAllOperStartClick(Sender: TObject);
begin
  OperationsManager.UnPauseAll;
end;

procedure TfrmMain.btnF8Click(Sender: TObject);
begin
  if GetKeyShiftStateEx * KeyModifiersShortcut = [ssShift] then
    Commands.cm_Delete(['trashcan=reversesetting'])
  else
    Commands.cm_Delete([]);
end;

{ TfrmMain.btnLeftDirectoryHotlistClick}
//To make appear the Directory Hotlist popup menu when pressing "*" button on left
//
procedure TfrmMain.btnLeftDirectoryHotlistClick(Sender: TObject);
var P:TPoint;
begin
  if tb_activate_panel_on_click in gDirTabOptions then SetActiveFrame(fpLeft);
  gDirectoryHotlist.PopulateMenuWithHotDir(pmHotList,@HotDirSelected,@miHotAddOrConfigClick,mpHOTDIRSWITHCONFIG,0);
  p := Classes.Point(btnLeftDirectoryHotlist.Left,btnLeftDirectoryHotlist.Height);
  p := pnlLeftTools.ClientToScreen(p);
  pmHotList.PopUp(P.x,P.y);
end;

procedure TfrmMain.btnRightClick(Sender: TObject);
begin
  PanelButtonClick(Sender as TSpeedButton, FrameRight);
end;

{ TfrmMain.btnRightDirectoryHotlistClick}
//To make appear the Directory Hotlist popup menu when pressing "*" button on right
//
procedure TfrmMain.btnRightDirectoryHotlistClick(Sender: TObject);
var P:TPoint;
begin
  if tb_activate_panel_on_click in gDirTabOptions then SetActiveFrame(fpRight);
  gDirectoryHotlist.PopulateMenuWithHotDir(pmHotList,@HotDirSelected,@miHotAddOrConfigClick,mpHOTDIRSWITHCONFIG,0);
  p := Classes.Point(btnRightDirectoryHotlist.Left,btnRightDirectoryHotlist.Height);
  p := pnlRightTools.ClientToScreen(p);
  pmHotList.PopUp(P.x,P.y);
end;

procedure TfrmMain.btnDriveMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  pt: TPoint;
begin
  if Button = mbRight then
      with Sender as TSpeedButton do
      begin
        if (Tag >= 0) and (Tag < DrivesList.Count) then
          begin
            pt.X := X;
            pt.Y := Y;
            pt := ClientToScreen(pt);
            ShowDriveContextMenu(Parent, DrivesList[Tag], pt.X, pt.Y, nil);
          end;
      end;
end;

procedure TfrmMain.ConsoleSplitterCanResize(Sender: TObject;
  var NewSize: Integer; var Accept: Boolean);
begin
  // ConsoleSplitter is trying to resize pnlCommand,
  // so NewSize is the new size of pnlCommand.
  // Instead, resize nbConsole by the same difference.
  nbConsole.Tag := NewSize;
end;

procedure TfrmMain.ConvertToolbarBarConfig(BarFileName: String);
var
  ToolBarLoader: TKASToolBarIniLoader;
  MainToolBarNode: TXmlNode;
begin
  MainToolBarNode := gConfig.FindNode(gConfig.RootNode, 'Toolbars/MainToolbar', False);
  if not Assigned(MainToolBarNode) then
  begin
    if mbFileExists(BarFileName) then
    begin
      ToolBarLoader := TKASToolBarIniLoader.Create(Commands.Commands);
      try
        ToolBarLoader.Load(BarFileName, MainToolBar, nil, @ConvertIniToolbarItem);
        SaveToolBar(MainToolBar);
        SaveGlobs; // Save toolbar and hotkeys
        mbRenameFile(BarFileName, BarFileName + '.obsolete');
      finally
        ToolBarLoader.Free;
      end;
    end;
  end;
end;

procedure TfrmMain.dskRightToolButtonClick(Sender: TObject);
var
  FileView : TFileView;
begin
  if gDriveBar2 then
    FileView := FrameRight
  else
    FileView := ActiveFrame;
  PanelButtonClick(Sender as TKASToolButton, FileView);
end;

procedure TfrmMain.dskLeftRightToolButtonDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  ToolItem: TKASToolItem;
  SourceFiles: TFiles;
  TargetFileSource: IFileSource;
  TargetPath: String;
begin
  if Sender is TKASToolButton then
  begin
    SourceFiles := ActiveFrame.CloneSelectedOrActiveFiles;
    try
      ToolItem := TKASToolButton(Sender).ToolItem;
      if ToolItem is TKASDriveItem then
      begin
        TargetPath := TKASDriveItem(ToolItem).Drive^.Path;
        TargetFileSource := ParseFileSource(TargetPath, ActiveFrame.FileSource);
        TargetPath := IncludeTrailingPathDelimiter(TargetPath);
        if not Assigned(TargetFileSource) then
          TargetFileSource := TFileSystemFileSource.GetFileSource;
        case GetDropEffectByKeyAndMouse(GetKeyShiftStateEx, mbLeft, gDefaultDropEffect) of
          DropCopyEffect:
            Self.CopyFiles(ActiveFrame.FileSource, TargetFileSource, SourceFiles, TargetPath, gShowDialogOnDragDrop);
          DropMoveEffect:
            Self.MoveFiles(ActiveFrame.FileSource, TargetFileSource, SourceFiles, TargetPath, gShowDialogOnDragDrop);
        end;
      end;
    finally
      SourceFiles.Free;
    end;
  end;
end;

procedure TfrmMain.dskToolButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  btnDriveMouseUp(Sender, Button, Shift, X, Y);
end;

procedure TfrmMain.EditToolbarButton(Toolbar: TKASToolBar;
  Button: TKASToolButton);
var
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
  EditorClass: TOptionsEditorClass;
begin
  if ToolBar = MainToolBar then
    EditorClass := TfrmOptionsToolbar
  else begin
    EditorClass := TfrmOptionsToolbarMiddle;
  end;
  Options := ShowOptions(EditorClass);
  Application.ProcessMessages;
  Editor := Options.GetEditor(EditorClass);
  if Assigned(Button) then
  begin
    (Editor as TfrmOptionsToolbarBase).SelectButton(Button.Tag);
  end;
  Application.ProcessMessages;
  if Editor.CanFocus then Editor.SetFocus;
end;

procedure TfrmMain.lblAllProgressPctClick(Sender: TObject);
begin
  ShowOperationsViewer;
end;

procedure TfrmMain.MainToolBarToolButtonDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  I: LongWord;
  SelectedFiles: TFiles = nil;
  Param: string;
  ToolItem: TKASToolItem;
  Toolbar: TKASToolBar;
begin
  Toolbar:= (Sender as TKASToolButton).ToolBar;
  if (ssShift in GetKeyShiftState) then
    // Button was moved.
    SaveToolBar(Toolbar)
  else
    if (Sender is TKASToolButton) and not Draging then
      begin
        ToolItem := TKASToolButton(Sender).ToolItem;
        if ToolItem is TKASProgramItem then
        begin
          SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
          try
            if SelectedFiles.Count > 0 then
              begin
                Param:= EmptyStr;
                for I := 0 to SelectedFiles.Count - 1 do
                begin
                  // Workaround for not fully implemented TMultiListFileSource.
                  if ActiveFrame.FileSource.IsClass(TMultiListFileSource) then
                    Param := Param + QuoteStr(SelectedFiles[I].FullPath) + ' '
                  else
                    Param := Param + QuoteStr(ActiveFrame.CurrentAddress + SelectedFiles[I].FullPath) + ' ';
                end;

                TKASProgramItem(ToolItem).Command   := ReplaceEnvVars(ReplaceTilde(TKASProgramItem(ToolItem).Command));
                Param := PrepareParameter(Param, nil, []);

                if not (Commands.Commands.ExecuteCommand(TKASProgramItem(ToolItem).Command, [Param]) = cfrSuccess) then
                  ProcessExtCommandFork(TKASProgramItem(ToolItem).Command, Param, TKASProgramItem(ToolItem).StartPath);
              end;
          finally
            FreeAndNil(SelectedFiles);
          end;
        end;
      end;
end;

procedure TfrmMain.MainToolBarToolButtonDragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean; NumberOfButton: Integer);
var
  aFile: TFile;
  tmp: Integer;
  ToolItem: TKASProgramItem;
  Toolbar: TKASToolBar;
begin
  Toolbar:= (Sender as TKASToolButton).ToolBar;
  if (ssShift in GetKeyShiftState) then
    begin
      if not (Source is TKASToolButton) and not Draging then
        begin
          aFile := ActiveFrame.CloneActiveFile;
          try
            if Assigned(aFile) and aFile.IsNameValid then
              begin
                ToolItem := TKASProgramItem.Create;
                ToolItem.Command := aFile.FullPath;
                ToolItem.StartPath := aFile.Path;
                ToolItem.Icon := aFile.FullPath;
                ToolItem.Hint := ExtractOnlyFileName(aFile.Name);
                // ToolItem.Text := ExtractOnlyFileName(aFile.Name);
                Toolbar.InsertButton(Sender as TKASToolButton, ToolItem);

                NumberOfMoveButton := (Sender as TSpeedButton).Tag;
                NumberOfNewMoveButton := (Sender as TSpeedButton).Tag-1;
                Draging := True;
                Accept := True;
              end
            else
              begin
                Accept := False;
                Exit;
              end;
          finally
            FreeAndNil(aFile);
          end;
        end;
      if (Source is TKASToolButton) and (Toolbar <> TKASToolButton(Source).ToolBar) then
      begin
        Accept := False;
        Exit;
      end;
      if (NumberOfMoveButton <> (Sender as TSpeedButton).Tag) then
        begin
          Draging := True;
          if Source is TSpeedButton then
            Toolbar.MoveButton((Source as TSpeedButton).Tag, (Sender as TSpeedButton).Tag)
          else
            begin
              tmp:= (Sender as TSpeedButton).Tag;
              Toolbar.MoveButton(NumberOfNewMoveButton, (Sender as TSpeedButton).Tag);
              NumberOfNewMoveButton := tmp;
            end;
          NumberOfMoveButton := (Sender as TSpeedButton).Tag;
          Accept := True;
        end;
    end
  else
    begin
      Accept := not Draging and
                (Sender is TKASToolButton) and
                (TKASToolButton(Sender).ToolItem is TKASProgramItem);
      if Accept then
      begin
        aFile := ActiveFrame.CloneActiveFile;
        try
          Accept := Assigned(aFile) and aFile.IsNameValid;
        finally
          FreeAndNil(aFile);
        end;
      end;
    end;
end;

procedure TfrmMain.MainToolBarToolButtonMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (ssShift in Shift) and (Button = mbLeft) then
    begin
      (Sender as TKASToolButton).BeginDrag(False, 5);
      NumberOfMoveButton:= (Sender as TKASToolButton).Tag;
    end;
  Draging:= False;
end;

procedure TfrmMain.MainToolBarToolButtonMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
    MainToolBarMouseUp(Sender, Button, Shift, X, Y);
end;

function TfrmMain.MainToolBarToolItemShortcutsHint(Sender: TObject;
  ToolItem: TKASNormalItem): String;
begin
  if Sender = MainToolBar then
    Result := ShortcutsToText(TfrmOptionsToolbar.GetShortcuts(ToolItem))
  else
    Result := ShortcutsToText(TfrmOptionsToolbarMiddle.GetShortcuts(ToolItem));
end;

procedure TfrmMain.miLogMenuClick(Sender: TObject);
begin
  case (Sender as TMenuItem).Tag of
    0:
      seLogWindow.CopyToClipboard;
    1:
      seLogWindow.SelectAll;
    2:
      Commands.cm_ClearLogWindow([]);
    3:
      ShowLogWindow(PtrInt(False));
  end;
end;

procedure TfrmMain.miTrayIconExitClick(Sender: TObject);
begin
  RestoreFromTray;
  Close;
end;

procedure TfrmMain.miTrayIconRestoreClick(Sender: TObject);
begin
  RestoreFromTray;
end;

procedure TfrmMain.PanelButtonClick(Button: TSpeedButton; FileView: TFileView);
begin
  with FileView do
  begin
    if Button.Caption = DirectorySeparator then
      Commands.DoChangeDirToRoot(FileView)
    else if Button.Caption = '..' then
      ChangePathToParent(True)
    else if Button.Caption = '~' then
      SetFileSystemPath(FileView, GetHomeDir);
  end;

  if tb_activate_panel_on_click in gDirTabOptions then
    SetActiveFrame(FileView);
end;

procedure TfrmMain.ShellTreeViewSelect;
begin
  ShellTreeView.Tag := 1;
  try
    SetFileSystemPath(ActiveFrame, (ShellTreeView as TShellTreeView).Path);
  finally
    ShellTreeView.Tag := 0;
  end;
end;

procedure TfrmMain.ShellTreeViewKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then ShellTreeViewSelect;
end;

procedure TfrmMain.ShellTreeViewMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  AFile: TFile;
  AFiles: TFiles;
  APoint: TPoint;
  AFileName: String;
begin
{$IF DEFINED(MSWINDOWS)}
  if Button = mbRight then
  try
    AFileName:= ExcludeTrailingBackslash((ShellTreeView as TShellTreeView).Path);
    AFile:= TFileSystemFileSource.CreateFileFromFile(AFileName);
    try
      AFiles:= TFiles.Create(AFile.Path);
      AFiles.Add(AFile);
      APoint := ShellTreeView.ClientToScreen(Classes.Point(X, Y));
      ShowContextMenu(ShellTreeView, AFiles, APoint.X, APoint.Y, False, nil);
    finally
      FreeAndNil(AFiles);
    end;
  except
    on E: EContextMenuException do
      ShowException(E)
    else;
  end;
{$ENDIF}
  if Button = mbLeft then
    ShellTreeViewSelect;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  DCDebug('Destroying main form');

  if Assigned(HotMan) then
  begin
    HotMan.UnRegister(edtCommand);
    HotMan.UnRegister(Self);
  end;

  OperationsManager.RemoveEventsListener([omevOperationAdded, omevOperationRemoved],
                                         @OperationManagerNotify);

  TDriveWatcher.RemoveObserver(@OnDriveWatcherEvent);
  TDriveWatcher.Finalize;
  DCDebug('Drive watcher finished');

  // Close all tabs.
  CloseNotebook(LeftTabs);
  CloseNotebook(RightTabs);

  FreeAndNil(DrivesList);

{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
  QObject_hook_destroy(QEventHook);
{$ENDIF}

  DCDebug('Main form destroyed');
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: boolean);
var
  Index: Integer;
  AForm: TfrmEditor;
begin
  if OperationsManager.OperationsCount > 0 then
  begin
    CanClose := MessageDlg(rsMsgFileOperationsActive,
      rsMsgFileOperationsActiveLong + LineEnding + rsMsgConfirmQuit,
      mtConfirmation, [mbYes, mbNo], 0, mbNo) = mrYes;
  end
  else if gConfirmQuit then
  begin
    CanClose := MessageDlg('', rsMsgConfirmQuit,
                           mtConfirmation, [mbYes, mbNo], 0, mbNo) = mrYes;
  end
  else begin
    CanClose := True;
  end;

  if CanClose then
  begin
    for Index:= 0 to Screen.FormCount - 1 do
    begin
      if Screen.Forms[Index] is TfrmEditor then
      begin
        AForm:= TfrmEditor(Screen.Forms[Index]);
        if AForm.Editor.Modified then
        begin
          if Assigned(AForm.OnCloseQuery) then
          begin
            AForm.ShowOnTop;
            AForm.OnCloseQuery(AForm, CanClose);
            if not CanClose then Exit;
          end;
        end;
      end;
    end;
  end;

{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
  CloseQueryResult:= CanClose;
{$ENDIF}
end;

procedure TfrmMain.FormDropFiles(Sender: TObject; const FileNames: array of String);
var
  TargetFileView: TFileView = nil;
  TargetControl: TControl;
  I: Integer;
  Files: TFiles = nil;
  FileNamesList: TStringList = nil;
  Point: TPoint;
  DropParams: TDropParams;
begin
  Point.x := 0;
  Point.y := 0;
  TargetControl := FindLCLControl(Mouse.CursorPos);
  while TargetControl <> nil do
  begin
    if TargetControl = FrameLeft then
    begin
      // drop on left panel
      TargetFileView := FrameLeft;
      break;
    end
    else if TargetControl = FrameRight then
    begin
      // drop on right panel
      TargetFileView := FrameRight;
      break;
    end;

    TargetControl := TargetControl.Parent;
  end;

  if Assigned(TargetFileView) then
  try
    // fill file list by files
    FileNamesList := TStringList.Create;
    for I := Low(FileNames) to High(FileNames) do
    begin
      if Length(FileNames[I]) > 0 then
        FileNamesList.Add(FileNames[I]);
    end;

    if FileNamesList.Count > 0 then
    try
      Files := TFileSystemFileSource.CreateFilesFromFileList(
          ExtractFilePath(FileNamesList[0]), FileNamesList);

      if Files.Count > 0 then
      begin
        GetCursorPos(Point);

        DropParams := TDropParams.Create(
            Files,
            GetDropEffectByKeyAndMouse(GetKeyShiftState, mbLeft, gDefaultDropEffect),
            Point, False,
            nil, TargetFileView,
            TargetFileView.FileSource,
            TargetFileView.CurrentPath);

        DropFiles(DropParams);
      end;

    except
      on e: EFileNotFound do
        MessageDlg(e.Message, mtError, [mbOK], 0);
    end;

  finally
    FreeAndNil(Files);
    FreeAndNil(FileNamesList);
  end;
end;

procedure TfrmMain.DropFiles(var DropParams: TDropParams);
begin
  if Assigned(DropParams) then
  begin
    if DropParams.Files.Count > 0 then
    begin
      case DropParams.DropEffect of

        DropMoveEffect:
          DropParams.TargetPanel.DoDragDropOperation(ddoMove, DropParams);

        DropCopyEffect:
          DropParams.TargetPanel.DoDragDropOperation(ddoCopy, DropParams);

        DropLinkEffect:
          DropParams.TargetPanel.DoDragDropOperation(ddoSymLink, DropParams);

        DropAskEffect:
          begin
            // Ask the user what he would like to do by displaying a menu.
            // Returns immediately after showing menu.
            PopupDragDropMenu(DropParams);
          end;

        else
          FreeAndNil(DropParams);

      end;
    end
    else
      FreeAndNil(DropParams);
  end;
end;

procedure TfrmMain.DoDragDropOperation(Operation: TDragDropOperation;
                                       var DropParams: TDropParams);
var
  SourceFileName, TargetFileName: string;
begin
  try
    with DropParams do
    begin
      if Assigned(TargetFileSource) then
      begin

{$IF DEFINED(MSWINDOWS)}
        // If drop from external application and from temporary directory then
        // in most cases it is a drop from archiver application that extracting
        // files via temporary directory and requires run operation in the main thread
        // See http://doublecmd.sourceforge.net/mantisbt/view.php?id=1124
        if (GetDragDropType = ddtExternal) and (Operation in [ddoMove, ddoCopy]) and
           IsInPath(GetTempDir, DropParams.Files[0].FullPath, True, True) then
        begin
          if gShowDialogOnDragDrop then
          begin
            case Operation of
              ddoMove: SourceFileName := GetFileDlgStr(rsMsgRenSel, rsMsgRenFlDr, DropParams.Files);
              ddoCopy: SourceFileName := GetFileDlgStr(rsMsgCpSel, rsMsgCpFlDr, DropParams.Files);
            end;
            if MessageDlg(SourceFileName, mtConfirmation, [mbOK, mbCancel], 0) <> mrOK then
              Exit;
          end;
          case Operation of
            ddoMove: Self.MoveFiles(TFileSystemFileSource.GetFileSource,
                           TargetFileSource, Files, TargetPath, False, ModalQueueId);
            ddoCopy: Self.CopyFiles(TFileSystemFileSource.GetFileSource,
                           TargetFileSource, Files, TargetPath, False, ModalQueueId);
          end;
        end else
{$ENDIF}

        case Operation of

          ddoMove:
            if GetDragDropType = ddtInternal then
            begin
              if Self.MoveFiles(SourcePanel.FileSource,
                                TargetFileSource,
                                Files, TargetPath,
                                gShowDialogOnDragDrop) then
              begin
                SourcePanel.MarkFiles(False);
              end;
            end
            else
            begin
              Self.MoveFiles(TFileSystemFileSource.GetFileSource,
                             TargetFileSource,
                             Files, TargetPath,
                             gShowDialogOnDragDrop);
            end;

          ddoCopy:
            if GetDragDropType = ddtInternal then
            begin
              if Self.CopyFiles(SourcePanel.FileSource,
                                TargetFileSource,
                                Files, TargetPath,
                                gShowDialogOnDragDrop) then
              begin
                SourcePanel.MarkFiles(False);
              end;
            end
            else
            begin
              Self.CopyFiles(TFileSystemFileSource.GetFileSource,
                             TargetFileSource,
                             Files, TargetPath,
                             gShowDialogOnDragDrop);
            end;

          ddoSymLink, ddoHardLink:
            begin
              // Only for filesystem.
              if  ((GetDragDropType = ddtExternal) or
                  (SourcePanel.FileSource.IsClass(TFileSystemFileSource)))
              and (TargetFileSource.IsClass(TFileSystemFileSource)) then
              begin
                // TODO: process multiple files

                SourceFileName := Files.Items[0].FullPath;
                TargetFileName := TargetPath + ExtractFileName(SourceFileName);

                if ((Operation = ddoSymLink) and
                   ShowSymLinkForm(Self, SourceFileName, TargetFileName, TargetPath))
                or ((Operation = ddoHardLink) and
                   ShowHardLinkForm(Self, SourceFileName, TargetFileName, TargetPath))
                then
                  TargetFileSource.Reload(TargetPath);
              end
              else
              begin
                msgWarning(rsMsgErrNotSupported);
              end;
            end;
        end;
      end
      else
        msgWarning(rsMsgErrNotSupported);
    end;

  finally
    FreeAndNil(DropParams);
  end;
end;

procedure TfrmMain.FormUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
begin
  // Either left or right panel has to be focused.
  if not FrameLeft.Focused and
     not FrameRight.Focused then
  begin
    Exit;
  end;

  // Check for certain Ascii keys.
  if (not ((Length(UTF8Key) = 1) and (UTF8Key[1] in ['-', '*', '+', #0..#32]))) then
  begin
    if (gKeyTyping[ktmNone] = ktaCommandLine)
{$IFDEF MSWINDOWS}
       // Allow entering international characters with Ctrl+Alt on Windows,
       // if there is no action for Ctrl+Alt and command line typing has no modifiers.
       or (HasKeyboardAltGrKey and
           (GetKeyShiftStateEx * KeyModifiersShortcutNoText = [ssCtrl, ssAlt]) and
           (gKeyTyping[ktmCtrlAlt] = ktaNone))
{$ENDIF}
      then
      begin
        TypeInCommandLine(UTF8Key);
        UTF8Key := '';
      end;
  end
end;

{$IF DEFINED(LCLGTK2)}
procedure TfrmMain.WindowStateUpdate(Data: PtrInt);
begin
  Resizing(lastWindowState);
end;
{$ENDIF}

procedure TfrmMain.FormWindowStateChange(Sender: TObject);
begin
  if FUpdateDiskCount and (WindowState <> wsMinimized) then
  begin
    UpdateDiskCount;
    FUpdateDiskCount:= False;
  end;

{$IF DEFINED(LCLGTK2)}
  if MinimizedWindowButton then
  begin
    MinimizedWindowButton:= False;
    Application.QueueAsyncCall(@WindowStateUpdate, 0);
    Exit;
  end;
{$ENDIF}

  if WindowState = wsMinimized then
  begin  // Minimized
    MainToolBar.Top:= 0; // restore toolbar position
    if not HiddenToTray then
    begin
{$IF DEFINED(LCLGTK2)}
      MinimizedWindowButton:= True;
{$ENDIF}
      if gMinimizeToTray or gAlwaysShowTrayIcon then
      begin
        HideToTray;
      end;
    end
    else begin
      // If we get wsMinimized while HiddenToTray is true,
      // then this means it was sent by LCL when a hidden, minimized window was shown.
      // We don't react to this message in this case.
      HiddenToTray := False;
{$IF DEFINED(LCLGTK2)}
      Application.QueueAsyncCall(@WindowStateUpdate, 0);
{$ENDIF}
    end;
  end
  else
  begin  // Not minimized
    // save window state before minimize for
    // future loading after restore from tray
    lastWindowState:=WindowState;
    HiddenToTray := False;
  end;
end;

procedure TfrmMain.MainSplitterDblClick(Sender: TObject);
begin
  // To prevent MainSplitterMouseUp processing
  MainSplitterLeftMouseBtnDown:=false;
  MainSplitter.ParentColor:=true;
  // Set splitter to 50/50
  Commands.DoPanelsSplitterPerPos(50);
end;

procedure TfrmMain.MainSplitterMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Button=mbLeft) and (not MainSplitterLeftMouseBtnDown) then
  begin
   // Under Linux MainSplitter.Color:=clBlack Doesn't work
   MainSplitter.ParentColor:=true;
   MainSplitter.Color:=ColorToRGB(clBlack);

   MainSplitterMouseDownX:=X;
   MainSplitterMouseDownY:=Y;
   MainSplitterLeftMouseBtnDown:=true;
  end;
end;

procedure TfrmMain.MainSplitterMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  Rect: TRect;
  sHint: String;
  Delta: Integer;
  APoint: TPoint;
  Moved: Boolean = False;
begin
  if MainSplitterLeftMouseBtnDown then
  begin
    if not gHorizontalFilePanels and
       (MainSplitter.Left + X > MainSplitter.Width) and
       (MainSplitter.Left + X + MainSplitter.Width < pnlNotebooks.Width) then
    begin
      MainSplitter.Left := MainSplitter.Left + X - MainSplitterMouseDownX;
      Moved := True;
    end
    else if gHorizontalFilePanels and
       (MainSplitter.Top + Y > MainSplitter.Height) and
       (MainSplitter.Top + Y + MainSplitter.Height < pnlNotebooks.Height) then
    begin
      MainSplitter.Top := MainSplitter.Top + Y - MainSplitterMouseDownY;
      Moved := True;
    end;

    if Moved then
    begin
      // create hint
      if not Assigned(MainSplitterHintWnd) then
      begin
        MainSplitterHintWnd := THintWindow.Create(nil);
        MainSplitterHintWnd.Color := Application.HintColor;
      end;

      // calculate percent
      if not gHorizontalFilePanels then
      begin
        Delta:= IfThen(MiddleToolBar.Visible, MiddleToolBar.Width);
        FMainSplitterPos:= MainSplitter.Left * 100 / (pnlNotebooks.Width-MainSplitter.Width - Delta);
      end
      else begin
        Delta:= IfThen(MiddleToolBar.Visible, MiddleToolBar.Height);
        FMainSplitterPos:= MainSplitter.Top * 100 / (pnlNotebooks.Height-MainSplitter.Height - Delta);
      end;

      // generate hint text
      sHint:= FloatToStrF(FMainSplitterPos, ffFixed, 15, 1) + '%';

      // calculate hint position
      Rect:= MainSplitterHintWnd.CalcHintRect(200, sHint, nil);
      APoint:= Mouse.CursorPos;
      with Rect do
      begin
        Right:= APoint.X + 8 + Right;
        Bottom:= APoint.Y + 12 + Bottom;
        Left:= APoint.X + 8;
        Top:= APoint.Y + 12;
      end;

      // show hint
      MainSplitterHintWnd.ActivateHint(Rect, sHint);
    end;
  end;
end;

procedure TfrmMain.MainSplitterMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  // hide and destroy hint
  if Assigned(MainSplitterHintWnd) then
  begin
    MainSplitterHintWnd.Hide;
    FreeAndNil(MainSplitterHintWnd);
  end;

  if (MainSplitterLeftMouseBtnDown) then
  begin
    MainSplitter.ParentColor:=true;
    MainSplitterLeftMouseBtnDown:=false;

    if not FResizingFilePanels then
    begin
      FResizingFilePanels := True;
      if not gHorizontalFilePanels then
        pnlLeft.Width := MainSplitter.Left
      else
        pnlLeft.Height := MainSplitter.Top;
      FResizingFilePanels := False;
    end;
  end;
end;

procedure TfrmMain.MainTrayIconClick(Sender: TObject);
begin
  // Only react to clicks if the icon is not scheduled to be hidden.
  if not HidingTrayIcon then
  begin
    if WindowState = wsMinimized then
    begin
      RestoreFromTray;
    end
    else
    begin
      MinimizeWindow;
      HideToTray;
    end;
  end;
end;

{ TfrmMain.lblDriveInfoDblClick }
//Shows the Directory Hotlist at the cursor position after double-clicking the panel.
//This is NOT like TC but was here as legacy in DC. Let it there for respect to original authors.
//Double-clicking on the "FPathLabel" of the "TFileViewHeader" does the same as in TC and is implemented now.
//
procedure TfrmMain.lblDriveInfoDblClick(Sender: TObject);
begin
  if tb_activate_panel_on_click in gDirTabOptions then
  begin
    if Sender = lblRightDriveInfo then
      SetActiveFrame(fpRight)
    else if Sender = lblLeftDriveInfo then
      SetActiveFrame(fpLeft);
  end;
  Commands.cm_DirHotList(['position=cursor']);
end;

procedure TfrmMain.LeftDriveBarExecuteDrive(ToolItem: TKASToolItem);
var
  DriveItem: TKASDriveItem;
begin
  DriveItem := ToolItem as TKASDriveItem;
  SetPanelDrive(fpLeft, DriveItem.Drive, True);
end;

procedure TfrmMain.MainToolBarDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  aFile: TFile;
  ToolItem: TKASProgramItem;
  Toolbar: TKASToolBar absolute Sender;
begin
  if not (Source is TSpeedButton) and not Draging then
  begin
    aFile := ActiveFrame.CloneActiveFile;
    try
      if Assigned(aFile) and aFile.IsNameValid then
      begin
        ToolItem := TKASProgramItem.Create;
        ToolItem.Command := GetToolbarFilenameToSave(tpmeCommand, aFile.FullPath);
        ToolItem.StartPath := GetToolbarFilenameToSave(tpmeStartingPath, aFile.Path);
        ToolItem.Hint := ExtractOnlyFileName(aFile.Name);
        // ToolItem.Text := ExtractOnlyFileName(aFile.Name);
        ToolItem.Icon := GetToolbarFilenameToSave(tpmeIcon, aFile.FullPath);
        Toolbar.AddButton(ToolItem);
      end;
    finally
      FreeAndNil(aFile);
    end;
  end;
  SaveToolBar(Toolbar);
  Draging := False;
end;

procedure TfrmMain.MainToolBarDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  aFile: TFile;
begin
  if (Source is TSpeedButton) then
    Accept := False
  else begin
    aFile := ActiveFrame.CloneActiveFile;
    try
      Accept := Assigned(aFile) and aFile.IsNameValid;
    finally
      FreeAndNil(aFile);
    end;
  end;
end;

function TfrmMain.MainToolBarLoadButtonGlyph(ToolItem: TKASToolItem;
  iIconSize: Integer; clBackColor: TColor): TBitmap;
begin
  if ToolItem is TKASNormalItem then
    Result := PixMapManager.LoadBitmapEnhanced(TKASNormalItem(ToolItem).Icon, iIconSize, True, clBackColor, nil)
  else
    Result := nil;
end;

function TfrmMain.MainToolBarLoadButtonOverlay(ToolItem: TKASToolItem;
  iIconSize: Integer; clBackColor: TColor): TBitmap;
begin
  if ToolItem is TKASMenuItem then
    Result := PixMapManager.LoadBitmapEnhanced('emblem-symbolic-link', iIconSize, True, clBackColor, nil)
  else
    Result := nil;
end;

procedure TfrmMain.tbDeleteClick(Sender: TObject);
var
  Toolbar: TKASToolBar;
  Button: TKASToolButton;
begin
  Button := TKASToolButton(pmToolBar.Tag);
  if Assigned(Button) then
  begin
    if msgYesNo(Format(rsMsgDelSel, [Button.Hint])) then
    begin
       Toolbar:= Button.ToolBar;
       ToolBar.RemoveButton(Button);
       SaveToolBar(Toolbar);
    end;
  end;
end;

procedure TfrmMain.dskLeftToolButtonClick(Sender: TObject);
begin
  PanelButtonClick(Sender as TKASToolButton, FrameLeft);
end;

procedure TfrmMain.MainToolBarMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  Point : TPoint;
begin
  case Button of
    mbRight:
      begin
        Point.X := X;
        Point.Y := Y;
        Point := (Sender as TControl).ClientToScreen(Point);
        if Sender is TKASToolButton then
        begin
          pmToolBar.Tag := PtrInt(Sender);
          pmToolBar.PopupComponent := TKASToolButton(Sender).ToolBar;
        end
        else begin
          pmToolBar.Tag := 0;
          pmToolBar.PopupComponent := TComponent(Sender);
        end;
        pmToolBar.PopUp(Point.X, Point.Y);
      end;
  end;
end;

procedure TfrmMain.frmMainClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  // Process all queued asynchronous events before closing
  // (frmMainAfterShow, nbPageAfterMouseDown, etc.).
  Application.ProcessMessages;

  if tb_close_duplicate_when_closing in gDirTabOptions then
  begin
    Commands.cm_CloseDuplicateTabs(['LeftTabs']);
    Commands.cm_CloseDuplicateTabs(['RightTabs']);
  end;

  if gSaveConfiguration then ConfigSaveSettings(False);

  FreeAndNil(Cons);

  Application.Terminate;
end;

procedure TfrmMain.frmMainAfterShow(Sender: TObject);
begin
  OnPaint := nil;
  if Assigned(ActiveFrame) then
    ActiveFrame.SetFocus
  else begin
    DCDebug('ActiveFrame = nil');
  end;
  HiddenToTray := False;
end;

procedure TfrmMain.frmMainShow(Sender: TObject);
begin
  DCDebug('frmMain.frmMainShow');
{$IF NOT (DEFINED(LCLWIN32) or DEFINED(LCLGTK2) or DEFINED(LCLCOCOA) OR (DEFINED(DARWIN) and DEFINED(LCLQT)))}
  OnPaint := @frmMainAfterShow;
{$ELSE}
  Application.QueueAsyncCall(TDataEvent(@frmMainAfterShow), 0);
{$ENDIF}
end;

procedure TfrmMain.mnuDropClick(Sender: TObject);
var
  DropParamsRef: TDropParams;
begin
  if (Sender is TMenuItem) and Assigned(FDropParams) then
    begin
      // Make a copy of the reference to parameters and clear FDropParams,
      // so that they're not destroyed if pmDropMenuClose is called while we're processing.
      DropParamsRef := FDropParams;
      FDropParams := nil; // release ownership

      with DropParamsRef do
      begin
        if (Sender as TMenuItem).Name = 'miMove' then
          begin
            TargetPanel.DoDragDropOperation(ddoMove, DropParamsRef);
          end
        else if (Sender as TMenuItem).Name = 'miCopy' then
          begin
            TargetPanel.DoDragDropOperation(ddoCopy, DropParamsRef);
          end
        else if (Sender as TMenuItem).Name = 'miSymLink' then
          begin
            TargetPanel.DoDragDropOperation(ddoSymLink, DropParamsRef);
          end
        else if (Sender as TMenuItem).Name = 'miHardLink' then
          begin
            TargetPanel.DoDragDropOperation(ddoHardLink, DropParamsRef);
          end
        else if (Sender as TMenuItem).Name = 'miCancel' then
          begin
            FreeAndNil(DropParamsRef);
          end;
      end; //with
    end;
end;

procedure TfrmMain.PopupDragDropMenu(var DropParams: TDropParams);
begin
  // Disposing of the params is handled in pmDropMenuClose or mnuDropClick.
  if Assigned(DropParams) then
  begin
    FDropParams := DropParams;
    DropParams := nil;
    pmDropMenu.PopUp(FDropParams.ScreenDropPoint.X,
                     FDropParams.ScreenDropPoint.Y);
  end;
end;

procedure TfrmMain.pmDropMenuClose(Sender: TObject);
begin
  // Free drop parameters given to drop menu.
  FreeAndNil(FDropParams);
end;

procedure TfrmMain.mnuSplitterPercentClick(Sender: TObject);
begin
  with (Sender as TMenuItem) do
  begin
    Commands.DoPanelsSplitterPerPos(Tag);
  end;
end;

procedure TfrmMain.mnuTabMenuExecute(Sender: TObject);
begin
  (Sender as TAction).OnExecute:= @actExecute;
end;

procedure TfrmMain.mnuTabMenuClick(Sender: TObject);
var
  Cmd: String;
  MenuItem: TMenuItem;
  NoteBook: TFileViewNotebook;
begin
  MenuItem := (Sender as TMenuItem);
  NoteBook := (pmTabMenu.Parent as TFileViewNotebook);

  // pmTabMenu.Tag stores tab page nr where the menu was activated.

  if MenuItem = miCloseTab then
    Commands.DoCloseTab(NoteBook, pmTabMenu.Tag)
  else if MenuItem = miRenameTab then
    Commands.DoRenameTab(NoteBook.Page[pmTabMenu.Tag])
  else if MenuItem = miTabOptionNormal then
    NoteBook.Page[pmTabMenu.Tag].LockState := tlsNormal
  else if MenuItem = miTabOptionPathLocked then
    NoteBook.Page[pmTabMenu.Tag].LockState := tlsPathLocked
  else if MenuItem = miTabOptionPathResets then
    NoteBook.Page[pmTabMenu.Tag].LockState := tlsPathResets
  else if MenuItem = miTabOptionDirsInNewTab then
    NoteBook.Page[pmTabMenu.Tag].LockState := tlsDirsInNewTab
  else
  begin
    Cmd:= MenuItem.Action.Name;
    Cmd:= 'cm_' + Copy(Cmd, 4, Length(Cmd) - 3);

    Commands.Commands.ExecuteCommand(Cmd, NoteBook.Name);
  end;
  // On click first the OnClick and then the Action.OnExecute is called
  MenuItem.Action.OnExecute:= @mnuTabMenuExecute;
end;

procedure TfrmMain.nbPageAfterMouseDown(Data: PtrInt);
var
  Notebook: TFileViewNotebook;
begin
  if TObject(Data) is TFileViewNotebook then
  begin
    Notebook := TObject(Data) as TFileViewNotebook;

    if (Notebook = nbLeft) and (FrameLeft <> nil) then
      begin
        if PanelSelected = fpLeft then // same panel
          FrameLeft.SetFocus
        else if (tb_activate_panel_on_click in gDirTabOptions) then
          SetActiveFrame(fpLeft)
        else
          FrameRight.SetFocus;
      end;
    if (Notebook = nbRight) and (FrameRight <> nil) then
      begin
        if PanelSelected = fpRight then // same panel
          FrameRight.SetFocus
        else if (tb_activate_panel_on_click in gDirTabOptions) then
          SetActiveFrame(fpRight)
        else
          FrameLeft.SetFocus;
      end;
  end;
end;

procedure TfrmMain.nbPageMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
{$IFNDEF LCLCOCOA}
begin
{$ELSE}
var
  Notebook: TFileViewNotebook;
  TabNr: Integer;
begin
  Notebook := TFileViewNotebook(Sender);
  TabNr := Notebook.IndexOfPageAt(Point(X, Y));
  if TabNr <> -1 then Notebook.ActivePageIndex := TabNr;
{$ENDIF}
  Application.QueueAsyncCall(@nbPageAfterMouseDown, PtrInt(Sender));
end;

procedure TfrmMain.nbPageChanged(Sender: TObject);
var
  Notebook: TFileViewNotebook;
  Page: TFileViewPage;
begin
  Notebook := Sender as TFileViewNotebook;
  Page := Notebook.ActivePage;
  if Assigned(Page) then
  begin
    if Page.LockState = tlsPathResets then // if locked with directory change
      ChooseFileSource(Page.FileView, Page.LockPath);

    // Update selected drive only on non-active panel,
    // because active panel is updated on focus change.
    if (PanelSelected <> Notebook.Side) and
       not (tb_activate_panel_on_click in gDirTabOptions) then
    begin
      UpdateSelectedDrive(Notebook);
      UpdateFreeSpace(Notebook.Side, True);
    end;
  end;

  QuickViewClose;
  if Visible then
     UpdatePrompt;
  UpdateTreeViewPath;
  UpdateMainTitleBar;
end;

procedure TfrmMain.nbPageMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  PopUpPoint: TPoint;
  NoteBook: TFileViewNotebook;
  TabNr: Integer;
begin
  NoteBook := Sender as TFileViewNotebook;

  case Button of

    mbMiddle:
      begin
        TabNr := NoteBook.IndexOfPageAt(Point(X, Y));
        if TabNr <> -1 then
        begin
          Commands.DoCloseTab(NoteBook, TabNr);
        end;
      end;

    mbRight:
      begin
        TabNr := NoteBook.IndexOfPageAt(Point(X, Y));
        if TabNr <> -1 then
        begin
          PopUpPoint := NoteBook.ClientToScreen(Point(X, Y));

          // Check tab options items.
          case NoteBook.Page[TabNr].LockState of
            tlsNormal:
              miTabOptionNormal.Checked := True;
            tlsPathLocked:
              miTabOptionPathLocked.Checked := True;
            tlsDirsInNewTab:
              miTabOptionDirsInNewTab.Checked := True;
            tlsPathResets:
              miTabOptionPathResets.Checked := True;
          end;

          pmTabMenu.Parent := NoteBook;
          pmTabMenu.Tag := TabNr;
          pmTabMenu.PopUp(PopUpPoint.x, PopUpPoint.y);
        end;
      end;

  end;
end;

procedure TfrmMain.NotebookDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  ATabIndex: Integer;
  TargetPath: String;
  SourceFiles: TFiles;
  TargetFileSource: IFileSource;
  ANotebook: TFileViewNotebook absolute Sender;
begin
  if (Source is TWinControl) and (TWinControl(Source).Parent is TFileView) then
  begin
    ATabIndex := ANotebook.IndexOfPageAt(Classes.Point(X, Y));
    if (ATabIndex > -1) then
    begin
      SourceFiles := ActiveFrame.CloneSelectedOrActiveFiles;
      try
        begin
          TargetPath := ANotebook.View[ATabIndex].CurrentPath;
          TargetFileSource := ANotebook.View[ATabIndex].FileSource;
          case GetDropEffectByKeyAndMouse(GetKeyShiftStateEx, mbLeft, gDefaultDropEffect) of
            DropCopyEffect:
              Self.CopyFiles(ActiveFrame.FileSource, TargetFileSource, SourceFiles, TargetPath, gShowDialogOnDragDrop);
            DropMoveEffect:
              Self.MoveFiles(ActiveFrame.FileSource, TargetFileSource, SourceFiles, TargetPath, gShowDialogOnDragDrop);
          end;
        end;
      finally
        SourceFiles.Free;
      end;
    end;
  end;
end;

procedure TfrmMain.NotebookDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  ATabIndex: Integer;
  APage: TFileViewPage;
  ANotebook: TFileViewNotebook absolute Sender;
begin
  Accept := False;
  if (Source is TWinControl) and (TWinControl(Source).Parent is TFileView) then
  begin
    ATabIndex := ANotebook.IndexOfPageAt(Classes.Point(X, Y));
    if (ATabIndex > -1) then
    begin
      APage:= ANotebook.Page[ATabIndex];
      Accept := (APage.FileView <> TWinControl(Source).Parent) and
                ((APage.LockState = tlsNormal) or (APage.LockPath = APage.FileView.CurrentPath));
    end;
  end;
end;

procedure TfrmMain.NotebookCloseTabClicked(Sender: TObject);
begin
  with (Sender As TFileViewPage) do
  if PageIndex <> -1 then
  begin
    RemovePage(Notebook, PageIndex);
  end;
end;

procedure TfrmMain.ConvertIniToolbarItem(Loader: TKASToolBarIniLoader; var Item: TKASToolItem; const Shortcut: String);
  procedure ConvertHotkeys(CommandItem: TKASCommandItem; Hotkeys: THotkeys; SearchHotkey: THotkey);
  var
    Hotkey: THotkey;
  begin
    Hotkey := Hotkeys.FindByContents(SearchHotkey);
    if Assigned(Hotkey) then
    begin
      Hotkey.Command := 'cm_ExecuteToolbarItem';
      Hotkey.Params := nil;
      SetValue(Hotkey.Params, 'ToolItemID', CommandItem.ID);
    end;
  end;
var
  HMForm: THMForm;
  Hotkey: THotkey;
  CommandItem: TKASCommandItem;
  MenuItem: TKASMenuItem;
  BarFileName: String;
begin
  if Item is TKASCommandItem then
  begin
    CommandItem := TKASCommandItem(Item);
    // Convert toolbar hotkey to use ID as parameter.
    if Shortcut <> '' then
    begin
      Hotkey := THotkey.Create;
      try
        Hotkey.Command := 'cm_Int_RunCommandFromBarFile';
        AddString(Hotkey.Shortcuts, Shortcut);
        Hotkey.Params := Hotkey.Shortcuts;
        HMForm := HotMan.Forms.Find('Main');
        if Assigned(HMForm) then
          ConvertHotkeys(CommandItem, HMForm.Hotkeys, Hotkey);
      finally
        Hotkey.Free;
      end;
    end;

    if ((CommandItem.Command = 'cm_OpenBar') or
        (CommandItem.Command = 'cm_ShowButtonMenu')) and
       (Length(CommandItem.Params) > 0) then
    begin
      BarFileName := CommandItem.Params[0];
      if Pos(PathDelim, BarFileName) <> 0 then
        BarFileName := GetCmdDirFromEnvVar(BarFileName)
      else
        BarFileName := gpCfgDir + BarFileName;
      if mbFileExists(BarFileName) then
      begin
        MenuItem := TKASMenuItem.Create;
        MenuItem.Assign(Item); // Copy whatever is possible from Command item
        Loader.Load(BarFileName, nil, MenuItem, @ConvertIniToolbarItem);
        mbRenameFile(BarFileName, BarFileName + '.obsolete');
        Item.Free;
        Item := MenuItem;
      end;
    end;
  end;
end;

procedure TfrmMain.FormKeyPress(Sender: TObject; var Key: Char);
var
  ModifierKeys: TShiftState;
begin
  // Either left or right panel has to be focused.
  if not FrameLeft.Focused and
     not FrameRight.Focused then
  begin
    Exit;
  end;

  ModifierKeys := GetKeyShiftStateEx;

  if gCmdLine and  // If command line is enabled
     (GetKeyTypingAction(ModifierKeys) = ktaCommandLine) and
     not ((Key in ['-', '*', '+', #0..#32]) and (Trim(edtCommand.Text) = '')) then
  begin
    TypeInCommandLine(Key);
    Key := #0;
  end;
end;

function TfrmMain.ActiveFrame: TFileView;
begin
  case PanelSelected of
    fpLeft:
      Result := FrameLeft;
    fpRight:
      Result := FrameRight;
  else
    assert(false,'Bad active frame');
  end;
end;

function TfrmMain.NotActiveFrame: TFileView;
begin
  case PanelSelected of
    fpRight:
      Result := FrameLeft;
    fpLeft:
      Result := FrameRight;
  else
    assert(false,'Bad active frame');
    Result:=FrameLeft;// only for compilator warning;
  end;
end;

function TfrmMain.ActiveNotebook: TFileViewNotebook;
begin
  case PanelSelected of
    fpLeft:
      Result := nbLeft;
    fpRight:
      Result := nbRight;
  else
    assert(false,'Bad active notebook');
  end;
end;

function TfrmMain.NotActiveNotebook: TFileViewNotebook;
begin
  case PanelSelected of
    fpLeft:
      Result := nbRight;
    fpRight:
      Result := nbLeft;
  else
    assert(false,'Bad active notebook');
  end;
end;

function TfrmMain.FrameLeft: TFileView;
begin
  Result := nbLeft.ActiveView;
end;

function TfrmMain.FrameRight: TFileView;
begin
  Result := nbRight.ActiveView;
end;

procedure TfrmMain.ForEachView(CallbackFunction: TForEachViewFunction; UserData: Pointer);
  procedure EnumerateNotebook(ANoteBook: TFileViewNotebook);
  var
    i: Integer;
  begin
    for i := 0 to ANoteBook.PageCount - 1 do
      CallbackFunction(ANoteBook.View[i], UserData);
  end;
begin
  EnumerateNotebook(nbLeft);
  EnumerateNotebook(nbRight);
end;

procedure TfrmMain.GetListOpenedPaths(const APaths: TStringList);

  procedure GetNotebookPaths(ANoteBook: TFileViewNotebook);
  var
    S: String;
    I: Integer;
  begin
    for I := 0 to ANoteBook.PageCount - 1 do
    begin
      S:= ANoteBook.View[I].CurrentPath;
      APaths.Add(S);
    end;
  end;

begin
  APaths.Clear;
  GetNotebookPaths(nbLeft);
  GetNotebookPaths(nbRight);
end;

procedure TfrmMain.AppException(Sender: TObject; E: Exception);
begin
  WriteExceptionToErrorFile;
  ShowExceptionDialog;
end;

procedure TfrmMain.AppShowHint(var HintStr: string; var CanShow: Boolean;
  var HintInfo: THintInfo);
begin
  // Refresh monitor list
  Screen.UpdateMonitors;
  // Show hint only when application is active
  CanShow:= Application.Active;
end;

constructor TfrmMain.Create(TheOwner: TComponent);
begin
  FMainSplitterPos := 50.0;
  inherited Create(TheOwner);
  FCommands := TMainCommands.Create(Self, actionLst);

  if Assigned(Application.Icon) then begin
    MainTrayIcon.Icon.Assign(Application.Icon);
  end;

  Screen.Cursors[crArrowCopy] := LoadCursorFromLazarusResource('ArrowCopy');
  Screen.Cursors[crArrowMove] := LoadCursorFromLazarusResource('ArrowMove');
  Screen.Cursors[crArrowLink] := LoadCursorFromLazarusResource('ArrowLink');
end;

procedure TfrmMain.AfterConstruction;
begin
  FResizingFilePanels:= True;
  inherited AfterConstruction;
  FResizingFilePanels:= False;
  pnlNotebooksResize(pnlNotebooks);
end;

procedure TfrmMain.UpdateActionIcons;
var
  I: Integer;
  imgIndex: Integer;
  iconsDir: String;
  fileName: String;
  iconImg: TPicture;
  actionName: TComponentName;
begin
  if not gIconsInMenus then Exit;

  actionLst.Images := nil;
  pmTabMenu.Images := nil;
  mnuMain.Images := nil;
  imgLstActions.Clear;

  // Temporarily while feature is not implemented
  // http://doublecmd.sourceforge.net/mantisbt/view.php?id=11
  fileName := IntToStr(gIconsInMenusSize);
  iconsDir := gpPixmapPath + 'dctheme' + PathDelim + fileName;
  iconsDir := iconsDir + 'x' + fileName + PathDelim + 'actions';
  if not mbDirectoryExists(iconsDir) then Exit;

  iconImg := TPicture.Create;
  try
    imgLstActions.Width := gIconsInMenusSize;
    imgLstActions.Height := gIconsInMenusSize;

    actionLst.Images := imgLstActions;
    pmTabMenu.Images := imgLstActions;
    mnuMain.Images := imgLstActions;

    for I:= 0 to actionLst.ActionCount - 1 do
    begin
      actionName := UTF8LowerCase(actionLst.Actions[I].Name);
      fileName := iconsDir + PathDelim + 'cm_' + UTF8Copy(actionName, 4, Length(actionName) - 3) + '.png';
      if mbFileExists(fileName) then
      try
        iconImg.LoadFromFile(fileName);
        imgIndex := imgLstActions.Add(iconImg.Bitmap, nil);
        if imgIndex >= 0 then
        begin
           TAction(actionLst.Actions[I]).ImageIndex := imgIndex;
        end;
      except
        // Skip
      end;
    end;

  finally
    FreeAndNil(iconImg);
  end;
end;

procedure TfrmMain.UpdateHotDirIcons;
var
  I: Integer;
  iconsDir: String;
  fileName: String;
  iconImg: TPicture;
begin
  pmHotList.Images:=nil; { TODO -oDB : The images of popup menu in configuration should also be nilled to be correct }
  imgLstDirectoryHotlist.Clear;

  fileName := IntToStr(gIconsInMenusSize);
  iconsDir := gpPixmapPath + 'dctheme' + PathDelim + fileName;
  iconsDir := iconsDir + 'x' + fileName + PathDelim + 'actions';
  if not mbDirectoryExists(iconsDir) then Exit;

  iconImg := TPicture.Create;
  try
    fileName := IntToStr(gIconsInMenusSize);
    iconsDir := gpPixmapPath + 'dctheme' + PathDelim + fileName;
    iconsDir := iconsDir + 'x' + fileName + PathDelim + 'dirhotlist';
    imgLstDirectoryHotlist.Width := gIconsInMenusSize;
    imgLstDirectoryHotlist.Height := gIconsInMenusSize;
    pmHotList.Images:=imgLstDirectoryHotlist;

    for I:=0 to pred(length(ICONINDEXNAME)) do
    begin
      filename:=iconsDir+PathDelim+ICONINDEXNAME[I]+'.png';
      if mbFileExists(fileName) then
      try
        iconImg.LoadFromFile(fileName);
        imgLstDirectoryHotlist.Add(iconImg.Bitmap, nil);
      except
        // Skip
      end;
    end;

  finally
    FreeAndNil(iconImg);
  end;
end;

procedure TfrmMain.CreateDefaultToolbar;
var
  AToolbar: TKASToolBar;

  procedure AddCommand(const Command: String);
  var
    CommandItem: TKASCommandItem;
  begin
    CommandItem := TKASCommandItem.Create(Commands.Commands);
    CommandItem.Icon := UTF8LowerCase(Command);
    CommandItem.Command := Command;
    // Leave CommandItem.Hint empty. It will be loaded at startup based on language.
    AToolbar.AddButton(CommandItem);
  end;

  procedure AddSeparator(Style: Boolean = False);
  var
    SeparatorItem: TKASSeparatorItem;
  begin
    SeparatorItem:= TKASSeparatorItem.Create;
    SeparatorItem.Style:= Style;
    AToolbar.AddButton(SeparatorItem);
  end;

var
  MainToolBarNode: TXmlNode;
begin
  if MainToolBar.ButtonCount = 0 then
  begin
    MainToolBarNode := gConfig.FindNode(gConfig.RootNode, 'Toolbars/' + MainToolBar.Name, False);
    if not Assigned(MainToolBarNode) then
    begin
      AToolbar := MainToolBar;
      AddCommand('cm_Refresh');
      AddCommand('cm_RunTerm');
      AddCommand('cm_Options');
      AddSeparator;
      AddCommand('cm_BriefView');
      AddCommand('cm_ColumnsView');
      AddCommand('cm_ThumbnailsView');
      AddSeparator;
      AddCommand('cm_FlatView');
      AddSeparator;
      AddCommand('cm_ViewHistoryPrev');
      AddCommand('cm_ViewHistoryNext');
      AddSeparator;
      AddCommand('cm_MarkPlus');
      AddCommand('cm_MarkMinus');
      AddCommand('cm_MarkInvert');
      AddSeparator;
      AddCommand('cm_PackFiles');
      AddCommand('cm_ExtractFiles');
      AddSeparator;
      AddCommand('cm_NetworkConnect');
      AddCommand('cm_Search');
      AddCommand('cm_MultiRename');
      AddCommand('cm_SyncDirs');
      AddCommand('cm_CopyFullNamesToClip');
      SaveToolBar(MainToolBar);
    end;
  end;
  if MiddleToolBar.ButtonCount = 0 then
  begin
    MainToolBarNode := gConfig.FindNode(gConfig.RootNode, 'Toolbars/' + MiddleToolBar.Name, False);
    if not Assigned(MainToolBarNode) then
    begin
      AToolbar := MiddleToolBar;
      AddCommand('cm_View');
      AddCommand('cm_Edit');
      AddCommand('cm_Copy');
      AddCommand('cm_Rename');
      AddSeparator(True);
      AddCommand('cm_PackFiles');
      AddCommand('cm_MakeDir');
      SaveToolBar(MiddleToolBar);
    end;
  end;
end;

function TfrmMain.GetFileDlgStr(sLngOne, sLngMulti: String; Files: TFiles): String;
begin
  if Files.Count = 0 then
    raise Exception.Create(rsMsgNoFilesSelected);

  if Files.Count > 1 then
    Result := Format(sLngMulti, [Files.Count])
  else
    Result := Format(sLngOne, [Files[0].Name]);
end;

procedure TfrmMain.miHotAddOrConfigClick(Sender: TObject);
begin
  with Sender as TComponent do Commands.cm_WorkWithDirectoryHotlist(['action='+HOTLISTMAGICWORDS[tag], 'source='+QuoteStr(ActiveFrame.CurrentLocation), 'target='+QuoteStr(NotActiveFrame.CurrentLocation), 'index=0']);
end;

procedure TfrmMain.CreatePopUpDirHistory(UseTreeViewMenu: Boolean;
  FromPathIndex: Integer);
var
  I, Finish: Integer;
  MenuItem: TMenuItem;
begin
  pmDirHistory.Items.Clear;

  if UseTreeViewMenu then
    Finish:= glsDirHistory.Count - 1
  else begin
    Finish:= Min(FromPathIndex + gDirHistoryCount, glsDirHistory.Count - 1);
  end;

  if (not UseTreeViewMenu) and (FromPathIndex > 0) then
  begin
    MenuItem := TMenuItem.Create(pmDirHistory);
    MenuItem.Caption := '...';
    MenuItem.OnClick := @HistorySomeSelected;
    MenuItem.Tag := Max(0, FromPathIndex - gDirHistoryCount - 1);
    pmDirHistory.Items.Add(MenuItem);
  end;

  for I:= FromPathIndex to Finish do
  begin
    MenuItem:= TMenuItem.Create(pmDirHistory);
    MenuItem.Caption:= glsDirHistory[I].Replace('&','&&');
    MenuItem.Hint:= glsDirHistory[I];
    MenuItem.OnClick:= @HistorySelected;
    pmDirHistory.Items.Add(MenuItem);
  end;

  if (not UseTreeViewMenu) and (Finish < glsDirHistory.Count - 1) then
  begin
    MenuItem := TMenuItem.Create(pmDirHistory);
    MenuItem.Caption := '...';
    MenuItem.OnClick := @HistorySomeSelected;
    MenuItem.Tag := Finish + 1;
    pmDirHistory.Items.Add(MenuItem);
  end;
end;

procedure TfrmMain.ShowFileViewHistory(const Params: array of string);
begin
  ShowFileViewHistory(Params, -1, -1, -1, -1);
end;

procedure TfrmMain.ShowFileViewHistory(const Params: array of string; FromFileSourceIndex, FromPathIndex, ToFileSourceIndex, ToPathIndex: Integer);
const
  MaxItemsShown = 20;
var
  ItemsBackward: Integer = 0;
  ItemsForward: Integer = 0;

  function GoBack(var FileSourceIndex, PathIndex: Integer): Boolean;
  begin
    if PathIndex = 0 then
    begin
      if FileSourceIndex = 0 then
        Result := False
      else
      begin
        Dec(FileSourceIndex);
        PathIndex := ActiveFrame.PathsCount[FileSourceIndex] - 1;
        Result := True;
      end;
    end
    else
    begin
      Dec(PathIndex);
      Result := True;
    end;
  end;

  function GoForward(var FileSourceIndex, PathIndex: Integer): Boolean;
  begin
    if PathIndex = ActiveFrame.PathsCount[FileSourceIndex] - 1 then
    begin
      if FileSourceIndex = ActiveFrame.FileSourcesCount - 1 then
        Result := False
      else
      begin
        Inc(FileSourceIndex);
        PathIndex := 0;
        Result := True;
      end;
    end
    else
    begin
      Inc(PathIndex);
      Result := True;
    end;
  end;

  procedure AddCaptionItem(s: String);
  var
    mi: TMenuItem;
  begin
    mi := TMenuItem.Create(pmDirHistory);
    mi.Caption := s;
    mi.Enabled := False;
    pmDirHistory.Items.Add(mi);
  end;

  procedure FindBoundsBackward;
  var
    I: Integer;
  begin
    GoBack(ToFileSourceIndex, ToPathIndex);

    FromFileSourceIndex := ToFileSourceIndex;
    FromPathIndex := ToPathIndex;

    for i := 0 to MaxItemsShown - 1 do
    begin
      if GoBack(FromFileSourceIndex, FromPathIndex) then
        Inc(ItemsBackward);
    end;
  end;

  procedure FindBoundsFromCenter;
  var
    I: Integer;
  begin
    FromFileSourceIndex := ActiveFrame.CurrentFileSourceIndex;
    FromPathIndex := ActiveFrame.CurrentPathIndex;
    ToFileSourceIndex := FromFileSourceIndex;
    ToPathIndex := FromPathIndex;

    for i := 0 to (MaxItemsShown div 2) - 1 do
    begin
      if GoBack(FromFileSourceIndex, FromPathIndex) then
        Inc(ItemsBackward);
      if GoForward(ToFileSourceIndex, ToPathIndex) then
        Inc(ItemsForward);
    end;

    for i := ItemsForward to (MaxItemsShown div 2) - 1 do
    begin
      if GoBack(FromFileSourceIndex, FromPathIndex) then
        Inc(ItemsBackward);
    end;

    for i := ItemsBackward to (MaxItemsShown div 2) - 1 do
    begin
      if GoForward(ToFileSourceIndex, ToPathIndex) then
        Inc(ItemsForward);
    end;
  end;

  procedure FindBoundsForward;
  var
    I: Integer;
  begin
    GoForward(FromFileSourceIndex, FromPathIndex);

    ToFileSourceIndex := FromFileSourceIndex;
    ToPathIndex := FromPathIndex;

    for i := 0 to MaxItemsShown - 1 do
    begin
      if GoForward(ToFileSourceIndex, ToPathIndex) then
        Inc(ItemsForward);
    end;
  end;

var
  bUseTreeViewMenu: boolean = false;
  bUsePanel: boolean = false; // As opposed as the other popup, for that one, by legacy, the position of the popup is the cursor position instead of top left corner of active panel.
  p: TPoint;
  iWantedWidth: integer = 0;
  iWantedHeight: integer = 0;
  sMaybeMenuItem: TMenuItem = nil;
  I: Integer;
  mi: TMenuItem;
begin
  pmDirHistory.Items.Clear;
  p.x := 0;
  p.y := 0;

  if FromFileSourceIndex <> -1 then
    FindBoundsForward
  else if ToFileSourceIndex <> - 1 then
    FindBoundsBackward
  else
    FindBoundsFromCenter;

  if (FromFileSourceIndex > 0) or (FromPathIndex > 0) then
  begin
    mi := TMenuItem.Create(pmDirHistory);
    mi.Caption := '...';
    mi.OnClick := @ViewHistoryPrevSelected;
    mi.Tag := HistoryIndexesToTag(FromFileSourceIndex, FromPathIndex);
    pmDirHistory.Items.Add(mi);
  end;

  for i := 0 to ItemsForward + ItemsBackward do
  begin
    mi := TMenuItem.Create(pmDirHistory);
    pmDirHistory.Items.Add(mi);

    mi.Caption := ActiveFrame.Path[FromFileSourceIndex, FromPathIndex].Replace('&','&&');
    mi.OnClick := @ViewHistorySelected;
    // Remember indexes into history.
    mi.Tag := HistoryIndexesToTag(FromFileSourceIndex, FromPathIndex);
    // Mark current history position.
    if (FromFileSourceIndex = ActiveFrame.CurrentFileSourceIndex) and
       (FromPathIndex = ActiveFrame.CurrentPathIndex) then
      mi.Checked := True;

    if not GoForward(FromFileSourceIndex, FromPathIndex) then
      Break;

    // Add separator and address of a file source as a caption.
    if FromPathIndex = 0 then
    begin
      AddCaptionItem('-');
      AddCaptionItem('- ' + ActiveFrame.FileSources[FromFileSourceIndex].CurrentAddress.Replace('&','&&') + ' -');
    end;
  end;

  if (ToFileSourceIndex < ActiveFrame.FileSourcesCount - 1) or
     (ToPathIndex < ActiveFrame.PathsCount[ToFileSourceIndex] - 1) then
  begin
    mi := TMenuItem.Create(pmDirHistory);
    mi.Caption := '...';
    mi.OnClick := @ViewHistoryNextSelected;
    mi.Tag := HistoryIndexesToTag(ToFileSourceIndex, ToPathIndex);
    pmDirHistory.Items.Add(mi);
  end;

  Application.ProcessMessages;

  // 1. Let's parse our parameters.
  Commands.DoParseParametersForPossibleTreeViewMenu(Params, gUseTreeViewMenuWithViewHistory, gUseTreeViewMenuWithViewHistory, bUseTreeViewMenu, bUsePanel, p);

  // 2. 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(pmDirHistory, tvmcViewHistory, p.X, p.Y, iWantedWidth, iWantedHeight);
    if sMaybeMenuItem <> nil then sMaybeMenuItem.OnClick(sMaybeMenuItem);
  end
  else
  begin
    pmDirHistory.Popup(p.X, p.Y);
  end;
end;

{ TfrmMain.HotDirActualSwitchToDir }
// Actual routine called when user click the item from the Directory Hotlist popup menu to switch to a hot directory
// The index received is the index from the "gDirectoryHotlist" to read hotdir entry from.
//
procedure TfrmMain.HotDirActualSwitchToDir(Index:longint);
var
  aPath: String;
  isSHIFTDown, isCTRLDown: boolean;
  PossibleCommande,PossibleParam: string;
  PosFirstSpace: integer;
  Editor: TOptionsEditor;
  Options: IOptionsDialog;
begin
  // This handler is used by HotDir AND SpecialDir.
  // HotDirs AND SpecialDirs are only supported by filesystem.

  // If the index is larger or equal to "TAGOFFSET_FORCHANGETOSPECIALDIR", it means it's a "SpecialDir" and we'll change accordingly
  if (Index < TAGOFFSET_FORCHANGETOSPECIALDIR) AND (Index >= 0) then
    begin
      isSHIFTDown:=((GetKeyState(VK_SHIFT) AND $80) <> 0);
      if not isSHIFTDown then //if SHIFT is NOT down, it's to change directory
      begin
        case gDirectoryHotlist.HotDir[Index].Dispatcher of
          hd_CHANGEPATH:
            begin
              isCTRLDown:=((GetKeyState(VK_CONTROL) AND $80) <> 0); //if CTRL is down, it's to request to DON'T CHANGE TARGET even if in the directoryhotlist entry it was requesting to do so

              aPath := gDirectoryHotlist.HotDir[Index].HotDirPath;
              if aPath<>'' then
                begin
                  case gDirectoryHotlist.HotDir[Index].HotDirPathSort of
                    1: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_NAME,STR_ASCENDING]); //Name, a-z
                    2: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_NAME,STR_DESCENDING]); //Name, z-a
                    3: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_EXTENSION,STR_ASCENDING]); //Ext, a-z
                    4: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_EXTENSION,STR_DESCENDING]); //Ext, z-a
                    5: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_SIZE,STR_DESCENDING]); //Size 9-0
                    6: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_SIZE,STR_ASCENDING]); //Size 0-9
                    7: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_MODIFICATIONDATETIME,STR_DESCENDING]); //Date 9-0
                    8: Commands.cm_UniversalSingleDirectSort([STR_ACTIVEFRAME,STR_MODIFICATIONDATETIME,STR_ASCENDING]); //Date 0-9
                  end;

                  aPath := mbExpandFileName(aPath);
                  ChooseFileSource(ActiveFrame, aPath);

                  if (not isCTRLDown) then //We don't change target folder if CTRL key is pressed
                  begin
                    aPath := gDirectoryHotlist.HotDir[Index].HotDirTarget;
                    if aPath<>'' then
                      begin
                        case gDirectoryHotlist.HotDir[Index].HotDirTargetSort of
                          1: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_NAME,STR_ASCENDING]); //Name, a-z
                          2: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_NAME,STR_DESCENDING]); //Name, z-a
                          3: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_EXTENSION,STR_ASCENDING]); //Ext, a-z
                          4: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_EXTENSION,STR_DESCENDING]); //Ext, z-a
                          5: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_SIZE,STR_DESCENDING]); //Size 9-0
                          6: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_SIZE,STR_ASCENDING]); //Size 0-9
                          7: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_MODIFICATIONDATETIME,STR_DESCENDING]); //Date 9-0
                          8: Commands.cm_UniversalSingleDirectSort([STR_NOTACTIVEFRAME,STR_MODIFICATIONDATETIME,STR_ASCENDING]); //Date 0-9
                        end;

                        aPath := mbExpandFileName(aPath);
                        ChooseFileSource(NotActiveFrame, aPath);
                      end;
                  end;
                end;
            end; //hd_CHANGEPATH:

          hd_COMMAND:
            begin
              PosFirstSpace:=pos(' ',gDirectoryHotlist.HotDir[Index].HotDirPath);

              if PosFirstSpace=0 then
                begin
                  PossibleCommande:=gDirectoryHotlist.HotDir[Index].HotDirPath;
                  PossibleParam:='';
                end
              else
                begin
                  PossibleCommande:=leftstr(gDirectoryHotlist.HotDir[Index].HotDirPath,(PosFirstSpace-1));
                  PossibleParam:=rightstr(gDirectoryHotlist.HotDir[Index].HotDirPath,length(gDirectoryHotlist.HotDir[Index].HotDirPath)-PosFirstSpace);
                end;

                Commands.Commands.ExecuteCommand(PossibleCommande, SplitString(PossibleParam,' '));
            end;
        end; //case gDirectoryHotlist.HotDir[Index].Dispatcher of
      end
      else
      begin //if SHIFT IS down, it's to EDIT current selected entry from the Directory Hotlist that the current selected popup menu selection is pointing.
        Options := ShowOptions(TfrmOptionsDirectoryHotlist);
        Editor := Options.GetEditor(TfrmOptionsDirectoryHotlist);
        Application.ProcessMessages;
        if Editor.CanFocus then  Editor.SetFocus;
        TfrmOptionsDirectoryHotlist(Editor).SubmitToAddOrConfigToHotDirDlg(ACTION_DIRECTLYCONFIGENTRY,ActiveFrame.CurrentPath,NotActiveFrame.CurrentPath,Index);
      end;
    end
  else
    begin
      if Index>=0 then
      begin
        //So it's a SpecialDir...
        Index:=Index-TAGOFFSET_FORCHANGETOSPECIALDIR;
        aPath := mbExpandFileName((gSpecialDirList.SpecialDir[Index].PathValue));
        ChooseFileSource(ActiveFrame, aPath);
      end;
    end;
end;

procedure TfrmMain.HotDirSelected(Sender: TObject);
begin
  HotDirActualSwitchToDir((Sender as TMenuItem).Tag);
end;

procedure TfrmMain.HistorySelected(Sender: TObject);
var
  aPath: String;
begin
  // This handler is used by DirHistory.
  aPath := (Sender as TMenuItem).Hint;
  aPath := mbExpandFileName(aPath);
  ChooseFileSource(ActiveFrame, aPath);
end;

procedure TfrmMain.HistorySomeSelected(Sender: TObject);
var
  P: TPoint;
begin
  if Sender is TMenuItem then
  begin
    P:= ActiveFrame.ClientToScreen(Classes.Point(0, 0));
    CreatePopUpDirHistory(False, TMenuItem(Sender).Tag);
    pmDirHistory.Popup(P.X, P.Y);
  end;
end;

procedure TfrmMain.ViewHistorySelected(Sender: TObject);
var
  FileSourceIndex, PathIndex: Integer;
begin
  if Sender is TMenuItem then
  begin
    HistoryIndexesFromTag((Sender as TMenuItem).Tag, FileSourceIndex, PathIndex);
    ActiveFrame.GoToHistoryIndex(FileSourceIndex, PathIndex);
  end;
end;

procedure TfrmMain.ViewHistoryPrevSelected(Sender:TObject);
var
  FileSourceIndex, PathIndex: Integer;
begin
  if Sender is TMenuItem then
  begin
    HistoryIndexesFromTag((Sender as TMenuItem).Tag, FileSourceIndex, PathIndex);
    ShowFileViewHistory([], -1, -1, FileSourceIndex, PathIndex);
  end;
end;

procedure TfrmMain.ViewHistoryNextSelected(Sender:TObject);
var
  FileSourceIndex, PathIndex: Integer;
begin
  if Sender is TMenuItem then
  begin
    HistoryIndexesFromTag((Sender as TMenuItem).Tag, FileSourceIndex, PathIndex);
    ShowFileViewHistory([], FileSourceIndex, PathIndex, -1, -1);
  end;
end;

procedure TfrmMain.edtCommandKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if (key=VK_Down) and (Shift=[ssCtrl]) and (edtCommand.Items.Count>0) then
  begin
    Key:=0;
    edtCommand.DroppedDown:=True;
    edtCommand.SetFocus;
  end;
end;

procedure TfrmMain.OnCopyOutTempStateChanged(Operation: TFileSourceOperation;
  State: TFileSourceOperationState);
begin
  FModalOperationResult:= Operation.Result = fsorFinished;
end;

function TfrmMain.CopyFiles(SourceFileSource, TargetFileSource: IFileSource;
                            var SourceFiles: TFiles; TargetPath: String;
                            bShowDialog: Boolean;
                            QueueIdentifier: TOperationsManagerQueueIdentifier): Boolean;
var
  BaseDir: String;
  sDestination: String;
  sDstMaskTemp: String;
  FileSource: IFileSource;
  TargetFiles: TFiles = nil;
  CopyDialog: TfrmCopyDlg = nil;
  OperationTemp: Boolean = False;
  OperationType: TFileSourceOperationType;
  OperationClass: TFileSourceOperationClass;
  Operation: TFileSourceCopyOperation = nil;
  OperationOptionsUIClass: TFileSourceOperationOptionsUIClass = nil;
begin
  Result := False;
  try
    if SourceFiles.Count = 0 then
      Exit;

    if (SourceFiles.Count = 1) and
       ((not (SourceFiles[0].IsDirectory or SourceFiles[0].IsLinkToDirectory)) or
        (TargetPath = ''))
    then
      sDestination := TargetPath + ReplaceInvalidChars(SourceFiles[0].Name)
    else
      sDestination := TargetPath + '*.*';

    // If same file source and address
    if (fsoCopy in SourceFileSource.GetOperationsTypes) and
       (fsoCopy in TargetFileSource.GetOperationsTypes) and
       SourceFileSource.Equals(TargetFileSource) and
       SameText(SourceFileSource.GetCurrentAddress, TargetFileSource.GetCurrentAddress) then
    begin
      OperationType := fsoCopy;
      FileSource := SourceFileSource;
      OperationClass := SourceFileSource.GetOperationClass(fsoCopy);
    end
    else if TargetFileSource.IsClass(TFileSystemFileSource) and
            (fsoCopyOut in SourceFileSource.GetOperationsTypes) then
    begin
      OperationType := fsoCopyOut;
      FileSource := SourceFileSource;
      OperationClass := SourceFileSource.GetOperationClass(fsoCopyOut);
    end
    else if SourceFileSource.IsClass(TFileSystemFileSource) and
            (fsoCopyIn in TargetFileSource.GetOperationsTypes) then
    begin
      OperationType := fsoCopyIn;
      FileSource := TargetFileSource;
      OperationClass := TargetFileSource.GetOperationClass(fsoCopyIn);
    end
    else if (fsoCopyOut in SourceFileSource.GetOperationsTypes) and
            (fsoCopyIn in TargetFileSource.GetOperationsTypes) then
    begin
      OperationTemp := True;
      OperationType := fsoCopyOut;
      FileSource := SourceFileSource;
      OperationClass := SourceFileSource.GetOperationClass(fsoCopyOut);
      if (fspCopyOutOnMainThread in SourceFileSource.Properties) or
         (fspCopyInOnMainThread in TargetFileSource.Properties) then
      begin
        QueueIdentifier:= ModalQueueId;
      end;
    end
    else
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
    end;

    if bShowDialog then
    begin
      if Assigned(OperationClass) then
        OperationOptionsUIClass := OperationClass.GetOptionsUIClass;

      CopyDialog := TfrmCopyDlg.Create(Self, cmdtCopy, FileSource, OperationOptionsUIClass);
      CopyDialog.edtDst.Text := sDestination;
      CopyDialog.edtDst.ReadOnly := OperationTemp;
      CopyDialog.lblCopySrc.Caption := GetFileDlgStr(rsMsgCpSel, rsMsgCpFlDr, SourceFiles);

      if OperationTemp and (QueueIdentifier = ModalQueueId) then
      begin
        CopyDialog.QueueIdentifier:= QueueIdentifier;
        CopyDialog.btnAddToQueue.Visible:= False;
        CopyDialog.btnCreateSpecialQueue.Visible:= False;
        CopyDialog.btnOptions.Visible:= False;
      end;

      while True do
      begin
        if CopyDialog.ShowModal = mrCancel then
          Exit;

        sDestination := CopyDialog.edtDst.Text;

        if SourceFileSource.IsClass(TArchiveFileSource) then
          BaseDir := ExtractFilePath(SourceFileSource.CurrentAddress)
        else begin
          BaseDir := SourceFiles.Path;
        end;

        GetDestinationPathAndMask(SourceFiles, SourceFileSource,
                                  TargetFileSource, sDestination,
                                  BaseDir, TargetPath, sDstMaskTemp);

        if (TargetFileSource = nil) or (Length(TargetPath) = 0) then
        begin
          MessageDlg(rsMsgInvalidPath, rsMsgErrNotSupported, mtWarning, [mbOK], 0);
          Continue;
        end;

        if HasPathInvalidCharacters(TargetPath) then
          MessageDlg(rsMsgInvalidPath, Format(rsMsgInvalidPathLong, [TargetPath]),
            mtWarning, [mbOK], 0)
        else
          Break;
      end;

      QueueIdentifier := CopyDialog.QueueIdentifier;
    end
    else
      GetDestinationPathAndMask(SourceFiles, TargetFileSource, sDestination,
                                SourceFiles.Path, TargetPath, sDstMaskTemp);

    // Copy via temp directory
    if OperationTemp then
    begin
      // Execute both operations in one new queue
      if QueueIdentifier = FreeOperationsQueueId then
        QueueIdentifier := OperationsManager.GetNewQueueIdentifier;
      // Save real target
      sDestination := TargetPath;
      FileSource := TargetFileSource;
      TargetFiles := SourceFiles.Clone;
      // Replace target by temp directory
      TargetFileSource := TTempFileSystemFileSource.Create();
      TargetPath := TargetFileSource.GetRootDir;
      ChangeFileListRoot(TargetPath, TargetFiles);
    end;

    case OperationType of
      fsoCopy:
        begin
          // Copy within the same file source.
          Operation := SourceFileSource.CreateCopyOperation(
                         SourceFiles,
                         TargetPath) as TFileSourceCopyOperation;
        end;
      fsoCopyOut:
        // CopyOut to filesystem.
        Operation := SourceFileSource.CreateCopyOutOperation(
                       TargetFileSource,
                       SourceFiles,
                       TargetPath) as TFileSourceCopyOperation;
      fsoCopyIn:
        // CopyIn from filesystem.
        Operation := TargetFileSource.CreateCopyInOperation(
                       SourceFileSource,
                       SourceFiles,
                       TargetPath) as TFileSourceCopyOperation;
    end;

    if Assigned(Operation) then
    begin
      // Set operation options based on settings in dialog.
      Operation.RenameMask := sDstMaskTemp;

      if Assigned(CopyDialog) then
        CopyDialog.SetOperationOptions(Operation);

      if OperationTemp and (QueueIdentifier = ModalQueueId) then
      begin
        Operation.AddStateChangedListener([fsosStopped], @OnCopyOutTempStateChanged);
      end;

      // Start operation.
      OperationsManager.AddOperation(Operation, QueueIdentifier, False, True);
      Result := True;
    end
    else
      msgWarning(rsMsgNotImplemented);

    // Copy via temp directory
    if OperationTemp and Result and ((QueueIdentifier <> ModalQueueId) or FModalOperationResult) then
    begin
      // CopyIn from temp filesystem
      Operation := FileSource.CreateCopyInOperation(
                     TargetFileSource,
                     TargetFiles,
                     sDestination) as TFileSourceCopyOperation;

      Result := Assigned(Operation);
      if Result then
      begin
        if Assigned(CopyDialog) then
          CopyDialog.SetOperationOptions(Operation);

        // Start operation.
        OperationsManager.AddOperation(Operation, QueueIdentifier, False, True);
      end;
    end;

  finally
    FreeAndNil(TargetFiles);
    FreeAndNil(SourceFiles);
    FreeAndNil(CopyDialog);
  end;
end;

function TfrmMain.MoveFiles(SourceFileSource, TargetFileSource: IFileSource;
                            var SourceFiles: TFiles; TargetPath: String;
                            bShowDialog: Boolean;
                            QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean;
var
  sDestination: String;
  sDstMaskTemp: String;
  Operation: TFileSourceMoveOperation;
  bMove: Boolean;
  MoveDialog: TfrmCopyDlg = nil;
begin
  Result := False;
  try
    // Special case for Search Result File Source
    if SourceFileSource.IsClass(TSearchResultFileSource) then begin
      SourceFileSource:= ISearchResultFileSource(SourceFileSource).FileSource;
    end;
    // Only allow moving within the same file source.
    if (SourceFileSource.IsInterface(TargetFileSource) or
        TargetFileSource.IsInterface(SourceFileSource)) and
       (SourceFileSource.CurrentAddress = TargetFileSource.CurrentAddress) and
       (fsoMove in SourceFileSource.GetOperationsTypes) and
       (fsoMove in TargetFileSource.GetOperationsTypes) then
    begin
      bMove := True;
    end
    else if ((fsoCopyOut in SourceFileSource.GetOperationsTypes) and
             (fsoCopyIn in TargetFileSource.GetOperationsTypes)) then
    begin
      bMove := False;  // copy + delete through temporary file system
      msgWarning(rsMsgNotImplemented);
      Exit;
    end
    else
    begin
      msgWarning(rsMsgErrNotSupported);
      Exit;
    end;

    if SourceFiles.Count = 0 then
      Exit;

    if (SourceFiles.Count = 1) and
       (not (SourceFiles[0].IsDirectory or SourceFiles[0].IsLinkToDirectory))
    then
      sDestination := TargetPath + ExtractFileName(SourceFiles[0].Name)
    else
      sDestination := TargetPath + '*.*';

    if bShowDialog then
    begin
      MoveDialog := TfrmCopyDlg.Create(Self, cmdtMove, SourceFileSource,
        SourceFileSource.GetOperationClass(fsoMove).GetOptionsUIClass);
      MoveDialog.edtDst.Text := sDestination;
      MoveDialog.lblCopySrc.Caption := GetFileDlgStr(rsMsgRenSel, rsMsgRenFlDr, SourceFiles);

      while True do
      begin
        if MoveDialog.ShowModal = mrCancel then
          Exit;

        sDestination := MoveDialog.edtDst.Text;

        GetDestinationPathAndMask(SourceFiles, SourceFileSource,
                                  TargetFileSource, sDestination,
                                  SourceFiles.Path, TargetPath, sDstMaskTemp);

        if (TargetFileSource = nil) or (Length(TargetPath) = 0) then
        begin
          MessageDlg(EmptyStr, rsMsgInvalidPath, mtWarning, [mbOK], 0);
          Continue;
        end;

        if HasPathInvalidCharacters(TargetPath) then
          MessageDlg(rsMsgInvalidPath, Format(rsMsgInvalidPathLong, [TargetPath]),
            mtWarning, [mbOK], 0)
        else
          Break;
      end;

      QueueIdentifier := MoveDialog.QueueIdentifier;
    end
    else
      GetDestinationPathAndMask(SourceFiles, TargetFileSource, sDestination,
                                SourceFiles.Path, TargetPath, sDstMaskTemp);

    if bMove then
    begin
      Operation := SourceFileSource.CreateMoveOperation(
                     SourceFiles, TargetPath) as TFileSourceMoveOperation;

      if Assigned(Operation) then
      begin
        // Set operation options based on settings in dialog.
        Operation.RenameMask := sDstMaskTemp;

        if Assigned(MoveDialog) then
          MoveDialog.SetOperationOptions(Operation);

        // Start operation.
        OperationsManager.AddOperation(Operation, QueueIdentifier, False, True);
        Result := True;
      end
      else
        msgWarning(rsMsgNotImplemented);
    end
    else
    begin
      // Use CopyOut, CopyIn operations.
    end;

  finally
    FreeAndNil(SourceFiles);
    FreeAndNil(MoveDialog);
  end;
end;

function TfrmMain.CopyFiles(sDestPath: String; bShowDialog: Boolean;
                            QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean;
var
  FileSource: IFileSource;
  SourceFiles: TFiles = nil;
begin
  SourceFiles := ActiveFrame.CloneSelectedOrActiveFiles;
  if Assigned(SourceFiles) then
  begin
    if Length(sDestPath) > 0 then
      FileSource := NotActiveFrame.FileSource
    else begin
      FileSource := ActiveFrame.FileSource;
    end;
    try
      Result := CopyFiles(ActiveFrame.FileSource, FileSource,
                          SourceFiles, sDestPath, bShowDialog, QueueIdentifier);
      if Result then
        ActiveFrame.MarkFiles(False);

    finally
      FreeAndNil(SourceFiles);
    end;
  end
  else
    Result := False;
end;

function TfrmMain.MoveFiles(sDestPath: String; bShowDialog: Boolean;
                            QueueIdentifier: TOperationsManagerQueueIdentifier = FreeOperationsQueueId): Boolean;
var
  SourceFiles: TFiles = nil;
begin
  SourceFiles := ActiveFrame.CloneSelectedOrActiveFiles;
  if Assigned(SourceFiles) then
  begin
    try
      Result := MoveFiles(ActiveFrame.FileSource, NotActiveFrame.FileSource,
                          SourceFiles, sDestPath, bShowDialog, QueueIdentifier);
      if Result then
        ActiveFrame.MarkFiles(False);

    finally
      FreeAndNil(SourceFiles);
    end;
  end
  else
    Result := False;
end;

procedure TfrmMain.GetDestinationPathAndMask(SourceFiles: TFiles;
                                             TargetFileSource: IFileSource;
                                             EnteredPath: String; BaseDir: String;
                                             out DestPath, DestMask: String);
var
  AbsolutePath: String;
begin
  if TargetFileSource.GetPathType(EnteredPath) = ptAbsolute then
    AbsolutePath := EnteredPath
  else
  begin
    // This only work for filesystem for now.
    if TargetFileSource.IsClass(TFileSystemFileSource) then
      AbsolutePath := BaseDir + EnteredPath
    else
      AbsolutePath := PathDelim{TargetFileSource.GetRoot} + EnteredPath;
  end;

  AbsolutePath := NormalizePathDelimiters(AbsolutePath);  // normalize path delimiters
  AbsolutePath := ExpandAbsolutePath(AbsolutePath);

  if Length(AbsolutePath) = 0 then Exit;

  // If the entered path ends with a path delimiter
  // treat it as a path to a not yet existing directory
  // which should be created.
  if (AbsolutePath[Length(AbsolutePath)] = PathDelim) or
     ((TargetFileSource.IsClass(TFileSystemFileSource)) and
      mbDirectoryExists(AbsolutePath)) then
  begin
    // Destination is a directory.
    DestPath := AbsolutePath;
    DestMask := '*.*';
  end
  else
  begin
    // Destination is a file name or mask.
    DestPath := ExtractFilePath(AbsolutePath);
    DestMask := ExtractFileName(AbsolutePath);

    if (SourceFiles.Count > 1) and not ContainsWildcards(DestMask) then
    begin
      // Assume it is a path to a directory because cannot put multiple
      // files/directories into one file.
      DestPath := AbsolutePath;
      DestMask := '*.*';
    end
    // For convenience, treat '*' as "whole file name".
    // To remove extension '*.' can be used.
    else if DestMask = '*' then
      DestMask := '*.*';
  end;
end;

procedure TfrmMain.GetDestinationPathAndMask(SourceFiles: TFiles;
  SourceFileSource: IFileSource; var TargetFileSource: IFileSource;
  EnteredPath: String; BaseDir: String; out DestPath, DestMask: String);
var
  FileSourceIndex, PathIndex: Integer;
begin
  // If it is a file source root and we trying to copy/move to parent directory
  if StrBegins(EnteredPath, '..') and SourceFileSource.IsPathAtRoot(SourceFiles.Path) then
  begin
    // Change to previous file source and last path.
    FileSourceIndex := ActiveFrame.CurrentFileSourceIndex - 1;
    if FileSourceIndex < 0 then
      TargetFileSource := nil // No parent file sources.
    else
    begin
      PathIndex := ActiveFrame.PathsCount[FileSourceIndex] - 1;
      if PathIndex < 0 then
        TargetFileSource := nil // No paths.
      else
      begin
        TargetFileSource := ActiveFrame.FileSources[FileSourceIndex];
        // Determine destination type
        if (Length(EnteredPath) = 2) or (EnteredPath[Length(EnteredPath)] = PathDelim) then
          EnteredPath:= EmptyStr // Destination is a directory
        else
          EnteredPath:= ExtractFileName(EnteredPath); // Destination is a file name or mask
        // Combine destination path
        EnteredPath := ActiveFrame.Path[FileSourceIndex, PathIndex] + EnteredPath;
      end;
    end;
  end;
  // Target file source is valid
  if Assigned(TargetFileSource) then
  begin
    GetDestinationPathAndMask(SourceFiles, TargetFileSource,
                              EnteredPath, BaseDir, DestPath, DestMask);
  end;
end;

procedure TfrmMain.SetDragCursor(Shift: TShiftState);
begin
  FrameLeft.SetDragCursor(Shift);
  FrameRight.SetDragCursor(Shift);
end;

procedure TfrmMain.CreateWnd;
begin
  // Must be before CreateWnd
  LoadWindowState;

  inherited CreateWnd;

  // Save real main form handle
  Application.MainForm.Tag:= Handle;
end;

procedure TfrmMain.DoFirstShow;
var
  ANode: TXmlNode;
begin
  inherited DoFirstShow;

  // Load window state
  ANode := gConfig.FindNode(gConfig.RootNode, 'MainWindow/Position', True);

  if gConfig.GetValue(ANode, 'Maximized', True) then
    Self.WindowState := wsMaximized;

  lastWindowState := WindowState;
end;

procedure TfrmMain.WMMove(var Message: TLMMove);
begin
  inherited WMMove(Message);

  if not (csDestroying in ComponentState) then
  begin
    FDelayedWMMove := True;
    Inc(FDelayedEventCtr);
    Application.QueueAsyncCall(@DelayedEvent, 0);
  end;
end;

procedure TfrmMain.WMSize(var message: TLMSize);
begin
  // https://github.com/doublecmd/doublecmd/issues/736
  if (Message.Width > High(Int16)) or (Message.Height > High(Int16)) then
  begin
    DCDebug('TfrmMain.WMSize invalid size %u x %u', [Message.Width, Message.Height]);
    Exit;
  end;

  inherited WMSize(Message);

  if not (csDestroying in ComponentState) then
  begin
    FDelayedWMSize := True;
    Inc(FDelayedEventCtr);
    Application.QueueAsyncCall(@DelayedEvent, 0);
  end;
end;

procedure TfrmMain.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
  const AXProportion, AYProportion: Double);
begin
  if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
  begin
    DisableAutoSizing;
    try
      // ScaleFontsPPI(AYProportion);
      BorderSpacing.AutoAdjustLayout(AXProportion, AYProportion);
      Constraints.AutoAdjustLayout(AXProportion, AYProportion);
    finally
      EnableAutoSizing;
    end;
  end;
end;

procedure TfrmMain.FormKeyUp( Sender: TObject; var Key: Word;
  Shift: TShiftState) ;
begin
  SetDragCursor(Shift);
end;

procedure TfrmMain.FormResize(Sender: TObject);
begin
  UpdatePrompt;
end;

procedure TfrmMain.lblDriveInfoResize(Sender: TObject);
begin
  with TLabel(Sender) do
  begin
    if Canvas.TextWidth(Caption) > Width then
      Alignment:= taLeftJustify
    else begin
      Alignment:= taCenter;
    end;
  end;
end;

procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  ShiftEx : TShiftState;
  CmdText : String;
begin
  SetDragCursor(Shift);

  // Either left or right panel has to be focused.
  if not FrameLeft.Focused and
     not FrameRight.Focused then
  begin
    Exit;
  end;

  ShiftEx := GetKeyShiftStateEx;

  case Key of
    VK_BACK:
      if IsCommandLineVisible and
         (GetKeyTypingAction(ShiftEx) = ktaCommandLine) and (edtCommand.Text <> '') then
      begin
        // Delete last character.
        CmdText := edtCommand.Text;
        UTF8Delete(CmdText, UTF8Length(CmdText), 1);
        edtCommand.Text := CmdText;

        edtCommand.SetFocus;
        edtCommand.SelStart := UTF8Length(edtCommand.Text) + 1;
        Key := 0;
      end;

    VK_ESCAPE:
      if IsCommandLineVisible and
         (GetKeyTypingAction(ShiftEx) = ktaCommandLine) and
         (edtCommand.Text <> '') then
      begin
        edtCommand.Text := '';
        Key := 0;
      end;

    VK_RETURN, VK_SELECT:
      if IsCommandLineVisible and
         (GetKeyTypingAction(ShiftEx) = ktaCommandLine) and
         (edtCommand.Text <> '') then
      begin
        // execute command line (in terminal with Shift)
        ExecuteCommandLine(Shift = [ssShift]);
        Key := 0;
      end;

    VK_SPACE:
      if (GetKeyTypingAction(ShiftEx) = ktaCommandLine) and (edtCommand.Text <> '') then
      begin
        TypeInCommandLine(' ');
        Key := 0;
      end;

    VK_TAB:
      begin
        if (QuickViewPanel = nil) then
        begin
          // Select opposite panel.
          case PanelSelected of
            fpLeft: SetActiveFrame(fpRight);
            fpRight: SetActiveFrame(fpLeft);
            else     SetActiveFrame(fpLeft);
          end;
        end;
        Key := 0;
      end;
  end;

  CheckCommandLine(ShiftEx, Key);
end;

procedure TfrmMain.pmToolBarPopup(Sender: TObject);
var
  sText: String;
  sDir: String;
  bPaste: Boolean;
  ToolItem: TKASToolItem;
  Button: TKASToolButton;
begin
  Button := TKASToolButton(pmToolBar.Tag);
  tbSeparator.Visible:= Assigned(Button);
  tbCut.Visible:= Assigned(Button);
  tbCopy.Visible:= Assigned(Button);
  tbChangeDir.Visible:= False;
  tbDelete.Visible:= Assigned(Button);
  if Assigned(Button) then
  begin
    ToolItem := Button.ToolItem;
    if ToolItem is TKASProgramItem then
    begin
      sDir := TKASProgramItem(ToolItem).StartPath;
      sDir:= PrepareParameter(sDir, nil, [ppoNormalizePathDelims, ppoReplaceTilde]);
      tbChangeDir.Caption := 'CD ' + sDir;
      tbChangeDir.Visible := True;
    end;
  end;

  sText:= Clipboard.AsText;
  bPaste:= StrBegins(sText, DCToolItemClipboardHeader) or
           StrBegins(sText, DCToolbarClipboardHeader) or
           StrBegins(sText, TCToolbarClipboardHeader);
  if bPaste then
    tbSeparator.Visible:= True;
  tbPaste.Visible:= bPaste;
end;

procedure TfrmMain.ShellTreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView;
  Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
  var PaintImages, DefaultDraw: Boolean);
begin
  Node.ImageIndex:= 0;
  Node.SelectedIndex:= 0;
end;

procedure TfrmMain.pnlLeftResize(Sender: TObject);
begin
  if gDriveBar1 and gDriveBar2 and not gHorizontalFilePanels then
  begin
    pnlDskLeft.Constraints.MinWidth:= pnlLeft.Width;
    pnlDskLeft.Constraints.MaxWidth:= pnlLeft.Width;
  end;

  // Put splitter after left panel.
  if not gHorizontalFilePanels then
  begin
    MainSplitter.Left   := pnlLeft.Width;
    MainSplitter.Top    := pnlLeft.Top;
    MainSplitter.Height := pnlLeft.Height;
    MainSplitter.Width  := 3;
  end
  else
  begin
    MainSplitter.Top    := pnlLeft.Height;
    MainSplitter.Left   := pnlLeft.Left;
    MainSplitter.Width  := pnlLeft.Width;
    MainSplitter.Height := 3;
  end;
end;

procedure TfrmMain.pnlLeftRightDblClick(Sender: TObject);
var
  APanel: TPanel;
  APoint: TPoint;
  FileViewNotebook: TFileViewNotebook;
begin
  if Sender is TPanel then
  begin
    APanel := Sender as TPanel;
    if APanel = pnlLeft then
      begin
        APoint := FrameLeft.ClientToScreen(Classes.Point(0, FrameLeft.Top));
        if Mouse.CursorPos.Y < APoint.Y then Commands.DoNewTab(nbLeft);
      end
    else if APanel = pnlRight then
      begin
        APoint := FrameRight.ClientToScreen(Classes.Point(0, FrameRight.Top));
        if Mouse.CursorPos.Y < APoint.Y then Commands.DoNewTab(nbRight);
      end;
  end;
  if Sender is TFileViewNotebook then
  begin
    FileViewNotebook:= Sender as TFileViewNotebook;
    if FileViewNotebook.DoubleClickPageIndex < 0 then
      Commands.DoNewTab(FileViewNotebook)
    else
    begin
      case gDirTabActionOnDoubleClick of
        tadc_Nothing: begin end;
        tadc_CloseTab: Commands.DoCloseTab(FileViewNotebook, FileViewNotebook.DoubleClickPageIndex);
        tadc_FavoriteTabs: Commands.cm_LoadFavoriteTabs(['position=cursor']);
        tadc_TabsPopup:
          begin
            if FileViewNotebook.DoubleClickPageIndex<>-1 then
            begin
              // Check tab options items.
              case FileViewNotebook.Page[FileViewNotebook.DoubleClickPageIndex].LockState of
                tlsNormal: miTabOptionNormal.Checked := True;
                tlsPathLocked: miTabOptionPathLocked.Checked := True;
                tlsDirsInNewTab: miTabOptionDirsInNewTab.Checked := True;
                tlsPathResets: miTabOptionPathResets.Checked := True;
              end;
              pmTabMenu.Parent := FileViewNotebook;
              pmTabMenu.Tag := FileViewNotebook.DoubleClickPageIndex;
              pmTabMenu.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y);
            end;
          end;
      end;
    end;
  end;
end;

procedure TfrmMain.pnlNotebooksResize(Sender: TObject);
var
  Delta: Integer;
begin
  if not FResizingFilePanels then
  begin
    FResizingFilePanels := True;
    if not gHorizontalFilePanels then
    begin
      pnlLeft.BorderSpacing.Bottom:= 0;
      Delta:= IfThen(MiddleToolBar.Visible, MiddleToolBar.Width);
      pnlLeft.BorderSpacing.Right:= 4 - (pnlNotebooks.Width - Delta) mod 2;
      pnlLeft.Width := Round(Double(pnlNotebooks.Width - pnlLeft.BorderSpacing.Right - Delta) * FMainSplitterPos / 100.0);
    end
    else begin
      pnlLeft.BorderSpacing.Right:= 0;
      Delta:= IfThen(MiddleToolBar.Visible, MiddleToolBar.Height);
      pnlLeft.BorderSpacing.Bottom:= 4 - (pnlNotebooks.Height - Delta) mod 2;
      pnlLeft.Height := Round(Double(pnlNotebooks.Height - pnlLeft.BorderSpacing.Bottom - Delta) * FMainSplitterPos / 100.0);
    end;
    FResizingFilePanels := False;
  end;
end;

procedure TfrmMain.pnlRightResize(Sender: TObject);
var
  AWidth: Integer;
begin
  if gDriveBar1 and not gHorizontalFilePanels then
  begin
    if gDriveBar2 then
      AWidth := pnlRight.Width + 1
    else begin
      AWidth := pnlNotebooks.Width - 2;
    end;
    if AWidth < 0 then AWidth := 0;
    pnlDskRight.Constraints.MinWidth := AWidth;
    pnlDskRight.Constraints.MaxWidth := AWidth;
  end
  else if gHorizontalFilePanels and not gDriveBar2 then
  begin
    AWidth := Max(0, pnlNotebooks.Width - 2);
    pnlDskRight.Constraints.MinWidth := AWidth;
    pnlDskRight.Constraints.MaxWidth := AWidth;
  end;
end;

procedure TfrmMain.sboxDrivePaint(Sender: TObject);
begin
  with gColors.FreeSpaceInd^ do
    PaintDriveFreeBar(Sender, gIndUseGradient, ForeColor, ThresholdForeColor, BackColor);
end;

procedure TfrmMain.PaintDriveFreeBar(Sender: TObject; const bIndUseGradient: boolean;
  const pIndForeColor, pIndThresholdForeColor, pIndBackColor: TColor);
const OccupiedThresholdPercent = 90;
var
  pbxDrive: TPaintBox absolute Sender;
  FillPercentage: PtrInt;
  i: Integer;
  AColor, AColor2: TColor;
  ARect: TRect;
begin
  FillPercentage:= pbxDrive.Tag;
  if FillPercentage <> -1 then
  begin
    pbxDrive.Canvas.Brush.Color:= clBlack;
    pbxDrive.Canvas.FrameRect(0, 0, pbxDrive.Width - 1, pbxDrive.Height - 1);

    ARect.Top    := 1;
    ARect.Bottom := pbxDrive.Height - 2;

    if not bIndUseGradient then
      begin
        ARect.Left  := 1;
        ARect.Right := 1 + FillPercentage * (pbxDrive.Width - 2) div 100;
        if FillPercentage <= OccupiedThresholdPercent then
          AColor := pIndForeColor
        else
          AColor := pIndThresholdForeColor;
        pbxDrive.Canvas.GradientFill(ARect, LightColor(AColor, 25), DarkColor(AColor, 25), gdVertical);
        ARect.Left  := ARect.Right + 1;
        ARect.Right := pbxDrive.Width - 2;
        AColor := pIndBackColor;
        pbxDrive.Canvas.GradientFill(ARect, DarkColor(AColor, 25), LightColor(AColor, 25), gdVertical);
      end
    else
      begin
        ARect.Right := 1;
        for i := 0 to FillPercentage - 1 do
        begin
          if i <= OccupiedThresholdPercent then
            AColor:= RGB((i * 255) div OccupiedThresholdPercent, 255, 0)
          else
            AColor:= RGB(255, ((100 - i) * 255) div (100 - OccupiedThresholdPercent), 0);
          AColor2:= DarkColor(AColor, 50);

          ARect.Left  := ARect.Right;
          ARect.Right := 1 + (i + 1) * (pbxDrive.Width - 2) div 100;

          pbxDrive.Canvas.GradientFill(ARect, AColor, AColor2, gdVertical);
        end;
        ARect.Left  := ARect.Right;
        ARect.Right := pbxDrive.Width - 2;
        pbxDrive.Canvas.GradientFill(ARect, clSilver, clWhite, gdVertical);
      end;
  end;
end;

procedure TfrmMain.seLogWindowSpecialLineColors(Sender: TObject; Line: integer;
  var Special: boolean; var FG, BG: TColor);
var
  LogMsgTypeObject: TObject;
  LogMsgType : TLogMsgType absolute LogMsgTypeObject;
begin
  LogMsgTypeObject := seLogWindow.Lines.Objects[Line-1];
  Special := True;
  with gColors.Log^ do
  begin
    case LogMsgType of
    lmtInfo:
      FG := InfoColor;
    lmtSuccess:
      FG := SuccessColor;
    lmtError:
      FG := ErrorColor
    else
      FG := clWindowText;
    end;
  end;
end;

procedure TfrmMain.FileViewFreeAsync(Data: PtrInt);
var
  FileView: TFileView absolute Data;
begin
  FileView.Free;
end;

function TfrmMain.FileViewAutoSwitch(FileSource: IFileSource; var FileView: TFileView;
  Reason: TChangePathReason; const NewPath: String): Boolean;
var
  AName: String;
  Index: Integer;
  AWidth: Integer;
  Percent: Double;
  Page: TFileViewPage;
  AClientWidth: Integer;
  RestoreFocus: Boolean;
  ColSe: TPanelColumnsClass;
  DefaultView: TFileSourceFields;
begin
  Result:= True;

  if FileSource.Equals(FileView.FileSource) then
    Exit;

  Page:= TFileViewPage(FileView.NotebookPage);

  if Page.Tag > 0 then Exit;

  Page.Tag:= MaxInt;
  try
    RestoreFocus:= (ActiveFrame = FileView);
    if (fspDefaultView in FileSource.Properties) then
    begin
      AName:= '<' + FileSource.FileSystem + '>';
      ColSe:= ColSet.GetColumnSet(AName, FileSource.FileSystem);
      if (ColSe = nil) then
      begin
        if FileSource.GetDefaultView(DefaultView) then
        begin
          AWidth:= 0;
          for Index:= 0 to High(DefaultView) do
          begin
            AWidth += DefaultView[Index].Width;
          end;
          AClientWidth:= FileView.ClientWidth;
          // Scale columns width
          if AWidth < AClientWidth then
          begin
            for Index:= 0 to High(DefaultView) do
            begin
              Percent:= DefaultView[Index].Width / AWidth;
              DefaultView[Index].Width:= Floor(AClientWidth * Percent);
            end;
          end;
          ColSe:= TPanelColumnsClass.Create;
          for Index:= 0 to High(DefaultView) do
          begin
            with DefaultView[Index] do
              ColSe.Add(Header, Content, Width, Align);
          end;
          ColSe.FileSystem:= FileSource.FileSystem;
          ColSe.Name:= AName;
          ColSet.Add(ColSe);
        end;
      end;
      if Assigned(ColSe) then
      begin
        // Save current file view type
        Page.BackupViewClass := TFileViewClass(FileView.ClassType);

        if (FileView is TColumnsFileView) then
        begin
          // Save current columns set name
          Page.BackupColumnSet:= TColumnsFileView(FileView).ActiveColm;
          TColumnsFileView(Page.FileView).SetColumnSet(ColSe.Name);
        end
        else begin
          Result:= False;
          Page.RemoveComponent(FileView);
          Application.QueueAsyncCall(@FileViewFreeAsync, PtrInt(FileView));
          FileView:= TColumnsFileView.Create(Page, FileView, ColSe.Name);
          if Assigned(Page.OnChangeFileView) then Page.OnChangeFileView(FileView);
          if RestoreFocus then Page.FileView.SetFocus;
        end;
        Page.BackupViewMode:= FileSource.FileSystem;
      end;
    end
    else if (Length(Page.BackupViewMode) > 0) then
    begin
      Page.BackupViewMode:= EmptyStr;
      // Restore previous file view type
      if (FileView is Page.BackupViewClass) then
      begin
        if (FileView is TColumnsFileView) then
          TColumnsFileView(FileView).SetColumnSet(Page.BackupColumnSet)
      end
      else begin
        Result:= False;
        Page.RemoveComponent(FileView);
        Application.QueueAsyncCall(@FileViewFreeAsync, PtrInt(FileView));
        if Page.BackupViewClass <> TColumnsFileView then
          FileView:= Page.BackupViewClass.Create(Page, FileView)
        else begin
          FileView:= TColumnsFileView.Create(Page, FileView, Page.BackupColumnSet);
        end;
        if Assigned(Page.OnChangeFileView) then Page.OnChangeFileView(FileView);
      end;
      if RestoreFocus then Page.FileView.SetFocus;
    end;
    if not Result then
    begin
      case Reason of
      cprAdd:
        FileView.AddFileSource(FileSource, NewPath);
      cprRemove:
        FileView.RemoveCurrentFileSource;
      end;
    end;
  finally
    Page.Tag:= 0;
  end;
end;

function TfrmMain.FileViewBeforeChangePath(FileView: TFileView;
  NewFileSource: IFileSource; Reason: TChangePathReason; const NewPath: String
  ): Boolean;
var
  i: Integer;
  AFileSource: IFileSource;
  ANoteBook: TFileViewNotebook;
  Page, NewPage: TFileViewPage;
  PageAlreadyExists: Boolean = False;
  tlsLockStateToEvaluate: TTabLockState;
begin
  Result:= True;
  if FileView.NotebookPage is TFileViewPage then
  begin
    Page := FileView.NotebookPage as TFileViewPage;

    tlsLockStateToEvaluate:=Page.LockState;
    if tlsLockStateToEvaluate=tlsPathLocked then
      if MsgBox(Format(rsMsgTabForOpeningInNewTab,[Page.Caption]), [msmbYes, msmbCancel], msmbCancel, msmbCancel) = mmrYes then
        tlsLockStateToEvaluate:=tlsDirsInNewTab;

    case tlsLockStateToEvaluate of
      tlsPathLocked:
        Result := False;  // do not change directory in this tab

      tlsDirsInNewTab:
        begin
          Result := False;  // do not change directory in this tab

          if Assigned(NewFileSource) then
          begin
            ANoteBook := Page.Notebook;

            if tb_reusing_tab_when_possible in gDirTabOptions then
            begin
              for i := 0 to ANotebook.PageCount - 1 do
              begin
                NewPage := ANotebook.Page[i];
                PageAlreadyExists := Assigned(NewPage.FileView) and
                  mbCompareFileNames(NewPage.FileView.CurrentAddress, NewFileSource.CurrentAddress) and
                  mbCompareFileNames(NewPage.FileView.CurrentPath, NewPath);
                if PageAlreadyExists then
                  Break;
              end;
            end;

            if not PageAlreadyExists then
            begin
              // Open in a new page, cloned view.
              NewPage := ANotebook.NewPage(Page.FileView);
              NewPage.FileView.AddFileSource(NewFileSource, NewPath);
            end;
            NewPage.MakeActive;
          end;
        end;
    end;

    if Result and Assigned(NewFileSource) then
    begin
      if not FileViewAutoSwitch(NewFileSource, FileView, Reason, NewPath) then
        Exit(False);
    end;

    if actSyncChangeDir.Checked and (FileView = NotActiveFrame) then
    begin
      if not Result then
        actSyncChangeDir.Checked:= False
      else begin
        if Assigned(NewFileSource) and not NewFileSource.SetCurrentWorkingDirectory(NewPath) then
        begin
          actSyncChangeDir.Checked:= False;
          Exit(False);
        end
        else if not FSyncChangeParent then
        begin
          if Assigned(NewFileSource) then
            AFileSource:= NewFileSource
          else begin
            AFileSource:= FileView.FileSource;
          end;
          if not AFileSource.FileSystemEntryExists(ExcludeTrailingBackslash(NewPath)) then
          begin
            actSyncChangeDir.Checked:= False;
            Exit(False);
          end;
        end
      end;
    end;
  end;
end;

procedure TfrmMain.FileViewAfterChangePath(FileView: TFileView);
var
  S: String;
  Index: Integer;
  Page: TFileViewPage;
  ANoteBook : TFileViewNotebook;
begin
  if FileView.NotebookPage is TFileViewPage then
    begin
      Page := FileView.NotebookPage as TFileViewPage;
      ANoteBook := Page.Notebook;

      if Page.IsActive then
      begin
        if Assigned(FileView.FileSource) then
        begin
          if FileView.FileSource.IsClass(TFileSystemFileSource) then
          begin
            // Store only first 255 items
            if glsDirHistory.Count > $FF then begin
              glsDirHistory.Delete(glsDirHistory.Count - 1);
            end;
            Index:= glsDirHistory.IndexOf(FileView.CurrentPath);
            if Index = -1 then
              glsDirHistory.Insert(0, FileView.CurrentPath)
            else begin
              glsDirHistory.Move(Index, 0);
            end;
            UpdateTreeViewPath;
            UpdateMainTitleBar;
          end;

          if actSyncChangeDir.Checked and (FileView = ActiveFrame) then
          begin
            S:= ExcludeTrailingBackslash(FileView.CurrentPath);
            // Go to child directory
            if Length(S) > Length(FSyncChangeDir) then
            begin
              FSyncChangeParent:= False;
              if ExtractFileDir(S) = FSyncChangeDir then
                NotActiveFrame.CurrentPath:= NotActiveFrame.CurrentPath + ExtractFileName(S)
              else
                actSyncChangeDir.Checked:= False;
            end
            // Go to parent directory
            else begin
              FSyncChangeParent:= True;
              if S = ExtractFileDir(FSyncChangeDir) then
                NotActiveFrame.ChangePathToParent(True)
              else
                actSyncChangeDir.Checked:= False;
            end;
            if actSyncChangeDir.Checked then
              FSyncChangeDir:= S
            else begin
              FSyncChangeDir:= EmptyStr;
            end;
          end;

          UpdateSelectedDrive(ANoteBook);
          UpdatePrompt;
        end;
        // Update page hint
        ANoteBook.Hint := FileView.CurrentPath;
      end;

      {if (fspDirectAccess in FileView.FileSource.GetProperties) then
        begin
          if gTermWindow and Assigned(Cons) then
            Cons.Terminal.SetCurrentDir(FileView.CurrentPath);
        end;}
    end;
end;

procedure TfrmMain.FileViewActivate(FileView: TFileView);
var
  Page: TFileViewPage;
begin
  if FileView.NotebookPage is TFileViewPage then
    begin
      Page := FileView.NotebookPage as TFileViewPage;
      SelectedPanel := Page.Notebook.Side;
      UpdateSelectedDrive(Page.Notebook);
      UpdateFreeSpace(Page.Notebook.Side, False);
    end;
  UpdateFileView;
end;

procedure TfrmMain.FileViewFilesChanged(FileView: TFileView);
var
  Page: TFileViewPage;
begin
  if FileView.NotebookPage is TFileViewPage then
    begin
      Page := FileView.NotebookPage as TFileViewPage;

      if Page.IsActive then
      begin
        UpdateFreeSpace(Page.Notebook.Side, False);
      end;
    end;
end;

procedure TfrmMain.SetActiveFrame(panel: TFilePanelSelect);
begin
  SelectedPanel:= panel;
  SetActiveFrame(ActiveFrame);
end;

procedure TfrmMain.SetActiveFrame(FileView: TFileView);
begin
  FileView.SetFocus;
  if (fspDirectAccess in FileView.FileSource.GetProperties) then
  begin
    if gTermWindow and Assigned(Cons) then
      Cons.SetCurrentDir(FileView.CurrentPath);
  end;
end;

procedure TfrmMain.UpdateFileView;
var
  AFileView: TFileView;
begin
  AFileView:= ActiveFrame;
  if AFileView is TColumnsFileView then
    actColumnsView.Checked:= True
  else if AFileView is TBriefFileView then
    actBriefView.Checked:= True
  else if AFileView is TThumbFileView then
    actThumbnailsView.Checked:= True;
end;

procedure TfrmMain.UpdateShellTreeView;
begin
  actTreeView.Checked := gSeparateTree;
  TreeSplitter.Visible := gSeparateTree;
  TreePanel.Visible := gSeparateTree;

  if gSeparateTree and (ShellTreeView = nil) then
  begin
    ShellTreeView := TShellTreeView.Create(TreePanel);
    ShellTreeView.Parent := TreePanel;
    ShellTreeView.Align := alClient;
    ShellTreeView.ScrollBars := ssAutoBoth;

    with ShellTreeView as TShellTreeView do
    begin
      UpdateTreeView;
      ReadOnly := True;
      RightClickSelect := True;
      FileSortType := fstNone;
      PopulateWithBaseFiles;
      CustomSort(@ShellTreeViewSort);

      Images := TImageList.Create(Self);
      Images.Width := gIconsSize;
      Images.Height := gIconsSize;
      Images.Add(PixMapManager.GetFolderIcon(gIconsSize, ShellTreeView.Color), nil);

      OnKeyDown := @ShellTreeViewKeyDown;
      OnMouseUp := @ShellTreeViewMouseUp;
      OnAdvancedCustomDrawItem := @ShellTreeViewAdvancedCustomDrawItem;

      ExpandSignType := tvestPlusMinus;
      Options := Options - [tvoThemedDraw];
      Options := Options + [tvoReadOnly, tvoRightClickSelect];
    end;
  end;

  if gSeparateTree then
  begin
    with gColors.FilePanel^ do
    begin
      ShellTreeView.Font.Color := ForeColor;
      ShellTreeView.BackgroundColor := BackColor;
      ShellTreeView.SelectionColor := CursorColor;
    end;
    FontOptionsToFont(gFonts[dcfMain], ShellTreeView.Font);
  end;
end;

procedure TfrmMain.UpdateTreeViewPath;
begin
  if (ShellTreeView = nil) then Exit;
  if (ShellTreeView.Tag <> 0) then Exit;
  if (fspDirectAccess in ActiveFrame.FileSource.Properties) then
  try
    (ShellTreeView as TShellTreeView).Path := ActiveFrame.CurrentPath;
  except
    // Skip
  end;
end;

procedure TfrmMain.UpdateTreeView;
begin
  if (ShellTreeView = nil) then Exit;
  with (ShellTreeView as TShellTreeView) do
  begin
    if gShowSystemFiles then
      ObjectTypes:= ObjectTypes + [otHidden]
    else begin
      ObjectTypes:= ObjectTypes - [otHidden];
    end;
  end;
end;

function CompareDrives(Item1, Item2: Pointer): Integer;
begin
  Result := CompareText(PDrive(Item1)^.DisplayName, PDrive(Item2)^.DisplayName);
end;

procedure TfrmMain.UpdateDiskCount;
var
  I: Integer;
  Drive: PDrive;
begin
  DrivesList.Free;
  DrivesList := TDriveWatcher.GetDrivesList;
  DrivesList.Sort(@CompareDrives);

  { Delete drives that in drives black list }
  for I:= DrivesList.Count - 1 downto 0 do
  begin
    Drive := DrivesList[I];
    if (gDriveBlackListUnmounted and not Drive^.IsMounted) or
       MatchesMaskList(Drive^.Path, gDriveBlackList) or
       MatchesMaskList(Drive^.DeviceId, gDriveBlackList) then
      DrivesList.Remove(I);
  end;

{$IF DEFINED(MSWINDOWS)}
  if (not (cimDrive in gCustomIcons)) then
  begin
    for I:= DrivesList.Count - 1 downto 0 do
    begin
      Drive := DrivesList[I];
      if Drive^.DriveType = dtNetwork then
      begin
        with TNetworkDriveLoader.Create(Drive, dskRight.GlyphSize, clBtnFace, @OnDriveIconLoaded) do
          Start;
      end;
    end;
  end;
  if (Win32MajorVersion > 5) then
  begin
    TShellFileSource.ListDrives(DrivesList, gUpperCaseDriveLetter);
  end;
{$ENDIF}

  UpdateDriveList(DrivesList);

  // Add virtual drive
  New(Drive);
  FillChar(Drive^, SizeOf(TDrive), 0);
  Drive^.IsMounted:= True;
  Drive^.DriveType:= dtVirtual;
  Drive^.Path:= 'vfs:' + PathDelim;
  Drive^.DisplayName:= PathDelim + PathDelim;
  Drive^.DriveLabel:= StripHotkey(actOpenVirtualFileSystemList.Caption);
  Drive^.FileSystem:= 'VFS';
  DrivesList.Add(Drive);

  // create drives drop down menu
  FDrivesListPopup.UpdateDrivesList(DrivesList);

  // create drives left/right panels
  if gDriveBar1 then
  begin
    CreateDiskPanel(dskRight);
    if gDriveBar2 then
      CreateDiskPanel(dskLeft);
  end;
  dskLeft.AddToolItemExecutor(TKASDriveItem, @LeftDriveBarExecuteDrive);
  dskRight.AddToolItemExecutor(TKASDriveItem, @RightDriveBarExecuteDrive);

  if gSeparateTree and Assigned(ShellTreeView) then
  begin
    TShellTreeView(ShellTreeView).PopulateWithBaseFiles;
    UpdateTreeViewPath;
  end;
end;

procedure TfrmMain.AddSpecialButtons(dskPanel: TKASToolBar);
  procedure AddItem(FromButton: TSpeedButton);
  var
    Button: TKASToolButton;
    ToolItem: TKASNormalItem;
  begin
    ToolItem := TKASNormalItem.Create;
    ToolItem.Text := FromButton.Caption;
    ToolItem.Hint := FromButton.Hint;
    Button := dskPanel.AddButton(ToolItem);
    Button.GroupIndex := 0;
  end;
begin
  AddItem(btnLeftRoot);
  AddItem(btnLeftUp);
  AddItem(btnLeftHome);
end;

procedure TfrmMain.CreateDiskPanel(dskPanel: TKASToolBar);
var
  I, Count: Integer;
  Drive : PDrive;
  BitmapTmp: Graphics.TBitmap;
  ToolItem: TKASDriveItem;
  Button: TKASToolButton;
begin
  dskPanel.BeginUpdate;
  try
    dskPanel.Clear;
    dskPanel.Flat := gDriveBarFlat;
    Count := DrivesList.Count - 1;

    for I := 0 to Count do
    begin
      Drive := DrivesList.Items[I];

      ToolItem := TKASDriveItem.Create;
      ToolItem.Drive := Drive;
      ToolItem.Text := Drive^.DisplayName;
      ToolItem.Hint := GetDriveLabelOrStatus(Drive);
      Button := dskPanel.AddButton(ToolItem);

      // Set drive icon.
      BitmapTmp := PixMapManager.GetDriveIcon(Drive, dskPanel.GlyphSize, clBtnFace, False);
      Button.Glyph.Assign(BitmapTmp);
      FreeAndNil(BitmapTmp);

      {Set Buttons Transparent. Is need? }
      Button.Glyph.Transparent := True;
      Button.Transparent := True;
      {/Set Buttons Transparent}
      Button.Layout := blGlyphLeft;
    end; // for

    // Add special buttons
    if not gDrivesListButton then
      AddSpecialButtons(dskPanel);

  finally
    dskPanel.EndUpdate;
  end;
end;

function TfrmMain.CreateFileView(sType: String; Page: TFileViewPage; AConfig: TXmlConfig; ANode: TXmlNode): TFileView;
var
  FileViewFlags: TFileViewFlags = [];
begin
  // This function should be changed to a separate TFileView factory.

  if gDelayLoadingTabs then
    FileViewFlags := [fvfDelayLoadingFiles];
  if sType = 'columns' then
    Result := TColumnsFileView.Create(Page, AConfig, ANode, FileViewFlags)
  else if sType = 'brief' then
    Result := TBriefFileView.Create(Page, AConfig, ANode, FileViewFlags)
  else if sType = 'thumbnails' then
    Result := TThumbFileView.Create(Page, AConfig, ANode, FileViewFlags)
  else begin
    DCDebug(rsMsgLogError + 'Invalid file view type "%s"', [sType]);
    Result := TColumnsFileView.Create(Page, AConfig, ANode, FileViewFlags);
  end;
end;

procedure TfrmMain.AssignEvents(AFileView: TFileView);
begin
  with AFileView do
  begin
    OnBeforeChangePath := @FileViewBeforeChangePath;
    OnAfterChangePath := @FileViewAfterChangePath;
    OnActivate := @FileViewActivate;
    OnFileListChanged := @FileViewFilesChanged;
  end;
end;

//                         We ask the closing locked tab confirmation according to...
// ConfirmCloseLocked = 0  ...option "tb_confirm_close_locked_tab".
// ConfirmCloseLocked = 1  ...no matter the option, we ask confirmation
// ConfirmCloseLocked = 2  ...no matter the option, we do not ask confirmation
function TfrmMain.RemovePage(ANoteBook: TFileViewNotebook; iPageIndex:Integer; CloseLocked: Boolean = True; ConfirmCloseLocked: integer = 0; ShowButtonAll: Boolean = False): LongInt;
var
  UserAnswer: TMyMsgResult;
begin
  Result:= -1;
  if (ANoteBook.PageCount > 1) and
     (iPageIndex >= 0) and
     (iPageIndex < ANoteBook.PageCount) then
  begin
    if (ANoteBook.Page[iPageIndex].LockState <> tlsNormal) AND (((ConfirmCloseLocked=0) AND (tb_confirm_close_locked_tab in gDirTabOptions)) OR (ConfirmCloseLocked=1)) then
    begin
       if CloseLocked then
       begin
         if ShowButtonAll then
           UserAnswer := MsgBox(Format(rsMsgCloseLockedTab, [ANoteBook.Page[iPageIndex].Caption]), [msmbYes, msmbAll, msmbNo, msmbCancel], msmbYes, msmbCancel)
         else
           UserAnswer := MsgBox(Format(rsMsgCloseLockedTab, [ANoteBook.Page[iPageIndex].Caption]), [msmbYes, msmbNo, msmbCancel], msmbYes, msmbCancel);

         case UserAnswer of
          mmrNo:
            Exit(1);
          mmrCancel, mmrNone:
            Exit(2);
         end
       end
       else
         Exit(1);
    end;
    QuickViewClose;
    ANoteBook.RemovePage(iPageIndex);

    if UserAnswer=mmrAll then Result:=3 else Result:= 0;
  end;
end;

procedure TfrmMain.ReLoadTabs(ANoteBook: TFileViewNotebook);
var
  I : Integer;
begin
  for I := 0 to ANoteBook.PageCount - 1 do
  begin
    ANoteBook.View[I].UpdateView;
  end;
end;

procedure TfrmMain.LoadTabsXml(AConfig: TXmlConfig; ABranch:string; ANoteBook: TFileViewNotebook);
// default was ABranch: 'Tabs/OpenedTabs/'
var
  sPath, sViewType: String;
  iActiveTab: Integer;
  Page: TFileViewPage;
  AFileView: TFileView;
  AFileViewFlags: TFileViewFlags;
  aFileSource: IFileSource;
  RootNode, TabNode, ViewNode: TXmlNode;
begin
  RootNode := AConfig.FindNode(AConfig.RootNode,ABranch);

  if Assigned(RootNode) then
  begin
    TabNode := RootNode.FirstChild;
    while Assigned(TabNode) do
    begin
      if TabNode.CompareName('Tab') = 0 then
      begin
        Page := nil;
        AFileView := nil;

        ViewNode := AConfig.FindNode(TabNode, 'FileView', False);
        if Assigned(ViewNode) then
        begin
          // File view has its own configuration.
          if AConfig.TryGetAttr(ViewNode, 'Type', sViewType) then
          begin
            Page := ANoteBook.AddPage;
            Page.LoadConfiguration(AConfig, TabNode);
            AFileView := CreateFileView(sViewType, Page, AConfig, ViewNode);
          end
          else
            DCDebug('File view type not specified in configuration: ' + AConfig.GetPathFromNode(ViewNode) + '.');
        end
        // Else try old configuration.
        else if AConfig.TryGetValue(TabNode, 'Path', sPath) then
        begin
          sPath := GetDeepestExistingPath(sPath);
          if sPath <> EmptyStr then
          begin
            Page := ANoteBook.AddPage;
            Page.LoadConfiguration(AConfig, TabNode);
            AFileView := CreateFileView('columns', Page, AConfig, TabNode);
            AFileView.AddFileSource(TFileSystemFileSource.GetFileSource, sPath);
          end;
        end
        else
          DCDebug('Invalid entry in configuration: ' + AConfig.GetPathFromNode(TabNode) + '.');

        if Assigned(Page) then
        begin
          if (not Assigned(AFileView)) or (AFileView.FileSourcesCount = 0) then
          begin
            ANoteBook.RemovePage(Page);
          end
          else
          begin
            if (Page.LockState in [tlsPathLocked, tlsPathResets, tlsDirsInNewTab]) and (Page.LockPath = '') then
              Page.LockPath := AFileView.CurrentPath;
            // Assign events after loading file source.
            AssignEvents(AFileView);
          end;
        end;
      end;
      TabNode := TabNode.NextSibling;
    end;
  end;
  // Create at least one tab.
  if ANoteBook.PageCount = 0 then
  begin
    Page := ANoteBook.AddPage;
    aFileSource := TFileSystemFileSource.GetFileSource;
    if gDelayLoadingTabs then
      AFileViewFlags := [fvfDelayLoadingFiles]
    else
      AFileViewFlags := [];
    AFileView := TColumnsFileView.Create(Page, aFileSource, gpExePath, AFileViewFlags);
    Commands.DoSortByFunctions(AFileView, ColSet.GetColumnSet('Default').GetColumnFunctions(0));
    AssignEvents(AFileView);
  end
  else if Assigned(RootNode) then
  begin
    // read active tab index
    iActiveTab := AConfig.GetValue(RootNode, 'ActiveTab', 0);
    // set active tab
    if (iActiveTab >= 0) and (iActiveTab < ANoteBook.PageCount) then
    begin
      if ANoteBook.PageIndex = iActiveTab then
        nbPageChanged(ANoteBook)
      else
        ANoteBook.PageIndex := iActiveTab;
    end;
  end;
end;


procedure TfrmMain.SaveTabsXml(AConfig: TXmlConfig;ABranch:string; ANoteBook: TFileViewNotebook; ASaveHistory:boolean);
// default was: 'Tabs/OpenedTabs'
var
  I: Integer;
  TabsSection: String;
  Page: TFileViewPage;
  RootNode, TabNode, ViewNode: TXmlNode;
begin
  RootNode := AConfig.FindNode(AConfig.RootNode, ABranch, True);
  if ANoteBook = nbLeft then
    TabsSection := 'Left'
  else
    TabsSection := 'Right';
  RootNode := AConfig.FindNode(RootNode, TabsSection, True);

  AConfig.ClearNode(RootNode);
  AConfig.AddValue(RootNode, 'ActiveTab', ANoteBook.PageIndex);

  for I:= 0 to ANoteBook.PageCount - 1 do
    begin
      TabNode := AConfig.AddNode(RootNode, 'Tab');
      ViewNode := AConfig.AddNode(TabNode, 'FileView');

      Page := ANoteBook.Page[I];
      Page.SaveConfiguration(AConfig, TabNode);
      Page.FileView.SaveConfiguration(AConfig, ViewNode, ASaveHistory);
    end;
end;

{ TfrmMain.LoadTheseTabsWithThisConfig }
// Will have tabs section from Xml from either left or right and store it back on actual form on left, right, active, inactive, both or none, with setting keep or not etc.
// The "ABranch" *must* include the trailing slash when calling here.
procedure TfrmMain.LoadTheseTabsWithThisConfig(Config: TXmlConfig; ABranch:string; Source, Destination:TTabsConfigLocation; DestinationToKeep : TTabsConfigLocation; var TabsAlreadyDestroyedFlags:TTabsFlagsAlreadyDestroyed);
var
  sSourceSectionName: string;
  CheckNode: TXmlNode;
begin
  if Destination<>tclNone then
  begin
    QuickViewClose;

    // 1. Normalize our destination side and destination to keep in case params specified active/inactive
    if ((Destination=tclActive) and (ActiveFrame=FrameLeft)) OR ((Destination=tclInactive) and (NotActiveFrame=FrameLeft)) then Destination:=tclLeft;
    if ((Destination=tclActive) and (ActiveFrame=FrameRight)) OR ((Destination=tclInactive) and (NotActiveFrame=FrameRight)) then Destination:=tclRight;
    if ((DestinationToKeep=tclActive) and (ActiveFrame=FrameLeft)) OR ((DestinationToKeep=tclInactive) and (NotActiveFrame=FrameLeft)) then DestinationToKeep:=tclLeft;
    if ((DestinationToKeep=tclActive) and (ActiveFrame=FrameRight)) OR ((DestinationToKeep=tclInactive) and (NotActiveFrame=FrameRight)) then DestinationToKeep:=tclRight;

    // 2. Setup our source section name.
    case Source of
      tclLeft: sSourceSectionName := 'Left';
      tclRight: sSourceSectionName := 'Right';
    end;

    // 3. Actual load infos from config file.
    if (Destination=tclLeft) OR (Destination=tclBoth) then
    begin
      CheckNode := Config.FindNode(Config.RootNode, ABranch + sSourceSectionName);
      if Assigned(CheckNode) then
      begin
        if (DestinationToKeep<>tclLeft) AND (DestinationToKeep<>tclBoth) AND (not(tfadLeft in TabsAlreadyDestroyedFlags)) then
        begin
          frmMain.LeftTabs.DestroyAllPages;
          TabsAlreadyDestroyedFlags := TabsAlreadyDestroyedFlags + [tfadLeft]; // To don't delete it twice in case both target are left.
        end;
      end;
      LoadTabsXml(Config, ABranch + sSourceSectionName, LeftTabs);
    end;

    if (Destination=tclRight) OR (Destination=tclBoth) then
    begin
      CheckNode := Config.FindNode(Config.RootNode, ABranch + sSourceSectionName);
      if Assigned(CheckNode) then
      begin
        if (DestinationToKeep<>tclRight) AND (DestinationToKeep<>tclBoth) AND (not(tfadRight in TabsAlreadyDestroyedFlags)) then
          begin
            frmMain.RightTabs.DestroyAllPages;
            TabsAlreadyDestroyedFlags := TabsAlreadyDestroyedFlags + [tfadRight]; // To don't delete it twice in case both target are right.
          end;
        LoadTabsXml(Config, ABranch + sSourceSectionName, RightTabs);
      end;
    end;

    // 4. Refresh content of tabs.
    case Destination of
      tclLeft: FrameLeft.Flags := FrameLeft.Flags - [fvfDelayLoadingFiles];
      tclRight: FrameRight.Flags := FrameRight.Flags - [fvfDelayLoadingFiles];
      tclBoth:
        begin
          FrameLeft.Flags := FrameLeft.Flags - [fvfDelayLoadingFiles];
          FrameRight.Flags := FrameRight.Flags - [fvfDelayLoadingFiles];
        end;
    end;
  end; // if Destination<>tclNone then
end;

procedure TfrmMain.ToggleConsole;
begin
  if gTermWindow then
    begin
      if not Assigned(cmdConsole) then
      begin
        cmdConsole:= TVirtualTerminal.Create(pgConsole);
        cmdConsole.Parent:= pgConsole;
        cmdConsole.Align:= alClient;
        cmdConsole.ShowHint:= False;
      end;
      FontOptionsToFont(gFonts[dcfConsole], cmdConsole.Font); //We set the font here because if we're coming back from configuration the font in options, we'll later pass here to affect that font if ever displayed.
      if not Assigned(Cons) then
        begin
          Cons:= TPtyDevice.Create(Self);
          cmdConsole.PtyDevice:= Cons;
          Cons.Connected:= True;
        end;
    end
  else
    begin
      if Assigned(cmdConsole) then
      begin
        cmdConsole.Hide;
        FreeAndNil(cmdConsole);
      end;
      FreeAndNil(Cons);
    end;

  nbConsole.Visible:= gTermWindow;
  ConsoleSplitter.Visible:= gTermWindow;
end;

procedure TfrmMain.ShowOptionsLayout(Data: PtrInt);
begin
  ShowOptions('TfrmOptionsLayout');
end;

procedure TfrmMain.ToggleFullscreenConsole;
begin
  if not gTermWindow then
  begin
    if MessageDlg(rsMsgTerminalDisabled, mtWarning, [mbYes, mbNo], 0, mbYes) = mrYes then
      Application.QueueAsyncCall(@ShowOptionsLayout, 0);
  end
  else if nbConsole.Height < (nbConsole.Height + pnlNotebooks.Height - 1) then
  begin
    nbConsole.Height := nbConsole.Height + pnlNotebooks.Height;
    if not cmdConsole.Visible then cmdConsole.Visible:= True;
    if cmdConsole.CanFocus then cmdConsole.SetFocus;
  end
  else begin
    cmdConsole.Hide;
    nbConsole.Height := 0;
    if ActiveFrame.CanFocus then ActiveFrame.SetFocus;
  end;
end;

procedure TfrmMain.ToolbarExecuteCommand(ToolItem: TKASToolItem);
var
  CommandItem: TKASCommandItem;
  CommandFuncResult: TCommandFuncResult;
begin
  if not Draging then
  begin
    CommandItem := ToolItem as TKASCommandItem;
    CommandFuncResult:=Commands.Commands.ExecuteCommand(CommandItem.Command, CommandItem.Params);
    if gToolbarReportErrorWithCommands AND (CommandFuncResult=cfrNotFound) then
    begin
      MsgError(Format(rsMsgCommandNotFound, [CommandItem.Command]));
    end;
  end;
  Draging := False;
end;

procedure TfrmMain.ToolbarExecuteProgram(ToolItem: TKASToolItem);
var
  ProgramItem: TKASProgramItem;
  CommandExecResult: boolean;
begin
  if not Draging then
  begin
    ProgramItem := ToolItem as TKASProgramItem;
    CommandExecResult:=ProcessExtCommandFork(ProgramItem.Command, ProgramItem.Params, ProgramItem.StartPath);
    if gToolbarReportErrorWithCommands AND (CommandExecResult=FALSE) then
      MsgError(Format(rsMsgProblemExecutingCommand,[ProgramItem.Command]));
  end;
  Draging := False;
end;

procedure TfrmMain.UpdateWindowView;

  procedure UpdateNoteBook(NoteBook: TFileViewNotebook);
  var
    I: Integer;
  begin
    NoteBook.ShowTabs := ((NoteBook.PageCount > 1) or (tb_always_visible in gDirTabOptions)) and gDirectoryTabs;

    if tb_show_close_button in gDirTabOptions then
      begin
        NoteBook.Options := NoteBook.Options + [nboShowCloseButtons];
      end
    else
      begin
        NoteBook.Options := NoteBook.Options - [nboShowCloseButtons];
      end;

    if nbcMultiline in NoteBook.GetCapabilities then
      NoteBook.MultiLine := tb_multiple_lines in gDirTabOptions;

    case gDirTabPosition of
      tbpos_top:    NoteBook.TabPosition := tpTop;
      tbpos_bottom: NoteBook.TabPosition := tpBottom;
      else          NoteBook.TabPosition := tpTop;
    end;

    if FInitializedView then
    begin
      // Update all tabs
      for I := 0 to NoteBook.PageCount - 1 do
      begin
        NoteBook.View[I].UpdateView;
      end;
      // Update active tab
      if Assigned(NoteBook.ActiveView) then
      begin
        NoteBook.ActiveView.ApplySettings;
      end;
    end;
  end;

  procedure AnchorHorizontalBetween(AControl, ALeftSibling, ARightSibling: TControl);
  begin
    AControl.Anchors := AControl.Anchors + [akLeft, akRight];
    AControl.AnchorSide[akLeft].Control   := ALeftSibling;
    AControl.AnchorSide[akLeft].Side      := asrRight;
    AControl.AnchorSide[akRight].Control  := ARightSibling;
    AControl.AnchorSide[akRight].Side     := asrLeft;
  end;

  procedure AnchorHorizontal(AControl, ASibling: TControl);
  begin
    AControl.Anchors := AControl.Anchors + [akLeft, akRight];
    AControl.AnchorSide[akLeft].Control   := ASibling;
    AControl.AnchorSide[akLeft].Side      := asrLeft;
    AControl.AnchorSide[akRight].Control  := ASibling;
    AControl.AnchorSide[akRight].Side     := asrRight;
  end;

  procedure AnchorFreeSpace(LeftControl, RightControl: TControl; ExcludeVert: Boolean);
  begin
    if gDrivesListButton then
    begin
      AnchorHorizontalBetween(LeftControl, btnLeftDrive, btnLeftDirectoryHotlist);
      AnchorHorizontalBetween(RightControl, btnRightDrive, btnRightDirectoryHotlist);

      if not ExcludeVert then
      begin
        LeftControl.AnchorVerticalCenterTo(pnlLeftTools);
        RightControl.AnchorVerticalCenterTo(pnlRightTools);
      end;
    end
    else
    begin
      AnchorHorizontal(LeftControl, pnlLeftTools);
      AnchorHorizontal(RightControl, pnlRightTools);

      if not ExcludeVert then
      begin
        LeftControl.AnchorSide[akTop].Control  := pnlLeftTools;
        LeftControl.AnchorSide[akTop].Side     := asrTop;
        RightControl.AnchorSide[akTop].Control := pnlRightTools;
        RightControl.AnchorSide[akTop].Side    := asrTop;
      end;
    end;
  end;

var
  I: Integer;
  HMForm: THMForm;
  FunButton: TSpeedButton;
  Hotkey: THotkey;
begin
  DisableAutoSizing;
  try
    if gHorizontalFilePanels then
    begin
      pnlLeft.Align := alTop;
      pnlLeft.BorderSpacing.Right  := 0;
      pnlLeft.BorderSpacing.Bottom := 3;
      MainSplitter.Cursor := crVSplit;
      MiddleToolBar.Align:= alTop;
      MiddleToolBar.Top:= pnlLeft.Height + 1;
    end
    else
    begin
      pnlLeft.Align := alLeft;
      pnlLeft.BorderSpacing.Right  := 3;
      pnlLeft.BorderSpacing.Bottom := 0;
      MainSplitter.Cursor := crHSplit;
      MiddleToolBar.Align:= alLeft;
      MiddleToolBar.Left:= pnlLeft.Width + 1;
    end;

    (* Middle Tool Bar *)
    MiddleToolbar.Visible:= gMiddleToolBar;
    MiddleToolbar.Flat:= gMiddleToolBarFlat;
    MiddleToolbar.GlyphSize:= gMiddleToolBarIconSize;
    MiddleToolbar.ShowCaptions:= gMiddleToolBarShowCaptions;
    MiddleToolbar.SetButtonSize(gMiddleToolBarButtonSize, gMiddleToolBarButtonSize);
    LoadToolbar(MiddleToolBar);

    pnlLeftResize(pnlLeft);
    pnlNotebooksResize(pnlNotebooks);

    (* Disk Panels *)
    if gHorizontalFilePanels and gDriveBar1 and gDriveBar2 then
    begin
      dskLeft.Parent := pnlDiskLeftInner;
      dskRight.Parent := pnlDiskRightInner;
    end
    else
    begin
      dskLeft.Parent := pnlDskLeft;
      dskRight.Parent := pnlDskRight;
    end;

    pnlRightResize(pnlRight);

    dskLeft.GlyphSize:= gDiskIconsSize;
    dskRight.GlyphSize:= gDiskIconsSize;
    dskLeft.ButtonHeight:= gDiskIconsSize + 6;
    dskRight.ButtonHeight:= gDiskIconsSize + 6;

    pnlDiskLeftInner.Visible := gHorizontalFilePanels and gDriveBar1 and gDriveBar2;
    pnlDiskRightInner.Visible := gHorizontalFilePanels and gDriveBar1 and gDriveBar2;
    pnlDskLeft.Visible := not gHorizontalFilePanels and gDriveBar1 and gDriveBar2;
    pnlDskRight.Visible := gDriveBar1 and (not gHorizontalFilePanels or not gDriveBar2);
    pnlDisk.Visible := pnlDskLeft.Visible or pnlDskRight.Visible;

    if gHorizontalFilePanels and gDriveBar1 and gDriveBar2 then
    begin
      pnlLeftTools.Top := pnlDiskLeftInner.Height + 1;
      pnlRightTools.Top := pnlDiskRightInner.Height + 1;
    end;

    // Create disk panels after assigning parent.
    UpdateDiskCount; // Update list of showed drives
    (*/ Disk Panels *)

    FDrivesListPopup.UpdateView;

    (*Main menu*)
    Commands.DoShowMainMenu(gMainMenu);

    (*Main Tool Bar*)
    MainToolBar.Visible:= gButtonBar;
    MainToolBar.Flat:= gToolBarFlat;
    MainToolBar.GlyphSize:= gToolBarIconSize;
    MainToolBar.ShowCaptions:= gToolBarShowCaptions;
    MainToolBar.SetButtonSize(gToolBarButtonSize, gToolBarButtonSize);
    LoadToolbar(MainToolBar);

    btnLeftDrive.Visible := gDrivesListButton;
    btnLeftDrive.Flat := gInterfaceFlat;
    btnLeftRoot.Visible := gDrivesListButton;
    btnLeftRoot.Flat := gInterfaceFlat;
    btnLeftUp.Visible := gDrivesListButton;
    btnLeftUp.Flat := gInterfaceFlat;
    btnLeftHome.Visible := gDrivesListButton;
    btnLeftHome.Flat := gInterfaceFlat;
    btnLeftDirectoryHotlist.Visible := gDrivesListButton;
    btnLeftDirectoryHotlist.Flat := gInterfaceFlat;
    btnLeftEqualRight.Visible := gDrivesListButton;
    btnLeftEqualRight.Flat:= gInterfaceFlat;
    lblLeftDriveInfo.Visible:= gDriveFreeSpace;
    pbxLeftDrive.Visible := gDriveInd;
    pnlLeftTools.Visible:= gDrivesListButton or gDriveFreeSpace or gDriveInd;
    pnlLeftTools.DoubleBuffered := True;

    btnRightDrive.Visible := gDrivesListButton;
    btnRightDrive.Flat := gInterfaceFlat;
    btnRightRoot.Visible := gDrivesListButton;
    btnRightRoot.Flat := gInterfaceFlat;
    btnRightUp.Visible := gDrivesListButton;
    btnRightUp.Flat := gInterfaceFlat;
    btnRightHome.Visible := gDrivesListButton;;
    btnRightHome.Flat := gInterfaceFlat;
    btnRightDirectoryHotlist.Visible := gDrivesListButton;
    btnRightDirectoryHotlist.Flat := gInterfaceFlat;
    btnRightEqualLeft.Visible := gDrivesListButton;
    btnRightEqualLeft.Flat:= gInterfaceFlat;
    lblRightDriveInfo.Visible:= gDriveFreeSpace;
    pbxRightDrive.Visible := gDriveInd;
    pnlRightTools.Visible:= gDrivesListButton or gDriveFreeSpace or gDriveInd;
    pnlRightTools.DoubleBuffered := True;

    // Free space indicator.
    if gDriveFreeSpace then
    begin
      AnchorFreeSpace(lblLeftDriveInfo, lblRightDriveInfo, gDriveInd);
      if gDriveInd then
      begin
        lblLeftDriveInfo.AnchorSide[akTop].Side  := asrTop;
        lblRightDriveInfo.AnchorSide[akTop].Side := asrTop;
      end;
    end;

    // Drive free space indicator
    if gDriveInd then
    begin
      AnchorFreeSpace(pbxLeftDrive, pbxRightDrive, gDriveFreeSpace);
      if gDriveFreeSpace then
      begin
        pbxLeftDrive.AnchorSide[akTop].Control  := lblLeftDriveInfo;
        pbxLeftDrive.AnchorSide[akTop].Side     := asrBottom;
        pbxRightDrive.AnchorSide[akTop].Control := lblRightDriveInfo;
        pbxRightDrive.AnchorSide[akTop].Side    := asrBottom;
      end;
    end;

    // Separate tree
    UpdateShellTreeView;
    UpdateTreeView;

    // Operations panel and menu
    if (gPanelOfOp = False) then
      FreeAndNil(FOperationsPanel)
    else if (FOperationsPanel = nil) then
    begin
      FOperationsPanel := TOperationsPanel.Create(Self);
      FOperationsPanel.Parent := PanelAllProgress;
      FOperationsPanel.DoubleBuffered := True;
      PanelAllProgress.OnResize := @FOperationsPanel.ParentResized;
    end;

    PanelAllProgress.Visible := gPanelOfOp;
    Timer.Enabled := (gPanelOfOp or gProgInMenuBar) and
                     (OperationsManager.OperationsCount > 0);

    // Log window
    seLogWindow.Visible := gLogWindow;
    LogSplitter.Visible := gLogWindow;
    // Align log window
    seLogWindow.Top := 0;
    LogSplitter.Top := 0;
    FontOptionsToFont(gFonts[dcfLog], seLogWindow.Font);

    // Command line
    pnlCmdLine.Visible := gCmdLine;
    pnlCommand.Visible := gCmdLine or gTermWindow;

    // Align command line and terminal window
    pnlCommand.Top := -Height;
    ConsoleSplitter.Top:= -Height;
    ToggleConsole;

    // Function keys
    pnlKeys.Visible := gKeyButtons;
    if gKeyButtons then
    begin
      pnlKeys.Top:= Height * 2;
      HMForm := HotMan.Forms.Find('Main');
      for I := 0 to pnlKeys.ControlCount - 1 do
      begin
        if pnlKeys.Controls[I] is TSpeedButton then
        begin
          FunButton := pnlKeys.Controls[I] as TSpeedButton;
          FunButton.Flat := gInterfaceFlat;
          if Assigned(HMForm) then
          begin
            Hotkey := HMForm.Hotkeys.FindByCommand(FunctionButtonsCaptions[I].ACommand);
            if Assigned(Hotkey) then
              FunButton.Caption := FunctionButtonsCaptions[I].ACaption + ' ' + ShortcutsToText(Hotkey.Shortcuts);
          end;
        end;
      end;
      UpdateGUIFunctionKeys;
    end;

    UpdateNoteBook(nbLeft);
    UpdateNoteBook(nbRight);

    if FInitializedView then
    begin
      UpdateSelectedDrives;
      UpdateFreeSpace(fpLeft, True);
      UpdateFreeSpace(fpRight, True);
    end;

    UpdateHotDirIcons; // Preferable to be loaded even if not required in popupmenu *because* in the tree it's a must, especially when checking for missing directories
    ShowTrayIcon(gAlwaysShowTrayIcon);
    UpdateMainTitleBar;

    FInitializedView := True;
  finally
    EnableAutoSizing;
  end;
end;

procedure TfrmMain.edtCommandKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if not edtCommand.DroppedDown and ((Key=VK_UP) or (Key=VK_DOWN)) then
    begin
      ActiveFrame.SetFocus;
      Key:= 0;
    end
  else if edtCommand.DroppedDown and (Key in [VK_RETURN, VK_SELECT, VK_ESCAPE]) then
    begin
      edtCommand.DroppedDown := False;
      Key := 0;
    end
  else
    case Key of
      VK_ESCAPE:
        begin
          if edtCommand.Text <> '' then
            edtCommand.Text := ''
          else
            ActiveFrame.SetFocus;
          Key := 0;
        end;

      VK_RETURN, VK_SELECT:
        begin
          if (Shift * [ssCtrl, ssAlt, ssMeta, ssAltGr] = []) then
          begin
            ExecuteCommandLine(ssShift in Shift);
            Key := 0;
          end;
        end;

      VK_TAB:
        begin
          ActiveFrame.SetFocus;
          Key := 0;
        end;

      VK_PAUSE:
        begin
          {
          if gTermWindow and Assigned(Cons) then
            Cons.Terminal.SendBreak_pty();
          }
        end;
    end;

  CheckCommandLine(GetKeyShiftStateEx, Key);
end;

procedure TfrmMain.edtCommandExit(Sender: TObject);
begin
  // Hide command line if it was temporarily shown.
  if (not gCmdLine) and IsCommandLineVisible and (edtCommand.Text = '') then
  begin
    pnlCmdLine.Visible := False;
    pnlCommand.Visible := gTermWindow;
  end;
end;

procedure TfrmMain.tbChangeDirClick(Sender: TObject);
var
  sDir: String;
  ToolItem: TKASToolItem;
  Button: TKASToolButton;
begin
  Button := TKASToolButton(pmToolBar.Tag);
  if Assigned(Button) then
  begin
    ToolItem := Button.ToolItem;
    if ToolItem is TKASProgramItem then
    begin
      sDir := TKASProgramItem(ToolItem).StartPath;
      sDir := PrepareParameter(sDir, nil, [ppoNormalizePathDelims, ppoReplaceTilde]);
      Commands.cm_ChangeDir([sDir]);
    end;
  end;
end;

procedure TfrmMain.tbCopyClick(Sender: TObject);
var
  ToolItem: TKASToolItem;
  ItemClone: TKASToolItem = nil;
  Serializer: TKASToolBarSerializer = nil;
  Stream: TStringStream = nil;
  Button: TKASToolButton;
  Toolbar: TKASToolBar;
begin
  Button := TKASToolButton(pmToolBar.Tag);
  if Assigned(Button) then
  try
    Toolbar := Button.ToolBar;
    ToolItem := Button.ToolItem;

    // Create a copy so that ID of the button is different.
    if Sender = tbCopy then
    begin
      ItemClone := ToolItem.Clone;
      ToolItem := ItemClone;
    end;

    Stream := TStringStream.Create('');
    Stream.WriteString(DCToolItemClipboardHeader);
    Serializer := TKASToolBarSerializer.Create;
    Serializer.Serialize(Stream, ToolItem);
    Clipboard.SetFormat(PredefinedClipboardFormat(pcfText), Stream);

    if Sender = tbCut then
      Toolbar.RemoveButton(Button);

    SaveToolBar(Toolbar);
  finally
    ItemClone.Free;
    Serializer.Free;
    Stream.Free;
  end;
end;

procedure TfrmMain.tbEditClick(Sender: TObject);
begin
  EditToolbarButton(TKASToolBar(pmToolBar.PopupComponent), TKASToolButton(pmToolBar.Tag));
end;

procedure TfrmMain.OnUniqueInstanceMessage(Sender: TObject; Params: TCommandLineParams);
begin
  RestoreWindow;
  LoadTabsCommandLine(Params);
end;

procedure TfrmMain.tbPasteClick(Sender: TObject);
var
  Data: TStringList = nil;
  ProgramItem: TKASProgramItem;
  ToolItem: TKASToolItem;
  Loader: TKASToolBarLoader = nil;
  Serializer: TKASToolBarSerializer = nil;
  Stream: TStringStream = nil;
  Pasted: Boolean = False;
  Button: TKASToolButton;
  Toolbar: TKASToolBar;
begin
  Stream := TStringStream.Create('');
  if Clipboard.GetFormat(PredefinedClipboardFormat(pcfText), Stream) then
  try
    Button := TKASToolButton(pmToolBar.Tag);
    Toolbar := TKASToolBar(pmToolBar.PopupComponent);

    // Cut any trailing zeros.
    while Stream.DataString[Length(Stream.DataString)] = #0 do
      Stream.Size := Stream.Size - 1;

    if StrBegins(Stream.DataString, TCToolbarClipboardHeader) or
       StrBegins(Stream.DataString, DCToolbarClipboardHeader) then
    begin
      Data:= TStringList.Create;
      Data.Text:= Stream.DataString;
      if Data.Count < 6 then Exit;
      if (Data[0] = TCToolbarClipboardHeader) or
         (Data[0] = DCToolbarClipboardHeader) then
      begin
        ProgramItem := TKASProgramItem.Create;
        ProgramItem.Command   := Data[1];
        ProgramItem.Params    := Data[2];
        ProgramItem.Icon      := Data[3];
        ProgramItem.Hint      := Data[4];
        ProgramItem.StartPath := Data[5];
        Toolbar.InsertButton(Button, ProgramItem);
        SaveToolBar(Toolbar);
        Pasted := True;
      end;
    end
    else if StrBegins(Stream.DataString, DCToolItemClipboardHeader) then
    begin
      Stream.Position := Length(DCToolItemClipboardHeader);
      Serializer := TKASToolBarSerializer.Create;
      Loader := TKASToolBarExtendedLoader.Create(Commands.Commands);
      try
        ToolItem := Serializer.Deserialize(Stream, Loader);
        Toolbar.InsertButton(Button, ToolItem);
        SaveToolBar(Toolbar);
        Pasted := True;
      except
        on EXMLReadError do;
      end;
    end;

    if not Pasted then
      MessageDlg(Application.Title, rsClipboardContainsInvalidToolbarData, mtWarning, [mbOK], 0);

  finally
    Data.Free;
    Loader.Free;
    Serializer.Free;
    Stream.Free;
  end;
end;

procedure TfrmMain.CheckCommandLine(ShiftEx: TShiftState; var Key: Word);
var
  ModifierKeys: TShiftState;
  UTF8Char: TUTF8Char;
  KeyTypingModifier: TKeyTypingModifier;
begin
  for KeyTypingModifier in TKeyTypingModifier do
  begin
    if gKeyTyping[KeyTypingModifier] = ktaCommandLine then
    begin
      ModifierKeys := TKeyTypingModifierToShift[KeyTypingModifier];
      if ((ModifierKeys <> []) and
         (ShiftEx * KeyModifiersShortcutNoText = ModifierKeys))
{$IFDEF MSWINDOWS}
      // Allow entering international characters with Ctrl+Alt on Windows,
      // if there is no action for Ctrl+Alt and command line typing has no modifiers.
      or (HasKeyboardAltGrKey and
          (ShiftEx * KeyModifiersShortcutNoText = [ssCtrl, ssAlt]) and
          (gKeyTyping[ktmCtrlAlt] = ktaNone) and
          (gKeyTyping[ktmNone] = ktaCommandLine))
{$ENDIF}
      then
      begin
        if (Key <> VK_SPACE) or (edtCommand.Text <> '') then
        begin
          UTF8Char := VirtualKeyToUTF8Char(Key, ShiftEx - ModifierKeys);
          if (UTF8Char <> '') and
             (not ((Length(UTF8Char) = 1) and (UTF8Char[1] in [#0..#31]))) then
          begin
            TypeInCommandLine(UTF8Char);
            Key := 0;
          end;
        end;
      end;
      Break;
    end;
  end;
end;

function TfrmMain.ExecuteCommandFromEdit(sCmd: String; bRunInTerm: Boolean): Boolean;
var
  iIndex: Integer;
  aFile: TFile = nil;
  sDir, sParams: String;
  sFilename: String = '';
  Operation: TFileSourceExecuteOperation = nil;
begin
  Result:= True;

  InsertFirstItem(sCmd, edtCommand);
  // only cMaxStringItems(see uGlobs.pas) is stored
  if edtCommand.Items.Count>cMaxStringItems then
    edtCommand.Items.Delete(edtCommand.Items.Count-1);
  edtCommand.DroppedDown:= False;

  if (fspDirectAccess in ActiveFrame.FileSource.GetProperties) then
    begin
      if FileNameCaseSensitive then
        sDir:= sCmd
      else begin
        sDir:= LowerCase(sCmd);
      end;
      iIndex:= Pos('cd ', sDir);
      if (iIndex = 1) or (sDir = 'cd') then
        begin
          sCmd:= ReplaceEnvVars(sCmd);

          if (iIndex <> 1) then
            sDir:= GetHomeDir
          else
            begin
              sDir:= RemoveQuotation(Copy(sCmd, iIndex + 3, Length(sCmd)));
              sDir:= NormalizePathDelimiters(Trim(sDir));

              if (sDir = DirectorySeparator) or (sDir = '..') then
              begin
                if (sDir = DirectorySeparator) then
                  Commands.DoChangeDirToRoot(ActiveFrame)
                else begin
                  ActiveFrame.ChangePathToParent(True);
                end;
                Exit;
              end;

              sDir:= ReplaceTilde(sDir);
              sDir:= GetAbsoluteFileName(ActiveFrame.CurrentPath, sDir);
              if mbFileExists(sDir) then //if user entered an existing file, let's switch to the parent folder AND select that file
              begin
                sFilename:= ExtractFileName(sDir);
                sDir:= ExtractFileDir(sDir);
              end;
            end;

          // Choose FileSource by path
          ChooseFileSource(ActiveFrame, sDir, True);
          if sFilename <> '' then
            ActiveFrame.SetActiveFile(sFilename);

          if SameText(ExcludeBackPathDelimiter(ActiveFrame.CurrentPath), sDir) then
            begin
              if gTermWindow and Assigned(Cons) then
                Cons.SetCurrentDir(sDir);
            end;
        end
      else
        begin
          {$IFDEF MSWINDOWS}
          //Let's see if user typed something like "c:", "X:", etc. and if so, switch active panel to that drive (like TC)
          if (length(sCmd)=2) AND (pos(':',sCmd)=2) then
          begin
            iIndex:=0;

            while (iIndex<DrivesList.Count) AND (sCmd<>'') do
            begin
              if lowercase(sCmd[1]) = lowercase(PDrive(DrivesList.Items[iIndex])^.DisplayName) then
              begin
                SetPanelDrive(PanelSelected, DrivesList.Items[iIndex],false);
                sCmd:='';
              end;
              inc(iIndex);
            end;
          end;
          {$ENDIF}

          if sCmd<>'' then
          begin
            if gTermWindow and Assigned(Cons) then
            begin
              sCmd:= ReplaceEnvVars(sCmd);
              Cons.WriteStr(sCmd + sLineBreak)
            end
            else
            begin
              try
                SplitCmdLineToCmdParams(sCmd, sCmd, sParams); //TODO:Hum...
                ProcessExtCommandFork(sCmd, sParams, ActiveFrame.CurrentPath, nil, bRunInTerm, bRunInTerm);
              except
                on e: EInvalidCommandLine do
                  MessageDlg(rsMsgInvalidCommandLine, rsMsgInvalidCommandLine + ': ' + e.Message, mtError, [mbOK], 0);
              end;
            end;
          end;
      end;
    end
  else
    begin
      aFile:= ActiveFrame.CloneActiveFile;
      if Assigned(aFile) then
      try
        sCmd:= 'quote' + #32 + sCmd;
        aFile.FullPath:= ActiveFrame.CurrentPath;
        Operation:= ActiveFrame.FileSource.CreateExecuteOperation(
                                         aFile,
                                         ActiveFrame.CurrentPath,
                                         sCmd) as TFileSourceExecuteOperation;
        if Assigned(Operation) then
        begin
          Operation.Execute;
          case Operation.ExecuteOperationResult of
            fseorSuccess:
              begin
                ActiveFrame.Reload(True);
              end;
            fseorError:
              begin
                // Show error message
                if Length(Operation.ResultString) = 0 then
                  msgError(rsMsgErrEOpen)
                else
                  msgError(Operation.ResultString);
              end;
            fseorSymLink:
              begin
                // Change directory to new path (returned in Operation.ResultString)
                with ActiveFrame do
                begin
                  // If path is URI
                  if Pos('://', Operation.ResultString) > 0 then
                    ChooseFileSource(ActiveFrame, Operation.ResultString)
                  else if not mbSetCurrentDir(ExcludeTrailingPathDelimiter(Operation.ResultString)) then
                  begin
                    // Simply change path
                    CurrentPath:= Operation.ResultString;
                  end
                  else begin
                    // Get a new filesystem file source
                    AddFileSource(TFileSystemFileSource.GetFileSource, Operation.ResultString);
                  end;
                end;
              end;
          end;
        end;
      finally
        FreeAndNil(aFile);
        FreeAndNil(Operation);
      end;
    end;
end;

procedure TfrmMain.SetMainSplitterPos(AValue: Double);
begin
  if (AValue >= 0) and (AValue <= 100) then
    FMainSplitterPos:= AValue;
end;

procedure TfrmMain.SetPanelSelected(AValue: TFilePanelSelect);
begin
  if PanelSelected = AValue then Exit;
  PanelSelected := AValue;
  UpdateTreeViewPath;
  UpdateMainTitleBar;
  UpdatePrompt;
  if actSyncChangeDir.Checked then begin
    FSyncChangeDir:= ExcludeTrailingBackslash(ActiveFrame.CurrentPath);
  end;
end;

procedure TfrmMain.TypeInCommandLine(Str: String);
begin
  Commands.cm_FocusCmdLine([]);
  edtCommand.Text := edtCommand.Text + Str;
  edtCommand.SelStart := UTF8Length(edtCommand.Text) + 1;
end;

//LaBero begin
//Minimize the main window
procedure TfrmMain.MinimizeWindow;
begin
  Self.WindowState := wsMinimized;
end;
//LaBero end

procedure TfrmMain.RestoreWindow;
begin
  if HiddenToTray then
    RestoreFromTray
  else
  begin
    WindowState:= lastWindowState;
    BringToFront;
  end;
end;

procedure TfrmMain.LoadTabs;
var
  AConfig: TXmlConfig;
begin
  if gConfig.FindNode(gConfig.RootNode, 'Tabs/OpenedTabs') <> nil then
    AConfig:= gConfig
  else begin
    AConfig:= TXmlConfig.Create(gpCfgDir + 'tabs.xml', True);
  end;

  try
    LoadTabsXml(AConfig, 'Tabs/OpenedTabs/Left', nbLeft);
    LoadTabsXml(AConfig, 'Tabs/OpenedTabs/Right', nbRight);
  finally
    if (AConfig <> gConfig) then AConfig.Free;
  end;

  if not CommandLineParams.ActivePanelSpecified then
  begin
    CommandLineParams.ActivePanelSpecified:= True;
    CommandLineParams.ActiveRight:= gActiveRight;
  end;

  LoadTabsCommandLine(CommandLineParams);

  if gDelayLoadingTabs then
  begin
    // Load only the current active tab of each notebook.
    FrameLeft.Flags  := FrameLeft.Flags  - [fvfDelayLoadingFiles];
    FrameRight.Flags := FrameRight.Flags - [fvfDelayLoadingFiles];
  end;
end;

procedure TfrmMain.LoadTabsCommandLine(Params: TCommandLineParams);

  procedure LoadPanel(aNoteBook: TFileViewNotebook; aPath: String);
  begin
    if Length(aPath) <> 0 then
    begin
      aPath:= ReplaceEnvVars(ReplaceTilde(aPath));
      if not mbFileSystemEntryExists(aPath) then
        aPath:= GetDeepestExistingPath(aPath);
      if Length(aPath) <> 0 then
      begin
        if Params.NewTab then
          AddTab(aNoteBook, aPath)
        else
          aNoteBook.ActivePage.FileView.ChangePathAndSetActiveFile(aPath)
      end;
    end;
  end;

begin
  //-- set path for left panel (if set)
  LoadPanel(nbLeft,  Params.LeftPath);

  //-- set path for right panel (if set)
  LoadPanel(nbRight, Params.RightPath);

  //-- set active panel, if needed
  if Params.ActivePanelSpecified then
  begin
    if Params.ActiveRight then
      SetActiveFrame(fpRight)
    else
      SetActiveFrame(fpLeft);
  end;

  //-- set path for active panel (if set)
  if ActiveFrame.NotebookPage is TFileViewPage then
  begin
    LoadPanel((ActiveFrame.NotebookPage as TFileViewPage).Notebook, Params.ActivePanelPath);
  end;

  ActiveFrame.SetFocus;
end;

procedure TfrmMain.AddTab(ANoteBook: TFileViewNotebook; aPath: String);
var
  Page: TFileViewPage;
  AFileView: TFileView;
  AFileViewFlags: TFileViewFlags;
  aFileSource: IFileSource;
begin
  Page := ANoteBook.AddPage;
  aFileSource := TFileSystemFileSource.GetFileSource;
  if gDelayLoadingTabs then
    AFileViewFlags := [fvfDelayLoadingFiles]
  else
    AFileViewFlags := [];
  AFileView := TColumnsFileView.Create(Page, aFileSource, aPath, AFileViewFlags);
  AssignEvents(AFileView);
  ANoteBook.PageIndex := ANoteBook.PageCount - 1;
end;

{$IF DEFINED(DARWIN)}
procedure TfrmMain.OnNSServiceOpenWithNewTab( filenames:TStringList );
begin
  if Assigned(filenames) and (filenames.Count>0) then
  begin
    AddTab( nbRight, filenames[0] );
    SetActiveFrame(fpRight);
    ActiveFrame.SetFocus;
  end;
end;

function TfrmMain.NSServiceMenuIsReady(): boolean;
begin
  Result:= true;
end;

function TfrmMain.NSServiceMenuGetFilenames(): TStringList;
var
  filenames: TStringList;
  i: Integer;
  files: TFiles;
  activeFile: TFile;
begin
  Result:= nil;
  filenames:= TStringList.Create;

  files:= ActiveFrame.CloneSelectedFiles();
  if files.Count>0 then
  begin
    for i:=0 to files.Count-1 do
    begin
      filenames.add( files[i].FullPath );
    end;
  end;
  FreeAndNil( files );

  if filenames.Count = 0 then
  begin
    activeFile:= ActiveFrame.CloneActiveFile;
    if activeFile.IsNameValid() then
      filenames.add( activeFile.FullPath )
    else
      filenames.add( activeFile.Path );
    FreeAndNil( activeFile );
  end;

  if filenames.Count>0 then Result:= filenames;
end;

procedure TfrmMain.NSThemeChangedHandler;
begin
  ThemeServices.IntfDoOnThemeChange;
end;
{$ENDIF}

procedure TfrmMain.LoadWindowState;
var
  ANode: TXmlNode;
  FPixelsPerInch: Integer;
begin
  // Load window bounds
  ANode := gConfig.FindNode(gConfig.RootNode, 'MainWindow/Position', True);
  begin
    MainSplitterPos := gConfig.GetValue(ANode, 'Splitter', 50.0);
    FRestoredLeft := gConfig.GetValue(ANode, 'Left', 80);
    FRestoredTop := gConfig.GetValue(ANode, 'Top', 48);
    FRestoredWidth := gConfig.GetValue(ANode, 'Width', 800);
    FRestoredHeight := gConfig.GetValue(ANode, 'Height', 480);
    FPixelsPerInch := gConfig.GetValue(ANode, 'PixelsPerInch', DesignTimePPI);
    if Scaled and (Screen.PixelsPerInch <> FPixelsPerInch) then
    begin
      FRestoredWidth := MulDiv(FRestoredWidth, Screen.PixelsPerInch, FPixelsPerInch);
      FRestoredHeight := MulDiv(FRestoredHeight, Screen.PixelsPerInch, FPixelsPerInch);
    end;
    if gConfig.GetValue(ANode, 'Maximized', True) then
      lastWindowState:= TWindowState.wsMaximized
    else
      lastWindowState:= TWindowState.wsNormal;
    SetBounds(FRestoredLeft, FRestoredTop, FRestoredWidth, FRestoredHeight);
  end;
end;

procedure TfrmMain.SaveWindowState;
var
  ANode: TXmlNode;
begin
  // Save window bounds and state
  ANode := gConfig.FindNode(gConfig.RootNode, 'MainWindow/Position', True);
  begin
    gConfig.SetValue(ANode, 'Left', FRestoredLeft);
    gConfig.SetValue(ANode, 'Top', FRestoredTop);
    gConfig.SetValue(ANode, 'Width', FRestoredWidth);
    gConfig.SetValue(ANode, 'Height', FRestoredHeight);
    gConfig.SetValue(ANode, 'PixelsPerInch', Screen.PixelsPerInch);
    gConfig.SetValue(ANode, 'Maximized', (WindowState in [wsMaximized,wsFullScreen]));
    gConfig.SetValue(ANode, 'Splitter', FMainSplitterPos);
  end;
end;

procedure TfrmMain.LoadToolbar(AToolBar: TKASToolBar);
var
  ToolBarLoader: TKASToolBarExtendedLoader;
  ToolBarNode: TXmlNode;
begin
  AToolBar.BeginUpdate;
  ToolBarLoader := TKASToolBarExtendedLoader.Create(Commands.Commands);
  try
    AToolBar.Clear;
    ToolBarNode := gConfig.FindNode(gConfig.RootNode, 'Toolbars/' + AToolBar.Name, False);
    if Assigned(ToolBarNode) then
      AToolBar.LoadConfiguration(gConfig, ToolBarNode, ToolBarLoader, tocl_FlushCurrentToolbarContent);
  finally
    ToolBarLoader.Free;
    AToolBar.EndUpdate;
  end;
end;

procedure TfrmMain.SaveToolBar(AToolBar: TKASToolBar);
var
  ToolBarNode: TXmlNode;
begin
  ToolBarNode := gConfig.FindNode(gConfig.RootNode, 'Toolbars/' + AToolBar.Name, True);
  gConfig.ClearNode(ToolBarNode);
  AToolBar.SaveConfiguration(gConfig, ToolBarNode);
end;

procedure TfrmMain.ShowLogWindow(Data: PtrInt);
var
  bShow: Boolean absolute Data;
begin
  LogSplitter.Visible:= bShow;
  seLogWindow.Visible:= bShow;
  LogSplitter.Top:= seLogWindow.Top - LogSplitter.Height;
end;

procedure TfrmMain.ConfigSaveSettings(bForce: Boolean);
var
  AConfig: TXmlConfig;
begin
  try
    DebugLn('Saving configuration');

    if gSaveCmdLineHistory then
      glsCmdLineHistory.Assign(edtCommand.Items);

    (* Save all tabs *)
    if gSaveFolderTabs or bForce then
    begin
      AConfig:= TXmlConfig.Create(gpCfgDir + 'tabs.xml');
      try
        SaveTabsXml(AConfig, 'Tabs/OpenedTabs/', nbLeft, gSaveDirHistory);
        SaveTabsXml(AConfig, 'Tabs/OpenedTabs/', nbRight, gSaveDirHistory);
        AConfig.Save;
      finally
        AConfig.Free;
      end;
      gConfig.DeleteNode(gConfig.RootNode, 'Tabs/OpenedTabs');
    end;

    if gSaveWindowState then SaveWindowState;

    if gButtonBar then SaveToolBar(MainToolBar);

    SaveGlobs; // Should be last, writes configuration file
  except
    on E: Exception do
      DebugLn('Cannot save main configuration: ', e.Message);
  end;
end;

function TfrmMain.IsCommandLineVisible: Boolean;
begin
  Result := (edtCommand.Visible and pnlCommand.Visible and pnlCmdLine.Visible);
end;

procedure TfrmMain.ShowCommandLine(AFocus: Boolean);
begin
  if edtCommand.Visible then
  begin
    // Show temporarily command line on user request.
    if not (gCmdLine and frmMain.IsCommandLineVisible) then
    begin
      pnlCommand.Show;
      pnlCmdLine.Show;
    end;
    if AFocus then edtCommand.SetFocus;
  end;
end;

function TfrmMain.FindMatchingDrive(Address, Path: String): Integer;
var
  I : Integer;
  DrivePath: String;
  DrivePathLen: PtrInt;
  LongestPathLen: Integer = 0;
begin
  Result := -1;

  if Assigned(DrivesList) then
  begin
    Path := UTF8UpperCase(Path);

    for I := 0 to DrivesList.Count - 1 do
    begin
      if (DrivesList[I]^.DriveType = dtSpecial) and (Length(Address) > 0) then
      begin
        if Pos(Address, DrivesList[I]^.Path) = 1 then
          Exit(I);
      end
      else begin
        DrivePath := UTF8UpperCase(DrivesList[I]^.Path);
        DrivePathLen := UTF8Length(DrivePath);
        if (DrivePathLen > LongestPathLen) and IsInPath(DrivePath, Path, True, True) then
        begin
          LongestPathLen := DrivePathLen;
          Result := I;
        end;
      end;
    end;
  end;
end;

procedure TfrmMain.UpdateDriveToolbarSelection(DriveToolbar: TKAStoolBar; FileView: TFileView);
var
  DriveIndex: Integer;
begin
  DriveIndex := FindMatchingDrive(FileView.CurrentAddress, FileView.CurrentPath);
  if (DriveIndex >= 0) and (DriveIndex < DriveToolbar.ButtonCount) then
    DriveToolbar.Buttons[DriveIndex].Down := True
  else
    // Path not found in toolbar.
    DriveToolbar.UncheckAllButtons;
end;

procedure TfrmMain.UpdateDriveButtonSelection(DriveButton: TSpeedButton; FileView: TFileView);
var
  Drive: PDrive;
  BitmapTmp: Graphics.TBitmap = nil;
begin
  DriveButton.Tag := FindMatchingDrive(FileView.CurrentAddress, FileView.CurrentPath);

  if not gDrivesListButton then
    Exit;

  if DriveButton.Tag >= 0 then
  begin
    Drive := DrivesList[DriveButton.Tag];
    DriveButton.Caption := Drive^.DisplayName;
    BitmapTmp := PixMapManager.GetDriveIcon(Drive, gDiskIconsSize, DriveButton.Color);
  end
  else
  begin
    DriveButton.Caption := '';

    if FileView.FileSource.IsClass(TArchiveFileSource) then
      BitmapTmp := PixMapManager.GetArchiveIcon(gDiskIconsSize, DriveButton.Color)
    else
      BitmapTmp := PixMapManager.GetDefaultDriveIcon(gDiskIconsSize, DriveButton.Color);
  end;

  DriveButton.Glyph := BitmapTmp;

  DriveButton.Width := DriveButton.Glyph.Width
                     + DriveButton.Canvas.TextWidth(DriveButton.Caption) + 24;

  FreeAndNil(BitmapTmp);
end;

procedure TfrmMain.UpdateSelectedDrive(ANoteBook: TFileViewNotebook);
var
  FileView: TFileView;
begin
  FileView := ANoteBook.ActiveView;
  if Assigned(FileView) then
  begin
    // Change left drive toolbar for left drive button.
    if (ANoteBook = nbLeft) then
    begin
      if gDriveBar1 then // If drives toolbar enabled at all
      begin
        if gDriveBar2 then // If showing two toolbars
          UpdateDriveToolbarSelection(dskLeft, FileView)
        else
          // dskRight is the main toolbar.
          UpdateDriveToolbarSelection(dskRight, FileView);
      end;
      UpdateDriveButtonSelection(btnLeftDrive, FileView);
    end
    // Change right drive toolbar for right drive button
    else if (ANoteBook = nbRight) then
    begin
      if gDriveBar1 then
        UpdateDriveToolbarSelection(dskRight, FileView);
      UpdateDriveButtonSelection(btnRightDrive, FileView);
    end;
  end;
end;

{$IF DEFINED(MSWINDOWS)}
procedure TfrmMain.OnDriveIconLoaded(Data: PtrInt);
var
  ADrive: TKASDriveItem;
  AIcon: TDriveIcon absolute Data;

  procedure UpdateDriveIcon(dskPanel: TKASToolBar);
  var
    Index: Integer;
  begin
    for Index:= 0 to dskPanel.ButtonCount - 1 do
    begin
     if dskPanel.Buttons[Index].ToolItem is TKASDriveItem then
     begin
       ADrive:= TKASDriveItem(dskPanel.Buttons[Index].ToolItem);
       if SameText(ADrive.Drive^.Path, AIcon.Drive.Path) then
       begin
         dskPanel.Buttons[Index].Glyph.Assign(AIcon.Bitmap);
         Break;
       end;
     end;
    end;
  end;

begin
  if gDriveBar2 then UpdateDriveIcon(dskLeft);
  if gDriveBar1 then UpdateDriveIcon(dskRight);
  AIcon.Free;
end;
{$ENDIF}

procedure TfrmMain.UpdateSelectedDrives;
begin
  if gDriveBar1 then
  begin
    if gDriveBar2 then
    begin
      UpdateDriveToolbarSelection(dskLeft, FrameLeft);
      UpdateDriveToolbarSelection(dskRight, FrameRight);
    end
    else
      // dskRight is the main toolbar.
      UpdateDriveToolbarSelection(dskRight, ActiveFrame);
  end;
  UpdateDriveButtonSelection(btnLeftDrive, FrameLeft);
  UpdateDriveButtonSelection(btnRightDrive, FrameRight);
end;

procedure TfrmMain.UpdateMainTitleBar;
var sTmp: String;
begin
    if gShowCurDirTitleBar and (fspDirectAccess in ActiveFrame.FileSource.Properties) then
    begin
        sTmp := ActiveFrame.CurrentPath;
        Self.Caption:= Format('%s (%s) - %s',
            [GetLastDir(sTmp),
            sTmp,
            sStaticTitleBarString]
            );
    end
    else
    begin
        Self.Caption := sStaticTitleBarString;
    end;
end;

procedure TfrmMain.UpdateGUIFunctionKeys;
var
  I: Integer;
  H: Integer = 0;
  AButton: TSpeedButton;
begin
  for I:= 0 to pnlKeys.ControlCount - 1 do
  begin
    if pnlKeys.Controls[I] is TSpeedButton then
    begin
      AButton:= TSpeedButton(pnlKeys.Controls[I]);
      FontOptionsToFont(gFonts[dcfFunctionButtons], AButton.Font);
      H:= Max(H, AButton.Canvas.TextHeight(AButton.Caption));
    end;
  end;
  pnlKeys.Height := H + 4;
end;

procedure TfrmMain.ShowDrivesList(APanel: TFilePanelSelect);
var
  p: TPoint;
  ADriveIndex: Integer;
begin
  if tb_activate_panel_on_click in gDirTabOptions then
    SetActiveFrame(APanel);

  case APanel of
    fpLeft:
      begin
        p := Classes.Point(btnLeftDrive.Left, btnLeftDrive.Height);
        p := pnlLeftTools.ClientToScreen(p);
        ADriveIndex := btnLeftDrive.Tag;
      end;
    fpRight:
      begin
        p := Classes.Point(btnRightDrive.Left, btnRightDrive.Height);
        p := pnlRightTools.ClientToScreen(p);
        ADriveIndex := btnRightDrive.Tag;
      end;
  end;
  p := ScreenToClient(p);
  FDrivesListPopup.Show(p, APanel, ADriveIndex);
end;

procedure TfrmMain.HideToTray;
{$IF DEFINED(LCLGTK2) or DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
var
  ActiveWindow: HWND;
  LCLObject: TObject;
{$ENDIF}
begin
{
  If a modal form is active we have to hide it first before hiding the main form
  to avoid bugs:

  On GTK2 a modal form loses it's modal state after the main window is
  restored (GTK still says the window is modal and resetting modal state
  doesn't do anything).

  On QT the tray icon does not receive any mouse events (because the modal
  window has capture) thus preventing the user from restoring the main window.
  So when the main form is hidden the modal window is hidden too.
}
{$IF DEFINED(LCLGTK2) or DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
  LastActiveWindow := nil;
  if not Self.Active then    // If there is another window active
  begin
    ActiveWindow := GetActiveWindow;
    if ActiveWindow <> 0 then
    begin
      LCLObject := GetLCLOwnerObject(ActiveWindow);
      if Assigned(LCLObject) and
         (LCLObject is TCustomForm) and
         (fsModal in (LCLObject as TCustomForm).FormState) then // only for modal windows
      begin
        LastActiveWindow := LCLObject as TCustomForm;
{$IFDEF LCLGTK2}
        // Cannot use Hide method, because it closes the modal form.
        // We only want to hide it.
        LastActiveWindow.Visible := False;
{$ENDIF}
{$IF DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
        // Have to use QT directly to hide the window for this to work.
        TQtWidget(LastActiveWindow.Handle).setVisible(False);
{$ENDIF}
      end;
    end;
  end;
{$ENDIF}

  Hide;
  ShowTrayIcon(True);
  HiddenToTray := True;
end;

procedure TfrmMain.RestoreFromTray;
begin
  if lastWindowState=wsMaximized then  WindowState:=wsMaximized;
  ShowOnTop;

  if not gAlwaysShowTrayIcon then
    ShowTrayIcon(False);

  // After the main form is shown, restore the last active modal form if there was any.
{$IF DEFINED(LCLGTK2) or DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
   if Assigned(LastActiveWindow) then
   begin
{$IF DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
     TQtWidget(LastActiveWindow.Handle).setVisible(true);
{$ENDIF}
{$IFDEF LCLGTK2}
     LastActiveWindow.Show;
{$ENDIF}
     LastActiveWindow := nil;
   end;
{$ENDIF}
end;

procedure TfrmMain.RightDriveBarExecuteDrive(ToolItem: TKASToolItem);
var
  DriveItem: TKASDriveItem;
  Panel: TFilePanelSelect;
begin
  DriveItem := ToolItem as TKASDriveItem;
  if gDriveBar2 then
    Panel := fpRight
  else
    Panel := ActiveNotebook.Side;
  SetPanelDrive(Panel, DriveItem.Drive, True);
end;

procedure TfrmMain.ShowTrayIcon(bShow: Boolean);
begin
  if (bShow <> MainTrayIcon.Visible) and (HidingTrayIcon = False) then
  begin
    if bShow then
    begin
      MainTrayIcon.Visible := True;
    end
    else
    begin
      // ShowTrayIcon might be called from within OnClick event of the icon
      // (MainTrayIconClick->RestoreFromTray->ShowTrayIcon), so the MainTrayIcon
      // cannot be hidden here, because it would be destroyed causing A/V.
      // Hiding it must be delayed until after the mouse click handler of the icon is finished.
      HidingTrayIcon := True;
      Application.QueueAsyncCall(@HideTrayIconDelayed, 0);
    end;
  end;
end;

procedure TfrmMain.HideTrayIconDelayed(Data: PtrInt);
begin
  MainTrayIcon.Visible := False;
  HidingTrayIcon := False;
end;

procedure TfrmMain.ExecuteCommandLine(bRunInTerm: Boolean);
begin
  mbSetCurrentDir(ActiveFrame.CurrentPath);
  ExecuteCommandFromEdit(edtCommand.Text, bRunInTerm);
  edtCommand.Text := '';
  ActiveFrame.Reload;
  ActiveFrame.SetFocus;
{$IF DEFINED(LCLGTK) or DEFINED(LCLGTK2)}
  // workaround for GTK
  // edtCommandExit is not always called when losing focus
  edtCommandExit(Self);
{$ENDIF}
end;

procedure TfrmMain.UpdatePrompt;
var
  st: String;
  Properties: TFileSourceProperties;
begin
  if (fsoExecute in ActiveFrame.FileSource.GetOperationsTypes) then
  begin
    with lblCommandPath do
    begin
      Visible := True;
      st := ExcludeTrailingBackslash(ActiveFrame.CurrentPath);
      Hint := st;

      Caption := MinimizeFilePath(Format(fmtCommandPath, [st]),
              Canvas, pnlCommand.Width div 3);
    end;

    // Change path in terminal
    if (fspDirectAccess in ActiveFrame.FileSource.GetProperties) then
      begin
        if gTermWindow and Assigned(Cons) then
          Cons.SetCurrentDir(ActiveFrame.CurrentPath);
      end;

    edtCommand.Visible := True;
  end
  else
  begin
    lblCommandPath.Visible := False;
    edtCommand.Visible := False;
  end;
  // Change program current path
  Properties := ActiveFrame.FileSource.GetProperties;
  if (fspDirectAccess in Properties) and not (fspLinksToLocalFiles in Properties) then
  begin
    mbSetCurrentDir(ActiveFrame.CurrentPath);
  end;
end;

procedure TfrmMain.OnDriveGetFreeSpace(Data: PtrInt);
var
  aFileView: TFileView;
  sboxDrive: TPaintBox;
  lblDriveInfo: TLabel;
  AData: TFreeSpaceData absolute Data;
begin
  case AData.Panel of
    fpLeft :
      begin
        sboxDrive := pbxLeftDrive;
        aFileView := FrameLeft;
        lblDriveInfo :=lblLeftDriveInfo;
      end;
    fpRight:
      begin
        sboxDrive := pbxRightDrive;
        aFileView := FrameRight;
        lblDriveInfo := lblRightDriveInfo;
      end;
  end;
  if mbCompareFileNames(AData.Path, aFileView.CurrentPath) then
  begin
    if not AData.Result then
    begin
      lblDriveInfo.Caption := '';
      lblDriveInfo.Hint := '';
      sboxDrive.Hint := '';
      sboxDrive.Tag := -1;
      sboxDrive.Invalidate;
    end
    else begin
      if gDriveInd = True then
      begin
        if AData.TotalSize > 0 then
          sboxDrive.Tag := 100 - Round((AData.FreeSize / AData.TotalSize) * 100) // Save busy percent
        else begin
          sboxDrive.Tag := -1;
        end;
        sboxDrive.Invalidate;
      end;
      lblDriveInfo.Hint := Format(rsFreeMsg, [cnvFormatFileSize(AData.FreeSize, uoscHeader), cnvFormatFileSize(AData.TotalSize, uoscHeader)]);
      if gShortFormatDriveInfo then
        lblDriveInfo.Caption := Format(rsFreeMsgShort, [cnvFormatFileSize(AData.FreeSize, uoscHeader)])
      else begin
        lblDriveInfo.Caption := lblDriveInfo.Hint;
      end;
      sboxDrive.Hint := lblDriveInfo.Hint;
    end;
  end;
  AData.Free;
end;

procedure TfrmMain.UpdateFreeSpace(Panel: TFilePanelSelect; Clear: Boolean);
var
  aFileView: TFileView;
  sboxDrive: TPaintBox;
  lblDriveInfo: TLabel;
  AData: TFreeSpaceData;
begin
  case Panel of
    fpLeft :
      begin
        sboxDrive := pbxLeftDrive;
        aFileView := FrameLeft;
        lblDriveInfo :=lblLeftDriveInfo;
      end;
    fpRight:
      begin
        sboxDrive := pbxRightDrive;
        aFileView := FrameRight;
        lblDriveInfo := lblRightDriveInfo;
      end;
  end;

  if Clear then
  begin
    lblDriveInfo.Caption := '';
    lblDriveInfo.Hint := '';
    sboxDrive.Hint := '';
    sboxDrive.Tag := -1;
    sboxDrive.Invalidate;
  end;

  AData := TFreeSpaceData.Create;
  AData.Panel := Panel;
  AData.Path := aFileView.CurrentPath;
  AData.OnFinish := @OnDriveGetFreeSpace;
  AData.FileSource := aFileView.FileSource;
  TThread.ExecuteInThread(@AData.GetFreeSpaceInThread);
end;

procedure TfrmMain.CloseNotebook(ANotebook: TFileViewNotebook);
var
  I: Integer;
begin
  for I := 0 to ANotebook.PageCount - 1 do
    ANotebook.View[I].Clear;
  ANotebook.DestroyAllPages;
end;

procedure TfrmMain.DriveListDriveSelected(Sender: TObject; ADriveIndex: Integer;
  APanel: TFilePanelSelect);
begin
  SetPanelDrive(APanel, DrivesList.Items[ADriveIndex], True);
end;

procedure TfrmMain.DriveListClose(Sender: TObject);
begin
  SetActiveFrame(SelectedPanel);
end;

procedure TfrmMain.AllProgressOnUpdateTimer(Sender: TObject);
var
  AllProgressPoint: Integer;
begin
  if gPanelOfOp = True then
  begin
    FOperationsPanel.UpdateView;
  end;

  // Show progress in the menu
  if gProgInMenuBar = True then
  begin
    AllProgressPoint:= Round(OperationsManager.AllProgressPoint * 100);
    mnuAllOperProgress.Caption:= IntToStr(AllProgressPoint) + ' %';
  end;

  Sleep(0);
end;

procedure TfrmMain.OperationManagerNotify(Item: TOperationsManagerItem;
  Event: TOperationManagerEvent);
begin
  if Event = omevOperationRemoved then
  begin
    // Hide progress bar if there are no operations
    if OperationsManager.OperationsCount = 0 then
    begin
      mnuAllOperProgress.Visible:= False;
      mnuAllOperPause.Visible:= False;
      mnuAllOperStart.Visible:= False;
      mnuAllOperStop.Visible:= False;
    end;
    PlaySound(Item);
  end
  else if Event = omevOperationAdded then
  begin
    if gProgInMenuBar = True then
    begin
      mnuAllOperProgress.Visible:= True;
      mnuAllOperPause.Visible:= True;
      mnuAllOperStart.Visible:= True;
      mnuAllOperStop.Visible:= True;
    end;
  end;
  AllProgressOnUpdateTimer(Timer);
  Timer.Enabled := (gPanelOfOp or gProgInMenuBar) and
                   (OperationsManager.OperationsCount > 0);
end;

procedure TfrmMain.SetPanelDrive(aPanel: TFilePanelSelect; Drive: PDrive; ActivateIfNeeded: Boolean);
var
  Index: Integer;
  DrivePath: String;
  DriveIndex: Integer;
  FoundPath: Boolean = False;
  aFileView, OtherFileView: TFileView;
begin
  if (Drive^.DriveType in [dtSpecial, dtVirtual]) or IsAvailable(Drive, True) then
  begin
    case aPanel of
      fpLeft:
        begin
          aFileView := FrameLeft;
          OtherFileView := FrameRight;
        end;
      fpRight:
        begin
          aFileView := FrameRight;
          OtherFileView := FrameLeft;
        end;
    end;

    // Special case for special drive
    if Drive^.DriveType = dtSpecial then
    begin
      ChooseFileSource(aFileView, Drive^.Path);
      if ActivateIfNeeded and (tb_activate_panel_on_click in gDirTabOptions) then
        SetActiveFrame(aPanel);
      Exit;
    end;

    // Special case for virtual drive
    if Drive^.DriveType = dtVirtual then
    begin
      if Drive^.FileSystem = 'VFS' then
        Commands.DoOpenVirtualFileSystemList(aFileView)
      else begin
        ChooseFileSource(aFileView, GetNetworkPath(Drive));
        if ActivateIfNeeded and (tb_activate_panel_on_click in gDirTabOptions) then
          SetActiveFrame(aPanel);
      end;
      Exit;
    end;

    DrivePath:= ExcludeTrailingPathDelimiter(Drive^.Path);
    // Copy path opened in the other panel if the file source and drive match
    // and that path is not already opened in this panel.
    if (not gGoToRoot) and OtherFileView.FileSource.IsClass(TFileSystemFileSource) and
       mbCompareFileNames(ExtractRootDir(OtherFileView.CurrentPath), DrivePath) and
       not mbCompareFileNames(OtherFileView.CurrentPath, aFileView.CurrentPath) then
    begin
      FoundPath:= True;
      SetFileSystemPath(aFileView, OtherFileView.CurrentPath);
    end
    // Open archive parent directory
    else if (gGoToRoot = False) and OtherFileView.FileSource.IsClass(TArchiveFileSource) and
       (not IsInPath(GetTempFolder, OtherFileView.FileSource.CurrentAddress, True, False)) and
       mbCompareFileNames(ExtractRootDir(OtherFileView.FileSource.CurrentAddress), DrivePath) and
       not mbCompareFileNames(ExtractFilePath(OtherFileView.FileSource.CurrentAddress), aFileView.CurrentPath) then
    begin
      FoundPath:= True;
      SetFileSystemPath(aFileView, ExtractFilePath(OtherFileView.FileSource.CurrentAddress));
    end
    // Open latest path from history for chosen drive
    else if (gGoToRoot = False) and aFileView.FileSource.IsClass(TFileSystemFileSource) and
            not mbCompareFileNames(ExtractRootDir(aFileView.CurrentPath), DrivePath) then
    begin
      for Index:= 0 to glsDirHistory.Count - 1 do
      begin
        DriveIndex:= FindMatchingDrive(EmptyStr, glsDirHistory[Index]);
        if (DriveIndex >= 0) and (DriveIndex < DrivesList.Count) then
        begin
          if mbCompareFileNames(Drive^.Path, DrivesList[DriveIndex]^.Path) then
          begin
            if mbDirectoryExists(ExcludeBackPathDelimiter(glsDirHistory[Index])) then
            begin
              SetFileSystemPath(aFileView, glsDirHistory[Index]);
              FoundPath:= True;
              Break;
            end;
          end;
        end;
      end;
    end;

    if not FoundPath then begin
      SetFileSystemPath(aFileView, Drive^.Path);
    end;

    if ActivateIfNeeded and (tb_activate_panel_on_click in gDirTabOptions) then
      SetActiveFrame(aPanel);
  end
  else
  begin
    msgWarning(rsMsgDiskNotAvail);

    // Restore previous selected button.
    case aPanel of
      fpLeft:
        UpdateSelectedDrive(LeftTabs);
      fpRight:
        UpdateSelectedDrive(RightTabs);
    end;
  end;
end;

procedure TfrmMain.OnDriveWatcherEvent(EventType: TDriveWatcherEvent; const ADrive: PDrive);
begin
  // Update disk panel does not work correctly when main
  // window is minimized. So set FUpdateDiskCount flag instead
  // and update disk count later in WindowStateChange event
  if WindowState = wsMinimized then
    FUpdateDiskCount:= True
  else begin
    UpdateDiskCount;
  end;

  if (FrameLeft = nil) or (FrameRight = nil) then Exit;

  if (EventType = dweDriveRemoved) and Assigned(ADrive) then
  begin
    if IsInPath(ADrive^.Path, ActiveFrame.CurrentPath, True, True) then
      ActiveFrame.CurrentPath:= GetHomeDir
    else if IsInPath(ADrive^.Path, NotActiveFrame.CurrentPath, True, True) then
      NotActiveFrame.CurrentPath:= GetHomeDir;
  end;

  UpdateSelectedDrives;
end;

procedure TfrmMain.DelayedEvent(Data: PtrInt);
begin
  { discard duplicate calls, accept last call only }
  Dec(FDelayedEventCtr);
  if FDelayedEventCtr > 0 then
    Exit;
  { update restored bounds }
  if WindowState = wsNormal then
  begin
    if FDelayedWMMove then
    begin
      FRestoredLeft := Left;
      FRestoredTop := Top;
    end;
    if FDelayedWMSize then
    begin
      FRestoredWidth := Width;
      FRestoredHeight := Height;
    end;
  end;
  FDelayedWMMove := False;
  FDelayedWMSize := False;

  // Sync position and size with real main form
  with BoundsRect do
    Application.MainForm.SetBounds(Left, Top, Width, Height);
end;

procedure TfrmMain.AppActivate(Sender: TObject);
begin
  if Assigned(FrameLeft) then
    FrameLeft.ReloadIfNeeded;
  if Assigned(FrameRight) then
    FrameRight.ReloadIfNeeded;
end;

procedure TfrmMain.AppDeActivate(Sender: TObject);
begin
  if Assigned(frmTreeViewMenu) then
  begin
    frmTreeViewMenu.Close;
  end;
  Application.CancelHint;
end;

procedure TfrmMain.AppEndSession(Sender: TObject);
var
  CloseAction: TCloseAction;
begin
  frmMainClose(Sender, CloseAction);
end;

procedure TfrmMain.AppThemeChange(Sender: TObject);

  procedure UpdateNoteBook(NoteBook: TFileViewNotebook);
  var
    Index: Integer;
  begin
    for Index := 0 to NoteBook.PageCount - 1 do
    begin
      NoteBook.View[Index].UpdateColor;
    end;
  end;

var
  Index: Integer;
begin
  UpdateNoteBook(LeftTabs);
  UpdateNoteBook(RightTabs);

  ColSet.UpdateStyle;
  gColorExt.UpdateStyle;
  gHighlighters.UpdateStyle;

  DCDebug('AppThemeChange');

  for Index:= 0 to Screen.CustomFormCount - 1 do
  begin
    Screen.CustomForms[Index].Perform(CM_THEMECHANGED, 0, 0);
  end;
end;

procedure TfrmMain.AppQueryEndSession(var Cancel: Boolean);
var
  CanClose: Boolean = True;
begin
  FormCloseQuery(Self, CanClose);
  Cancel := not CanClose;
end;

{$IFDEF DARWIN}
procedure TfrmMain.createDarwinAppMenu;
var
  appMenu: TMenuItem;
  aboutItem: TMenuItem;
  sepItem: TMenuItem;
  prefItem: TMenuItem;
begin
  appMenu:= TMenuItem.Create(mnuMain);
  appMenu.Caption:= '';
  mnuMain.Items.Insert(0, appMenu);

  aboutItem:= TMenuItem.Create(mnuMain);
  aboutItem.Caption:= 'About ' + Application.Title;
  aboutItem.OnClick:= @aboutOnClick;
  appMenu.Add(aboutItem);

  sepItem := TMenuItem.Create(mnuMain);
  sepItem.Caption := '-';
  appMenu.Add(sepItem);

  prefItem := TMenuItem.Create(mnuMain);
  prefItem.Caption := 'Preferences...';
  prefItem.OnClick := @optionsOnClick;
  prefItem.Shortcut := ShortCut(VK_OEM_COMMA, [ssMeta]);
  appMenu.Add(prefItem);
end;

procedure TfrmMain.aboutOnClick(Sender: TObject);
begin
  Commands.cm_About([]);
end;

procedure TfrmMain.optionsOnClick(Sender: TObject);
begin
  Commands.cm_Options([]);
end;
{$ENDIF}

{$IF (DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)) and not DEFINED(MSWINDOWS)}
function TfrmMain.QObjectEventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
begin
  Result:= False;
  case QEvent_type(Event) of
    QEventApplicationPaletteChange:
    begin
      ThemeServices.IntfDoOnThemeChange;
    end;
    QEventClose:
    begin
      TQtWidget(Self.Handle).SlotClose;
      Result:= CloseQueryResult;
      if Result then
        QEvent_accept(Event)
      else
        QEvent_ignore(Event);
    end;
  end;
end;
{$ENDIF}

initialization
  {$I DragCursors.lrs}
  TFormCommands.RegisterCommandsForm(TfrmMain, HotkeysCategory, @rsHotkeyCategoryMain);

end.

