(*
    Tux Commander - UMain - Main form and window-related functions
    Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
    Check for updates on tuxcmd.sourceforge.net

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

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

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)
unit UMain;

interface

uses
  gtk2, gdk2, gdk2pixbuf, glib2, pango, StrUtils, SysUtils, Types, Classes, DateUtils,
  GTKForms, GTKControls, GTKMenus, GTKStdCtrls, GTKExtCtrls, GTKView, GTKConsts, GTKUtils,
  GTKClasses, GTKPixbuf, UEngines, UConfig, UGnome, UVFSCore, UCoreClasses;

type
  TFMain = class(TGTKForm)
    MainVBox : TGTKVBox;
    MainMenuHandleBox : TGTKHandleBox;
    MainMenu : TGTKMenuBar;
    LeftPanelBox, RightPanelBox : TGTKVBox;
    PanelSeparator : TGTKHPaned;
    LeftStatusBox, RightStatusBox, LeftPathLabelHBox, RightPathLabelHBox : TGTKHBox;
    LeftPathLabel, RightPathLabel : TGTKLabel;
    LeftPathLabelEventBox, RightPathLabelEventBox : TGTKEventBox;
    LeftScrolledWindow, RightScrolledWindow : TGTKScrolledWindow;
    LeftListView, RightListView: TGTKListView;
    LeftStatusLine, RightStatusLine : TGTKLabel;
    LeftUpButton, LeftRootButton, LeftHomeButton, RightUpButton, RightRootButton, RightHomeButton,
    LeftBookmarkButton, RightBookmarkButton : TGTKButton;
    LeftEqualButton, RightEqualButton : TGTKButton;
    LeftDiskInfoLabel, RightDiskInfoLabel : TGTKLabel;
    ButtonsBox : TGTKTable;
    F2Button, F3Button, F4Button, F5Button, F6Button, F7Button, F8Button : TGTKButton;
    mnuFile, mnuMark, mnuCommands, mnuShow, mnuSettings, mnuHelp : TGTKMenuItem;
    miExit : TGTKMenuItem;
    miSelectGroup, miUnselectGroup, miSelectAll, miUnselectAll, miInvertSelection : TGTKMenuItem;
    miRefresh : TGTKMenuItem;
    miShowDotFiles : TGTKMenuItem;
    miFileTypes: TGTKMenuItem;
    miAbout : TGTKMenuItem;
    miVerifyChecksums, miCreateChecksums : TGTKMenuItem;
    miSplitFile, miMergeFiles : TGTKMenuItem;
    miChangePermissions, miChangeOwner: TGTKMenuItem;
    miCreateSymlink, miEditSymlink: TGTKMenuItem;
    LeftQuickFindVBox, RightQuickFindVBox: TGTKVBox;
    LeftQuickFindHBox, RightQuickFindHBox: TGTKHBox;
    LeftQuickFindLabel, RightQuickFindLabel: TGTKLabel;
    LeftQuickFindEntry, RightQuickFindEntry: TGTKEntry;
    LeftQuickFindSeparator, RightQuickFindSeparator, ButtonBoxSeparator: TGTKHSeparator;
    CommandLineHBox: TGTKHBox;
    CommandLineCombo: TGTKCombo;
    CommandLineLabel: TGTKLabel;
    SplitterPopupMenu: TGTKMenuItem;
    FilePopupMenu: TGTKMenuItem;
    miPreferences: TGTKMenuItem;
    mnuBookmarks, miAddBookmark, miEditBookmarks, BookmarkPopup, BookmarkPopupDelete, miBookmarksSeparator: TGTKMenuItem;
    miShowDirectorySizes, miTargetSource: TGTKMenuItem;
    miCopyNames, miCopyFullPaths: TGTKMenuItem;
    ButtonBoxSpace: TGTKEventBox;
    MounterBarHandleBox : TGTKHandleBox;
    MounterBarTable: TGTKTable;
    MounterButtonPopupMenu, miMount, miUmount, miEject: TGTKMenuItem;
    miMounterSettings, miNoMounterBar, miShowOneMounterBar, miShowTwoMounterBar: TGTKMenuItem;
    LeftMounterTable, RightMounterTable: TGTKTable;
    miColumns: TGTKMenuItem;
    mnuPlugins, miTestPlugin: TGTKMenuItem; 
    miSavePosition: TGTKMenuItem;
    LeftPanelNotebook, RightPanelNotebook: TEphyNotebook;
    LeftListBox, RightListBox: TGTKVBox;
    TabPopupMenu, miDuplicateTab, miCloseTab, miCloseAllTabs: TGTKMenuItem;
    mnuNetwork, miConnections, miOpenConnection, miQuickConnect, miDisconnect: TGTKMenuItem;
    miSearch: TGTKMenuItem;
    LeftDisconnectButton, RightDisconnectButton, LeftLeaveArchiveButton, RightLeaveArchiveButton: TGTKButton;
    OpenTerminalButton: TGTKButton; 
    miNewTab: TGTKMenuItem;
    LeftPasswordButton, RightPasswordButton: TGTKImageButton;
    PathBoxPopupMenu: TGTKMenuItem;
    miPathBoxRefresh, miPathBoxCopyPath: TGTKMenuItem;
    procedure FormCreate(Sender: TObject); override;
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure PanelSeparatorResize(Sender: TObject);
    procedure PanelSeparatorMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure miExitClick(Sender: TObject);
    procedure miAboutClick(Sender: TObject);
    procedure miRefreshClick(Sender: TObject);
    procedure mnuMarkClick(Sender: TObject);
    procedure ListViewKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
    procedure ListViewEnter(Sender: TObject; var Accept: boolean);
    procedure PathLabelMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure PathButtonClick(Sender: TObject);
    function CompareFunc(Sender: TObject; var model: PGtkTreeModel; var a, b: PGtkTreeIter): integer;
    procedure F5ButtonClick(Sender: TObject);
    procedure F6ButtonClick(Sender: TObject);
    procedure F7ButtonClick(Sender: TObject);
    procedure F8ButtonClick(Sender: TObject);
    procedure ListViewDblClick(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure ListViewMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure ListViewMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure ListViewMouseMove(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure InplaceEditTimerTimer(Sender: TObject);
    procedure ListViewEdited(Sender: TObject; Column: TGTKTreeViewColumn; Item: TGTKListItem; var NewText: string; var AllowChange: boolean; var DataColumn: integer);
    procedure ListViewSelectionChanged(Sender: TObject);
    procedure QuickFindEntryEnter(Sender: TObject; var Accept: boolean);
    procedure miVerifyChecksumsClick(Sender: TObject);
    procedure miCreateChecksumsClick(Sender: TObject);
    procedure miMergeFilesClick(Sender: TObject);
    procedure miSplitFileClick(Sender: TObject);
    procedure miShowDotFilesClick(Sender: TObject);
    procedure F3F4ButtonClick(Sender: TObject);
    procedure miFileTypesClick(Sender: TObject);
    procedure CommandLineComboKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
    procedure FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
    procedure InactiveItemsTimerTimer(Sender: TObject);
    function OldGTKConvertToSorted(Sender: TObject; const Index: integer): integer;
    function OldGTKConvertFromSorted(Sender: TObject; const Index: integer): integer;
    procedure SplitterPopupMenuClick(Sender: TObject);
    procedure miChangePermissionsClick(Sender: TObject);
    procedure miChangeOwnerClick(Sender: TObject);
    procedure miCreateSymlinkClick(Sender: TObject);
    procedure miEditSymlinkClick(Sender: TObject);
    procedure FilePopupMenuPopup(Sender: TObject);
    procedure FilePopupMenuItemClick(Sender: TObject);
    procedure miPreferencesClick(Sender: TObject);
    procedure miAddBookmarkClick(Sender: TObject);
    procedure miBookmarkClick(Sender: TObject);
    procedure BookmarkPopupDeleteClick(Sender: TObject);
    procedure BookmarkItemMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure BookmarkButtonClick(Sender: TObject);
    procedure mnuBookmarksPopup(Sender: TObject);
    procedure miShowDirectorySizesClick(Sender: TObject);
    procedure miTargetSourceClick(Sender: TObject);
    procedure MounterButtonClick(Sender: TObject);
    procedure MounterButtonPopupMenuPopup(Sender: TObject);
    procedure MounterButtonMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure miMountClick(Sender: TObject);
    procedure miUmountClick(Sender: TObject);
    procedure miEjectClick(Sender: TObject);
    procedure miMounterSettingsClick(Sender: TObject);
    procedure miShowMounterBarClick(Sender: TObject);
    procedure miColumnsClick(Sender: TObject);
    procedure ListViewColumnsChanged(Sender: TObject);
    procedure RebuildListViewsTimerTimer(Sender: TObject);
    procedure miTestPluginClick(Sender: TObject);
    procedure miPluginAboutClick(Sender: TObject);
    procedure miSavePositionClick(Sender: TObject);
    procedure TabNotebookSwitchPage(Sender: TObject; const NewTabNum: integer; const ShouldFocus: boolean);
    procedure miDuplicateTabClick(Sender: TObject);
    procedure miCloseTabClick(Sender: TObject);
    procedure miCloseAllTabsClick(Sender: TObject);
    procedure TabPopupMenuPopup(Sender: TObject);
    procedure miOpenConnectionClick(Sender: TObject);
    procedure miSearchClick(Sender: TObject);
    procedure miDisconnectClick(Sender: TObject);
    procedure DisconnectButtonClick(Sender: TObject);
    procedure LeaveArchiveButtonClick(Sender: TObject);
    procedure OpenTerminalButtonClick(Sender: TObject);
    procedure ListViewColumnClicked(Sender: TObject);
    procedure NotebookReordered(Sender: TObject; const Source, Dest: integer);
    procedure NotebookTabClosed(Sender: TObject; const TabNum: integer; var CanClose: boolean);
    procedure NotebookTabDoubleClick(Sender: TObject; const TabNum: integer);
    function NotebookFindNotebookAtPointerEvent(Sender: TObject; const AbsX, AbsY: integer): TEphyNotebook;
    function NotebookMoveTabToAnotherNotebook(Sender: TObject; Destination: TEphyNotebook; const SourceTabNo, DestTabNo: integer): boolean;
    procedure NotebookTabFocusOnlyEvent(Sender: TObject; const TabNum: integer);
    procedure miFilePropertiesClick(Sender: TObject);
    procedure PasswordButtonClick(Sender: TObject);
    procedure miPathBoxCopyPathClick(Sender: TObject);
    procedure miCopyNamesClick(Sender: TObject);
    procedure RightMouseSelectPopupTimerTimer(Sender: TObject);
    procedure miQuickConnectClick(Sender: TObject);
  private
    LeftLastFocused, Editing, QuickFind, RedrawLeftInactive, RedrawRightInactive, StartUp, LeftTabPopup: boolean;
    LastWidth, RunningEscSensitive: integer;
    InplaceEditTimer, InactiveItemsTimer, RebuildListViewsTimer, RightMouseSelectPopupTimer: TGTKTimer;
    InplaceEditItem: TGTKListItem;
    SavedCmdLine: string;
    LastMounterButton: TGTKButton;
    MounterTableList, MounterTableListLeft, MounterTableListRight: TList;
    LeftNotebookBoxList, RightNotebookBoxList: TList;
    LeftPathsHighlight, RightPathsHighlight: TStringList;
    LeftTabEngines, RightTabEngines: TList;
    LastUsedFilter: string;
    PanelRightMouseSelMode, PanelRightMouseInProgress: boolean;
    procedure ConstructPanels;
    procedure ConstructMenu;
    procedure ConstructColumns(ListView: TGTKListView);
    procedure AfterStart;
    procedure ActivateItem(const ItemIndex: longint);
    procedure UpdatePanelInfo;
    procedure UpdatePanelInfoDown(LeftPanel: boolean);
    procedure UpdateCaption;
    function FormatPathString(Engine: TPanelEngine): string;
    procedure ChangingDir(LeftPanel: boolean; NewPath: string; HiliString1: string = ''; HiliString2: string = ''; const PreserveSelection: boolean = False; const AutoFallback: boolean = False; Plugin: TVFSPlugin = nil);
    procedure DoSelect(SelectType: integer);
    procedure ListViewCellDataFunc(Sender: TObject; tree_view: PGtkTreeView; tree_column : PGtkTreeViewColumn; cell : PGtkCellRenderer; tree_model : PGtkTreeModel; iter : PGtkTreeIter);
    procedure DoGetDirSize(AllItems: boolean);
    procedure DoDelete(LeftPanel: boolean; ListView: TGTKListView; Engine: TPanelEngine; DataList: TList);
    procedure DoCopyMove(LeftPanel, CopyMode, ShiftPressed: boolean; ListView: TGTKListView; Engine: TPanelEngine; DataList: TList);
    procedure DoRefresh(LeftPanel, StaySame, AutoFallback: boolean);
    procedure DoQuickRename(LeftPanel: boolean; ListView: TGTKListView; const CalledFromKey: boolean);
    procedure ActivateQuickFind(LeftPanel: boolean);
    procedure DeactivateQuickFind(LeftPanel: boolean);
    function QuickFindSendKey(LeftPanel: boolean; Key: word): boolean;
    procedure ProcessMarkKey(KeyType, Key: integer);
    procedure SwitchOtherPanel(LeftPanel, RequestNewAltO: boolean);
    procedure EditViewFile(LeftPanel: boolean; AListView: TGTKListView; View, NewFile: boolean);
    procedure RunFile(Path: string; Engine: TPanelEngine; CustomAction: integer);
    function ActivateCommandLine(Key: word; const ActualPosition: boolean = False): boolean;
    procedure ApplySettings(RebuildListViews, RebuildIcons, Startup: boolean);
    procedure RefreshBookmarksMenu;
    procedure PopupFileMenuPos;
    procedure HandleFormFocusIn;
    procedure SwitchPanelCtrlLeftRight(LeftPanel, LeftArrowPressed: boolean);
    procedure FillMounterBar;
    procedure RebuildListViews(DoRefresh: boolean);
    procedure FillPluginMenu;
    procedure NewTab(LeftPanel, SendSelectedDirToBg: boolean; CustomPath: string = '');
    procedure SwitchTab(TabNo: integer; LeftPanel, SetFocus: boolean);
    procedure CloseTab(TabNo: integer; LeftPanel, CloseVFSEngine: boolean);
    procedure AddTabs(LeftPanel: boolean; TabList: TStringList; TabSortIDs, TabSortTypes: TList; SetTabActive: integer);
    function HandleVFSArchive(LeftPanel: boolean; const FullPath, HighlightItem, TargetPath: string): boolean;
    function CloseVFS(LeftPanel, SuppressRefresh: boolean): string;
    procedure ShowBookmarkQuick(LeftPanel: boolean);
    procedure SetTabLabel(Notebook: TEphyNotebook; PageIndex: integer; ALabel, Tooltip: string);
    procedure NewTabInternal(LeftPanel: boolean; _Engine: TPanelEngine; _Path: string; NewTabPosition: integer; SwitchToNewTab: boolean);
    procedure CopyFilenamesToClipboard(FullPaths, LeftPanel: boolean);
    function HandleRunFromArchive(var APath: string; Engine: TPanelEngine; Command, FileTypeDesc: string; BypassDialog: boolean): boolean;
    function ExtractFromArchive(var NewPath: string; Engine: TPanelEngine; const FilePath: string; ExtractAll: boolean): boolean;
    function HandleKey(Key: Word; Shift: TShiftState; LeftPanel: boolean): boolean;
    function IsEditing(AListView: TGTKListView): boolean;
    function PanelFindEditableWidget(AListView: TGTKListView): PGtkWidget;
    function CheckForUnsavedConnection(Engine: TVFSEngine; AllowCancel: boolean): boolean;  //  Returns False to Cancel
    procedure SaveCursorPositionTabbed(LeftPanel: boolean);
  public
    LeftPanelEngine, RightPanelEngine : TPanelEngine;
    ColumnSortIDs: array[1..ConstNumPanelColumns] of integer;
    LastClick: TDateTime;
    procedure EditViewFileInternal(ParentWindow: TGTKControl; Filename: string; Engine: TPanelEngine; View, NewFile: boolean);
  end;

var
  FMain: TFMain;

implementation

uses ULibc,
  UCore, USelect, UNewDir, UDirDelete, UProgress, UCopyMove,
  UCoreUtils, ULocale, UChecksum, UChecksumDruid, USplitFile,
  UFileTypeSettings, UFileAssoc, UChmod, UChown, USymlink,
  UPreferences, UViewer, UToolTips, UMounterPrefs, UColumns,
  UTestPlugin, UConnectionManager, USearch, UProperties,
  URemoteWait, URunFromVFS, uVFSprototypes, UQuickConnect,
  UConnectionProperties;



(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
function form_event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl; forward;

procedure TFMain.FormCreate(Sender: TObject);
begin
  ReportGTKVersion;
  SetupAppIcon;
  StartUp := True;
  RunningEscSensitive := 0;
  Editing := False;
  QuickFind := False;
  LeftTabPopup := True;
  LastClick := 0;
  PanelRightMouseSelMode := True;
  PanelRightMouseInProgress := False;
  LastUsedFilter := '*.*';
  RedrawLeftInactive := False;
  RedrawRightInactive := False;
  MounterTableList := TList.Create;
  MounterTableListLeft := TList.Create;
  MounterTableListRight := TList.Create;
  LeftNotebookBoxList := TList.Create;
  RightNotebookBoxList := TList.Create;
  LeftPathsHighlight := TStringList.Create;
  RightPathsHighlight := TStringList.Create;
  LeftTabEngines := TList.Create;
  RightTabEngines := TList.Create;
  Caption := ConstAppTitle;
  WindowPosition := wpCenter;
  MainVBox := TGTKVBox.Create(Self);
  AddControl(MainVBox);
  MainMenuHandleBox := TGTKHandleBox.Create(Self);
  MainVBox.AddControlEx(MainMenuHandleBox, False, True, 0);
  MainMenu := TGTKMenuBar.Create(Self);
  MainMenuHandleBox.AddControl(MainMenu);
  ConstructMenu;
  InplaceEditTimer := TGTKTimer.Create(Self);
  InplaceEditTimer.Enabled := False;
  InplaceEditTimer.OnTimer := InplaceEditTimerTimer;
  InactiveItemsTimer := TGTKTimer.Create(Self);
  InactiveItemsTimer.Enabled := False;
  InactiveItemsTimer.Interval := ConfInactiveTimerDelay;
  if not Application.GTKVersion_2_6_0_Up then InactiveItemsTimer.OnTimer := InactiveItemsTimerTimer;
  RebuildListViewsTimer := TGTKTimer.Create(Self);
  RebuildListViewsTimer.Enabled := False;
  RebuildListViewsTimer.OnTimer := RebuildListViewsTimerTimer;
  RightMouseSelectPopupTimer := TGTKTimer.Create(Self);
  RightMouseSelectPopupTimer.Enabled := False;
  RightMouseSelectPopupTimer.OnTimer := RightMouseSelectPopupTimerTimer;
  MounterBarHandleBox := TGTKHandleBox.Create(Self);
  MounterBarHandleBox.SetSizeRequest(10, -1);
  MainVBox.AddControlEx(MounterBarHandleBox, False, True, 0);
  MounterBarTable := TGTKTable.Create(Self);
  MounterBarHandleBox.AddControl(MounterBarTable);
  LeftPanelBox := TGTKVBox.Create(Self);
  RightPanelBox := TGTKVBox.Create(Self);
  PanelSeparator := TGTKHPaned.Create(Self);
  MainVBox.AddControlEx(PanelSeparator, True, True, 0);
  PanelSeparator.Child1 := LeftPanelBox;
  PanelSeparator.Child2 := RightPanelBox;
  ConstructPanels;
  CommandLineHBox := TGTKHBox.Create(Self);
  CommandLineHBox.Homogeneous := False;
  CommandLineCombo := TGTKCombo.Create(Self);
  CommandLineCombo.DisableActivate;
  CommandLineCombo.MatchValue := False;
  CommandLineCombo.CaseSensitive := True;
//  CommandLineCombo.Entry.OnKeyDown := CommandLineComboKeyDown;
  CommandLineLabel := TGTKLabel.Create(Self);
  CommandLineLabel.Alignment := taRightJustify;
  CommandLineLabel.SetAlignment(1, 0.5);
  CommandLineLabel.SetSizeRequest(300, -1);
  OpenTerminalButton := TGTKButton.Create(Self);
  OpenTerminalButton.Caption := LANGOpenTerminalButton_Caption;
  OpenTerminalButton.BorderStyle := bsNone;
  OpenTerminalButton.CanFocus := False;
  OpenTerminalButton.Tooltip := LANGOpenTerminalButton_Tooltip;
  OpenTerminalButton.OnClick := OpenTerminalButtonClick;
  CommandLineHBox.AddControlEx(CommandLineLabel, False, False, 0);
  CommandLineHBox.AddControlEx(CommandLineCombo, True, True, 5);
  CommandLineHBox.AddControlEx(TGTKVSeparator.Create(Self), False, False, 2);
  CommandLineHBox.AddControlEx(OpenTerminalButton, False, False, 2);
  MainVBox.AddControlEx(TGTKHSeparator.Create(Self), False, False, 2);
  MainVBox.AddControlEx(CommandLineHBox, False, False, 0);
  ButtonsBox := TGTKTable.Create(Self);
  F2Button := TGTKButton.Create(Self);
  F3Button := TGTKButton.Create(Self);
  F4Button := TGTKButton.Create(Self);
  F5Button := TGTKButton.Create(Self);
  F6Button := TGTKButton.Create(Self);
  F7Button := TGTKButton.Create(Self);
  F8Button := TGTKButton.Create(Self);
  ButtonsBox.AddControlEx(0, 0, 1, 1, F2Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(1, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(2, 0, 1, 1, F3Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(3, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(4, 0, 1, 1, F4Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(5, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(6, 0, 1, 1, F5Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(7, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(8, 0, 1, 1, F6Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(9, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(10, 0, 1, 1, F7Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonsBox.AddControlEx(11, 0, 1, 1, TGTKVSeparator.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 4);
  ButtonsBox.AddControlEx(12, 0, 1, 1, F8Button, [taoExpand, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  ButtonBoxSeparator := TGTKHSeparator.Create(Self);
  ButtonBoxSpace := TGTKEventBox.Create(Self);
  MainVBox.AddControlEx(ButtonBoxSpace, False, False, 2);
  MainVBox.AddControlEx(ButtonBoxSeparator, False, False, 2);
  MainVBox.AddControlEx(ButtonsBox, False, False, 0);
  F2Button.BorderStyle := bsNone;
  F3Button.BorderStyle := bsNone;
  F4Button.BorderStyle := bsNone;
  F5Button.BorderStyle := bsNone;
  F6Button.BorderStyle := bsNone;
  F7Button.BorderStyle := bsNone;
  F8Button.BorderStyle := bsNone;
  F2Button.Caption := LANGF2Button_Caption;
  F3Button.Caption := LANGF3Button_Caption;
  F4Button.Caption := LANGF4Button_Caption;
  F5Button.Caption := LANGF5Button_Caption;
  F6Button.Caption := LANGF6Button_Caption;
  F7Button.Caption := LANGF7Button_Caption;
  F8Button.Caption := LANGF8Button_Caption;
  F2Button.OnClick := F6ButtonClick;
  F3Button.OnClick := F3F4ButtonClick;
  F4Button.OnClick := F3F4ButtonClick;
  F5Button.OnClick := F5ButtonClick;
  F6Button.OnClick := F6ButtonClick;
  F7Button.OnClick := F7ButtonClick;
  F8Button.OnClick := F8ButtonClick;
  F2Button.CanFocus := False;
  F3Button.CanFocus := False;
  F4Button.CanFocus := False;
  F5Button.CanFocus := False;
  F6Button.CanFocus := False;
  F7Button.CanFocus := False;
  F8Button.CanFocus := False;
  PanelSeparator.OnMouseUp := PanelSeparatorMouseUp;

  //  Events
  OnResize := FormResize;
  OnDestroy := FormDestroy;
  OnKeyDown := FormKeyDown;
  OnClose := FormClose;
  g_signal_connect_after(FWidget, 'event-after', G_CALLBACK(@form_event_handler), nil);
  PanelSeparator.OnResize := PanelSeparatorResize;
  LeftListView.OnKeyDown := ListViewKeyDown;
  RightListView.OnKeyDown := ListViewKeyDown;
  LeftListView.OnEnter := ListViewEnter;
  RightListView.OnEnter := ListViewEnter;
  LeftPathLabelEventBox.OnMouseDown := PathLabelMouseDown;
  RightPathLabelEventBox.OnMouseDown := PathLabelMouseDown;
  LeftUpButton.OnClick := PathButtonClick;
  LeftRootButton.OnClick := PathButtonClick;
  LeftHomeButton.OnClick := PathButtonClick;
  RightUpButton.OnClick := PathButtonClick;
  RightRootButton.OnClick := PathButtonClick;
  RightHomeButton.OnClick := PathButtonClick;
  LeftEqualButton.OnClick := miTargetSourceClick;
  RightEqualButton.OnClick := miTargetSourceClick;
  LeftDisconnectButton.OnClick := DisconnectButtonClick;
  RightDisconnectButton.OnClick := DisconnectButtonClick;
  LeftLeaveArchiveButton.OnClick := LeaveArchiveButtonClick;
  RightLeaveArchiveButton.OnClick := LeaveArchiveButtonClick;
  LeftListView.CompareFunc := CompareFunc;
  RightListView.CompareFunc := CompareFunc;
  LeftListView.CellDataFunc := ListViewCellDataFunc;
  RightListView.CellDataFunc := ListViewCellDataFunc;
  LeftListView.OnMouseDown := ListViewMouseDown;
  RightListView.OnMouseDown := ListViewMouseDown;
  LeftListView.OnMouseUp := ListViewMouseUp;
  RightListView.OnMouseUp := ListViewMouseUp;
{  LeftListView.OnDblClick := ListViewDblClick;
  RightListView.OnDblClick := ListViewDblClick;}
  LeftListView.OnSelectionChanged := ListViewSelectionChanged;
  RightListView.OnSelectionChanged := ListViewSelectionChanged;
  LeftListView.OnMouseMove := ListViewMouseMove;
  RightListView.OnMouseMove := ListViewMouseMove;
  LeftQuickFindEntry.OnEnter := QuickFindEntryEnter;
  RightQuickFindEntry.OnEnter := QuickFindEntryEnter;
  LeftPanelNotebook.OnTabSwitched := TabNotebookSwitchPage;
  RightPanelNotebook.OnTabSwitched := TabNotebookSwitchPage;
  LeftPanelNotebook.PopupMenu := TabPopupMenu;
  RightPanelNotebook.PopupMenu := TabPopupMenu;
  AfterStart;
end;

procedure TFMain.ConstructPanels;
begin
  LeftMounterTable := TGTKTable.Create(Self);
  LeftMounterTable.SetSizeRequest(10, -1);
  RightMounterTable := TGTKTable.Create(Self);
  RightMounterTable.SetSizeRequest(10, -1);
  LeftMounterTable.BorderWidth := 2;
  RightMounterTable.BorderWidth := 2;
  LeftPanelBox.AddControlEx(LeftMounterTable, False, False, 0);
  RightPanelBox.AddControlEx(RightMounterTable, False, False, 0);
  LeftStatusBox := TGTKHBox.Create(Self);
  RightStatusBox := TGTKHBox.Create(Self);
  LeftStatusBox.Homogeneous := False;
  RightStatusBox.Homogeneous := False;
  LeftPathLabel := TGTKLabel.Create(Self);
  RightPathLabel := TGTKLabel.Create(Self);
  LeftPathLabel.SetSizeRequest(10, -1);
  RightPathLabel.SetSizeRequest(10, -1);
  LeftPathLabelEventBox := TGTKEventBox.Create(Self);
  RightPathLabelEventBox := TGTKEventBox.Create(Self);
  LeftPathLabelHBox := TGTKHBox.Create(Self);
  RightPathLabelHBox := TGTKHBox.Create(Self);
  LeftPathLabelHBox.Homogeneous := False;
  RightPathLabelHBox.Homogeneous := False;
  LeftBookmarkButton := TGTKButton.Create(Self);    LeftBookmarkButton.Caption := '❇';
  RightBookmarkButton := TGTKButton.Create(Self);   RightBookmarkButton.Caption := '❇';
  LeftPasswordButton := TGTKImageButton.Create(Self);   LeftPasswordButton.Icon := StockLock16;
  RightPasswordButton := TGTKImageButton.Create(Self);   RightPasswordButton.Icon := StockLock16;
  LeftPasswordButton.BorderStyle := bsNone;
  RightPasswordButton.BorderStyle := bsNone;
  LeftBookmarkButton.SetSizeRequest(22, 22);        LeftBookmarkButton.Tooltip := LANGBookmarkButton_Tooltip + ' (Ctrl+D)';
  RightBookmarkButton.SetSizeRequest(22, 22);       RightBookmarkButton.Tooltip := LANGBookmarkButton_Tooltip + ' (Ctrl+D)';
  LeftPasswordButton.SetSizeRequest(28, 22);        LeftPasswordButton.Tooltip := LANGPasswordButton_Tooltip;
  RightPasswordButton.SetSizeRequest(28, 22);       RightPasswordButton.Tooltip := LANGPasswordButton_Tooltip;
  LeftPasswordButton.Visible := False;
  RightPasswordButton.Visible := False;
  LeftBookmarkButton.CanFocus := False;
  RightBookmarkButton.CanFocus := False;
  LeftPasswordButton.CanFocus := False;
  RightPasswordButton.CanFocus := False;
  LeftBookmarkButton.OnClick := BookmarkButtonClick;
  RightBookmarkButton.OnClick := BookmarkButtonClick;
  LeftPasswordButton.OnClick := PasswordButtonClick;
  RightPasswordButton.OnClick := PasswordButtonClick;
  LeftScrolledWindow := TGTKScrolledWindow.Create(Self);
  RightScrolledWindow := TGTKScrolledWindow.Create(Self);
  LeftListView := TGTKListView.CreateTyped(Self, True, [lcPointer]);
  RightListView := TGTKListView.CreateTyped(Self, True, [lcPointer]);
  if not Application.GTKVersion_2_0_5_Up then begin
    LeftListView.FromSortedCoversionFunc := OldGTKConvertFromSorted;
    LeftListView.ToSortedCoversionFunc := OldGTKConvertToSorted;
    RightListView.FromSortedCoversionFunc := OldGTKConvertFromSorted;
    RightListView.ToSortedCoversionFunc := OldGTKConvertToSorted; 
  end;
  LeftStatusLine := TGTKLabel.Create(Self);
  RightStatusLine := TGTKLabel.Create(Self);
  LeftQuickFindVBox := TGTKVBox.Create(Self);
  LeftQuickFindHBox := TGTKHBox.Create(Self);
  LeftQuickFindLabel := TGTKLabel.Create(Self);
  LeftQuickFindEntry := TGTKEntry.Create(Self);
  LeftQuickFindEntry.CanFocus := False;
  LeftQuickFindSeparator := TGTKHSeparator.Create(Self);
  LeftQuickFindHBox.AddControlEx(LeftQuickFindLabel, False, False, 0);
  LeftQuickFindHBox.AddControlEx(LeftQuickFindEntry, True, True, 5);
  LeftQuickFindHBox.Homogeneous := False;
  LeftQuickFindVBox.AddControlEx(LeftQuickFindHBox, False, False, 5);
  LeftQuickFindVBox.AddControlEx(LeftQuickFindSeparator, False, False, 0);
  LeftQuickFindLabel.Caption := LANGQuickFind;
  LeftQuickFindVBox.Hide;
  RightQuickFindVBox := TGTKVBox.Create(Self);
  RightQuickFindHBox := TGTKHBox.Create(Self);
  RightQuickFindLabel := TGTKLabel.Create(Self);
  RightQuickFindEntry := TGTKEntry.Create(Self);
  RightQuickFindEntry.CanFocus := False;
  RightQuickFindSeparator := TGTKHSeparator.Create(Self);
  RightQuickFindHBox.AddControlEx(RightQuickFindLabel, False, False, 0);
  RightQuickFindHBox.AddControlEx(RightQuickFindEntry, True, True, 5);
  RightQuickFindHBox.Homogeneous := False;
  RightQuickFindVBox.AddControlEx(RightQuickFindHBox, False, False, 5);
  RightQuickFindVBox.AddControlEx(RightQuickFindSeparator, False, False, 0);
  RightQuickFindLabel.Caption := LANGQuickFind;
  RightQuickFindVBox.Hide;
  LeftDisconnectButton := TGTKButton.Create(Self);
  LeftDisconnectButton.Caption := '✖';
  LeftDisconnectButton.CanFocus := False;
  LeftDisconnectButton.SetSizeRequest(22, 22);
  LeftDisconnectButton.Tooltip := LANGDisconnectButton_Tooltip + ' (Shift+Ctrl+F)';
  LeftDisconnectButton.Visible := False;
  RightDisconnectButton := TGTKButton.Create(Self);
  RightDisconnectButton.Caption := '✖';
  RightDisconnectButton.CanFocus := False;
  RightDisconnectButton.SetSizeRequest(22, 22);
  RightDisconnectButton.Tooltip := LANGDisconnectButton_Tooltip + ' (Shift+Ctrl+F)';
  RightDisconnectButton.Visible := False;
  LeftLeaveArchiveButton := TGTKButton.Create(Self);
  LeftLeaveArchiveButton.Caption := '⇚';
  LeftLeaveArchiveButton.CanFocus := False;
  LeftLeaveArchiveButton.SetSizeRequest(22, 22);
  LeftLeaveArchiveButton.Tooltip := LANGLeaveArchiveButton_Tooltip;
  LeftLeaveArchiveButton.Visible := False;
  RightLeaveArchiveButton := TGTKButton.Create(Self);
  RightLeaveArchiveButton.Caption := '⇚';
  RightLeaveArchiveButton.CanFocus := False;
  RightLeaveArchiveButton.SetSizeRequest(22, 22);
  RightLeaveArchiveButton.Tooltip := LANGLeaveArchiveButton_Tooltip;
  RightLeaveArchiveButton.Visible := False;
  LeftPathLabelHBox.AddControlEx(LeftPathLabelEventBox, True, True, 0);
  LeftPathLabelHBox.AddControlEx(LeftPasswordButton, False, False, 0);
  LeftPathLabelHBox.AddControlEx(LeftDisconnectButton, False, True, 0);
  LeftPathLabelHBox.AddControlEx(LeftLeaveArchiveButton, False, True, 0);
  LeftPathLabelHBox.AddControlEx(LeftBookmarkButton, False, False, 0);
  RightPathLabelHBox.AddControlEx(RightPathLabelEventBox, True, True, 0);
  RightPathLabelHBox.AddControlEx(RightPasswordButton, False, False, 0);
  RightPathLabelHBox.AddControlEx(RightDisconnectButton, False, True, 0);
  RightPathLabelHBox.AddControlEx(RightLeaveArchiveButton, False, True, 0);
  RightPathLabelHBox.AddControlEx(RightBookmarkButton, False, False, 0);
  LeftPathLabelEventBox.AddControl(LeftPathLabel);
  RightPathLabelEventBox.AddControl(RightPathLabel);
  LeftPanelNotebook := TEphyNotebook.Create(Self);
  LeftPanelNotebook.Visible := False;
  LeftPanelNotebook.SetSizeRequest(10, -1);
  LeftPanelNotebook.Scrollable := True;
  LeftPanelNotebook.ShowBorder := False;
  LeftPanelNotebook.CanFocus := False;   //  Notebook should be focusable to allow scrolling when many tabs -- temporarily disabled
  LeftPanelNotebook.ShowCloseButtons := True;
  LeftPanelNotebook.AllowDragDrop := True;
  LeftPanelNotebook.AllowDragOutside := True;
  LeftPanelNotebook.ShowTooltips := True;
  LeftPanelNotebook.OnNotebookReordered := NotebookReordered;
  LeftPanelNotebook.OnTabClose := NotebookTabClosed;
  LeftPanelNotebook.OnTabDoubleClick := NotebookTabDoubleClick;
  LeftPanelNotebook.OnFindNotebookAtPointer := NotebookFindNotebookAtPointerEvent;
  LeftPanelNotebook.OnMoveTabToAnotherNotebook := NotebookMoveTabToAnotherNotebook;
  LeftPanelNotebook.OnTabFocusOnlyEvent := NotebookTabFocusOnlyEvent;
  RightPanelNotebook := TEphyNotebook.Create(Self);
  RightPanelNotebook.Visible := False;
  RightPanelNotebook.SetSizeRequest(10, -1);
  RightPanelNotebook.Scrollable := True;
  RightPanelNotebook.ShowBorder := False;
  RightPanelNotebook.CanFocus := False;
  RightPanelNotebook.ShowCloseButtons := True;
  RightPanelNotebook.AllowDragDrop := True;
  RightPanelNotebook.AllowDragOutside := True;
  RightPanelNotebook.ShowTooltips := True;
  RightPanelNotebook.OnNotebookReordered := NotebookReordered;
  RightPanelNotebook.OnTabClose := NotebookTabClosed;
  RightPanelNotebook.OnTabDoubleClick := NotebookTabDoubleClick;
  RightPanelNotebook.OnFindNotebookAtPointer := NotebookFindNotebookAtPointerEvent;
  RightPanelNotebook.OnMoveTabToAnotherNotebook := NotebookMoveTabToAnotherNotebook;
  RightPanelNotebook.OnTabFocusOnlyEvent := NotebookTabFocusOnlyEvent;
  LeftListBox := TGTKVBox.Create(Self);
  LeftListBox.AddControlEx(LeftScrolledWindow, True, True, 0);
  RightListBox := TGTKVBox.Create(Self);
  RightListBox.AddControlEx(RightScrolledWindow, True, True, 0);
  LeftPanelBox.AddControlEx(LeftStatusBox, False, True, 0);
  RightPanelBox.AddControlEx(RightStatusBox, False, True, 0);
  LeftPanelBox.AddControlEx(LeftPathLabelHBox, False, False, 1);
  RightPanelBox.AddControlEx(RightPathLabelHBox, False, False, 1);
  LeftPanelBox.AddControlEx(LeftPanelNotebook, True, True, 0);
  RightPanelBox.AddControlEx(RightPanelNotebook, True, True, 0);
  LeftPanelBox.AddControlEx(LeftListBox, True, True, 0);
  RightPanelBox.AddControlEx(RightListBox, True, True, 0);
  LeftPanelBox.AddControlEx(LeftQuickFindVBox, False, True, 0);
  RightPanelBox.AddControlEx(RightQuickFindVBox, False, True, 0);
  LeftPanelBox.AddControlEx(LeftStatusLine, False, True, 0);
  RightPanelBox.AddControlEx(RightStatusLine, False, True, 0);
  LeftScrolledWindow.AddControl(LeftListView);
  RightScrolledWindow.AddControl(RightListView);
  LeftScrolledWindow.HorizScrollBarPolicy := sbAutomatic;
  RightScrolledWindow.HorizScrollBarPolicy := sbAutomatic;
  LeftScrolledWindow.VertScrollBarPolicy := sbAutomatic;
  RightScrolledWindow.VertScrollBarPolicy := sbAutomatic;
  LeftScrolledWindow.ShadowType := stShadowIn;
  RightScrolledWindow.ShadowType := stShadowIn;
  LeftUpButton := TGTKButton.Create(Self);     LeftUpButton.Caption := '..';
  LeftRootButton := TGTKButton.Create(Self);   LeftRootButton.Caption := '/';
  LeftHomeButton := TGTKButton.Create(Self);   LeftHomeButton.Caption := '~';
  LeftEqualButton := TGTKButton.Create(Self);  LeftEqualButton.Caption := '=';
  LeftUpButton.SetSizeRequest(22, 22);         LeftUpButton.Tooltip := LANGUpButton_Tooltip;
  LeftRootButton.SetSizeRequest(22, 22);       LeftRootButton.Tooltip := LANGRootButton_Tooltip + #10'(Ctrl+/)';
  LeftHomeButton.SetSizeRequest(22, 22);       LeftHomeButton.Tooltip := LANGHomeButton_Tooltip + #10'(Ctrl+Home)';
  LeftEqualButton.SetSizeRequest(22, 22);      LeftEqualButton.Tooltip := LANGLeftEqualButton_Tooltip;
  LeftDiskInfoLabel := TGTKLabel.Create(Self);
  LeftStatusBox.AddControlEx(LeftDiskInfoLabel, True, True, 0);
  LeftStatusBox.AddControlEndEx(LeftUpButton, False, False, 0);
  LeftStatusBox.AddControlEndEx(LeftRootButton, False, False, 0);
  LeftStatusBox.AddControlEndEx(LeftHomeButton, False, False, 0);
  LeftStatusBox.AddControlEndEx(LeftEqualButton, False, False, 0);
  RightUpButton := TGTKButton.Create(Self);     RightUpButton.Caption := '..';
  RightRootButton := TGTKButton.Create(Self);   RightRootButton.Caption := '/';
  RightHomeButton := TGTKButton.Create(Self);   RightHomeButton.Caption := '~';
  RightEqualButton := TGTKButton.Create(Self);  RightEqualButton.Caption := '=';
  RightUpButton.SetSizeRequest(22, 22);         RightUpButton.Tooltip := LANGUpButton_Tooltip;
  RightRootButton.SetSizeRequest(22, 22);       RightRootButton.Tooltip := LANGRootButton_Tooltip + #10'(Ctrl+/)';
  RightHomeButton.SetSizeRequest(22, 22);       RightHomeButton.Tooltip := LANGHomeButton_Tooltip + #10'(Ctrl+Home)';
  RightEqualButton.SetSizeRequest(22, 22);      RightEqualButton.Tooltip := LANGRightEqualButton_Tooltip;
  LeftUpButton.CanFocus := False;
  LeftRootButton.CanFocus := False;
  LeftHomeButton.CanFocus := False;
  LeftEqualButton.CanFocus := False;
  RightUpButton.CanFocus := False;
  RightRootButton.CanFocus := False;
  RightHomeButton.CanFocus := False;
  RightEqualButton.CanFocus := False;
  RightDiskInfoLabel := TGTKLabel.Create(Self);
  RightStatusBox.AddControlEx(RightDiskInfoLabel, True, True, 0);
  RightStatusBox.AddControlEndEx(RightUpButton, False, False, 0);
  RightStatusBox.AddControlEndEx(RightRootButton, False, False, 0);
  RightStatusBox.AddControlEndEx(RightHomeButton, False, False, 0);
  RightStatusBox.AddControlEndEx(RightEqualButton, False, False, 0);
  ConstructColumns(LeftListView);
  ConstructColumns(RightListView);
  LeftPathLabel.XAlign := 0;
  LeftPathLabel.XPadding := 5;
  LeftPathLabel.YPadding := 1;
  RightPathLabel.XAlign := 0;
  RightPathLabel.XPadding := 5;
  RightPathLabel.YPadding := 1;
  LeftDiskInfoLabel.XAlign := 0;
  LeftDiskInfoLabel.XPadding := 5;
  LeftDiskInfoLabel.YAlign := 0.5;
  RightDiskInfoLabel.XAlign := 0;
  RightDiskInfoLabel.XPadding := 5;
  RightDiskInfoLabel.YAlign := 0.5;
  LeftStatusLine.XAlign := 0;
  LeftStatusLine.XPadding := 5;
  LeftStatusLine.YAlign := 0.5;
  RightStatusLine.XAlign := 0;
  RightStatusLine.XPadding := 5;
  RightStatusLine.YAlign := 0.5;
{  LeftStatusBox.SetSizeRequest(1, 18);
  RightStatusBox.SetSizeRequest(1, 18); }
  LeftStatusLine.SetSizeRequest(1, 18);
  RightStatusLine.SetSizeRequest(1, 18);
  LeftDiskInfoLabel.SetSizeRequest(1, -1);
  RightDiskInfoLabel.SetSizeRequest(1, -1);
  LeftQuickFindVBox.SetSizeRequest(1, -1);
  RightQuickFindVBox.SetSizeRequest(1, -1);
end;

procedure TFMain.ConstructMenu;
const ShowDotFilesShortcut: TGDKShortCut = ( Key: 46; Locked: False; ModAlt: False; ModShift: False; ModCtrl: True);
var i: integer;
    Item: TGTKMenuItem;
    Group: TGTKMenuItemGroup;
begin
  mnuFile := TGTKMenuItem.Create(Self);
  mnuFile.Caption := LANGmnuFile_Caption;
  MainMenu.Items.Add(mnuFile);
//  mnuFile.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miChangePermissions := TGTKMenuItem.CreateTyped(Self, itImageText);
  miChangePermissions.Caption := LANGmiChangePermissions_Caption;
  miChangePermissions.StockIcon := 'gtk-convert';
  miChangePermissions.OnClick := miChangePermissionsClick;
  mnuFile.Add(miChangePermissions);
  miChangeOwner := TGTKMenuItem.Create(Self);
  miChangeOwner.Caption := LANGmiChangeOwner_Caption;
  miChangeOwner.OnClick := miChangeOwnerClick;
  mnuFile.Add(miChangeOwner);
  mnuFile.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miCreateSymlink := TGTKMenuItem.CreateTyped(Self, itImageText);
  miCreateSymlink.Caption := LANGmiCreateSymlink_Caption;
  miCreateSymlink.StockIcon := 'gtk-jump-to';
  miCreateSymlink.OnClick := miCreateSymlinkClick;
  mnuFile.Add(miCreateSymlink);
  miEditSymlink := TGTKMenuItem.Create(Self);
  miEditSymlink.Caption := LANGmiEditSymlink_Caption;
  miEditSymlink.OnClick := miEditSymlinkClick;
  mnuFile.Add(miEditSymlink);
  mnuFile.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miSplitFile := TGTKMenuItem.Create(Self);
  miSplitFile.Caption := LANGmiSplitFileCaption;
  miSplitFile.OnClick := miSplitFileClick;
  mnuFile.Add(miSplitFile);
  miMergeFiles := TGTKMenuItem.Create(Self);
  miMergeFiles.Caption := LANGmiMergeFilesCaption;
  miMergeFiles.OnClick := miMergeFilesClick;
  mnuFile.Add(miMergeFiles);
  mnuFile.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miVerifyChecksums := TGTKMenuItem.Create(Self);
  miVerifyChecksums.Caption := LANGmiVerifyChecksums;
  miVerifyChecksums.OnClick := miVerifyChecksumsClick;
  mnuFile.Add(miVerifyChecksums);
  miCreateChecksums := TGTKMenuItem.Create(Self);
  miCreateChecksums.Caption := LANGmiCreateChecksumsCaption;
  miCreateChecksums.OnClick := miCreateChecksumsClick;
  mnuFile.Add(miCreateChecksums);
  mnuFile.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miExit := TGTKMenuItem.CreateTyped(Self, itImageText);
  miExit.Caption := LANGmiExit_Caption;
  miExit.StockIcon := 'gtk-quit';
  miExit.OnClick := miExitClick;
  mnuFile.Add(miExit);

  mnuMark := TGTKMenuItem.Create(Self);
  mnuMark.Caption := LANGmnuMark_Caption;
  MainMenu.Items.Add(mnuMark);
//  mnuMark.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miSelectGroup := TGTKMenuItem.CreateTyped(Self, itImageText);
  miSelectGroup.Caption := LANGmiSelectGroup_Caption;
  miSelectGroup.ShortCuts.Add(MakeGDKShortCut(GDK_KP_PLUS, False, False, False, False));
  miSelectGroup.StockIcon := 'gtk-add';
  miSelectGroup.OnClick := mnuMarkClick;
  mnuMark.Add(miSelectGroup);
  miUnselectGroup := TGTKMenuItem.CreateTyped(Self, itImageText);
  miUnselectGroup.Caption := LANGmiUnselectGroup_Caption;
  miUnselectGroup.ShortCuts.Add(MakeGDKShortCut(GDK_KP_MINUS, False, False, False, False));
  miUnselectGroup.StockIcon := 'gtk-remove';
  miUnselectGroup.OnClick := mnuMarkClick;
  mnuMark.Add(miUnselectGroup);
  miSelectAll := TGTKMenuItem.Create(Self);
  miSelectAll.Caption := LANGmiSelectAll_Caption;
  miSelectAll.ShortCuts.Add(MakeGDKShortCut(GDK_KP_PLUS, False, False, False, True));
  miSelectAll.OnClick := mnuMarkClick;
  mnuMark.Add(miSelectAll);
  miUnselectAll := TGTKMenuItem.Create(Self);
  miUnselectAll.Caption := LANGmiUnselectAll_Caption;
  miUnselectAll.ShortCuts.Add(MakeGDKShortCut(GDK_KP_MINUS, False, False, False, True));
  miUnselectAll.OnClick := mnuMarkClick;
  mnuMark.Add(miUnselectAll);
  miInvertSelection := TGTKMenuItem.Create(Self);
  miInvertSelection.Caption := LANGmiInvertSelection_Caption;
  miInvertSelection.ShortCuts.Add(MakeGDKShortCut(GDK_KP_ASTERISK, False, False, False, False));
  miInvertSelection.OnClick := mnuMarkClick;
  mnuMark.Add(miInvertSelection);

  mnuCommands := TGTKMenuItem.Create(Self);
  mnuCommands.Caption := LANGmnuCommands_Caption;
  MainMenu.Items.Add(mnuCommands);
//  mnuCommands.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miSearch := TGTKMenuItem.CreateTyped(Self, itImageText);
  miSearch.StockIcon := 'gtk-find';
  miSearch.Caption := LANGmiSearchCaption2;
  miSearch.ShortCuts.AddName('<Alt>F7');
  miSearch.OnClick := miSearchClick;
  mnuCommands.Add(miSearch);
  mnuCommands.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miRefresh := TGTKMenuItem.CreateTyped(Self, itImageText);
  miRefresh.Caption := LANGmiRefresh_Caption;
  miRefresh.StockIcon := 'gtk-refresh';
  miRefresh.ShortCuts.AddName('<Control>R');
  miRefresh.OnClick := miRefreshClick;
  mnuCommands.Add(miRefresh);
  miShowDirectorySizes := TGTKMenuItem.CreateTyped(Self, itImageText);
  miShowDirectorySizes.Caption := LANGmiShowDirectorySizes_Caption;
  miShowDirectorySizes.OnClick := miShowDirectorySizesClick;
  mnuCommands.Add(miShowDirectorySizes);
  mnuCommands.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miCopyNames := TGTKMenuItem.CreateTyped(Self, itImageText);
  miCopyNames.Caption := LANGCopyFileNamesToClipboard;
  miCopyNames.ShortCuts.AddName('<Shift>F2');
  miCopyNames.OnClick := miCopyNamesClick;
  mnuCommands.Add(miCopyNames);
  miCopyFullPaths := TGTKMenuItem.CreateTyped(Self, itImageText);
  miCopyFullPaths.Caption := LANGCopyFullPathNamesToClipboard;
  miCopyFullPaths.ShortCuts.AddName('<Control>F2');
  miCopyFullPaths.StockIcon := 'gtk-copy';
  miCopyFullPaths.OnClick := miCopyNamesClick;
  mnuCommands.Add(miCopyFullPaths);
  mnuCommands.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miNewTab := TGTKMenuItem.CreateTyped(Self, itImageText);
  miNewTab.Caption := LANGmiNewTab_Caption;
  miNewTab.ShortCuts.AddName('<Control>T');
  miNewTab.OnClick := miDuplicateTabClick;
  miNewTab.StockIcon := 'gtk-index';
  mnuCommands.Add(miNewTab);
  mnuCommands.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miTargetSource := TGTKMenuItem.CreateTyped(Self, itImageText);
  miTargetSource.Caption := LANGmiTargetSource_Caption;
//  miTargetSource.ShortCuts.AddName('<Alt>O');
  miTargetSource.OnClick := miTargetSourceClick;
  mnuCommands.Add(miTargetSource);

  mnuShow := TGTKMenuItem.Create(Self);
  mnuShow.Caption := LANGmnuShow_Caption;
  MainMenu.Items.Add(mnuShow);
//  mnuShow.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miShowDotFiles := TGTKMenuItem.CreateTyped(Self, itCheck);
  miShowDotFiles.Caption := LANGmiShowDotFiles_Caption;
  miShowDotFiles.Checked := ConfShowDotFiles;
  miShowDotFiles.OnClick := miShowDotFilesClick;
  miShowDotFiles.ShortCuts.Add(ShowDotFilesShortcut);
  mnuShow.Add(miShowDotFiles);
  mnuShow.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miNoMounterBar := TGTKMenuItem.CreateTyped(Self, itRadio, nil);
  miNoMounterBar.Caption := LANGmiNoMounterBarCaption;
  miNoMounterBar.Checked := ConfShowMounterBar = 0;
  Group := miNoMounterBar.Group;
  mnuShow.Add(miNoMounterBar);
  miShowOneMounterBar := TGTKMenuItem.CreateTyped(Self, itRadio, Group);
  miShowOneMounterBar.Caption := LANGmiShowOneMounterBarCaption;
  miShowOneMounterBar.Checked := ConfShowMounterBar = 1;
  Group := miShowOneMounterBar.Group;
  mnuShow.Add(miShowOneMounterBar);
  miShowTwoMounterBar := TGTKMenuItem.CreateTyped(Self, itRadio, Group);
  miShowTwoMounterBar.Caption := LANGmiShowTwoMounterBarCaption;
  miShowTwoMounterBar.Checked := ConfShowMounterBar = 2;
  mnuShow.Add(miShowTwoMounterBar);
  //  Assign of the events has to be done after all radio items are created
  miNoMounterBar.OnClick := miShowMounterBarClick;
  miShowOneMounterBar.OnClick := miShowMounterBarClick;
  miShowTwoMounterBar.OnClick := miShowMounterBarClick;

  mnuBookmarks := TGTKMenuItem.Create(Self);
  mnuBookmarks.Caption := LANGmnuBookmarks_Caption;
  mnuBookmarks.OnPopup := mnuBookmarksPopup;
  mnuBookmarks.OnClick := mnuBookmarksPopup;
  MainMenu.Items.Add(mnuBookmarks);
//  mnuBookmarks.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miAddBookmark := TGTKMenuItem.CreateTyped(Self, itImageText);
  miAddBookmark.Caption := LANGmiAddBookmark_Caption;
  miAddBookmark.StockIcon := 'gtk-add';
  miAddBookmark.OnClick := miAddBookmarkClick;
  mnuBookmarks.Add(miAddBookmark);
  miEditBookmarks := TGTKMenuItem.CreateTyped(Self, itImageText);
  miEditBookmarks.Caption := LANGmiEditBookmarks_Caption;
  miEditBookmarks.Enabled := False;
  miEditBookmarks.Visible := False;
  miEditBookmarks.Tooltip := 'Currently not implemented - use the popup menu';
  mnuBookmarks.Add(miEditBookmarks);
  miBookmarksSeparator := TGTKMenuItem.CreateTyped(Self, itSeparator);
  mnuBookmarks.Add(miBookmarksSeparator);

  BookmarkPopup := TGTKMenuItem.Create(Self);
  BookmarkPopupDelete := TGTKMenuItem.Create(Self);
  BookmarkPopupDelete.Caption := LANGBookmarkPopupDelete_Caption;
  BookmarkPopupDelete.OnClick := BookmarkPopupDeleteClick;
  BookmarkPopup.Add(BookmarkPopupDelete);

  mnuNetwork := TGTKMenuItem.Create(Self);
  mnuNetwork.Caption := LANGmnuNetworkCaption;
  MainMenu.Items.Add(mnuNetwork);
//  mnuNetwork.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miConnections := TGTKMenuItem.CreateTyped(Self, itImageText);
  miConnections.Caption := LANGmiConnectionsCaption;
  miConnections.StockIcon := 'gtk-network';
  miConnections.Enabled := False;
  mnuNetwork.Add(miConnections);
  mnuNetwork.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miOpenConnection := TGTKMenuItem.CreateTyped(Self, itImageText);
  miOpenConnection.Caption := LANGmiOpenConnectionCaption;
  miOpenConnection.OnClick := miOpenConnectionClick;
  miOpenConnection.StockIcon := 'gtk-connect';
  miOpenConnection.ShortCuts.AddName('<Control>F');
  mnuNetwork.Add(miOpenConnection);
  miQuickConnect := TGTKMenuItem.CreateTyped(Self, itImageText);
  miQuickConnect.Caption := LANGmiQuickConnectCaption;
  miQuickConnect.OnClick := miQuickConnectClick;
  miQuickConnect.ShortCuts.AddName('<Control>N');
  mnuNetwork.Add(miQuickConnect);
  mnuNetwork.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miDisconnect := TGTKMenuItem.CreateTyped(Self, itImageText);
  miDisconnect.Caption := LANGmiDisconnect_Caption;
  miDisconnect.Enabled := False;
  miDisconnect.ShortCuts.AddName('<Shift><Control>F');
  miDisconnect.StockIcon := 'gtk-disconnect';
  miDisconnect.OnClick := miDisconnectClick;
  mnuNetwork.Add(miDisconnect);

  mnuPlugins := TGTKMenuItem.Create(Self);
  mnuPlugins.Caption := LANGmnuPluginsCaption;
  MainMenu.Items.Add(mnuPlugins);
//  mnuPlugins.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miTestPlugin := TGTKMenuItem.CreateTyped(Self, itImageText);
  miTestPlugin.Caption := LANGmiTestPluginCaption;
  miTestPlugin.OnClick := miTestPluginClick;
  mnuPlugins.Add(miTestPlugin);
  mnuPlugins.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));

  mnuSettings := TGTKMenuItem.Create(Self);
  mnuSettings.Caption := LANGmnuSettings_Caption;
  MainMenu.Items.Add(mnuSettings);
//  mnuSettings.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miPreferences := TGTKMenuItem.CreateTyped(Self, itImageText);
  miPreferences.Caption := LANGmiPreferences_Caption;
  miPreferences.StockIcon := 'gtk-preferences';
  miPreferences.OnClick := miPreferencesClick;
  mnuSettings.Add(miPreferences);
  miFileTypes := TGTKMenuItem.CreateTyped(Self, itImageText);
  miFileTypes.Caption := LANGmiFileTypes_Caption;
  miFileTypes.OnClick := miFileTypesClick;
  mnuSettings.Add(miFileTypes);
  miMounterSettings := TGTKMenuItem.CreateTyped(Self, itImageText);
  miMounterSettings.Caption := LANGmiMounterSettingsCaption;
  miMounterSettings.OnClick := miMounterSettingsClick;
  mnuSettings.Add(miMounterSettings);
  miColumns := TGTKMenuItem.CreateTyped(Self, itImageText);
  miColumns.Caption := LANGmiColumnsCaption;
  miColumns.OnClick := miColumnsClick;
  mnuSettings.Add(miColumns);
  mnuSettings.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miSavePosition := TGTKMenuItem.CreateTyped(Self, itImageText);
  miSavePosition.Caption := LANGmiSavePositionCaption;
  miSavePosition.OnClick := miSavePositionClick;
  mnuSettings.Add(miSavePosition);

  mnuHelp := TGTKMenuItem.Create(Self);
  mnuHelp.Caption := LANGmnuHelp_Caption;
  MainMenu.Items.Add(mnuHelp);
//  mnuHelp.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  miAbout := TGTKMenuItem.CreateTyped(Self, itImageText);
  miAbout.Caption := LANGmiAbout_Caption;
  miAbout.StockIcon := 'gtk-about';
  miAbout.OnClick := miAboutClick;
  mnuHelp.Add(miAbout);

  //  Splitter popup menu
  SplitterPopupMenu := TGTKMenuItem.Create(Self);
//  SplitterPopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itTearOff));
  for i := 2 to 8 do begin
    Item := TGTKMenuItem.Create(Self);
    Item.Caption := Format('%d - %d', [i * 10, (10 - i) * 10]);
    Item.Data := Pointer(i * 10);
    Item.OnClick := SplitterPopupMenuClick;
    SplitterPopupMenu.Add(Item);
  end;

  //  Files popup menu
  FilePopupMenu := TGTKMenuItem.Create(Self);
  FilePopupMenu.OnPopup := FilePopupMenuPopup;

  //  Mounter popup menu
  MounterButtonPopupMenu := TGTKMenuItem.Create(Self);
  MounterButtonPopupMenu.OnPopup := MounterButtonPopupMenuPopup;
  miMount := TGTKMenuItem.CreateTyped(Self, itImageText);
  miMount.Caption := LANGmiMountCaption;
  miMount.StockIcon := 'gtk-connect';
  miMount.OnClick := miMountClick;
  MounterButtonPopupMenu.Add(miMount);
  miUmount := TGTKMenuItem.CreateTyped(Self, itImageText);
  miUmount.Caption := LANGmiUmountCaption;
  miUmount.StockIcon := 'gtk-disconnect';
  miUmount.OnClick := miUmountClick;
  MounterButtonPopupMenu.Add(miUmount);
  miEject := TGTKMenuItem.CreateTyped(Self, itImageText);
  miEject.Caption := LANGmiEjectCaption;
//  miEject.StockIcon := 'gtk-cdrom';
  miEject.OnClick := miEjectClick;
  MounterButtonPopupMenu.Add(miEject);

  //  Tab popup menu
  TabPopupMenu := TGTKMenuItem.Create(Self);
  TabPopupMenu.OnPopup := TabPopupMenuPopup;
  miDuplicateTab := TGTKMenuItem.CreateTyped(Self, itImageText);
  miDuplicateTab.Caption := LANGmiDuplicateTabCaption;
  miDuplicateTab.ShortCuts.AddName('<Control>T');
  miDuplicateTab.OnClick := miDuplicateTabClick;
  miDuplicateTab.StockIcon := 'gtk-index';
  TabPopupMenu.Add(miDuplicateTab);
  TabPopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miCloseTab := TGTKMenuItem.CreateTyped(Self, itImageText);
  miCloseTab.Caption := LANGmiCloseTabCaption;
  miCloseTab.ShortCuts.AddName('<Control>W');
  miCloseTab.OnClick := miCloseTabClick;
  miCloseTab.StockIcon := 'gtk-close';
  TabPopupMenu.Add(miCloseTab);
  miCloseAllTabs := TGTKMenuItem.CreateTyped(Self, itImageText);
  miCloseAllTabs.Caption := LANGmiCloseAllTabsCaption;
  miCloseAllTabs.OnClick := miCloseAllTabsClick;
  TabPopupMenu.Add(miCloseAllTabs);
  
  //  Path box popup menu
  PathBoxPopupMenu := TGTKMenuItem.Create(Self);
  miPathBoxRefresh := TGTKMenuItem.CreateTyped(Self, itImageText);
  miPathBoxRefresh.Caption := LANGmiRefresh_Caption;
  miPathBoxRefresh.StockIcon := 'gtk-refresh';
  miPathBoxRefresh.ShortCuts.AddName('<Control>R');
  miPathBoxRefresh.OnClick := miRefreshClick;
  PathBoxPopupMenu.Add(miPathBoxRefresh);
  PathBoxPopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  miPathBoxCopyPath := TGTKMenuItem.CreateTyped(Self, itImageText);
  miPathBoxCopyPath.Caption := LANGCopyPathToClipboard;
  miPathBoxCopyPath.StockIcon := 'gtk-copy';
  miPathBoxCopyPath.OnClick := miPathBoxCopyPathClick;
  PathBoxPopupMenu.Add(miPathBoxCopyPath);
end;

procedure TFMain.ConstructColumns(ListView: TGTKListView);
var i, FirstColumn, LastColumn: integer;
    Column: TGTKTreeViewColumn;
    FontDesc: PPangoFontDescription;
begin
  ListView.SelectionMode := smBrowse;
  GetFirstLastPanelColumn(FirstColumn, LastColumn);

  for i := 1 to ConstNumPanelColumns do
    if ConfColumnVisible[i] then begin

      //  First column should have filetype icon
      if (i = FirstColumn) and ConfUseFileTypeIcons then begin
        Column := ListView.Columns.AddTyped(ctImageText);
        Column.SetImageProperty('ypad', 0);
        Column.SetImageProperty('yalign', 0.5);
        Column.SetImageProperty('xpad', 0);
        Column.SetImageProperty('xalign', 0.5);
        if (ConfRowHeight > 0) and ConfUseFileTypeIcons then begin
          Column.SetImageProperty('width', ConfRowHeight);
          Column.SetImageProperty('height', ConfRowHeight);
        end; 
      end else Column := ListView.Columns.Add;


      Column.Caption := ConfColumnTitlesShort[ConfColumnIDs[i]];

      if (i < LastColumn){ or Application.GTKVersion_2_4_0_Up} then begin
{        Column.MinWidth := 10;
        Column.MaxWidth := 500; }
{        Column.SizingMode := smFixed;
        Column.FixedWidth := ConfColumnSizes[i]; }
        g_object_set(G_OBJECT(Column.FColumn), 'sizing', 2, 'fixed-width', ConfColumnSizes[i], nil);
      end else g_object_set(G_OBJECT(Column.FColumn), 'sizing', 0, nil);
      //  Column.SizingMode := smFixed; // smAutoSize;

      gtk_tree_view_column_set_spacing(Column.FColumn, 1);   //  Bug with column spacing?

      Column.Resizable := True;
      Column.Reorderable := True;
//    if not ConfUseSystemFont then ListView.Columns[i - 1].SetProperty('font', ConfPanelFont);
      Column.SetProperty('ypad', 0);
      Column.SetProperty('yalign', 0.5);
      Column.Tag := i;
      g_object_set_data(G_OBJECT(Column.FColumn), 'Column_ID', Pointer(i));
      if ConfRowHeight > 0 then Column.SetProperty('height', ConfRowHeight);
      Column.SortID := ListView.Columns.Count - 1;
      ColumnSortIDs[Column.SortID + 1] := ConfColumnIDs[i];
      if ConfRowHeight < 0 then gtk_cell_renderer_text_set_fixed_height_from_font(PGtkCellRendererText(Column.FRenderer), 1);
{      gtk_cell_renderer_text_set_fixed_height_from_font(PGtkCellRendererText(Column.FRenderer), 1);
      gtk_cell_renderer_set_fixed_size(PGtkCellRenderer(Column.FRenderer), ConfColumnSizes[i], ConfRowHeight); }
      if ConfColumnIDs[i] < 3 then begin    //  Filename column
        Column.OnEdited := ListViewEdited;
        if Application.GTKVersion_2_6_0_Up then Column.SetProperty('ellipsize', 3);
      end;
      if ConfColumnIDs[i] in [4, 8, 9] then begin
        Column.SetProperty('xalign', 1);
        Column.Alignment := 1;
      end;
      if Application.GTKVersion_2_4_0_Up then Column.SetProperty('single-paragraph-mode', 1);
      Column.OnClicked := ListViewColumnClicked;

    end;

  //  Set the list font
  if not ConfUseSystemFont then begin
    FontDesc := pango_font_description_from_string(PChar(ConfPanelFont));
    gtk_widget_modify_font(ListView.FWidget, FontDesc);
  end else gtk_widget_modify_font(ListView.FWidget, nil);

  //  Set the fixed row height - temporarily disabled due to bug in custom drawing
//  if Application.GTKVersion_2_4_0_Up then g_object_set(ListView.FWidget, 'fixed_height_mode', integer(True), nil);
end;

procedure TFMain.FormDestroy(Sender: TObject);
begin
  LeftNotebookBoxList.Free;
  RightNotebookBoxList.Free;
end;

procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);

  function InternalCloseEngine(Engine, FallbackEngine: TPanelEngine): TPanelEngine;
  begin
    Result := FallbackEngine;
    if not Assigned(Engine.ParentEngine) or (not (Engine is TVFSEngine)) then Exit;
    Result := Engine.ParentEngine;
    CheckForUnsavedConnection(Engine as TVFSEngine, False);
    if not TVFSEngine(Engine).VFSClose then DebugMsg(['Error closing the engine...']);
    Engine.Free;
  end;

var i: integer;
    b, DontShowAgain: boolean;
    res: TMessageButton;
    s: string;
begin
  //  Find all opened connections and warn user
  b := False;
  if LeftPanelNotebook.Visible and Assigned(LeftTabEngines) and (LeftTabEngines.Count > 0) then
    for i := 0 to LeftTabEngines.Count - 1 do
      if TPanelEngine(LeftTabEngines[i]) is TVFSEngine then begin
        b := True;
        Break;
      end;
  if (not b) or (RightPanelNotebook.Visible and Assigned(RightTabEngines) and (RightTabEngines.Count > 0)) then
    for i := 0 to RightTabEngines.Count - 1 do
      if TPanelEngine(RightTabEngines[i]) is TVFSEngine then begin
        b := True;
        Break;
      end;
  b := b or (LeftPanelEngine is TVFSEngine) or (RightPanelEngine is TVFSEngine);

(*  -- Disabled, makes users confused. We have that 'usaved connection' message now anyway
  if b and ConfOpenConnectionsWarning then begin
    res := MessageBoxShowOnce(PGtkWindow(FWidget), LANGOpenConnectionsWarning, LANGDontShowAgain, DontShowAgain, [mbYes, mbNo], mbWarning, mbYes, mbNo);
    if DontShowAgain then begin
      ConfOpenConnectionsWarning := False;
      WriteMainGUISettings;
    end;
    if res = mbNo then begin
      Action := caNone;
      Exit;
    end;
  end;  
*)
  ApplicationShuttingDown := True;  //  Avoid emitting config files refresh event
  
  //  Close all active connections
  if b then begin
    if (not LeftPanelNotebook.Visible) and (LeftPanelEngine is TVFSEngine) then begin
      while LeftPanelEngine is TVFSEngine do s := CloseVFS(True, True);
      if LeftPanelEngine is TLocalTreeEngine then LeftPanelEngine.ChangeDir(s);
    end else
      for i := 0 to LeftTabEngines.Count - 1 do
        if (TPanelEngine(LeftTabEngines[i]) <> nil) and (TPanelEngine(LeftTabEngines[i]) is TVFSEngine) then
        try
          if LeftPanelNotebook.PageIndex = i then begin
            while LeftPanelEngine is TVFSEngine do s := CloseVFS(True, True);
            LeftPanelTabs[i] := s;
          end else
          while Assigned(LeftTabEngines[i]) and (TPanelEngine(LeftTabEngines[i]) is TVFSEngine) do begin
            s := TPanelEngine(LeftTabEngines[i]).SavePath;
            LeftTabEngines[i] := InternalCloseEngine(LeftTabEngines[i], LeftLocalEngine);
            if s <> '' then LeftPanelTabs[i] := s
                       else LeftPanelTabs[i] := TPanelEngine(LeftTabEngines[i]).Path;
          end;
        except end;

    if (not RightPanelNotebook.Visible) and (RightPanelEngine is TVFSEngine) then begin
      while RightPanelEngine is TVFSEngine do s := CloseVFS(False, True);
      if RightPanelEngine is TLocalTreeEngine then RightPanelEngine.ChangeDir(s);
    end else
      for i := 0 to RightTabEngines.Count - 1 do
        if (TPanelEngine(RightTabEngines[i]) <> nil) and (TPanelEngine(RightTabEngines[i]) is TVFSEngine) then
        try
          if RightPanelNotebook.PageIndex = i then begin
            while RightPanelEngine is TVFSEngine do s := CloseVFS(False, True);
            RightPanelTabs[i] := s;
          end else
          while Assigned(RightTabEngines[i]) and (TPanelEngine(RightTabEngines[i]) is TVFSEngine) do begin
            s := TPanelEngine(RightTabEngines[i]).SavePath;
            RightTabEngines[i] := InternalCloseEngine(RightTabEngines[i], RightLocalEngine);
            if s <> '' then RightPanelTabs[i] := s
                       else RightPanelTabs[i] := TPanelEngine(RightTabEngines[i]).Path;
          end;
        except end;
  end;

  //  Unset the columns changed signal because it's called on window close
  LeftListView.OnColumnsChanged := nil;
  RightListView.OnColumnsChanged := nil;
  
  ConfMainWindowState := Integer(WindowState);
  if ConfMainWindowState <> 0 then begin
    ConfMainWindowPosLeft := TGTKControl(Self).Left;
    ConfMainWindowPosTop := TGTKControl(Self).Top;
    ConfMainWindowWidth := TGTKControl(Self).Width;
    ConfMainWindowHeight := TGTKControl(Self).Height;
  end else begin
    ConfMainWindowPosLeft := Left;
    ConfMainWindowPosTop := Top;
    ConfMainWindowWidth := Width;
    ConfMainWindowHeight := Height;
  end;
  for i := 0 to LeftListView.Columns.Count - 1 do
    ConfColumnSizes[LeftListView.Columns[i].Tag] := LeftListView.Columns[i].Width;
  ConfMainWindowLeftSortColumn := LeftListView.SortColumnID;
  ConfMainWindowLeftSortType := Integer(LeftListView.SortOrder);
  ConfMainWindowRightSortColumn := RightListView.SortColumnID;
  ConfMainWindowRightSortType := Integer(RightListView.SortOrder);

  ConfLeftTabBarTabIndex := LeftPanelNotebook.PageIndex;
  ConfRightTabBarTabIndex := RightPanelNotebook.PageIndex;
end;



(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.AfterStart;
var i: integer;
    TmpList: TStringList;
    TmpList2, TmpList3: TList;
begin
  LeftPanelEngine := LeftLocalEngine;
  RightPanelEngine := RightLocalEngine;

  //  Apply the settings
  ApplySettings(False, False, True);

  MounterBarHandleBox.Visible := ConfShowMounterBar = 1;
  LeftMounterTable.Visible := ConfShowMounterBar = 2;
  RightMounterTable.Visible := ConfShowMounterBar = 2; 
  FillMounterBar;

  LeftListView.SetFocus;
  if CommandLineHistory.Count > 0 then
    for i := 0 to CommandLineHistory.Count - 1 do
      CommandLineCombo.Items.Append(CommandLineHistory[i]);
  CommandLineCombo.Entry.Text := '';

  RefreshBookmarksMenu;

  ButtonsBox.Visible := ConfShowFuncButtons;
  ButtonBoxSeparator.Visible := ConfShowFuncButtons;
  ButtonBoxSpace.Visible := not ConfShowFuncButtons;

  FillPluginMenu;

  FileListTipsInstall(PGtkTreeView(LeftListView.FWidget));
  FileListTipsInstall(PGtkTreeView(RightListView.FWidget));
  FileListTipsEnable;

  //  Load and restore panel tabs
  if ConfSavePanelTabs then try
    TmpList := TStringList.Create;
    TmpList2 := TList.Create;
    TmpList3 := TList.Create;
    ReadTabs(True, TmpList, TmpList2, TmpList3);
    if (TmpList.Count > 0) and (TmpList2.Count > 0) and (TmpList3.Count > 0) then
      AddTabs(True, TmpList, TmpList2, TmpList3, ConfLeftTabBarTabIndex);
    TmpList.Clear;
    TmpList2.Clear;
    TmpList3.Clear;
    ReadTabs(False, TmpList, TmpList2, TmpList3);
    if (TmpList.Count > 0) and (TmpList2.Count > 0) and (TmpList3.Count > 0) then
      AddTabs(False, TmpList, TmpList2, TmpList3, ConfRightTabBarTabIndex);
    TmpList.Free;
    TmpList2.Free;
    TmpList3.Free;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TFMain.AfterStart, loading tabs: (', E.ClassName, '): ', E.Message]);
  end;

  //  Refresh the lists
  if not (LeftPanelNotebook.Visible and (LeftTabEngines.Count > 0)) then begin
    ChangingDir(True, ConfLeftPath, '', '', False, True);     //  AutoFallback
    LeftListView.SetSortInfo(ConfMainWindowLeftSortColumn, TGTKTreeViewSortOrder(ConfMainWindowLeftSortType));
  end;
  if not (RightPanelNotebook.Visible and (RightTabEngines.Count > 0)) then begin
    ChangingDir(False, ConfRightPath, '', '', False, True);
    RightListView.SetSortInfo(ConfMainWindowRightSortColumn, TGTKTreeViewSortOrder(ConfMainWindowRightSortType));
  end;

  //  Set window position and size
  SetDefaultSize(ConfMainWindowWidth, ConfMainWindowHeight);
  if (ConfMainWindowPosLeft > -1) and (ConfMainWindowPosTop > -1)
    then WindowMove(ConfMainWindowPosLeft, ConfMainWindowPosTop);
  if ConfWMCompatMode then Show;
  case integer(ConfMainWindowState) of
    Ord(wsMaximized) : Maximize;
    Ord(wsMinimized) : Minimize;
  end;
  if not ConfWMCompatMode then Show;

  //  Other things
  StartUp := False;   //  Set the flag to process Splitter repositioning
  PanelSeparator.Position := Round(Width * (ConfPanelSep / 100));

  Application.ProcessMessages;   //  Need to process all messages before unlocking
  InternalLockInit(False);

  LeftListView.OnColumnsChanged := ListViewColumnsChanged;
  RightListView.OnColumnsChanged := ListViewColumnsChanged;
  LeftListView.SetFocus;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.PanelSeparatorResize(Sender: TObject);
begin
  if not StartUp then ConfPanelSep := Round((PanelSeparator.Position / Width) * 100);
end;

procedure TFMain.PanelSeparatorMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
var XLeft, XRight: gint;
begin
  if Button = mbRight then begin
    Accept := False;
    gtk_widget_get_pointer(LeftPanelBox.FWidget, @XLeft, nil);
    gtk_widget_get_pointer(RightPanelBox.FWidget, @XRight, nil);
    if (XLeft >= LeftPanelBox.Width) and (XRight < 0) then SplitterPopupMenu.PopUp;
  end;
end;

procedure TFMain.miExitClick(Sender: TObject);
begin
  Close;
end;

procedure TFMain.miAboutClick(Sender: TObject);
begin
  InternalLock;
  ShowAbout;
  Application.ProcessMessages;
  InternalLockInit(False);
end;

procedure TFMain.ListViewKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
var AListView: TGTKListView;
    ANotebook: TEphyNotebook;
    LeftPanel, KeyHandled: boolean;
    x: integer;
begin
  if not Assigned(Sender) or not (Sender is TGTKListView) then DebugMsg(['**** ListViewKeyDown: Sender is not TGTKListView or not valid']);
  AListView := Sender as TGTKListView;
  if LeftListView.Focused then LeftPanel := True
  else if RightListView.Focused then LeftPanel := False
  else LeftPanel := LeftLastFocused;
  if LeftPanel then ANotebook := LeftPanelNotebook
               else ANotebook := RightPanelNotebook;
  FileListTipsHide;
  KeyHandled := False;

  case Key of
    GDK_TAB, 65056 : if (ssCtrl in Shift) and ANotebook.Visible then begin
                Accept := False;
                KeyHandled := True;
                x := (ANotebook.PageIndex + 1 - 2*Ord(ssShift in Shift)) mod ANotebook.ChildrenCount;
                if x < 0 then x := ANotebook.ChildrenCount - 1;
                ANotebook.PageIndex := x;
              end else begin
                Accept := False;
                KeyHandled := True;
                DeactivateQuickFind(LeftPanel);
//                Application.ProcessMessages;
                if InternalLockUnlocked then    //  prevent changing focus when busy
                  if LeftPanel then RightListView.SetFocus
                               else LeftListView.SetFocus;
              end;
    GDK_RETURN, GDK_KP_ENTER : begin
                                 KeyHandled := True;
                                 if {(Key = GDK_RETURN) and} (Shift = [ssAlt, ssShift]) then DoGetDirSize(True)
                                 else
                                 if ([ssAlt] = Shift) or ([ssCtrl] = Shift) then begin
                                   CommandLineComboKeyDown(Sender, Key, Shift, Accept);
                                   Accept := False;
                                   CommandLineCombo.Entry.SetFocus;
                                   CommandLineCombo.Entry.SelectRegion(Length(CommandLineCombo.Entry.Text), Length(CommandLineCombo.Entry.Text));
                                 end else
                                 if Length(Trim(CommandLineCombo.Entry.Text)) > 0 then CommandLineComboKeyDown(Sender, Key, Shift, Accept)
                                 else if Assigned(AListView.Selected) then ActivateItem(AListView.Selected.Index);
                               end;
    GDK_BACKSPACE : begin
                      KeyHandled := True;
                      if QuickFind then QuickFindSendKey(LeftPanel, Key)
                      else begin
                        if LeftPanel then PathButtonClick(LeftUpButton)
                                     else PathButtonClick(RightUpButton);
                      end;
                    end;
    GDK_Right : begin
                  if [ssCtrl] = Shift then SwitchPanelCtrlLeftRight(LeftPanel, False) else
                  if ConfLynxLikeMotion then begin
                    if Assigned(AListView.Selected) then ActivateItem(AListView.Selected.Index);
                  end;
                  Accept := False;
                  KeyHandled := True;
                end;
    GDK_Left : begin
                 if [ssCtrl] = Shift then SwitchPanelCtrlLeftRight(LeftPanel, True) else
                 if ConfLynxLikeMotion then
                   if LeftPanel then PathButtonClick(LeftUpButton)
                                else PathButtonClick(RightUpButton);
                  Accept := False;
                  KeyHandled := True;
               end;
    GDK_INSERT : begin
                   KeyHandled := True;
                   DoSelect(5);
                 end;
    GDK_SPACE : if not QuickFind then begin
                  Accept := False;
                  KeyHandled := True;
                  if Length(CommandLineCombo.Entry.Text) > 0 then ActivateCommandLine(Key)
                  else begin
                    if not PDataItem(AListView.Selected.Data)^.Selected then DoGetDirSize(False);
                    DoSelect(8);
                  end;
                end;
    GDK_F1 : begin
               KeyHandled := True;
               if Shift = [ssAlt] then begin
                 ShowBookmarkQuick(True);
                 Accept := False;
               end;
             end;
    GDK_F2 : begin
               KeyHandled := True;
               if Shift = [ssAlt] then begin
                 ShowBookmarkQuick(False);
                 Accept := False;
               end else begin
                 DeactivateQuickFind(LeftPanel);
                 Accept:= False;
                 F6ButtonClick(nil);
               end;
             end;
    GDK_F3 : begin
               DeactivateQuickFind(LeftPanel);
               Accept:= False;
               KeyHandled := True;
               F3F4ButtonClick(F3Button);
             end;
    GDK_F4 : begin
               DeactivateQuickFind(LeftPanel);
               Accept:= False;
               KeyHandled := True;
               if ssShift in Shift then EditViewFile(LeftPanel, AListView, False, True)
                                   else F3F4ButtonClick(F4Button);
             end;
    GDK_F5 : begin
               DeactivateQuickFind(LeftPanel);
               Accept:= False;
               KeyHandled := True;
               if ssShift in Shift then F5ButtonClick(nil)
                                   else F5ButtonClick(Sender);
             end;
    GDK_F6 : begin
               DeactivateQuickFind(LeftPanel);
               Accept:= False;
               KeyHandled := True;
               if ssShift in Shift then begin
                                          Editing := IsEditing(AListView);
                                          DoQuickRename(LeftPanel, AListView, True);
                                        end else F6ButtonClick(Sender);
             end;
    GDK_F7 : begin
               DeactivateQuickFind(LeftPanel);
               Accept:= False;
               KeyHandled := True;
               if ssAlt in Shift then miSearchClick(Sender)
                                 else F7ButtonClick(Sender);
             end;
    GDK_F8, GDK_Delete_Key : begin
                               DeactivateQuickFind(LeftPanel);
                               Accept:= False;
                               KeyHandled := True;
                               F8ButtonClick(Sender);
                             end;
    GDK_ESCAPE : begin
                   if not QuickFind then CommandLineCombo.Entry.Text := '';
                   DeactivateQuickFind(LeftPanel);
                   KeyHandled := True;
                   if RunningEscSensitive > 0 then FMainEscPressed := True;
                 end;
    GDK_WIN_POPUP : begin
                      Accept := False;
                      KeyHandled := True;
                      PopupFileMenuPos;
                    end;
    GDK_HOME: begin
                if Shift = [ssCtrl] then begin
                  if LeftPanel then PathButtonClick(LeftHomeButton)
                               else PathButtonClick(RightHomeButton);
                  Accept := False;
                end else if Assigned(AListView.Selected) and (AListView.ConvertToSorted(AListView.Selected.Index) = 0) then Accept := False;
                KeyHandled := True;
              end;
    GDK_END: begin
               if Assigned(AListView.Selected) and (AListView.ConvertToSorted(AListView.Selected.Index) = AListView.Items.Count - 1)
                 then Accept := False;
               KeyHandled := True;
             end;
    GDK_SLASH, GDK_KP_SLASH: begin
                               if Shift = [ssCtrl] then begin
                                 if LeftPanel then PathButtonClick(LeftRootButton)
                                              else PathButtonClick(RightRootButton);
                                 Accept := False;
                               end else
                               if (Shift = []) then ActivateQuickFind(LeftPanel);
                               KeyHandled := True;
                             end;
{    GDK_0..GDK_9: if ConfBookmarkQuickJump and (Shift = [ssAlt]) then QuickJumpToBookmark(LeftPanel, Key - GDK_1)
                  else begin
                         Accept := False;
                         if QuickFind then QuickFindSendKey(LeftPanel, Key)
                                      else ActivateCommandLine(Key);
                       end; }
    GDK_Down : begin
                 KeyHandled := False;
                 if [ssCtrl] = Shift then begin
                   Accept := False;
                   CommandLineCombo.Entry.SetFocus;
                   CommandLineCombo.Entry.SelectAll;
                   KeyHandled := True;
                 end else begin
                   if QuickFind and (Shift = []) then begin
                     KeyHandled := QuickFindSendKey(LeftPanel, Key);
                     Accept := not KeyHandled;
                   end;
                   if not KeyHandled then begin
                     KeyHandled := True;
                     if Assigned(AListView.Selected) and (AListView.ConvertToSorted(AListView.Selected.Index) = AListView.Items.Count - 1) then Accept := False;
                   end;
                 end;
               end;
    GDK_Up : begin
               KeyHandled := False;
               if QuickFind and (Shift = []) then begin
                 KeyHandled := QuickFindSendKey(LeftPanel, Key);
                 Accept := not KeyHandled;
               end;
               if not KeyHandled then begin
                 KeyHandled := True;
                 if Assigned(AListView.Selected) and (AListView.ConvertToSorted(AListView.Selected.Index) = 0) then Accept := False;
               end;
             end;
    GDK_Page_Up, GDK_Page_Down: begin
                                  KeyHandled := True;
                                  if (Shift = [ssCtrl]) and ANotebook.Visible then begin
                                    Accept := False;
                                    x := (ANotebook.PageIndex + 1 - 2*Ord(Key = GDK_Page_Up)) mod ANotebook.ChildrenCount;
                                    if x < 0 then x := ANotebook.ChildrenCount - 1;
                                    ANotebook.PageIndex := x;
                                  end else begin
                                    if Assigned(AListView.Selected) and
                                       (((Key = GDK_Page_Up) and (AListView.ConvertToSorted(AListView.Selected.Index) = 0)) or
                                       ((Key = GDK_Page_Down) and (AListView.ConvertToSorted(AListView.Selected.Index) = AListView.Items.Count - 1)))
                                       then Accept := False;
                                  end;
                                end;


    GDK_A, GDK_Capital_A: if ((Shift = [ssAlt]) and (ConfQuickSearchActivationKey <> 2)) or (Shift = [ssCtrl]) then begin
                            KeyHandled := True;
                            CommandLineComboKeyDown(Sender, Key, Shift, Accept);
                          end;
    GDK_D, GDK_Capital_D: if Shift = [ssCtrl] then begin
                            Accept := False;
                            KeyHandled := True;
                            ShowBookmarkQuick(LeftPanel);
                          end;
    GDK_O, GDK_Capital_O : if (Shift = [ssAlt]) and (ConfQuickSearchActivationKey <> 2) then begin
                             Accept := False;
                             KeyHandled := True;
                             SwitchOtherPanel(LeftPanel, False);
                           end;
    GDK_P, GDK_Capital_P, GDK_N, GDK_Capital_N:
      if (((Shift = [ssAlt]) and (ConfQuickSearchActivationKey <> 2)) or (Shift = [ssCtrl])) { and (CommandLineHistory.Count > 0) } then begin
        KeyHandled := True;
        CommandLineComboKeyDown(Sender, Key, Shift, Accept);
      end;
    GDK_S, GDK_Capital_S : if ((Shift = [ssAlt]) and (ConfQuickSearchActivationKey <> 2)) or (Shift = [ssCtrl]) then begin
                             KeyHandled := True;
                             ActivateQuickFind(LeftPanel);
                           end;
  end;
  if not KeyHandled then Accept := not HandleKey(Key, Shift, LeftPanel);
end;

function TFMain.HandleKey(Key: Word; Shift: TShiftState; LeftPanel: boolean): boolean;
var s: string;
    b: boolean;
begin
  Result := False;
  if Key = 0 then Exit;

  //  Filter out all non-character keys
  s := UTF8Encode(WideChar(KeyValToUnicode(Key)));
  if (Length(s) = 0) or (s = #0) then begin
//    DebugMsg(['HandleKey: not a character key. Ignoring.']);
    Exit;
  end;

  //  Triggers:
  //    0 = Ctrl+S/Alt+S and "/" only
  //    1 = Ctrl+Alt+letters
  //    2 = Alt+letters
  //    3 = letters directly
  if QuickFind then Result := QuickFindSendKey(LeftPanel, Key) else begin
    b := False;
    case ConfQuickSearchActivationKey of
      1: b := Shift = [ssCtrl, ssAlt];
      2: b := Shift = [ssAlt];
      3: b := Shift = [];
    end;
    if b then begin
      ActivateQuickFind(LeftPanel);
      Result := QuickFindSendKey(LeftPanel, Key);
    end else Result := ActivateCommandLine(Key);
  end;
end;


(******************************************************************************************************************************************)
procedure TFMain.ListViewEnter(Sender: TObject; var Accept: boolean);
var s: string;
begin
  LeftLastFocused := Sender = LeftListView;
  if LeftLastFocused then begin
    LeftPathLabelEventBox.ControlState := csSelected;
    RightPathLabelEventBox.ControlState := csActive;
    s := LeftPathLabel.Caption;
  end else begin
    LeftPathLabelEventBox.ControlState := csActive;
    RightPathLabelEventBox.ControlState := csSelected;
    s := RightPathLabel.Caption;
  end;
  CommandLineLabel.Caption := Format('%s@%s:%s>', [GetUserName, GetHostName, s]);
  UpdateCaption;
  miDisconnect.Enabled := (LeftLastFocused and (LeftPanelEngine is TVFSEngine) and (not TVFSEngine(LeftPanelEngine).ArchiveMode)) or
                          ((not LeftLastFocused) and (RightPanelEngine is TVFSEngine) and (not TVFSEngine(RightPanelEngine).ArchiveMode));
end;

procedure TFMain.FormResize(Sender: TObject);
begin
  if Width <> LastWidth then begin
//    DebugMsg(['FormResize: ', Width, 'x', Height]);
    PanelSeparator.Position := Round(Width * (ConfPanelSep / 100));
    CommandLineLabel.SetSizeRequest(Round(Width / 2.5), -1);
    LastWidth := Width;
  end;
end;

procedure TFMain.PathLabelMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
begin
  if Button in [mbLeft, mbRight] then begin
    if Sender = LeftPathLabelEventBox then LeftListView.SetFocus
    else if Sender = RightPathLabelEventBox then RightListView.SetFocus;
  end;
  if Button = mbRight then begin
    Accept := False;
    PathBoxPopupMenu.PopUp;
  end;
end;

procedure TFMain.ActivateItem(const ItemIndex: longint);
var Data: PDataItem;
    LeftPanel: boolean;
    DataList: TList;
    Engine: TPanelEngine;
    AListView: TGTKListView;
    Ext: string;
begin
  if LeftListView.Focused then LeftPanel := True
  else if RightListView.Focused then LeftPanel := False
  else LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    DataList := LeftPanelData;
    Engine := LeftPanelEngine;
    AListView := LeftListView;
  end else begin
    DataList := RightPanelData;
    Engine := RightPanelEngine;
    AListView := RightListView;
  end;
  DeactivateQuickFind(LeftPanel);
  if Application.GTKVersion_2_0_5_Up then Data := DataList[ItemIndex]
                                     else Data := AListView.Items[ItemIndex].AsPointer(0);
  DebugMsg(['Selected:', Data^.FDisplayName]);
  if not Assigned(Data) then Exit;
  if Data^.UpDir then ChangingDir(LeftPanel, '..') else
  if Data^.IsDir then ChangingDir(LeftPanel, Data^.FName)
  else begin
    Ext := WideUpperCase(Trim(Copy(String(Data^.FDisplayName), LastDelimiter('.', String(Data^.FDisplayName)) + 1, Length(String(Data^.FDisplayName)) - LastDelimiter('.', String(Data^.FDisplayName)))));
    //  Test for known internal functions
    if ((Ext = 'SFV') or (Ext = 'MD5')) and (Engine is TLocalTreeEngine) then miVerifyChecksumsClick(Self) else
    if ((Ext = 'CRC') or (Ext = '001')) and (Engine is TLocalTreeEngine) then miMergeFilesClick(Self) else
    if not HandleVFSArchive(LeftPanel, IncludeTrailingPathDelimiter(Engine.Path) + String(Data^.FName), String(Data^.FName), '/') then
    if (not ConfUseURI) or (not (Engine is TVFSEngine)) or ((Engine is TVFSEngine) and TVFSEngine(Engine).ArchiveMode)
      then RunFile(IncludeTrailingPathDelimiter(Engine.Path) + String(Data^.FName), Engine, -1)
      else RunFile(IncludeTrailingPathDelimiter((Engine as TVFSEngine).GetPathURI) + String(Data^.FName), Engine, -1);
  end;
end;

procedure TFMain.ChangingDir(LeftPanel: boolean; NewPath: string; HiliString1: string = ''; HiliString2: string = ''; const PreserveSelection: boolean = False; const AutoFallback: boolean = False; Plugin: TVFSPlugin = nil);
var ListView: TGTKListView;
    Engine: TPanelEngine;
    s, ss: string;
    i, Sel: integer;
    b: boolean;
    tt: TDateTime;
    DataList, DirList: TList;
    SelectedFiles: TStringList;
    ANotebook: TEphyNotebook;
    ATabList: TStringList;
    TabEngines: TList;
    OpenDirThread: TOpenDirThread;

  function LookupItem(const AName: string; const CaseSensitive: boolean): boolean;
  var i: integer;
  begin
    Result := False;
    for i := 0 to DataList.Count - 1 do
     if (CaseSensitive and (WideCompareStr(string(PDataItem(DataList[i])^.FName), AName) = 0)) or
        ((not CaseSensitive) and (WideCompareText(string(PDataItem(DataList[i])^.FName), AName) = 0)) then
     begin
       Sel := i;
       Result := True;
       Break;
     end;
  end;
  
  procedure DoThread;
  var DialogParent: PGtkWidget;
  begin
    try
      OpenDirThread.AEngine := Engine;
      OpenDirThread.APath := NewPath;
      OpenDirThread.ASelItem := '';
      OpenDirThread.AAutoFallBack := AutoFallback;
      OpenDirThread.ADirList := DirList;
      if Plugin <> nil then begin
      DebugMsg(['Plugin <> nil']);
        OpenDirThread.APlugin := Plugin;
        OpenDirThread.AFullPath := HiliString1;
        OpenDirThread.AHighlightItem := HiliString2;
      end;
      DebugMsg(['(II) TFMain.ChangingDir: begin thread']);
      tt := Now;
      b := False;
      FRemoteWait := TFRemoteWait.Create(Application);
      FRemoteWait.ParentForm := FMain;
      
      //  WARNING: For an unknown reason, there's a race condition in Kylix (FPC is fine).
      //  The thread must be started *after* the FRemoteWait is created.
      OpenDirThread.Resume;
      repeat
        Sleep(ConstInternalProgressTimer);
//        DebugMsg(['  (II) TFMain.ChangingDir: sleep.']);
        Application.ProcessMessages;
        if not b and (MilliSecondsBetween(tt, Now) >= ConstRemoteWaitDialogDelay) then begin
          FRemoteWait.ShowModal;
          b := True;
        end;
        if FRemoteWait.Cancelled then OpenDirThread.CancelIt := True;
        if OpenDirThread.VFSAskQuestion_Display then begin
          OpenDirThread.VFSAskQuestion_Display := False;
          DebugMsg(['Main thread: displaying question dialog']);
          if FRemoteWait.Visible then DialogParent := FRemoteWait.FWidget
                                 else DialogParent := FWidget;
          HandleVFSAskQuestionCallback(DialogParent, OpenDirThread.VFSAskQuestion_Message, OpenDirThread.VFSAskQuestion_Choices, OpenDirThread.VFSAskQuestion_Choice);
          OpenDirThread.VFSCallbackEvent.SetEvent;
        end;
        if OpenDirThread.VFSAskPassword_Display then begin
          OpenDirThread.VFSAskPassword_Display := False;
          DebugMsg(['Main thread: displaying password prompt']);
          if FRemoteWait.Visible then DialogParent := FRemoteWait.FWidget
                                 else DialogParent := FWidget;
          OpenDirThread.VFSAskPassword_Result := HandleVFSAskPasswordCallback(DialogParent,
                                                                              OpenDirThread.VFSAskPassword_Message,
                                                                              OpenDirThread.VFSAskPassword_default_user,
                                                                              OpenDirThread.VFSAskPassword_default_domain,
                                                                              OpenDirThread.VFSAskPassword_default_password,
                                                                              OpenDirThread.VFSAskPassword_flags,
                                                                              OpenDirThread.VFSAskPassword_username,
                                                                              OpenDirThread.VFSAskPassword_password,
                                                                              OpenDirThread.VFSAskPassword_anonymous,
                                                                              OpenDirThread.VFSAskPassword_domain,
                                                                              OpenDirThread.VFSAskPassword_password_save);
          OpenDirThread.VFSCallbackEvent.SetEvent;
        end;
      until OpenDirThread.Finished;
      FRemoteWait.Free;
      DebugMsg(['(II) TFMain.ChangingDir: end thread, running time = ', OpenDirThread.RunningTime, 'ms']);
    except
      on E: Exception do DebugMsg(['*** Exception raised in TFMain.ChangingDir:DoThread (', E.ClassName, '): ', E.Message]);
    end;
  end;

begin
  DebugMsg(['*** Begin changing dir to ', NewPath]);
  if LeftPanel then begin
    ListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
    ANotebook := LeftPanelNotebook;
    ATabList := LeftPanelTabs;
    TabEngines := LeftTabEngines;
  end else begin
    ListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
    ANotebook := RightPanelNotebook;
    ATabList := RightPanelTabs;
    TabEngines := RightTabEngines;
  end;
  try
    if (NewPath = '..') and (Engine.ParentEngine <> nil) and (Engine.Path = '/') then begin
      CloseVFS(LeftPanel, False);
      Exit;
    end;

  InternalLock;
  FileListTipsDisable;
  SelectedFiles := nil;
  if PreserveSelection then begin
    SelectedFiles := TStringList.Create;
    if DataList.Count > 0 then
      for i := 0 to DataList.Count - 1 do
        if PDataItem(DataList[i])^.Selected and (not PDataItem(DataList[i])^.UpDir)
          then SelectedFiles.Add(PDataItem(DataList[i])^.FName);
  end;
  Editing := False;
  DeactivateQuickFind(LeftPanel);
  DeactivateQuickFind(not LeftPanel);
  DirList := TList.Create;

  //  Threading...
  OpenDirThread := TOpenDirThread.Create;
  DebugMsg(['TFMain.ChangingDir: Creating thread...']);
  DoThread;

  if Plugin <> nil then begin
    HiliString1 := '';
    HiliString2 := '';
  end;
  if OpenDirThread.VFSOpenResult <> 0 then begin
    //  Silence the error if password dialog has been cancelled
    if not OpenDirThread.VFSCallbackCancelled then
      Application.MessageBox(LANGCouldntOpenURIArchive, [mbOK], mbError, mbOK, mbOK);
    DebugMsg(['TFMain.ChangingDir: Freeing thread...']);
    OpenDirThread.Free;
  end else
  if OpenDirThread.ChDirResult <> 0 then begin
    if not OpenDirThread.VFSCallbackCancelled then begin
      //  Drop the error message if one of the callback dialogs were cancelled
      if OpenDirThread.ChDirResult = 1 then Application.MessageBox(Format(LANGErrorGettingListingForSPanelNoPath, [LANGPanelStrings[LeftPanel], 'Exception']), [mbOK], mbError, mbNone, mbOK)
      else Application.MessageBox(Format(LANGErrorGettingListingForSPanel, [LANGPanelStrings[LeftPanel], GetErrorString(OpenDirThread.ChDirResult), NewPath]), [mbOK], mbError, mbNone, mbOK);
    end;
    DebugMsg(['TFMain.ChangingDir: Freeing thread...']);
    OpenDirThread.Free;
  end else begin
    if OpenDirThread.ListingResult <> 0 then begin
      Application.MessageBox(Format(LANGErrorGettingListingForSPanel, [LANGPanelStrings[LeftPanel], GetErrorString(OpenDirThread.ListingResult), Engine.Path]), [mbOK], mbError, mbNone, mbOK);
      Exit;
    end;
    s := OpenDirThread.ASelItem;
    Engine := OpenDirThread.AEngine;  //  set current Engine from the thread (might have been modified due to VFS)
    if LeftPanel then LeftPanelEngine := Engine
                 else RightPanelEngine := Engine;
    DebugMsg(['TFMain.ChangingDir: Freeing thread...']);
    OpenDirThread.Free;

    FillPanel(DirList, ListView, Engine, LeftPanel);    //  This is time consuming
    DirList.Free;
    if DataList.Count > 0 then begin
      if PreserveSelection and (SelectedFiles.Count > 0) and (DataList.Count > 0) then
        for i := 0 to DataList.Count - 1 do
          if (not PDataItem(DataList[i])^.UpDir) and (SelectedFiles.IndexOf(PDataItem(DataList[i])^.FName) >= 0)
            then PDataItem(DataList[i])^.Selected := True;
        Sel := 0;
        b := (NewPath = '..') and (Length(Trim(s)) > 0) and LookupItem(StrToUTF8(s), True);
        if not b then b := (HiliString1 <> '') and LookupItem(HiliString1, True);
        if (not b) and (HiliString1 <> '') then b := LookupItem(HiliString1, False);
        if (not b) and (HiliString2 <> '') then b := LookupItem(HiliString2, True);
        if (not b) and (HiliString2 <> '') then b := LookupItem(HiliString2, False);
//        DebugMsg(['TFMain.ChangingDir: Engine.Path = "', Engine.Path, '", NewPath = "', NewPath, '", HiliString1 = "', HiliString1, '", HiliString2 = "', HiliString2, '"']);
        if (not b) and ((Engine.Path = '/') or (NewPath = '/')) and (HiliString1 = '') and (HiliString2 = '') then Sel := ListView.ConvertFromSorted(0);
        ListView.Items[Sel].Selected := True;
//        Application.ProcessMessages;
        ListView.Items[Sel].SetCursor(0, False, not Application.GTKVersion_2_2_0_Up, 0.5, 0);
//        Application.ProcessMessages;
    end;
    UpdatePanelInfo;
    UpdatePanelInfoDown(LeftPanel);
    UpdatePanelInfoDown(not LeftPanel);
    if ANotebook.Visible then begin
      ATabList[ANotebook.PageIndex] := Engine.Path;
      TabEngines[ANotebook.PageIndex] := Engine;
      s := ExtractFileName(ExcludeTrailingPathDelimiter(Engine.Path));
      if s = '' then s := '/';
      SetTabLabel(ANotebook, ANotebook.PageIndex, StrToUTF8(s), StrToUTF8(Engine.Path));
    end;
  end;  //  of Chdir, Listing, ...
  Engine.ExplicitChDir('/');
  Application.ProcessMessages;
  InternalUnLock;
  FileListTipsEnable;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TFMain.ChangingDir (', E.ClassName, '): ', E.Message]);
  end;
end;

procedure TFMain.DoRefresh(LeftPanel, StaySame, AutoFallback: boolean);
var ListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
    s1, s2: string;
begin
  if LeftPanel then begin
    ListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    ListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;
  s1 := '';  s2 := '';
  FindNextSelected(ListView, DataList, s1, s2);
  ChangingDir(LeftPanel, Engine.Path, s1, s2, StaySame, AutoFallback);
end;

function TFMain.FormatPathString(Engine: TPanelEngine): string;
begin
  if not (Engine is TVFSEngine) then Result := Engine.Path else
  if (Engine as TVFSEngine).ArchiveMode then Result := Format(ConstFullPathFormatStr, [(Engine as TVFSEngine).ArchivePath, Engine.Path])
                                        else Result := (Engine as TVFSEngine).GetPathURI;
end;

procedure TFMain.UpdateCaption;
var LeftPanel: boolean;
    Engine: TPanelEngine;
begin
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;

  if LeftPanel then Engine := LeftPanelEngine
               else Engine := RightPanelEngine;

  Caption := Format('Tux Commander  [%s]', [StrToUTF8(FormatPathString(Engine))]);

  miVerifyChecksums.Enabled := Engine is TLocalTreeEngine;
  miCreateChecksums.Enabled := Engine is TLocalTreeEngine;
  miSplitFile.Enabled := Engine is TLocalTreeEngine;
  miMergeFiles.Enabled := Engine is TLocalTreeEngine;
end;

procedure TFMain.UpdatePanelInfo;
var FSFree, FSSize: Int64;
    FSName, s: string;
    Time1, Time2: TDateTime;
begin
  UpdateCaption;
  Time1 := Now;
  LeftPathLabel.Caption := StrToUTF8(FormatPathString(LeftPanelEngine));
  RightPathLabel.Caption := StrToUTF8(FormatPathString(RightPanelEngine));
  LeftPathLabel.UseMarkup := True;
  RightPathLabel.UseMarkup := True;
  if LeftLastFocused then s := LeftPathLabel.Caption
                     else s := RightPathLabel.Caption;
  CommandLineLabel.Caption := Format('%s@%s:%s>', [GetUserName, GetHostName, s]);
  LeftPanelEngine.GetFileSystemInfo(LeftPanelEngine.Path, FSSize, FSFree, FSName);
  if FSName <> ''
    then LeftDiskInfoLabel.Caption := Format(LANGDiskStatVolNameFmt, [FSName,
           FormatSize(FSFree, 1024),
           FormatSize(FSSize, 1024)])
    else LeftDiskInfoLabel.Caption := Format(LANGDiskStatFmt,
          [FormatSize(FSFree, 1024),
           FormatSize(FSSize, 1024)]);
  RightPanelEngine.GetFileSystemInfo(RightPanelEngine.Path, FSSize, FSFree, FSName);
  if FSName <> ''
    then RightDiskInfoLabel.Caption := Format(LANGDiskStatVolNameFmt, [FSName,
           FormatSize(FSFree, 1024),
           FormatSize(FSSize, 1024)])
    else RightDiskInfoLabel.Caption := Format(LANGDiskStatFmt,
          [FormatSize(FSFree, 1024),
           FormatSize(FSSize, 1024)]);
  LeftDiskInfoLabel.UseMarkup := True;
  RightDiskInfoLabel.UseMarkup := True;

  //  Update visibility of VFS buttons
  LeftDisconnectButton.Visible := (LeftPanelEngine is TVFSEngine) and (not TVFSEngine(LeftPanelEngine).ArchiveMode);
  LeftLeaveArchiveButton.Visible := (LeftPanelEngine is TVFSEngine) and TVFSEngine(LeftPanelEngine).ArchiveMode;
  RightDisconnectButton.Visible := (RightPanelEngine is TVFSEngine) and (not TVFSEngine(RightPanelEngine).ArchiveMode);
  RightLeaveArchiveButton.Visible := (RightPanelEngine is TVFSEngine) and TVFSEngine(RightPanelEngine).ArchiveMode;
  LeftPasswordButton.Visible := (LeftPanelEngine is TVFSEngine) and TVFSEngine(LeftPanelEngine).GetPasswordRequired;
  RightPasswordButton.Visible := (RightPanelEngine is TVFSEngine) and TVFSEngine(RightPanelEngine).GetPasswordRequired;
  miDisconnect.Enabled := (LeftLastFocused and (LeftPanelEngine is TVFSEngine) and (not TVFSEngine(LeftPanelEngine).ArchiveMode)) or
                          ((not LeftLastFocused) and (RightPanelEngine is TVFSEngine) and (not TVFSEngine(RightPanelEngine).ArchiveMode));
  Time2 := Now;
  DebugMsg(['UpdatePanelInfo: ', SecondOf(Time2 - Time1), ':', MillisecondOf(Time2 - Time1)]);
end;

procedure TFMain.UpdatePanelInfoDown(LeftPanel: boolean);
var Size, TotalSize: Int64;
    NumSel, TotalFiles: longint;
    i: integer;
    Data: PDataItem;
    s: string;
    DataList: TList;
begin
  if LeftPanel then DataList := LeftPanelData
               else DataList := RightPanelData;
  Size := 0;
  TotalSize := 0;
  NumSel := 0;
  TotalFiles := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do begin
      Data := DataList[i];
      if (not Data^.UpDir) and ((not Data^.IsDir) or (Data^.IsDir and (Data^.Size > 0))) then begin
        Inc(TotalSize, Data^.Size);
        if not Data^.IsDir then Inc(TotalFiles);
        if Data^.Selected then begin
          if not Data^.IsDir then Inc(NumSel);
          Inc(Size, Data^.Size);
        end;
      end;
    end;
  s := Format(LANGStatusLineFmt, [FormatSize(Size, 1024),
                                  FormatSize(TotalSize, 1024), NumSel, TotalFiles]);
  if LeftPanel then LeftStatusLine.Caption := s
               else RightStatusLine.Caption := s;
end;

procedure TFMain.PathButtonClick(Sender: TObject);
var NewPath: string;
    LeftPanel: boolean;
begin
  LeftPanel := (Sender = LeftUpButton) or (Sender = LeftHomeButton) or (Sender = LeftRootButton);
  if (Sender = LeftUpButton) or (Sender = RightUpButton) then NewPath := '..'
  else if (Sender = LeftRootButton) or (Sender = RightRootButton) then NewPath := '/'
  else if (Sender = LeftHomeButton) or (Sender = RightHomeButton) then NewPath := GetHomePath
  else Exit;
  
  //  Close VFS connections
  if (Sender = LeftHomeButton) and (not (LeftPanelEngine is TLocalTreeEngine)) then
    while (LeftPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True);
  if (Sender = RightHomeButton) and (not (RightPanelEngine is TLocalTreeEngine)) then
    while (RightPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True);
  if ((Sender = LeftUpButton) and (LeftPanelEngine is TVFSEngine) and (not (LeftPanelEngine as TVFSEngine).ArchiveMode) and (LeftPanelEngine.Path = '/')) or
     ((Sender = RightUpButton) and (RightPanelEngine is TVFSEngine) and (not (RightPanelEngine as TVFSEngine).ArchiveMode) and (RightPanelEngine.Path = '/'))
    then NewPath := '/';

  ChangingDir(LeftPanel, NewPath);
  if LeftPanel then LeftListView.SetFocus
               else RightListView.SetFocus;
end;

procedure TFMain.miRefreshClick(Sender: TObject);
begin
  DoRefresh(LeftListView.Focused, True, True);
end;

function TFMain.CompareFunc(Sender: TObject; var model: PGtkTreeModel; var a, b: PGtkTreeIter): integer;
var Data1, Data2: PDataItem;
    Path: PGtkTreePath;
    DataList: TList;
begin
  Result := 0;
  if not Application.GTKVersion_2_0_5_Up then begin
    gtk_tree_model_get(model, a, 0, @Data1, -1);
    gtk_tree_model_get(model, b, 0, @Data2, -1);
  end else begin
    if Sender = LeftListView then DataList := LeftPanelData
                             else DataList := RightPanelData;
    Path := gtk_tree_model_get_path(model, a);
    if not Assigned(Path) then Exit;
    Data1 := DataList[gtk_tree_path_get_indices(Path)^];
    gtk_tree_path_free(Path);
    Path := gtk_tree_model_get_path(model, b);
    if not Assigned(Path) then Exit;
    Data2 := DataList[gtk_tree_path_get_indices(Path)^];
    gtk_tree_path_free(Path);
  end;
  Result := LVCompareItems(Data1, Data2, (Sender as TGTKView).SortOrder = soAscending, FMain.ColumnSortIDs[(Sender as TGTKView).SortColumnID + 1]);
end;

(********************************************************************************************************************************)
procedure TFMain.DoSelect(SelectType: integer);
var Filter, s: string;
    LeftPanel, ExpandSel, b, Found: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    i, j: integer;
    Item: TGTKListItem;
    Data: PDataItem;
    DataList: TList;
    Wilds: array of string;
begin
  try
  InternalLock;
  try
    if LeftListView.Focused then LeftPanel := True else
    if RightListView.Focused then LeftPanel := False else Exit;
    if LeftPanel then begin
      ListView := LeftListView;
      Engine := LeftPanelEngine;
      DataList := LeftPanelData;  
    end else begin
      ListView := RightListView;
      Engine := RightPanelEngine;
      DataList := RightPanelData;
    end;
    DeactivateQuickFind(LeftPanel);
    ExpandSel := False;
    if SelectType in [1, 2] then try
      FSelect := TFSelect.Create(Self);
      case SelectType of
        1 : FSelect.Caption := LANGExpandSelection;
        2 : FSelect.Caption := LANGShrinkSelection;
      end;
{      FSelect.TitleLabel.Caption := Format('<span size="large" weight="ultrabold">%s</span>', [FSelect.Caption]);
      FSelect.TitleLabel.UseMarkup := True; }
      ExpandSel := SelectType = 1;
      FSelect.ComboBox.Entry.Text := LastUsedFilter;
      FSelect.ComboBox.Entry.SelectAll;
      if FSelect.Run = mbOK
        then begin
               Filter := FSelect.ComboBox.Entry.Text;
               LastUsedFilter := Filter;
               if FSelect.ComboBox.Entry.Text <> '*.*' then
                 SaveItemToHistory(FSelect.ComboBox.Entry.Text, SelectHistory);
        end else Exit;
    finally
      FSelect.Free;
    end;

    case SelectType of
      1, 2 : if ListView.Items.Count > 1 then begin
               SetLength(Wilds, 0);
               while LastDelimiter(ConfSelItemsDelim, Filter) > 0 do begin
                 i := LastDelimiter(ConfSelItemsDelim, Filter);
                 if i < Length(Filter) then begin
                   s := Copy(Filter, i + 1, Length(Filter) - i);
                   Delete(Filter, i, Length(Filter) - i + 1);
                   SetLength(Wilds, Length(Wilds) + 1);
                   Wilds[Length(Wilds) - 1] := s;
                 end;  
               end;
               if Length(Filter) > 0 then begin
                 SetLength(Wilds, Length(Wilds) + 1);
                 Wilds[Length(Wilds) - 1] := Filter;
               end;
               Found := False;
               for i := 0 to DataList.Count - 1 do begin
                 Data := DataList[i];
                 if Assigned(Data) and (not Data^.UpDir) and (ConfSelectAllDirs or (not Data^.IsDir)) {and (Data^.Selected <> ExpandSel)} then begin
                   b := False;
                   for j := 0 to Length(Wilds) - 1 do
                     b := b or IsWild(String(Data^.FDisplayName), Wilds[j], True);
                   if b then begin
                     Data^.Selected := ExpandSel;
                     Found := True;
                   end;
                 end;
               end;
               SetLength(Wilds, 0);
               if not Found then Application.MessageBox(LANGNoMatchesFound, [mbOK], mbWarning, mbNone, mbOK);
             end;  
      3, 4 : if DataList.Count > 0 then
               for i := 0 to DataList.Count - 1 do
                 if (not PDataItem(DataList[i])^.UpDir) and (ConfSelectAllDirs or (not PDataItem(DataList[i])^.IsDir)) then
                   PDataItem(DataList[i])^.Selected := not PDataItem(DataList[i])^.Selected;
      5, 8 : begin
            Item := ListView.Selected;
            if Assigned(Item) and Assigned(Item.Data) then begin
              Data := PDataItem(Item.Data);
              if (Engine.Path = '/') or (Item.Index > 0) then Data^.Selected := not Data^.Selected;
              if (ListView.ConvertToSorted(Item.Index) < ListView.Items.Count - 1) and
                 (((SelectType = 5) and ConfInsMoveDown) or ((SelectType = 8) and ConfSpaceMovesDown)) then begin
                ListView.Selected := ListView.Items[ListView.ConvertFromSorted(ListView.ConvertToSorted(Item.Index) + 1)];
                ListView.Selected.SetCursor(0, False, False, 0, 0);
              end else Item.RedrawRow;    //  Move to the next item will invalidate it automatically
            end;
          end;
      6, 7: if DataList.Count > 0 then
              for i := 0 to DataList.Count - 1 do
                if (not PDataItem(DataList[i])^.UpDir) and (ConfSelectAllDirs or (not PDataItem(DataList[i])^.IsDir)) then
                  PDataItem(DataList[i])^.Selected := SelectType = 6;
    end;
    if SelectType in [1..4, 6..7] then ListView.Invalidate;   //  Make changes appear
    UpdatePanelInfoDown(LeftPanel);
    Application.ProcessMessages;
  except end;
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

function TFMain.IsEditing(AListView: TGTKListView): boolean;
var i: integer;
begin
  Result := False;
  for i := 0 to AListView.Columns.Count - 1 do
    if Assigned(AListView.Columns[i].FColumn^.editable_widget) then Result := True;
end;

function TFMain.PanelFindEditableWidget(AListView: TGTKListView): PGtkWidget;
var i: integer;
begin
  Result := nil;
  for i := 0 to AListView.Columns.Count - 1 do
    if Assigned(AListView.Columns[i].FColumn^.editable_widget) then begin
      Result := AListView.Columns[i].FColumn^.editable_widget;
      Break;
    end;
end;

procedure TFMain.ProcessMarkKey(KeyType, Key: integer);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Pos: integer;
    editable: PGtkEditable;
begin
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then ListView := LeftListView
               else ListView := RightListView;
  editable := PanelFindEditableWidget(ListView);
  if Editing and Assigned(editable) then begin
    Pos := gtk_editable_get_position(editable);
    gtk_editable_insert_text(editable, PChar(UTF8Encode(WideChar(KeyValToUnicode(Key)))), 1, @Pos);
    gtk_editable_set_position(editable, Pos);
  end;
  if CommandLineCombo.Entry.Focused then ActivateCommandLine(Key, True) else
  if QuickFind then QuickFindSendKey(LeftPanel, Key)
               else DoSelect(KeyType);
end;

procedure TFMain.mnuMarkClick(Sender: TObject);
begin
  if Sender = miSelectGroup then ProcessMarkKey(1, GDK_KP_PLUS) else
  if Sender = miUnselectGroup then ProcessMarkKey(2, GDK_KP_MINUS) else
  if Sender = miSelectAll then DoSelect(6) else
  if Sender = miUnselectAll then DoSelect(7) else
  if Sender = miInvertSelection then ProcessMarkKey(4, GDK_KP_ASTERISK);
end;

(********************************************************************************************************************************)
procedure TFMain.ListViewCellDataFunc(Sender: TObject; tree_view: PGtkTreeView; tree_column : PGtkTreeViewColumn; cell : PGtkCellRenderer; tree_model : PGtkTreeModel; iter : PGtkTreeIter);
var s: PChar;
    Sel, ImageCol: boolean;
    Data: PDataItem;
    i, ColumnID, ColumnIdx: integer;
    DataList: TList;
    TreePath: PGtkTreePath;
    AFGColor, ABGColor: PGdkColor;
{    Rect, VisibleRect: TGdkRectangle; }
begin
  if Application.GTKVersion_2_0_5_Up then ColumnIdx := gtk_tree_view_column_get_sort_column_id(tree_column) else
  begin
    ColumnIdx := 0;
    for i := 0 to (Sender as TGTKListView).Columns.Count - 1 do
      if (cell = (Sender as TGTKListView).Columns[i].FRenderer) or (cell = (Sender as TGTKListView).Columns[i].FPixbufRenderer) then begin
        ColumnIdx := i;
        Break;
      end;
  end;
  ColumnID := ColumnSortIDs[ColumnIdx + 1] - 1;
  ImageCol := False;
  if ColumnIdx = 0 then ImageCol := GTK_IS_CELL_RENDERER_PIXBUF(cell);
  Data := nil;
  if not Application.GTKVersion_2_0_5_Up then gtk_tree_model_get(tree_model, iter, 0, @Data, -1)
  else begin
    if Sender = LeftListView then DataList := LeftPanelData
                             else DataList := RightPanelData;
    TreePath := gtk_tree_model_get_path(tree_model, iter);
    if not Assigned(TreePath) then Exit;
    (Sender as TGTKListView).ConvertPathToChild(TreePath);
    Data := DataList[gtk_tree_path_get_indices(TreePath)^];
    gtk_tree_path_free(TreePath);
  end;
(* gtk_tree_view_get_cell_area(tree_view, TreePath, nil, @Rect);
  gtk_tree_view_get_visible_rect(tree_view, @VisibleRect);
  if (Rect.height = 0) or (Rect.height <> ConfRowHeight) or (Rect.y = 0) or (Rect.y > VisibleRect.y + VisibleRect.height) then Exit; *)

//  DebugMsg(['Rendering text ', Data^.ColumnData[ColumnID]]);

  if not Assigned(Data) then Exit;
  Sel := gtk_tree_selection_iter_is_selected((Sender as TGTKView).FSelection, iter);
  with Data^ do begin
    // ################  Prepare colors
    if Selected then AFGColor := SelectedItemGDKColor else begin
      if Sel then begin
        if (Sender as TGTKView).Focused
          then AFGColor := ActiveItemGDKColor
          else AFGColor := InactiveItemGDKColor;
      end else AFGColor := ItemColor;
    end;
    if not Sel then ABGColor := NormalItemGDKBackground else
    if (Sender as TGTKView).Focused then ABGColor := ActiveItemGDKBackground
                                    else ABGColor := InactiveItemGDKBackground;



    // ################  Setting the properties
    if not ImageCol then begin
      if Editing and (InplaceEditItem.Data = Data) and (ColumnID < 3) and ((ColumnID = 0) or (ColumnID = 1) or Assigned(tree_column^.editable_widget))
        then begin
               if (ColumnID = 0) or (ColumnID = 1) then s := FDisplayName else s := nil;
               g_object_set(cell, 'text', s, 'foreground-gdk', AFGColor, nil);
               if Application.GTKVersion_2_2_0_Up or (not ConfUseFileTypeIcons) then
                 g_object_set(cell, 'background-gdk', ABGColor, nil);
             end
        else begin  //  not editing
               if ConfDirsInBold then begin
                 if IsDir or UpDir then g_object_set(cell, 'markup', PChar(Format('<span weight="bold">%s</span>', [QuoteMarkupStr(ColumnData[ColumnID])])), 'foreground-gdk', AFGColor, nil)
                                   else g_object_set(cell, 'markup', PChar(QuoteMarkupStr(ColumnData[ColumnID])), 'foreground-gdk', AFGColor, nil);
               end else g_object_set(cell, 'text', ColumnData[ColumnID], 'foreground-gdk', AFGColor, nil);
               if Application.GTKVersion_2_2_0_Up or (not ConfUseFileTypeIcons) then
                 g_object_set(cell, 'background-gdk', ABGColor, nil);    //  Older versions have bug in color filling
             end;
    end else  //  this is the image column
    if ConfUseFileTypeIcons then begin   //  Assign icons
      if Application.GTKVersion_2_2_0_Up then
        g_object_set(cell, 'cell-background-gdk', ABGColor, nil);   //  Older versions don't have this property
      if Sel and (not (Sender as TGTKView).Focused) and Application.GTKVersion_2_2_0_Up then begin
        if InactiveItemsTimer.Enabled then InactiveItemsTimer.Enabled := False;
        if Sender = LeftListView then RedrawLeftInactive := True
                                 else RedrawRightInactive := True;
        InactiveItemsTimer.Enabled := not Application.GTKVersion_2_6_0_Up;
      end;
      if Icon <> nil then g_object_set(cell, 'pixbuf', Icon, nil);
    end;
  end;
end;

procedure TFMain.F7ButtonClick(Sender: TObject);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    NewDir: string;
begin
  try
  InternalLock;
  try
    if Sender = F7Button then LeftPanel := LeftLastFocused else
    if LeftListView.Focused then LeftPanel := True else
    if RightListView.Focused then LeftPanel := False else Exit;
    if LeftPanel then ListView := LeftListView
                 else ListView := RightListView;
    if LeftPanel then Engine := LeftPanelEngine
                 else Engine := RightPanelEngine;

    try
      FNewDir := TFNewDir.Create(Self);
      if FNewDir.Run = mbOK
        then NewDir := UTF8ToStr(FNewDir.Entry.Text)
        else Exit;
    finally
      FNewDir.Free;
    end;

    if NewDir = '' then Exit;
    if not MakeDirectory(ListView, Engine, LeftPanel, NewDir) then Exit;
    if Pos('/', NewDir) > 0 then Delete(NewDir, Pos('/', NewDir), Length(NewDir) - Pos('/', NewDir) + 1);
    ChangingDir(LeftPanel, Engine.Path, NewDir);
    DoRefresh(not LeftPanel, True, True);
  except end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.DoGetDirSize(AllItems: boolean);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else Exit;
  if LeftPanel then begin
    ListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    ListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  RunningEscSensitive := 1;
  GetDirSize(ListView, Engine, DataList, AllItems);

  if ConfSortDirectoriesLikeFiles and (ColumnSortIDs[ListView.SortColumnID + 1] = 4) then begin
    if ListView.SortOrder = soAscending then begin
      ListView.SetSortInfo(ListView.SortColumnID, soDescending);
      ListView.SetSortInfo(ListView.SortColumnID, soAscending);
    end else begin
      ListView.SetSortInfo(ListView.SortColumnID, soAscending);
      ListView.SetSortInfo(ListView.SortColumnID, soDescending);
    end;
    ListView.Selected.SetCursor(0, False, False, 0, 0);
  end;
  FMainEscPressed := False;
  RunningEscSensitive := 0;
end;

procedure TFMain.DoDelete(LeftPanel: boolean; ListView: TGTKListView; Engine: TPanelEngine; DataList: TList);
var i, j : integer;
    SelCount: longint;
    s, s2, smsg, NextItem1, NextItem2: string;
    Data: PDataItem;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
begin
  try
  InternalLock;
  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);

  if (SelCount = 0) and ((not Assigned(ListView.Selected)) or PDataItem(ListView.Selected.Data)^.UpDir) then begin
//    WriteLn(integer(mbApply), ', ', integer(Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbApply)));
    Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbOK);
    Exit;
  end;

  Data := nil;
  if Assigned(ListView.Selected) then Data := ListView.Selected.Data;
  if SelCount > 0 then begin
    j := 0;
    s2 := '';
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then begin
          s2 := s2 + #10 + FDisplayName;
          Inc(j);
          if j = 5 then Break;
        end;
    if SelCount > j then s2 := s2 + #10 + '...';
    s := Format(LANGSelectedFilesDirectories, [SelCount]);
    smsg := Format(LANGDoYouReallyWantToDeleteTheSS, [s, s2]);
  end else begin
    if Assigned(Data) then
      if Data^.IsDir then s := Format(LANGDirectoryS, [string(Data^.FDisplayName)])
                     else s := Format(LANGFileS, [string(Data^.FDisplayName)]);
    smsg := Format(LANGDoYouReallyWantToDeleteTheS, [s]);
  end;
  if Application.MessageBox(QuotePercentStr(smsg), [mbYes, mbNo], mbQuestion, mbNone, mbNo) <> mbYes then Exit;

  FindNextSelected(ListView, DataList, NextItem1, NextItem2);
  AWorkingThread := TWorkerThread.Create;
  DebugMsg(['TFMain.DoDelete: Creating thread...']);
  AFProgress := TFProgress.Create(Self);
  try
    AFProgress.SetNumBars(False);
//    AFProgress.ProgressBar.Max := 100;
    AFProgress.ProgressBar.Value := 0;
    AWorkingThread.ProgressForm := AFProgress;
    if Assigned(ListView.Selected) then AWorkingThread.SelectedItem := ListView.Selected.Data;
    AWorkingThread.Engine := Engine;
    AWorkingThread.LeftPanel := LeftPanel;
    AWorkingThread.DataList := DataList;
    AWorkingThread.WorkerProcedure := DeleteFilesWorker;
    AWorkingThread.Resume;

//    AFProgress.Run;

    AFProgress.ParentForm := FMain;
    AFProgress.ShowModal;
    ProcessProgressThread(AWorkingThread, AFProgress);
    AFProgress.Close;


{    FProgress.ShowModal;
    Application.ProcessMessages;
    repeat
      Sleep(100);
      Application.ProcessMessages;
//      FProgress.Run;
    until False; }

{    FProgress.ShowModal;
    DeleteFiles(FProgress, ListView, Engine, LeftPanel, DataList); }

  finally
    AFProgress.Free;
    AWorkingThread.Free;
    DebugMsg(['TFMain.DoDelete: Freeing thread...']);
  end;
  ChangingDir(LeftPanel, Engine.Path, NextItem1, NextItem2);
  DoRefresh(not LeftPanel, True, True);
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.F8ButtonClick(Sender: TObject);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  try
    if (Sender = F8Button) or (Sender is TGTKMenuItem)
      then LeftPanel := LeftLastFocused else
    if LeftListView.Focused then LeftPanel := True else
    if RightListView.Focused then LeftPanel := False else Exit;
    if LeftPanel then begin
      ListView := LeftListView;
      Engine := LeftPanelEngine;
      DataList := LeftPanelData;  
    end else begin
      ListView := RightListView;
      Engine := RightPanelEngine;
      DataList := RightPanelData;
    end;
    DoDelete(LeftPanel, ListView, Engine, DataList);
  except end;
end;

procedure TFMain.F5ButtonClick(Sender: TObject);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  try
    if Sender = F5Button then LeftPanel := LeftLastFocused else
    if LeftListView.Focused then LeftPanel := True else
    if RightListView.Focused then LeftPanel := False else Exit;
    if LeftPanel then begin
      ListView := LeftListView;
      Engine := LeftPanelEngine;
      DataList := LeftPanelData;  
    end else begin
      ListView := RightListView;
      Engine := RightPanelEngine;
      DataList := RightPanelData;
    end;
    DoCopyMove(LeftPanel, True, Sender = nil, ListView, Engine, DataList);
  except end;
end;

procedure TFMain.F6ButtonClick(Sender: TObject);
var LeftPanel: boolean;
    ListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  try
    if (Sender = F6Button) or (Sender = F2Button) or (Sender is TGTKMenuItem)
      then LeftPanel := LeftLastFocused else
    if LeftListView.Focused then LeftPanel := True else
    if RightListView.Focused then LeftPanel := False else Exit;
    if LeftPanel then begin
      ListView := LeftListView;
      Engine := LeftPanelEngine;
      DataList := LeftPanelData;
    end else begin
      ListView := RightListView;
      Engine := RightPanelEngine;
      DataList := RightPanelData;
    end;
    DoCopyMove(LeftPanel, False, (Sender = nil) or (Sender = F2Button) or (Sender is TGTKMenuItem), ListView, Engine, DataList);
  except end;
end;

procedure TFMain.DoCopyMove(LeftPanel, CopyMode, ShiftPressed: boolean; ListView: TGTKListView; Engine: TPanelEngine; DataList: TList);
var i: integer;
    SelCount: longint;
    NewPath, NewPathx, SelSingle, NextItem1, NextItem2: string;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
    CurrentEngine, OppositeEngine: TPanelEngine;
    p: PChar;
    BypassSelAll: boolean;
begin
  try
  InternalLock;
  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);

  if (SelCount = 0) and ((not Assigned(ListView.Selected)) or PDataItem(ListView.Selected.Data)^.UpDir) then begin
    Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbOK);
    Exit;
  end;

  BypassSelAll := False;
  SelSingle := '';
  if SelCount = 0 then begin
    SelCount := 1;
    SelSingle := PDataItem(ListView.Selected.Data)^.FDisplayName;
  end;

  if LeftPanel then begin
    CurrentEngine := LeftPanelEngine;
    OppositeEngine := RightPanelEngine;
  end else begin
    CurrentEngine := RightPanelEngine;
    OppositeEngine := LeftPanelEngine;
  end;

  try
    FCopyMove := TFCopyMove.Create(Self);
    if CopyMode then begin
      FCopyMove.Caption := LANGCopyFiles;
      FCopyMove.Label1.Caption := Format(LANGCopyDFileDirectoriesTo, [SelCount]);
    end else begin
      FCopyMove.Caption := LANGMoveRenameFiles;
      FCopyMove.Label1.Caption := Format(LANGMoveRenameDFileDirectoriesTo, [SelCount]);
    end;
    if ShiftPressed then begin
      if SelSingle <> '' then begin
        FCopyMove.Entry.Text := SelSingle;
        if ConfQuickRenameSkipExt then begin
          p := gtk_entry_get_text(PGtkEntry(FCopyMove.Entry.FWidget));
          if (p <> nil) and (g_utf8_strlen(p, -1) > 0) then begin
//            DebugMsg(['TFMain.DoCopyMove: p = "', p, '", g_utf8_strlen(p) = ', g_utf8_strlen(p, -1)]);
            if AnsiPos('.', p) > 0 then begin
              FCopyMove.Entry.SelectRegion(0, g_utf8_strlen(p, -1) - g_utf8_strlen(PChar(ExtractFileExt(p)), -1));
              BypassSelAll := True;
            end;
          end;
        end;
      end else FCopyMove.Entry.Text := '*.*';
    end else FCopyMove.Entry.Text := OppositeEngine.Path;
{      if OppositeEngine is TLocalTreeEngine then FCopyMove.Entry.Text := OppositeEngine.Path
                                            else FCopyMove.Entry.Text := Format(ConstFullPathFormatStr, [OppositeEngine.GetPrefix, OppositeEngine.Path]); }
    if not BypassSelAll then FCopyMove.Entry.SelectAll;
    if FCopyMove.Run <> mbOK then Exit;
    NewPathx := FCopyMove.Entry.Text;
    NewPath := UTF8ToStr(FCopyMove.Entry.Text);
  finally
    FCopyMove.Free;
  end;
  DebugMsg(['TFMain.DoCopyMove: NewPath = "', NewPath, '"']);

  NextItem1 := '';  NextItem2 := '';
  FindNextSelected(ListView, DataList, NextItem1, NextItem2);
  DebugMsg(['TFMain.DoCopyMove: FindNextSelected, NextItem1 = "', NextItem1, '", NextItem2 = "', NextItem2, '"']);
  if ShiftPressed then NextItem1 := NewPathx;
  
  AWorkingThread := TWorkerThread.Create;
  DebugMsg(['TFMain.DoCopyMove: Creating thread...']);
  AFProgress := TFProgress.Create(Self);
  try
    if CopyMode then AFProgress.Label1.Caption := LANGCopySC
                else AFProgress.Label1.Caption := LANGMoveRenameSC;
    AFProgress.SetNumBars(True);
    AFProgress.ProgressBar.Value := 0;
    AWorkingThread.ProgressForm := AFProgress;
    if Assigned(ListView.Selected) then AWorkingThread.SelectedItem := ListView.Selected.Data;
    //  Determine target engine according to absolute/relative path
    if (NewPath[1] = '/') or (NewPath[1] = '~') then AWorkingThread.DestEngine := OppositeEngine
                                                else AWorkingThread.DestEngine := Engine;
    AWorkingThread.SrcEngine := Engine;
    
    //  Determine on which engine the entered path is
{
    if Pos(ConstPathDelim, NewPath) = 0 then begin
      if OppositeEngine is TLocalTreeEngine then AWorkingThread.DestEngine := OppositeEngine else
      if CurrentEngine is TLocalTreeEngine then AWorkingThread.DestEngine := CurrentEngine
      else begin
             Application.MessageBox(LANGCannotDetermineDestinationEngine, [mbOK], mbError, mbOK, mbOK);
             Exit;
           end;
    end else begin
      s := Copy(NewPath, 1, Pos(ConstPathDelim, NewPath) - 1);
      if s = OppositeEngine.GetPrefix then AWorkingThread.DestEngine := OppositeEngine else
      if s = CurrentEngine.GetPrefix then AWorkingThread.DestEngine := CurrentEngine
      else begin
             Application.MessageBox(LANGCannotDetermineDestinationEngine, [mbOK], mbError, mbOK, mbOK);
             Exit;
           end;
    end;
}
    if AWorkingThread = nil then begin    //  Something went terribly wrong
      Application.MessageBox(LANGCannotDetermineDestinationEngine, [mbOK], mbError, mbOK, mbOK);
      Exit;
    end;

    //  Strip the engine prefix
//    if Pos(ConstPathDelim, NewPath) > 0 then Delete(NewPath, 1, Pos(ConstPathDelim, NewPath));

    AWorkingThread.LeftPanel := LeftPanel;
    AWorkingThread.DataList := DataList;
    AWorkingThread.WorkerProcedure := CopyFilesWorker;
//    AWorkingThread.WorkerProcedure := DummyThreadWorker;
    AWorkingThread.ParamBool3 := CopyMode;
    AWorkingThread.ParamBool4 := False; 
    AWorkingThread.ParamString1 := NewPath;
    AWorkingThread.ParamDataItem1 := nil;

    AFProgress.ParentForm := FMain;
    AFProgress.ShowModal;
    Application.ProcessMessages;

    DebugMsg(['*** Copy: AWorkingThread.Resume']);
//    DebugMsg(['*** Copy: xg_thread_supported = ', xg_thread_supported()]);
    AWorkingThread.Resume;
//    AWorkingThread.WorkerProcedure(AWorkingThread);
    DebugMsg(['*** Copy: AWorkingThread.Resumed.']);
    ProcessProgressThread(AWorkingThread, AFProgress);
//    DebugMsg(['***************************x1']);
    AFProgress.Close;
//    Beep;
  finally
//    DebugMsg(['** ddddddddddddddd ???']);
    AFProgress.Free;
    DebugMsg(['TFMain.DoCopyMove: Freeing thread...']);
    AWorkingThread.Free;
  end;
  ChangingDir(LeftPanel, Engine.Path, NextItem1, NextItem2);
  DoRefresh(not LeftPanel, True, False);
  finally
    DebugMsg(['** TFMain.DoCopyMove finished']);
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.ListViewDblClick(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
begin
  DebugMsg(['DblClick']);
  Accept := True;  //  This causes selecting the item if clicked is different than selected
  if (Sender as TGTKListView).GetItemAtPos(X, Y) <> (Sender as TGTKListView).Selected then Exit;
  Accept := False;
  DebugMsg(['DblClick OK']);
  if not (Sender as TGTKListView).Focused then (Sender as TGTKListView).SetFocus;
  if Assigned((Sender as TGTKListView).Selected) and Assigned((Sender as TGTKListView).Selected.Data)
    then ActivateItem((Sender as TGTKListView).Selected.Index);
end;

procedure TFMain.ListViewMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
var Item: TGTKListItem;
    Click: TDateTime;
    Data: PDataItem;
    AEngine: TPanelEngine;
begin
  try
  InternalLock;
  PanelRightMouseInProgress := False;
//  DebugMsg(['ListViewMouseDown, X = ', X, ', Y = ', Y]);
  if Button = mbLeft then begin
    Click := Now;
    if LastClick + ConfDblClickDelay/MSecsPerDay > Click then begin
      Accept := False;
      InplaceEditTimer.Enabled := False;
      LastClick := 0;
      ListViewDblClick(Sender, Button, Shift, X, Y, Accept);
      Exit;
    end;
    LastClick := Click;
    DebugMsg(['Click, Focus = ', (Sender as TGTKControl).Focused]);
    if not (Sender as TGTKListView).Focused then Exit; //  (Sender as TGTKListView).SetFocus;
    Item := (Sender as TGTKListView).GetItemAtPos(X, Y);
    if (Item = (Sender as TGTKListView).Selected) and Assigned(Item) and Assigned((Sender as TGTKListView).Selected) and
       Assigned(Item.Data) and (not PDataItem(Item.Data)^.UpDir) and (Sender as TGTKListView).Focused and (not ConfDisableMouseRename) then
    begin
      DebugMsg(['Quick-Rename']);
      InplaceEditTimer.Interval := ConfQuickRenameDelay;
      InplaceEditTimer.Enabled := True;
      InplaceEditItem := Item;
    end;
    Accept := True;
  end else
  if Button = mbRight then begin
    if not (Sender as TGTKListView).Focused then (Sender as TGTKListView).SetFocus;
    Item := (Sender as TGTKListView).GetItemAtPos(X, Y);
    if Assigned(Item) then begin
      Item.Selected := True;
      Item.SetCursor(0, False, not Application.GTKVersion_2_2_0_Up, 0.5, 0);
    end;

    //  Show file popup menu or start selection mode, depending on ConfRightClickSelect
    if ConfRightClickSelect then begin
      if (Item <> nil) and (Item.Data <> nil) then begin
        Data := PDataItem(Item.Data);
        if not Data^.UpDir then begin
          PanelRightMouseSelMode := not Data^.Selected;
          Data^.Selected := PanelRightMouseSelMode;
          Item.RedrawRow;
          UpdatePanelInfoDown(Sender = LeftListView);
          PanelRightMouseInProgress := True;
          RightMouseSelectPopupTimer.Interval := ConfQuickRenameDelay;
          RightMouseSelectPopupTimer.Enabled := True;
        end;
      end;
    end else 
      if (Item <> nil) then FilePopupMenu.PopUp;
    Accept := False;
  end else
  if Button = mbMiddle then begin
    if not (Sender as TGTKListView).Focused then (Sender as TGTKListView).SetFocus;
    Item := (Sender as TGTKListView).GetItemAtPos(X, Y);
    if Assigned(Item) then begin
      Item.Selected := True;
      Item.SetCursor(0, False, not Application.GTKVersion_2_2_0_Up, 0.5, 0);
    end;

    //  Open directory in background tab
    if (Item <> nil) and (Item.Data <> nil) then begin
      Data := PDataItem(Item.Data);
      if Sender = LeftListView then AEngine := LeftPanelEngine
                               else AEngine := RightPanelEngine;
      if (not Data^.UpDir) and (Data^.IsDir) then NewTab(Sender = LeftListView, True, IncludeTrailingPathDelimiter(AEngine.Path) + Data^.FName);
    end;

    Accept := False;
  end;
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.ListViewMouseMove(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
var Item: TGTKListItem;
    Data: PDataItem;
begin
  Accept := True;
  if not ((Sender is TGTKListView) and ConfRightClickSelect) then Exit;

  if (Button = mbRight) and PanelRightMouseInProgress then begin
    Item := (Sender as TGTKListView).GetItemAtPos(X, Y);
    if (Item <> nil) and (Item.Data <> nil) and (not Item.Selected) then begin
      Data := PDataItem(Item.Data);
      if not Data^.UpDir then begin
        RightMouseSelectPopupTimer.Enabled := False;
        Data^.Selected := PanelRightMouseSelMode;
        Item.Selected := True;
        Item.SetCursor(0, False, not Application.GTKVersion_2_2_0_Up, 0.5, 0);
        UpdatePanelInfoDown(Sender = LeftListView);
      end;
    end;
  end else PanelRightMouseInProgress := False;
end;

procedure TFMain.RightMouseSelectPopupTimerTimer(Sender: TObject);
var AListView: TGTKListView;
    Data: PDataItem;
begin
  RightMouseSelectPopupTimer.Enabled := False;
  if LeftListView.Focused then AListView := LeftListView else
  if RightListView.Focused then AListView := RightListView else
  Exit;
  if (AListView.Selected <> nil) then begin
    Data := AListView.Selected.Data;
    //  Explicitly select the item before showing the popup menu
    if (Data <> nil) and (not Data^.UpDir) then begin
      Data^.Selected := True;
      AListView.Selected.RedrawRow;
      UpdatePanelInfoDown(LeftListView.Focused);
      FilePopupMenu.PopUp;
    end;
  end;
end;


procedure TFMain.InplaceEditTimerTimer(Sender: TObject);
var LeftPanel: boolean;
    ListView: TGTKListView;
begin
  InplaceEditTimer.Enabled := False;
  try
    LeftPanel := LeftLastFocused;
    if LeftPanel then begin
      ListView := LeftListView;
    end else begin
      ListView := RightListView;
    end;
    if ListView.Selected <> InplaceEditItem then Exit;
    DoQuickRename(LeftPanel, ListView, False);
  except end;
end;

procedure TFMain.DoQuickRename(LeftPanel: boolean; ListView: TGTKListView; const CalledFromKey: boolean);
var i: integer;
    s: PChar;
begin
  if (not Assigned(ListView.Selected)) or (not Assigned(ListView.Selected.Data)) or
     PDataItem(ListView.Selected.Data)^.UpDir or Editing then Exit;
  Editing := True;
  if CalledFromKey then InplaceEditItem := ListView.Selected;
  for i := 1 to ConstNumPanelColumns do
    if ColumnSortIDs[i] in [1, 2] then begin
      ListView.Columns[i - 1].SetProperty('editable', 1);
      ListView.StartEditing(i - 1);
      if ConfQuickRenameSkipExt and (ListView.Columns[i - 1].FColumn^.editable_widget <> nil) then begin
        s := gtk_entry_get_text(PGtkEntry(ListView.Columns[i - 1].FColumn^.editable_widget));
        if (s <> nil) and (g_utf8_strlen(s, -1) > 0) then begin
//          DebugMsg(['TFMain.DoQuickRename: s = "', s, '", g_utf8_strlen(s) = ', g_utf8_strlen(s, -1)]);
          if AnsiPos('.', s) > 0 then
            gtk_editable_select_region(PGtkEditable(ListView.Columns[i - 1].FColumn^.editable_widget), 0, g_utf8_strlen(s, -1) - g_utf8_strlen(PChar(ExtractFileExt(s)), -1));
        end;
      end;
      Break;
    end;
end;

procedure TFMain.ListViewEdited(Sender: TObject; Column: TGTKTreeViewColumn; Item: TGTKListItem; var NewText: string; var AllowChange: boolean; var DataColumn: integer);
var AListView: TGTKListView;
    DataList: TList;
    Engine: TPanelEngine;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
    i: integer;
    s1, s2: string;
begin
  try
  InternalLock;
  AListView := (((Sender as TGTKTreeViewColumn).Parent as TGTKTreeViewColumns).Parent as TGTKListView);
  for i := 1 to ConstNumPanelColumns do
    if ColumnSortIDs[i] in [1, 2] then begin
      AListView.Columns[i - 1].SetProperty('editable', 0);
      Break;
    end;
  if AListView = LeftListView then begin
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;
  Editing := False;
  DebugMsg(['TFMain.ListViewEdited: FDisplayName = "', String(PDataItem(InplaceEditItem.Data)^.FDisplayName), '", NewText = "', NewText, '"']);
  if (AListView.Selected = InplaceEditItem) and (AListView.Selected.Data = InplaceEditItem.Data) and
     (String(PDataItem(InplaceEditItem.Data)^.FDisplayName) <> NewText) then
  begin
    AWorkingThread := TWorkerThread.Create;
    DebugMsg(['TFMain.ListViewEdited: Creating thread...']);
    AFProgress := TFProgress.Create(Self);
    try
      AFProgress.SetNumBars(True);
      AFProgress.ProgressBar.Value := 0;
      AFProgress.Label1.Caption := LANGMoveRenameSC;
      AWorkingThread.ProgressForm := AFProgress;
      if Assigned(AListView.Selected) then AWorkingThread.SelectedItem := AListView.Selected.Data;
      AWorkingThread.SrcEngine := Engine;
      AWorkingThread.DestEngine := Engine;
      AWorkingThread.LeftPanel := AListView = LeftListView;
      AWorkingThread.DataList := DataList;
      AWorkingThread.WorkerProcedure := CopyFilesWorker;
      AWorkingThread.ParamBool3 := False;
      AWorkingThread.ParamBool4 := True;
      AWorkingThread.ParamString1 := UTF8ToStr(NewText);
      AWorkingThread.ParamDataItem1 := InplaceEditItem.Data;
      AWorkingThread.Resume;
//      AWorkingThread.WorkerProcedure(AWorkingThread);
      AFProgress.ParentForm := FMain;
      AFProgress.ShowModal;
      ProcessProgressThread(AWorkingThread, AFProgress);
      AFProgress.Close;
    finally
      AFProgress.Free;
      DebugMsg(['TFMain.ListViewEdited: Freeing thread...']);
      AWorkingThread.Free;
    end;
    s1 := '';  s2 := '';
    FindNextSelected(AListView, DataList, s1, s2);
    DebugMsg(['TFMain.ListViewEdited: FindNextSelected, s1 = "', s1, '", s2 = "', s2, '"']);
    ChangingDir(AListView = LeftListView, Engine.Path, {String(PDataItem(InplaceEditItem.Data)^.AName),} NewText, s2);
    DoRefresh(AListView <> LeftListView, True, True);
  end;
  AListView.SetFocus;
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.ListViewSelectionChanged(Sender: TObject);
var i: integer;
//    AListView: TGTKListView;
begin
  if Editing and ((Sender as TGTKListView).Selected <> InplaceEditItem) then begin
    Editing := False;
    for i := 1 to ConstNumPanelColumns do
      if ColumnSortIDs[i] in [1, 2] then begin
        (Sender as TGTKListView).Columns[i - 1].SetProperty('editable', 0);
        Break;
      end;
  end;
  if QuickFind then DeactivateQuickFind(Sender = LeftListView);
  SaveCursorPositionTabbed(Sender = LeftListView);
{  if Application.GTKVersion_2_6_0_Up then begin
    AListView := Sender as TGTKListView;
    if Assigned(AListView) and Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and Assigned(PDataItem(AListView.Selected.Data)^.ItemColor)
      then gtk_widget_modify_text(AListView.FWidget, GTK_STATE_SELECTED, PDataItem(AListView.Selected.Data)^.ItemColor);
  end; }
end;

procedure TFMain.SaveCursorPositionTabbed(LeftPanel: boolean);
begin
  if LeftPanel then begin
    if LeftPanelNotebook.Visible and (LeftPathsHighlight.Count > LeftPanelNotebook.PageIndex) and (LeftPanelNotebook.PageIndex >= 0) and
       Assigned(LeftListView.Selected) and Assigned(LeftListView.Selected.Data)
      then LeftPathsHighlight[LeftPanelNotebook.PageIndex] := PDataItem(LeftListView.Selected.Data)^.FName;
  end else
    if RightPanelNotebook.Visible and (RightPathsHighlight.Count > RightPanelNotebook.PageIndex) and (RightPanelNotebook.PageIndex >= 0) and
       Assigned(RightListView.Selected) and Assigned(RightListView.Selected.Data)
      then RightPathsHighlight[RightPanelNotebook.PageIndex] := PDataItem(RightListView.Selected.Data)^.FName;
end;

procedure TFMain.ActivateQuickFind(LeftPanel: boolean);
var Entry: TGTKEntry;
begin
  QuickFind := True; 
  if LeftPanel then begin
    Entry := LeftQuickFindEntry;
    LeftQuickFindVBox.Show;
  end else begin
    Entry := RightQuickFindEntry;
    RightQuickFindVBox.Show;
  end;
  Entry.Text := '';
end;

procedure TFMain.QuickFindEntryEnter(Sender: TObject; var Accept: boolean);
begin
  Accept := False;
  (Sender as TGTKControl).SetFocus;
end;

procedure TFMain.DeactivateQuickFind(LeftPanel: boolean);
begin
  QuickFind := False;
  if LeftPanel then LeftQuickFindVBox.Hide
               else RightQuickFindVBox.Hide;
end;

function TFMain.QuickFindSendKey(LeftPanel: boolean; Key: word): boolean;
var Entry: TGTKEntry;
    s, NewText: string;
    i: integer;
    AListView: TGTKListView;
    DataList: TList;
    Data: PDataItem;
    OldSelectionChangedEvent: TNotifyEvent;
    g: PChar;
    NewIndex, StartIndex: Longint;
begin
  Result := False;
  if not QuickFind then Exit;
  if LeftPanel then begin
    Entry := LeftQuickFindEntry;
    AListView := LeftListView;
    DataList := LeftPanelData;
  end else begin
    Entry := RightQuickFindEntry;
    AListView := RightListView;
    DataList := RightPanelData;
  end;
  
//  DebugMsg(['TFMain.QuickFindSendKey: Key = ', Key, ', GDK_Down = ', GDK_Down, ', GDK_Up = ', GDK_Up]);
  if Key = GDK_BACKSPACE then begin
    if g_utf8_strlen(PChar(Entry.Text), -1) > 0 then begin
//      DebugMsg(['TFMain.QuickFindSendKey: before delete: "', Entry.Text, '", ansi = "', UTF8ToStr(Entry.Text), '"']);
      g := malloc(Length(Entry.Text));
      memset(g, 0, Length(Entry.Text));
      g_utf8_strncpy(g, PChar(Entry.Text), g_utf8_strlen(PChar(Entry.Text), -1) - 1);
//      DebugMsg(['TFMain.QuickFindSendKey: after delete: "', g, '", ansi = "', UTF8ToStr(g), '"']);
      Entry.Text := g;
      libc_free(g);
    end;
    NewText := Entry.Text;
  end else
  if (Key = GDK_Down) or (Key = GDK_Up) then begin
    if Length(Entry.Text) = 0 then begin
      DeactivateQuickFind(LeftPanel);
      Result := False;
      Exit;
    end else NewText := Entry.Text;
  end else begin
    s := UTF8Encode(WideChar(KeyValToUnicode(Key)));
    if (Length(s) = 0) or (s = #0) then Exit;
    NewText := Entry.Text + s;
  end;
  
  if (DataList.Count > 0) and (Length(NewText) > 0) then begin
    NewIndex := -1;
    StartIndex := (AListView.ConvertToSorted(AListView.Selected.Index) + Ord(Key = GDK_Down) - Ord(Key = GDK_Up)) mod DataList.Count;
    if StartIndex < 0 then StartIndex := 0;
    if StartIndex > DataList.Count - 1 then StartIndex := DataList.Count - 1;
//    DebugMsg(['TFMain.QuickFindSendKey: StartIndex = ', StartIndex]);

    if Key <> GDK_Up then begin
      //  Search down
      for i := StartIndex to DataList.Count - 1 do begin
        Data := DataList[AListView.ConvertFromSorted(i)];
        if Assigned(Data) and (not Data^.UpDir) and (Pos(WideUpperCase(NewText), WideUpperCase(Data^.FDisplayName)) = 1) then begin
          NewIndex := i;
          Break;
        end;
      end;
      if NewIndex < 0 then
        for i := 0 to StartIndex do begin
          Data := DataList[AListView.ConvertFromSorted(i)];
          if Assigned(Data) and (not Data^.UpDir) and (Pos(WideUpperCase(NewText), WideUpperCase(Data^.FDisplayName)) = 1) then begin
            NewIndex := i;
            Break;
          end;
        end;
    end else begin
      //  Search up
      for i := StartIndex downto 0 do begin
        Data := DataList[AListView.ConvertFromSorted(i)];
        if Assigned(Data) and (not Data^.UpDir) and (Pos(WideUpperCase(NewText), WideUpperCase(Data^.FDisplayName)) = 1) then begin
          NewIndex := i;
          Break;
        end;
      end;
      if NewIndex < 0 then
        for i := DataList.Count - 1 downto StartIndex do begin
          Data := DataList[AListView.ConvertFromSorted(i)];
          if Assigned(Data) and (not Data^.UpDir) and (Pos(WideUpperCase(NewText), WideUpperCase(Data^.FDisplayName)) = 1) then begin
            NewIndex := i;
            Break;
          end;
        end;
    end;

    if NewIndex >= 0 then begin
      OldSelectionChangedEvent := AListView.OnSelectionChanged;
      AListView.OnSelectionChanged := nil;
      AListView.Selected := AListView.Items[AListView.ConvertFromSorted(NewIndex)];
      AListView.Items[AListView.ConvertFromSorted(NewIndex)].SetCursor(0, False, False, 0, 0);
      SaveCursorPositionTabbed(LeftPanel);
      AListView.OnSelectionChanged := OldSelectionChangedEvent;
      Entry.Text := NewText;
    end else Beep;
    Result := True;
  end;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.miVerifyChecksumsClick(Sender: TObject);
var i, SelCount: integer;
    b, LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) and (not IsDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) or PDataItem(AListView.Selected.Data)^.UpDir or PDataItem(AListView.Selected.Data)^.IsDir) then begin
    Application.MessageBox(LANGYouMustSelectAValidFile, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;

  try
    FChecksum := TFChecksum.Create(Self);
    FChecksum.Engine := Engine;
    FChecksum.DataList := DataList;
    FChecksum.AListView := AListView;
    if SelCount = 0 then b := FChecksum.ProcessFile(IncludeTrailingPathDelimiter(Engine.Path) + string(PDataItem(AListView.Selected.Data)^.FName))
    else begin
      b := False;
      for i := 0 to DataList.Count - 1 do
        with PDataItem(DataList[i])^ do
          if (not UpDir) and (not IsDir) and Selected then {$B+}
            b := b or FChecksum.ProcessFile(IncludeTrailingPathDelimiter(Engine.Path) + string(FName));
          {$B-}
    end;
    if b { and (FChecksum.List.Count > 0) } then FChecksum.Run;
  finally
    FChecksum.Free;
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.miCreateChecksumsClick(Sender: TObject);
var i, SelCount: integer;
    LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;  

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) and (not IsDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) or PDataItem(AListView.Selected.Data)^.UpDir or PDataItem(AListView.Selected.Data)^.IsDir) then begin
    Application.MessageBox(LANGYouMustSelectAtLeastOneFileToCalculateChecksum, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;

  try
    FChecksumDruid := TFChecksumDruid.Create(Self);
    Engine.ExplicitChDir(Engine.Path);
    FChecksumDruid.Engine := Engine;
    if Engine.Path = '/' then FChecksumDruid.DirName := 'root'
                         else FChecksumDruid.DirName := ExtractFileName(ExcludeTrailingPathDelimiter(Engine.Path));
    if SelCount = 0 then FChecksumDruid.FileNames.Add(IncludeTrailingPathDelimiter(Engine.Path) + string(PDataItem(AListView.Selected.Data)^.FName))
    else
      for i := 0 to DataList.Count - 1 do
        with PDataItem(DataList[i])^ do
          if (not UpDir) and (not IsDir) and Selected then
            FChecksumDruid.FileNames.Add(IncludeTrailingPathDelimiter(Engine.Path) + string(FName));
    FChecksumDruid.Run;
  finally
    if FChecksumDruid.SeparateFileCheckBox.Checked
      then DoRefresh(AListView = LeftListView, True, True)
      else ChangingDir(AListView = LeftListView, Engine.Path, FChecksumDruid.FileNameEntry.Text, PDataItem(AListView.Selected.Data)^.FName);
    DoRefresh(AListView <> LeftListView, True, True);
    FChecksumDruid.Free;
    Engine.ExplicitChDir('/');
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.miMergeFilesClick(Sender: TObject);
var LeftPanel, HasInitialCRC: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    FilePath, s, TargetName: string;
    TargetCRC: LongWord;
    TargetSize: Int64;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
  end;

  if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or PDataItem(AListView.Selected.Data)^.IsDir or
     PDataItem(AListView.Selected.Data)^.UpDir then
  begin
    Application.MessageBox(LANGYouMustSelectAValidFile, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;

  FilePath := ''; 
  try
    FNewDir := TFNewDir.Create(Self);
    FNewDir.Caption := LANGMergeCaption;
    FNewDir.Label1.SetSizeRequest(500, -1);
    FNewDir.Label1.Caption := Format(LANGMergeSAndAllFilesWithAscendingNamesToTheFollowingDirectory, [PDataItem(AListView.Selected.Data)^.FDisplayName]);
    if LeftPanel then FNewDir.Entry.Text := StrToUTF8(RightPanelEngine.Path)
                 else FNewDir.Entry.Text := StrToUTF8(LeftPanelEngine.Path);
    FNewDir.Entry.SelectAll;
    if FNewDir.Run = mbOK
      then FilePath := UTF8ToStr(FNewDir.Entry.Text)
      else Exit;
  finally
    FNewDir.Free;
  end;
  if FilePath = '' then Exit;

  AWorkingThread := TWorkerThread.Create;
  DebugMsg(['TFMain.miMergeFilesClick: Creating thread...']);
  AFProgress := TFProgress.Create(Self);
  try
    AWorkingThread.ParamString1 := FilePath;
    AWorkingThread.ParamString2 := IncludeTrailingPathDelimiter(Engine.Path) + PDataItem(AListView.Selected.Data)^.FName;
    HasInitialCRC := CRCGetInfo(AWorkingThread.ParamString2, Engine, TargetName, TargetCRC, TargetSize);
    AWorkingThread.ParamString3 := TargetName;
    AWorkingThread.ParamBool1 := HasInitialCRC;
    AWorkingThread.ParamLongWord1 := TargetCRC;
    AWorkingThread.ParamInt64 := TargetSize;
    AFProgress.SetNumBars(HasInitialCRC);
    AFProgress.ProgressBar.Value := 0;
    AFProgress.Label1.Caption := LANGMergeSC;
    AWorkingThread.ProgressForm := AFProgress;
    AWorkingThread.Engine := Engine;
    AWorkingThread.LeftPanel := LeftPanel;
    AWorkingThread.WorkerProcedure := MergeFilesWorker;
    AWorkingThread.Resume;
    AFProgress.ParentForm := FMain;
    AFProgress.ShowModal;
    ProcessProgressThread(AWorkingThread, AFProgress);
    AFProgress.Close;
    s := AWorkingThread.ParamString3;
  finally
    AFProgress.Free;
    DebugMsg(['TFMain.miMergeFilesClick: Freeing thread...']);
    AWorkingThread.Free;
  end;

  ChangingDir(LeftPanel, Engine.Path, s, PDataItem(AListView.Selected.Data)^.FName);
  DoRefresh(not LeftPanel, True, True);
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.miSplitFileClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    FilePath: string;
    DeleteTarget: boolean;
    MaxSize: Int64;
    i: integer;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
  end;

  if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or PDataItem(AListView.Selected.Data)^.IsDir or
     PDataItem(AListView.Selected.Data)^.UpDir then
  begin
    Application.MessageBox(LANGYouMustSelectAValidFile, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;

  FilePath := '';
  try
    FSplitFile := TFSplitFile.Create(Self);
    FSplitFile.Label1.Caption := Format(LANGSplitTheFileSToDirectory, [PDataItem(AListView.Selected.Data)^.FDisplayName]);
    FSplitFile.Label1.UseUnderline := True;
    if LeftPanel then FSplitFile.Entry.Text := StrToUTF8(RightPanelEngine.Path)
                 else FSplitFile.Entry.Text := StrToUTF8(LeftPanelEngine.Path);
    FSplitFile.Entry.SelectAll;
    if FSplitFile.Run = mbOK
      then FilePath := UTF8ToStr(FSplitFile.Entry.Text)
      else Exit;
    DeleteTarget := FSplitFile.DeleteTargetCheckBox.Checked;
    MaxSize := 0;
    for i := 1 to Length(SplitConsts) do
      if Trim(WideUpperCase(SplitConsts[i].Title)) = Trim(WideUpperCase(FSplitFile.SizeCombo.Entry.Text)) then
      begin
        MaxSize := SplitConsts[i].PartSize;
        Break;
      end;
    if MaxSize = 0 then MaxSize := GetStrSize(FSplitFile.SizeCombo.Entry.Text);
  finally
    FSplitFile.Free;
  end;
  if FilePath = '' then Exit;
  
  AWorkingThread := TWorkerThread.Create;
  DebugMsg(['TFMain.miSplitFileClick: Creating thread...']);
  AFProgress := TFProgress.Create(Self);
  try
    AWorkingThread.ParamString1 := IncludeTrailingPathDelimiter(Engine.Path) + PDataItem(AListView.Selected.Data)^.FName;
    AWorkingThread.ParamString2 := FilePath;
    AWorkingThread.ParamBool1 := DeleteTarget;
    AWorkingThread.ParamInt64 := MaxSize; 
    AFProgress.SetNumBars(MaxSize > 0);
    AFProgress.ProgressBar.Value := 0;
    AFProgress.Label1.Caption := LANGSplitSC;
    AWorkingThread.ProgressForm := AFProgress;
    AWorkingThread.Engine := Engine;
    AWorkingThread.LeftPanel := LeftPanel;
    AWorkingThread.WorkerProcedure := SplitFilesWorker;
    AWorkingThread.Resume;
    AFProgress.ParentForm := FMain;
    AFProgress.ShowModal;
    ProcessProgressThread(AWorkingThread, AFProgress);
    AFProgress.Close;
  finally
    AFProgress.Free;
    DebugMsg(['TFMain.miSplitFileClick: Freeing thread...']);
    AWorkingThread.Free;
  end;

  DoRefresh(LeftPanel, True, True);
  DoRefresh(not LeftPanel, True, True);
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.SwitchOtherPanel(LeftPanel, RequestNewAltO: boolean);
var AListView: TGTKListView;
    SrcEngine, OrigSrcEngine, TargetEngine: TPanelEngine;
    s: string;
    DontShowAgain: boolean;
begin
  if LeftPanel then begin
    AListView := LeftListView;
    SrcEngine := LeftPanelEngine;
    TargetEngine := RightPanelEngine;
  end else begin
    AListView := RightListView;
    SrcEngine := RightPanelEngine;
    TargetEngine := LeftPanelEngine;
  end;
  OrigSrcEngine := SrcEngine;

  if not (SrcEngine is TLocalTreeEngine) then begin
    if ConfSwitchOtherPanelBehaviour < 0 then begin
      MessageBoxShowOnce(PGtkWindow(FWidget), LANGSwitchOtherPanelWarning, LANGDontShowAgain, DontShowAgain, [mbOK], mbWarning, mbOK, mbOK);
      if DontShowAgain then begin
        ConfSwitchOtherPanelBehaviour := 1;
        WriteMainGUISettings;
      end;
    end;
//    Exit;
  end;

  //  don't change dir in VFS engines
  while not (SrcEngine is TLocalTreeEngine) do SrcEngine := SrcEngine.ParentEngine;
  while not (TargetEngine is TLocalTreeEngine) do TargetEngine := TargetEngine.ParentEngine;

  if (not ConfNewStyleAltO) and (not RequestNewAltO) then begin
    s := ExcludeTrailingPathDelimiter(SrcEngine.Path);
    if OrigSrcEngine = SrcEngine then begin
      if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or PDataItem(AListView.Selected.Data)^.UpDir or
         (not PDataItem(AListView.Selected.Data)^.IsDir)
        then begin  if Length(s) > 1 then s := IncludeTrailingPathDelimiter(Copy(s, 1, LastDelimiter(PathDelim, s))); end
        else s := IncludeTrailingPathDelimiter(IncludeTrailingPathDelimiter(s) + PDataItem(AListView.Selected.Data)^.FName);
    end;
    if IncludeTrailingPathDelimiter(TargetEngine.Path) <> s then begin
      if LeftPanel then RightPanelEngine := TargetEngine
                   else LeftPanelEngine := TargetEngine;
      ChangingDir(not LeftPanel, s);
    end;
    //  Move one item down
    if (OrigSrcEngine = SrcEngine) and Assigned(AListView.Selected) and (AListView.ConvertToSorted(AListView.Selected.Index) < AListView.Items.Count - 1) then begin
      AListView.Selected := AListView.Items[AListView.ConvertFromSorted(AListView.ConvertToSorted(AListView.Selected.Index) + 1)];
      AListView.Selected.SetCursor(0, False, False, 0, 0);
    end;
  end else begin
    if IncludeTrailingPathDelimiter(TargetEngine.Path) <> IncludeTrailingPathDelimiter(SrcEngine.Path) then begin
      if LeftPanel then RightPanelEngine := TargetEngine
                   else LeftPanelEngine := TargetEngine;
      ChangingDir(not LeftPanel, SrcEngine.Path);
    end;
  end;
end;

procedure TFMain.miShowDotFilesClick(Sender: TObject);
begin
  ConfShowDotFiles := miShowDotFiles.Checked;
  DoRefresh(True, True, True);
  DoRefresh(False, True, True);
end;

procedure TFMain.F3F4ButtonClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
  end else begin
    AListView := RightListView;
  end;
  if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or PDataItem(AListView.Selected.Data)^.IsDir or
     PDataItem(AListView.Selected.Data)^.UpDir then
  begin
    Application.MessageBox(LANGYouMustSelectAValidFile, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;
  EditViewFile(LeftPanel, AListView, (Sender = F3Button) or ((Sender is TGTKMenuItem) and (Integer((Sender as TGTKMenuItem).Data) = 200)), False);
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.EditViewFile(LeftPanel: boolean; AListView: TGTKListView; View, NewFile: boolean);
var ANewDir: TFNewDir;
    Engine: TPanelEngine;
    AFile: string;
begin
  try
  InternalLock;
  if LeftPanel then Engine := LeftPanelEngine
               else Engine := RightPanelEngine;
  if NewFile then begin
    ANewDir := TFNewDir.Create(Self);
    try
      ANewDir.Caption := LANGEdit;
      ANewDir.Label1.Caption := LANGEnterFilenameToEdit;
      ANewDir.Label1.UseUnderline := True;
      ANewDir.Label1.FocusControl := ANewDir.Entry; 
      if Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and (not PDataItem(AListView.Selected.Data)^.IsDir) and
         (not PDataItem(AListView.Selected.Data)^.UpDir)
        then ANewDir.Entry.Text := PDataItem(AListView.Selected.Data)^.FDisplayName
        else ANewDir.Entry.Text := '';
      if Length(ANewDir.Entry.Text) > 0 then ANewDir.Entry.SelectAll;
      if ANewDir.Run <> mbOK then Exit;
      AFile := IncludeTrailingPathDelimiter(Engine.Path) + UTF8ToStr(ANewDir.Entry.Text);
    finally
      ANewDir.Free;
    end;
  end else AFile := IncludeTrailingPathDelimiter(Engine.Path) + PDataItem(AListView.Selected.Data)^.FName;
  
  EditViewFileInternal(Self, AFile, Engine, View, NewFile);

  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.EditViewFileInternal(ParentWindow: TGTKControl; Filename: string; Engine: TPanelEngine; View, NewFile: boolean);
var s: string;
    Stat: PDataItemSL;
    Error, x: integer;
//    AViewer: TViewerThread;
    AViewer: TFViewer;
begin
  Stat := Engine.GetFileInfoSL(Filename);
  if Assigned(Stat) and (Stat^.Size > ConfEditViewFileSizeLimit) and
     (Application.MessageBox(LANGTheFileYouAreTryingToOpenIsQuiteBig, [mbYes, mbNo], mbWarning, mbNone, mbNo) = mbNo)
    then begin
           FreeDataItem(Stat);
           Exit;
         end;
  FreeDataItem(Stat);
  if View then s := ConfViewer
          else s := ConfEditor;

  if (Engine is TVFSEngine) and (not NewFile) then
    if not HandleRunFromArchive(Filename, Engine, s, '', True) then Exit;

  if ConfUseInternalViewer and View then begin
(*      AViewer := TViewerThread.Create(Self);
      if not AViewer.LoadFile(AFile) then begin
        Application.MessageBox(Format('Cannot load file ''%s''. Please check the permissions.', [ANSIToUTF8(AFile)]), [mbOK], mbError);
        AViewer.Free;
      end else AViewer.Resume; *)
      AViewer := TFViewer.Create(ParentWindow);
      if not AViewer.LoadFile(Filename) then begin
        Application.MessageBox(Format(LANGCannotLoadFile, [Filename]), [mbOK], mbError, mbNone, mbOK);
        AViewer.Free;
      end else begin
//        gtk_window_set_transient_for(PGtkWindow(AViewer.FWidget), PGtkWindow(ParentWindow.FWidget));
        AViewer.Show;
      end;
  end else begin
    if View then x := ConfViewerTerminalBehaviour
            else x := ConfEditorTerminalBehaviour;
    if not ExecuteProgram(Format('%s %s', [s, QuoteStr(Filename)]), ExtractFilePath(Filename), x = 0, x = 1, Error) then
      Application.MessageBox(Format(LANGCannotExecuteSPleaseCheckTheConfiguration, [s]), [mbOK], mbError, mbNone, mbOK);
  end;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.miFileTypesClick(Sender: TObject);
var x: pointer;
begin
  try
    InternalLock;
    FFileTypeSettings := TFFileTypeSettings.Create(Self);
    ReadAssoc;
    FFileTypeSettings.AssignAssocList(AssocList);
    FFileTypeSettings.FillList;
    if FFileTypeSettings.Run = mbOK then begin
      RemoveIconRefs(FFileTypeSettings.IntAssocList, True);
      RemoveIconRefs(AssocList, False);
      FFileTypeSettings.CleanItems;
      x := AssocList;
      AssocList := FFileTypeSettings.IntAssocList;
      FFileTypeSettings.IntAssocList := x;
      RecreateIcons(AssocList);
      DoRefresh(True, True, True);
      DoRefresh(False, True, True);
      WriteAssoc;
    end;
  finally
    FFileTypeSettings.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.RunFile(Path: string; Engine: TPanelEngine; CustomAction: integer);
var Command, FileTypeDesc: string;
    i, ac: integer;
    b, AutodetectGUI, RunInTerminal: boolean;
    Stat: PDataItemSL;
    s: string;
    Assoc: TFileAssoc;
begin
  try
  InternalLock;
  Command := '';
  FileTypeDesc := '';
  AutodetectGUI := True;
  RunInTerminal := False;
  
  Assoc := FindAssoc(ExtractFileName(Path));
  if Assoc <> nil then begin
    FileTypeDesc := Assoc.FileTypeName;
    if (CustomAction > Assoc.ActionList.Count - 1) or (CustomAction = -1)
      then ac := Assoc.DefaultAction
      else ac := CustomAction;
    if ac > Assoc.ActionList.Count - 1 then ac := 0;
    if Assoc.ActionList.Count > ac then begin
      Command := UTF8ToStr(Trim(TAssocAction(Assoc.ActionList[ac]).ActionCommand));
      AutodetectGUI := TAssocAction(Assoc.ActionList[ac]).AutodetectGUI;
      RunInTerminal := TAssocAction(Assoc.ActionList[ac]).RunInTerminal;
    end;
  end;

  //  Association not found, try to execute file itself
  if Command = '' then begin
    DebugMsg(['File association not found']);
    if CustomAction > -1 then begin
      DebugMsg(['Some strange error occured...']);
      Exit;
    end;
    Stat := Engine.GetFileInfoSL(Path);
    if Assigned(Stat) and Stat^.IsExecutable then begin
      b := True;
      if Engine is TVFSEngine then b := HandleRunFromArchive(Path, Engine, Command, FileTypeDesc, False);   //  not a local engine, extract to local first
      if b then begin
        libc_chdir(PChar(ExtractFilePath(Path)));
        b := ExecuteProgram(QuoteStr(Path), ExtractFilePath(Path), AutodetectGUI, RunInTerminal, i);
        libc_chdir(PChar('/'));
      end else b := True;   //  Mask cancelled extraction from VFS
      FreeDataItem(Stat);
    end else begin
           if Engine is TVFSEngine then begin
             HandleRunFromArchive(Path, Engine, Command, FileTypeDesc, False);
//             b := True;
           end else
           if Application.MessageBox(Format(LANGThereIsNoApplicationAssociatedWithS, [StrToUTF8(ExtractFileName(Path))]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes
              then miFileTypesClick(Self);
           Exit;
         end;

  end else begin
    DebugMsg(['File association found: ', Command]);
    s := Command;
    b := True;
    if Engine is TVFSEngine then b := HandleRunFromArchive(Path, Engine, Command, FileTypeDesc, False);   //  not a local engine, extract to local first
    if Pos('%s', s) > 0 then s := Format(s, ['''' + QuoteStr(Path) + ''''])
                        else s := Format('%s %s', [s, QuoteStr(Path)]);
//      DebugMsg(['execute: ', s, ' , ', Command, ' , ', QuoteStr(Path)]);
    if b then begin
      libc_chdir(PChar(ExtractFilePath(Path)));
      b := ExecuteProgram(s, ExtractFilePath(Path), AutodetectGUI, RunInTerminal, i);
      libc_chdir(PChar('/'));
    end else b := True;   //  Mask cancelled extraction from VFS
  end;
  if not b then Application.MessageBox(Format(LANGCannotExecuteSPleaseCheckTheConfiguration, [s]), [mbOK], mbError, mbNone, mbOK);
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

procedure TFMain.CommandLineComboKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
var Error, OldPos, i: integer;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    s, s2, Orig: string;
begin
  try
  InternalLock;
  case Key of
    GDK_RETURN, GDK_KP_ENTER: begin
      Accept := False;
      if LeftLastFocused then Engine := LeftPanelEngine
                         else Engine := RightPanelEngine;
      //  Insert filename
      if (ssAlt in Shift) or (ssCtrl in Shift) then begin
        if LeftLastFocused then AListView := LeftListView
                           else AListView := RightListView;
        if Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and (not PDataItem(AListView.Selected.Data)^.UpDir) then begin
          s2 := QuoteStr(String(PDataItem(AListView.Selected.Data)^.FDisplayName)) + ' ';
          if Length(CommandLineCombo.Entry.Text) = 0 then begin
            CommandLineCombo.Entry.Text := Format('./%s', [s2]);
            CommandLineCombo.Entry.CursorPosition := Length(s2);
          end else begin
            OldPos := CommandLineCombo.Entry.CursorPosition;
            s := CommandLineCombo.Entry.Text;
            Insert(s2, s, OldPos + 1);
            CommandLineCombo.Entry.Text := s;
            CommandLineCombo.Entry.CursorPosition := OldPos + Length(s2);
          end;
        end;  
        Exit;
      end;
      
      Orig := Trim(CommandLineCombo.Entry.Text);
      if Length(Orig) > 0 then begin
        if WideUpperCase(Orig) = 'CD' then begin
          if LeftLastFocused then PathButtonClick(LeftHomeButton)
                             else PathButtonClick(RightHomeButton);
        end else
        if (Length(Orig) > 3) and (WideUpperCase(Copy(Orig, 1, 3)) = 'CD ') then begin
          ChangingDir(LeftLastFocused, ProcessPattern(Engine, UTF8ToStr(Copy(Orig, 4, Length(Orig) - 3)), Engine.Path, '', True));
        end else begin
          while not (Engine is TLocalTreeEngine) do Engine := Engine.ParentEngine;
          libc_chdir(PChar(Engine.Path));
          if not ExecuteProgram(UTF8ToStr(Orig), Engine.Path, ConfCmdLineTerminalBehaviour = 0 , ConfCmdLineTerminalBehaviour = 1, Error) then
            Application.MessageBox(LANGErrorExecutingCommand, [mbOK], mbError, mbNone, mbOK);
          libc_chdir('/');
        end;
      end;
      CommandLineCombo.Entry.Text := '';
      if LeftLastFocused then LeftListView.SetFocus
                         else RightListView.SetFocus;

      if Length(Orig) > 0 then begin
        SaveItemToHistory(Orig, CommandLineHistory);
        if CommandLineCombo.Items.Count > 0 then
          for i := CommandLineCombo.Items.Count - 1 downto 0 do
            CommandLineCombo.Items.Delete(i);
        if CommandLineHistory.Count > 0 then
          for i := 0 to CommandLineHistory.Count - 1 do
            CommandLineCombo.Items.Append(CommandLineHistory[i]);
        CommandLineCombo.Entry.Text := '';
      end;
    end;
    GDK_Down, GDK_Up:
                  begin
                    Accept := False;
                    if LeftLastFocused then AListView := LeftListView
                                       else AListView := RightListView;
                    if (not (([ssCtrl] = Shift) and (Key = GDK_Up))) and (
                       ((Key = GDK_Down) and (AListView.ConvertToSorted(AListView.Selected.Index) < AListView.Items.Count - 1)) or
                       ((Key = GDK_Up) and (AListView.ConvertToSorted(AListView.Selected.Index) > 0))) then
                    begin
                      AListView.Selected := AListView.Items[AListView.ConvertFromSorted(AListView.ConvertToSorted(AListView.Selected.Index) + (Ord(Key = GDK_Down) * 2) - 1)];
                      AListView.Selected.SetCursor(0, False, False, 0, 0);
                    end;
                    AListView.SetFocus;
                  end;
    GDK_ESCAPE: begin
                  Accept := False;
                  CommandLineCombo.Entry.Text := '';
                  if LeftLastFocused then LeftListView.SetFocus
                                     else RightListView.SetFocus;
                end;
    GDK_TAB: begin
               Accept := False;
               if LeftLastFocused then RightListView.SetFocus
                                  else LeftListView.SetFocus;
             end;
    GDK_P, GDK_Capital_P: if ((Shift = [ssAlt]) or (Shift = [ssCtrl])) and (CommandLineHistory.Count > 0) then
      begin
        Accept := False;
        Orig := Trim(CommandLineCombo.Entry.Text);
        i := CommandLineHistory.IndexOf(Orig);
        if i < 0 then begin
          SavedCmdLine := Orig;
          i := 0;
        end else
        if CommandLineHistory.Count > i + 1 then Inc(i);
        CommandLineCombo.Entry.Text := CommandLineHistory[i];
        CommandLineCombo.Entry.SetFocus;
        CommandLineCombo.Entry.SelectRegion(Length(CommandLineCombo.Entry.Text), Length(CommandLineCombo.Entry.Text));
      end else if not CommandLineCombo.Entry.Focused then ActivateCommandLine(Key);
    GDK_N, GDK_Capital_N: if ((Shift = [ssAlt]) or (Shift = [ssCtrl])) and (CommandLineHistory.Count > 0) then
      begin
        Accept := False;
        Orig := Trim(CommandLineCombo.Entry.Text);
        i := CommandLineHistory.IndexOf(Orig);
        if i < 0 then Exit else
        if i = 0 then begin
          s := SavedCmdLine;
          SavedCmdLine := '';
        end else
        if CommandLineHistory.Count > i then s := CommandLineHistory[i - 1];
        CommandLineCombo.Entry.Text := s;
        CommandLineCombo.Entry.SetFocus;
        CommandLineCombo.Entry.SelectRegion(Length(CommandLineCombo.Entry.Text), Length(CommandLineCombo.Entry.Text));
      end else if not CommandLineCombo.Entry.Focused then ActivateCommandLine(Key);
    GDK_A, GDK_Capital_A: if (Shift = [ssAlt]) or (Shift = [ssCtrl]) then
      begin
        Accept := False;
        if LeftLastFocused then Engine := LeftPanelEngine
                           else Engine := RightPanelEngine;
        s2 := QuoteStr(IncludeTrailingPathDelimiter(Engine.Path));
        OldPos := CommandLineCombo.Entry.CursorPosition;
        s := CommandLineCombo.Entry.Text;
        Insert(s2, s, OldPos + 1);
        CommandLineCombo.Entry.SetFocus;
        CommandLineCombo.Entry.Text := s;
        CommandLineCombo.Entry.CursorPosition := OldPos + Length(s2);
      end else if not CommandLineCombo.Entry.Focused then ActivateCommandLine(Key);
  end;
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

function TFMain.ActivateCommandLine(Key: word; const ActualPosition: boolean = False): boolean;
var s, s2: string;
    OldPos: integer;
begin
  Result := False;
  s := UTF8Encode(WideChar(KeyValToUnicode(Key)));
  if (Length(s) = 0) or (s = #0) then Exit;
  if ActualPosition and CommandLineCombo.Entry.Focused then begin
    OldPos := CommandLineCombo.Entry.CursorPosition;
    s2 := CommandLineCombo.Entry.Text;
    Insert(s, s2, Length(LeftStr(s2, OldPos)) + 1);
    CommandLineCombo.Entry.Text := s2;
    CommandLineCombo.Entry.CursorPosition := OldPos + 1;
  end else begin
    CommandLineCombo.Entry.Text := CommandLineCombo.Entry.Text + s;
    CommandLineCombo.Entry.SetFocus;
    CommandLineCombo.Entry.SelectRegion(Length(CommandLineCombo.Entry.Text), Length(CommandLineCombo.Entry.Text));
  end;
  Result := True;
end;

procedure TFMain.FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
var AListView: TGTKListView;
begin
  if (ConfQuickSearchActivationKey = 2) and (Shift = [ssAlt]) and (LeftListView.Focused or RightListView.Focused) then begin
    Accept := not HandleKey(Key, Shift, LeftListView.Focused);
    if not Accept then Exit;
  end;

  if CommandLineCombo.Entry.Focused then CommandLineComboKeyDown(Sender, Key, Shift, Accept);
  if Editing and (Key = GDK_ESCAPE) then begin
    Editing := False;
    if LeftLastFocused then AListView := LeftListView
                       else AListView := RightListView;
    AListView.Columns[0].SetProperty('editable', 0);
  end;
end;

procedure TFMain.InactiveItemsTimerTimer(Sender: TObject);

  procedure Redraw(AListView: TGTKListView; tree_view: PGtkTreeView);
  var TreePath: PGtkTreePath;
      Iter: TGtkTreeIter;
      Rect: TGdkRectangle;
      PixBuf, Icon: PGdkPixbuf;
      cell_width, cell_height: integer;
  begin
    if (csDestroying in ComponentState) or (AListView = nil) or (AListView.FSelection = nil) then Exit;
    if not gtk_tree_selection_get_selected(AListView.FSelection, nil, @Iter) then Exit;
    TreePath := gtk_tree_model_get_path(gtk_tree_view_get_model(tree_view), @Iter);
    if not Assigned(TreePath) then Exit;
    gtk_tree_view_get_background_area(tree_view, TreePath, AListView.Columns[0].FColumn, @Rect);
    gtk_tree_path_free(TreePath);
    gtk_cell_renderer_get_size(AListView.Columns[0].FPixbufRenderer, PGtkWidget(tree_view), nil, nil, nil, @cell_width, @cell_height);
    Icon := PDataItem(AListView.Selected.Data)^.Icon;
    Rect.width := Rect.x + cell_width + AListView.Columns[0].FRenderer^.xpad * 2 + 1;
    PixBuf := gdk_pixbuf_new(GDK_COLORSPACE_RGB, True, 8, Rect.width, Rect.height);
    gdk_pixbuf_fill(PixBuf, InactiveItemBGColorNum);
    gdk_pixbuf_render_to_drawable_alpha(PixBuf, PGdkDrawable(gtk_tree_view_get_bin_window(tree_view)), 0, 0, Rect.x, Rect.y,
                                        Rect.width, Rect.height, GDK_PIXBUF_ALPHA_FULL, 0, GDK_RGB_DITHER_NORMAL, 0, 0);
    gdk_pixbuf_unref(PixBuf);
    gdk_pixbuf_render_to_drawable_alpha(Icon, PGdkDrawable(gtk_tree_view_get_bin_window(tree_view)), 0, 0,
                                        Rect.x + AListView.Columns[0].FRenderer^.xpad, Rect.y + Rect.height div 2 - (16 div 2),
                                        16, 16, GDK_PIXBUF_ALPHA_FULL, 0, GDK_RGB_DITHER_NORMAL, 0, 0);
  end;


begin
  if Assigned(Sender) and Assigned(InactiveItemsTimer) and Assigned(LeftListView) and Assigned(RightListView) and
    (not (csDestroying in ComponentState)) then
  try
    InactiveItemsTimer.Enabled := False;
    if RedrawLeftInactive and (not LeftListView.Focused) // and (not ConfInactiveItemDefaultColors)
      then Redraw(LeftListView, PGtkTreeView(LeftListView.FWidget));
    if RedrawRightInactive and (not RightListView.Focused) //  and (not ConfInactiveItemDefaultColors)
      then Redraw(RightListView, PGtkTreeView(RightListView.FWidget));
    RedrawLeftInactive := False;
    RedrawRightInactive := False;
  except end;
end;


(********************************************************************************************************************************)
function TFMain.OldGTKConvertToSorted(Sender: TObject; const Index: integer): integer;
var DataList, List: TList;
    i: integer;
    AListView: TGTKListView;
begin
  Result := -1;
  if Sender = LeftListView then begin
    DataList := LeftPanelData;
    AListView := LeftListView;
  end else begin
    DataList := RightPanelData;
    AListView := RightListView;
  end;
  List := nil;   //  Silent compiler warnings
  if Assigned(DataList) and (DataList.Count > 0) then
  try
    List := TList.Create;
    for i := 0 to DataList.Count - 1 do List.Add(DataList[i]);
    if (List.Count > 1) and (AListView.SortOrder <> soNone) then
      SortDataList(List, AListView.SortOrder = soAscending, AListView.SortColumnID);
    Result := List.IndexOf(DataList[Index]);
  finally
    List.Free;
  end;
end;

function TFMain.OldGTKConvertFromSorted(Sender: TObject; const Index: integer): integer;
var DataList, List: TList;
    i: integer;
    AListView: TGTKListView;
begin
  Result := -1;
  if Sender = LeftListView then begin
    DataList := LeftPanelData;
    AListView := LeftListView;
  end else begin
    DataList := RightPanelData;
    AListView := RightListView;
  end;
  List := nil;   //  Silent compiler warnings
  if Assigned(DataList) and (DataList.Count > 0) then
  try
    List := TList.Create;
    for i := 0 to DataList.Count - 1 do List.Add(DataList[i]);
    if (List.Count > 1) and (AListView.SortOrder <> soNone) then
      SortDataList(List, AListView.SortOrder = soAscending, AListView.SortColumnID);
    Result := DataList.IndexOf(List[Index]);
  finally
    List.Free;
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.SplitterPopupMenuClick(Sender: TObject);
begin
  if not (Sender is TGTKMenuItem) then Exit;
  ConfPanelSep := Integer((Sender as TGTKMenuItem).Data);
  PanelSeparator.Position := Round(Width * (ConfPanelSep / 100));
end;

(********************************************************************************************************************************)
procedure TFMain.miChangePermissionsClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
    i: longint;
    SelCount: longint;
    AFile, NextItem1, NextItem2: string;
    Stat: PDataItemSL;
    UsrManager: TUserManager;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) or PDataItem(AListView.Selected.Data)^.UpDir) then begin
    Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbOK);
    Exit;
  end;

  AFile := '';
  if SelCount = 0 then AFile := PDataItem(AListView.Selected.Data)^.FName else
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then begin
          AFile := FName;
          Break;
        end;

  if AFile <> '' then
  try
    FChmod := TFChmod.Create(Self);
    Stat := Engine.GetFileInfoSL(IncludeTrailingPathDelimiter(Engine.Path) + AFile);
    if not Assigned(Stat) then Exit;
    UsrManager := TUserManager.Create;
    try
      FChmod.AssignMode(Stat^.Mode, AFile, UsrManager.GetUserName(Stat^.UID), UsrManager.GetGroupName(Stat^.GID));
    finally
      UsrManager.Free;
    end;
    if FChmod.Run = mbOK then begin
      FindNextSelected(AListView, DataList, NextItem1, NextItem2);

      AWorkingThread := TWorkerThread.Create;
      AFProgress := TFProgress.Create(Self);
      try
        AFProgress.SetNumBars(False);
        AFProgress.ProgressBar.Value := 0;
        AFProgress.Label1.Caption := LANGChmodProgress;
        AWorkingThread.ProgressForm := AFProgress;
        if Assigned(AListView.Selected) then AWorkingThread.SelectedItem := AListView.Selected.Data;
        AWorkingThread.ParamBool1 := FChmod.RecursiveCheckButton.Checked;
        AWorkingThread.ParamInt1 := FChmod.RecursiveOptionMenu.ItemIndex;
        AWorkingThread.ParamCardinal1 := FChmod.LastMode;
        AWorkingThread.Engine := Engine;
        AWorkingThread.LeftPanel := LeftPanel;
        AWorkingThread.DataList := DataList;
        AWorkingThread.WorkerProcedure := ChmodFilesWorker;
        AWorkingThread.Resume;
        AFProgress.ParentForm := FMain;
        if (SelCount > 1) or FChmod.RecursiveCheckButton.Checked then AFProgress.ShowModal;
        ProcessProgressThread(AWorkingThread, AFProgress);
        AFProgress.Close;
      finally
        AFProgress.Free;
        AWorkingThread.Free;
      end;
      ChangingDir(LeftPanel, Engine.Path, NextItem1, NextItem2);
      DoRefresh(not LeftPanel, True, True);
    end;
  finally
    FreeDataItem(Stat);
    FChmod.Free;
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miChangeOwnerClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
    i: integer;
    SelCount: longint;
    AFile, NextItem1, NextItem2: string;
    Stat: PDataItemSL;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) or PDataItem(AListView.Selected.Data)^.UpDir) then begin
    Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbOK);
    Exit;
  end;

  AFile := '';
  if SelCount = 0 then AFile := PDataItem(AListView.Selected.Data)^.FName else
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then begin
          AFile := FName;
          Break;
        end;

  if AFile <> '' then
  try
    FChown := TFChown.Create(Self);
    Stat := Engine.GetFileInfoSL(IncludeTrailingPathDelimiter(Engine.Path) + AFile);
    if not Assigned(Stat) then Exit;
    FChown.AssignMode(Stat^.Mode, AFile, Stat^.UID, Stat^.GID);
    if FChown.Run = mbOK then begin
      FindNextSelected(AListView, DataList, NextItem1, NextItem2);

      AWorkingThread := TWorkerThread.Create;
      AFProgress := TFProgress.Create(Self);
      try
        AFProgress.SetNumBars(False);
        AFProgress.ProgressBar.Value := 0;
        AFProgress.Label1.Caption := LANGChownProgress;
        AWorkingThread.ProgressForm := AFProgress;
        if Assigned(AListView.Selected) then AWorkingThread.SelectedItem := AListView.Selected.Data;
        AWorkingThread.ParamBool1 := FChown.RecursiveCheckButton.Checked;
        AWorkingThread.ParamCardinal1 := FChown.LastUID;
        AWorkingThread.ParamCardinal2 := FChown.LastGID;
        AWorkingThread.Engine := Engine;
        AWorkingThread.LeftPanel := LeftPanel;
        AWorkingThread.DataList := DataList;
        AWorkingThread.WorkerProcedure := ChownFilesWorker;
        AWorkingThread.Resume;
        AFProgress.ParentForm := FMain;
        if (SelCount > 1) or FChown.RecursiveCheckButton.Checked then AFProgress.ShowModal;
        ProcessProgressThread(AWorkingThread, AFProgress);
        AFProgress.Close;
      finally
        AFProgress.Free;
        AWorkingThread.Free;
      end;

      ChangingDir(LeftPanel, Engine.Path, NextItem1, NextItem2);
      DoRefresh(not LeftPanel, True, True);
    end;
  finally
    FreeDataItem(Stat);
    FChown.Free;
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.miCreateSymlinkClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine, EngineOpposite: TPanelEngine;
    s1, s2: string;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    EngineOpposite := RightPanelEngine;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    EngineOpposite := LeftPanelEngine;
  end;

  if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or PDataItem(AListView.Selected.Data)^.UpDir
    then s1 := ExcludeTrailingPathDelimiter(Engine.Path)
    else s1 := IncludeTrailingPathDelimiter(Engine.Path) + PDataItem(AListView.Selected.Data)^.FName;
  if Engine.Path <> EngineOpposite.Path
    then s2 := IncludeTrailingPathDelimiter(EngineOpposite.Path) + PDataItem(AListView.Selected.Data)^.FName
    else s2 := IncludeTrailingPathDelimiter(EngineOpposite.Path) + Format(LANGLinkToS, [PDataItem(AListView.Selected.Data)^.FName]);

  if CreateSymlink(s1, s2, Engine) then begin
    DoRefresh(LeftPanel, True, True);
    DoRefresh(not LeftPanel, True, True);
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miEditSymlinkClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
  end;

  if (not Assigned(AListView.Selected)) or (not Assigned(AListView.Selected.Data)) or (not PDataItem(AListView.Selected.Data)^.IsLnk) or
     PDataItem(AListView.Selected.Data)^.UpDir then
  begin
    Application.MessageBox(LANGYouMustSelectAValidSymbolicLink, [mbOK], mbError, mbNone, mbOK);
    Exit;
  end;

  if EditSymlink(IncludeTrailingPathDelimiter(Engine.Path) + PDataItem(AListView.Selected.Data)^.FName, Engine) then begin
    DoRefresh(LeftPanel, True, True);
    DoRefresh(not LeftPanel, True, True);
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.FilePopupMenuPopup(Sender: TObject);

  procedure ClearPopupMenu(Popup: TGTKMenuItem);
  begin
    while Popup.Count > 0 do begin
      if Popup.Items[0].Count > 0 then ClearPopupMenu(Popup.Items[0]);
      Popup.Items[0].Free;
      Popup.Delete(0);
    end;
  end;

var Item: TGTKMenuItem;
    DataItem: PDataItemSL;
    Engine: TPanelEngine;
    AListView: TGTKListView;
    FileName, ShortFName: string;
    UpDir, Found: boolean;
    i, j: integer;
    Assoc: TFileAssoc;
begin
  ClearPopupMenu(FilePopupMenu);
  if LeftLastFocused then begin
    Engine := LeftPanelEngine;
    AListView := LeftListView;
  end else begin
    Engine := RightPanelEngine;
    AListView := RightListView;
  end;
  FileName := IncludeTrailingPathDelimiter(Engine.Path);
  if Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and (not PDataItem(AListView.Selected.Data)^.UpDir)
    then FileName := FileName + PDataItem(AListView.Selected.Data)^.FName;
  ShortFName := ExtractFileName(ExcludeTrailingPathDelimiter(FileName));
  DataItem := Engine.GetFileInfoSL(FileName);
  if not Assigned(DataItem) then begin
    DebugMsg(['Error: File data not assigned. Bug ???!  FileName = ', FileName]);
    Exit;
  end;
  UpDir := PDataItem(AListView.Selected.Data)^.UpDir;

  if not DataItem^.IsDir then begin
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    Item.Caption := Format(LANGPopupRunS, [QuoteMarkupStr(StrToUTF8(ShortFName), True)]);
    Item.StockIcon := 'gtk-execute';
    Item.Data := Pointer(1);
    Item.OnClick := FilePopupMenuItemClick;
    Item.Enabled := Engine.FileCanRun(FileName);
    FilePopupMenu.Add(Item);
  end else begin
    //  Open directory
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    if UpDir then Item.Caption := LANGPopupGoUp
             else Item.Caption := Format(LANGPopupOpenS, [QuoteMarkupStr(StrToUTF8(ShortFName), True)]);
    Item.StockIcon := 'gtk-open';
    Item.Data := Pointer(1);
    Item.OnClick := FilePopupMenuItemClick;
    FilePopupMenu.Add(Item);

    //  Open directory in background tab
    if not UpDir then begin
      Item := TGTKMenuItem.CreateTyped(Self, itImageText);
      Item.Caption := LANGOpenDirectoryInBackgroundTab;
      Item.Data := Pointer(3);
      Item.OnClick := FilePopupMenuItemClick;
      FilePopupMenu.Add(Item);
    end;
  end;

  //  Find actions for meta-item
  if AssocList.Count > 0 then
    for i := 0 to AssocList.Count - 1 do
      if ((DataItem^.IsDir and (TFileAssoc(AssocList[i]).FileTypeName = ConstFTAMetaDirectory)) or
         ((not DataItem^.IsDir) and (TFileAssoc(AssocList[i]).FileTypeName = ConstFTAMetaFile))) and
         (TFileAssoc(AssocList[i]).ActionList.Count > 0) then
      with TFileAssoc(AssocList[i]) do begin
        FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
        for j := 0 to ActionList.Count - 1 do begin
          Item := TGTKMenuItem.CreateTyped(Self, itImageText);
          Item.Caption := Format(LANGPopupOpenWithS, [TAssocAction(ActionList[j]).ActionName]);
          Item.Data := ActionList[j];
          Item.OnClick := FilePopupMenuItemClick;
          FilePopupMenu.Add(Item);
        end;
        Break;
      end;
  FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));

  //  Find and add actions for this file type
  Found := False;
  Assoc := FindAssoc(ShortFName);
  if (Assoc <> nil) and (Assoc.ActionList.Count > 0) then begin
    Found := True;
    for j := 0 to Assoc.ActionList.Count - 1 do begin
      Item := TGTKMenuItem.CreateTyped(Self, itImageText);
      Item.Caption := Format(LANGPopupOpenWithS, [TAssocAction(Assoc.ActionList[j]).ActionName]);
      if ((j = 0) and (Assoc.DefaultAction > Assoc.ActionList.Count - 1)) or (j = Assoc.DefaultAction)
        then Item.Caption := Item.Caption + LANGPopupDefault;
      Item.Data := Assoc.ActionList[j];
      Item.OnClick := FilePopupMenuItemClick;
      FilePopupMenu.Add(Item);
    end;
  end;

  if (not Found) and (not DataItem^.IsDir) then begin
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    Item.Caption := LANGPopupOpenWith;
    Item.Data := Pointer(2);
    Item.OnClick := FilePopupMenuItemClick;
    FilePopupMenu.Add(Item);
  end;

  //  Other items
  if not DataItem^.IsDir then begin
    FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    Item.Caption := LANGPopupViewFile;
    Item.StockIcon := 'gtk-find';
    Item.Data := Pointer(200);  //  This number HAVE to be here due to F3F4ButtonClick method using
    Item.OnClick := F3F4ButtonClick;
    FilePopupMenu.Add(Item);
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    Item.Caption := LANGPopupEditFile;
    Item.Data := Pointer(201);  //  Here too
    Item.OnClick := F3F4ButtonClick;
    FilePopupMenu.Add(Item);
  end;
  if not DataItem^.IsDir then FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGPopupMakeSymlink;
  Item.StockIcon := 'gtk-jump-to';
  Item.OnClick := miCreateSymlinkClick;
  FilePopupMenu.Add(Item);
  if DataItem^.IsLnk then begin
    Item := TGTKMenuItem.CreateTyped(Self, itImageText);
    Item.Caption := LANGmiEditSymlink_Caption;
    Item.OnClick := miEditSymlinkClick;
    FilePopupMenu.Add(Item);
  end;
  FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGmiChangePermissions_Caption;
  Item.StockIcon := 'gtk-convert';
  Item.OnClick := miChangePermissionsClick;
  Item.Enabled := not UpDir;
  FilePopupMenu.Add(Item);
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGmiChangeOwner_Caption;
  Item.OnClick := miChangeOwnerClick;
  Item.Enabled := not UpDir;
  FilePopupMenu.Add(Item);
  FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGPopupRename;
  Item.OnClick := F6ButtonClick;
  Item.Enabled := not UpDir;
  FilePopupMenu.Add(Item);
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGPopupDelete;
  Item.StockIcon := 'gtk-delete';
  Item.OnClick := F8ButtonClick;
  Item.Enabled := not UpDir;
  FilePopupMenu.Add(Item);
//  FilePopupMenu.Add(TGTKMenuItem.CreateTyped(Self, itSeparator));
  Item := TGTKMenuItem.CreateTyped(Self, itImageText);
  Item.Caption := LANGFilePopupMenu_Properties;
  Item.StockIcon := 'gtk-properties';
  Item.OnClick := miFilePropertiesClick;
  Item.Enabled := False;
  Item.Visible := False;
//  Item.Enabled := not UpDir;
  FilePopupMenu.Add(Item);

  FreeDataItem(DataItem);
end;

procedure TFMain.FilePopupMenuItemClick(Sender: TObject);
var Engine: TPanelEngine;
    DataItem: PDataItemSL;
    AListView: TGTKListView;
    FileName, ShortFName, s: string;
    Error: integer;
    b: boolean;
begin
  try
  InternalLock;
  if (not Assigned(Sender)) or (not (Sender is TGTKMenuItem)) then begin
    DebugMsg(['Error: Popup menuitem is invalid']);
  end;
  if LeftLastFocused then begin
    Engine := LeftPanelEngine;
    AListView := LeftListView;
  end else begin
    Engine := RightPanelEngine;
    AListView := RightListView;
  end;
  if ConfUseURI and (Engine is TVFSEngine) and (not (Engine as TVFSEngine).ArchiveMode)
    then FileName := IncludeTrailingPathDelimiter((Engine as TVFSEngine).GetPathURI)
    else FileName := IncludeTrailingPathDelimiter(Engine.Path);
  if Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and (not PDataItem(AListView.Selected.Data)^.UpDir)
    then FileName := FileName + PDataItem(AListView.Selected.Data)^.FName;
  ShortFName := ExtractFileName(ExcludeTrailingPathDelimiter(FileName));
  DataItem := Engine.GetFileInfoSL(FileName);
  if not Assigned(DataItem) then begin
    DebugMsg(['Error: File data not assigned. Bug ???!  FileName = ', FileName]);
    Exit;
  end;

  case Integer((Sender as TGTKMenuItem).Data) of
    1: if DataItem^.IsDir then ActivateItem(AListView.Selected.Index)
       else begin
              b := True;
              if Engine is TVFSEngine then b := HandleRunFromArchive(FileName, Engine, '', '', False);   //  not a local engine, extract to local first
              if b then begin
                libc_chdir(PChar(ExtractFilePath(FileName)));
                b := ExecuteProgram(QuoteStr(FileName), ExtractFilePath(FileName), True, False, Error);
                libc_chdir(PChar('/'));
              end else b := True;   //  Mask cancelled extraction from VFS
              if not b then Application.MessageBox(LANGErrorExecutingCommand, [mbOK], mbError, mbNone, mbOK);
            end;
    2: if Application.MessageBox(Format(LANGThereIsNoApplicationAssociatedWithS, [ShortFName]), [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes
         then miFileTypesClick(Self);
    3: NewTab(LeftLastFocused, True, FileName);
    else begin
      b := True;
      s := UTF8ToStr(Trim(TAssocAction((Sender as TGTKMenuItem).Data).ActionCommand));
      if Engine is TVFSEngine then b := HandleRunFromArchive(FileName, Engine, s, '', False);   //  not a local engine, extract to local first
      if Pos('%s', s) > 0 then s := Format(s, ['''' + QuoteStr(FileName) + ''''])
                          else s := Format('%s %s', [s, QuoteStr(FileName)]);
      if b then begin
        libc_chdir(PChar(ExtractFilePath(FileName)));
        b := ExecuteProgram(s, ExtractFilePath(FileName), TAssocAction((Sender as TGTKMenuItem).Data).AutodetectGUI,
                            TAssocAction((Sender as TGTKMenuItem).Data).RunInTerminal, Error);
        libc_chdir(PChar('/'));
      end else b := True;   //  Mask cancelled extraction from VFS
      if not b then Application.MessageBox(Format(LANGCannotExecuteSPleaseCheckTheConfiguration, [FileName]), [mbOK], mbError, mbNone, mbOK);
    end;
  end;
  FreeDataItem(DataItem);
  finally
    Application.ProcessMessages;
    InternalUnLock;
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.ListViewMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
var AListView: TGTKListView;
    i, FirstColumn, LastColumn: integer;
    LeftLV, b: boolean;
begin
  PanelRightMouseInProgress := False;
  RightMouseSelectPopupTimer.Enabled := False;
  if Button = mbLeft then begin
    AListView := Sender as TGTKListView;
    LeftLV := AListView = LeftListView;
    GetFirstLastPanelColumn(FirstColumn, LastColumn);
    b := True;
    for i := 0 to AListView.Columns.Count - 1 do
      if (AListView.Columns[i].Tag <> LastColumn) or (FirstColumn = LastColumn) then
        b := b and (ConfColumnSizes[AListView.Columns[i].Tag] = AListView.Columns[i].Width);
    if not b then begin
      DebugMsg(['*** ListView Mouse Up -> resizing columns']);
      for i := 0 to AListView.Columns.Count - 1 do
        ConfColumnSizes[AListView.Columns[i].Tag] := AListView.Columns[i].Width;
      if LeftLV then AListView := RightListView
                else AListView := LeftListView;
      //  Change target widths
      for i := 0 to AListView.Columns.Count - 1 do
        if ConfColumnSizes[AListView.Columns[i].Tag] <> AListView.Columns[i].Width then
          AListView.Columns[i].FixedWidth := ConfColumnSizes[AListView.Columns[i].Tag];
    end;
  end;
  if Button = mbRight then Accept := False;
end;

procedure TFMain.ListViewColumnClicked(Sender: TObject);
var ANotebook: TEphyNotebook;
    AListView: TGTKListView;
    LeftLV: boolean;
begin
  AListView := ((Sender as TGTKTreeViewColumn).Parent as TGTKTreeViewColumns).Parent as TGTKListView;
  LeftLV := AListView = LeftListView;

   //  Check for sort change
  if LeftLV then ANotebook := LeftPanelNotebook
            else ANotebook := RightPanelNotebook;
  try
    if ANotebook.Visible then begin
      DebugMsg(['*** ListView Mouse Up -> saving sort info to tab ', ANotebook.PageIndex, ', SortColumnID = ', AListView.SortColumnID]);
      if LeftLV then begin
                       LeftTabSortIDs[ANotebook.PageIndex] := Pointer(AListView.SortColumnID);
                       LeftTabSortTypes[ANotebook.PageIndex] := Pointer(Integer(AListView.SortOrder));
                end else begin
                       RightTabSortIDs[ANotebook.PageIndex] := Pointer(AListView.SortColumnID);
                       RightTabSortTypes[ANotebook.PageIndex] := Pointer(Integer(AListView.SortOrder));
                end;
    end;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TFMain.ListViewColumnClicked(', E.ClassName, '): ', E.Message]);
  end;

end;

(********************************************************************************************************************************)
procedure TFMain.miPreferencesClick(Sender: TObject);
begin
  try
    InternalLock;
    FPreferences := TFPreferences.Create(Self);
    FPreferences.AssignDefaultValues;
    if FPreferences.Run = mbOK then begin
      FPreferences.SaveSettings;
      WriteMainGUISettings;
      ApplySettings(FPreferences.RebuildListViews, FPreferences.RebuildIcons, False);
    end;
  finally
    FPreferences.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.ApplySettings(RebuildListViews, RebuildIcons, Startup: boolean);
var i: integer;
begin
  InactiveItemsTimer.Enabled := False;

  ButtonsBox.Visible := ConfShowFuncButtons;
  ButtonBoxSeparator.Visible := ConfShowFuncButtons;
  ButtonBoxSpace.Visible := not ConfShowFuncButtons;

  //  Rebuild Icons
  if RebuildIcons then begin
    if ConfRowHeight > 0 then ConfRowHeightReal := ConfRowHeight
                         else ConfRowHeightReal := ConfDefaultRowHeight;
    FolderIcon.Free;
    FileIcon.Free;
    UpDirIcon.Free;
    SymLinkEmblem.Free;
    FolderIconLnk.Free;
    FileIconLnk.Free;
    LoadIcons;
    RecreateIcons(AssocList, False);
  end;

  //  Rebuild ListViews
  if RebuildListViews then FMain.RebuildListViews(False);
  InactiveItemsTimer.Enabled := False;

  //  Colors Section
  SetupColors;
  gtk_widget_modify_base(LeftListView.FWidget, GTK_STATE_NORMAL, NormalItemGDKBackground);
  gtk_widget_modify_base(RightListView.FWidget, GTK_STATE_NORMAL, NormalItemGDKBackground);
  gtk_widget_modify_base(LeftListView.FWidget, GTK_STATE_SELECTED, ActiveItemGDKBackground);
  gtk_widget_modify_base(RightListView.FWidget, GTK_STATE_SELECTED, ActiveItemGDKBackground);
//  if Application.GTKVersion_2_6_0_Up then begin
    gtk_widget_modify_base(LeftListView.FWidget, GTK_STATE_ACTIVE, InactiveItemGDKBackground);
    gtk_widget_modify_base(RightListView.FWidget, GTK_STATE_ACTIVE, InactiveItemGDKBackground);

    gtk_widget_modify_text(LeftListView.FWidget, GTK_STATE_NORMAL, NormalItemGDKColor);
    gtk_widget_modify_text(RightListView.FWidget, GTK_STATE_NORMAL, NormalItemGDKColor);
    gtk_widget_modify_text(LeftListView.FWidget, GTK_STATE_SELECTED, ActiveItemGDKColor);
    gtk_widget_modify_text(RightListView.FWidget, GTK_STATE_SELECTED, ActiveItemGDKColor);
    gtk_widget_modify_text(LeftListView.FWidget, GTK_STATE_ACTIVE, InactiveItemGDKColor);
    gtk_widget_modify_text(RightListView.FWidget, GTK_STATE_ACTIVE, InactiveItemGDKColor);
//  end;

  //  Resize commandline history
  if ConfNumHistoryItems < CommandLineHistory.Count then begin
    try
      for i := CommandLineHistory.Count downto ConfNumHistoryItems + 1 do
        CommandLineHistory.Delete(i - 1);
      if CommandLineCombo.Items.Count > 0 then
        for i := CommandLineCombo.Items.Count - 1 downto 0 do
          CommandLineCombo.Items.Delete(i);
      if CommandLineHistory.Count > 0 then
        for i := 0 to CommandLineHistory.Count - 1 do
          CommandLineCombo.Items.Append(CommandLineHistory[i]);
    except end;
    CommandLineCombo.Entry.Text := '';
  end;

  //  Refresh the lists
  if not Startup then begin
    DoRefresh(True, True, True);
    DoRefresh(False, True, True);
    if RebuildListViews then begin
      LeftListView.SetSortInfo(ConfMainWindowLeftSortColumn, TGTKTreeViewSortOrder(ConfMainWindowLeftSortType));
      RightListView.SetSortInfo(ConfMainWindowRightSortColumn, TGTKTreeViewSortOrder(ConfMainWindowRightSortType));
    end;
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.RefreshBookmarksMenu;
const ShortcutKeys = '1234567890';
var i: integer;
    Item: TGTKMenuItem;
begin
  if mnuBookmarks.Count > 3 then
    for i := mnuBookmarks.Count - 1 downto 3 do begin
      mnuBookmarks.Items[i].Free;
      mnuBookmarks.Delete(i);
    end;
  miAddBookmark.Visible := True;
  miEditBookmarks.Visible := False;
  miBookmarksSeparator.Visible := False;

  if Bookmarks.Count > 0 then begin
    miBookmarksSeparator.Visible := True;
    for i := 0 to Bookmarks.Count - 1 do begin
      if Length(Trim(Bookmarks[i])) = 0 then Continue;
      Item := TGTKMenuItem.CreateTyped(Self, itLabel);
      Item.Caption := Format('_%s   %s', [Chr(Ord('a') + i), StrToUTF8(QuoteMarkupStr(Bookmarks[i]))]);
      Item.Data := Pointer(i);
      Item.OnClick := miBookmarkClick;
      Item.OnMouseUp := BookmarkItemMouseUp;
      if i < Length(ShortcutKeys) - 1 then Item.ShortCuts.AddName(Format('<Alt>%s', [ShortcutKeys[i + 1]]));
      mnuBookmarks.Add(Item);
    end;
  end;
end;

procedure TFMain.miAddBookmarkClick(Sender: TObject);
var LeftPanel: boolean;
    s: string;
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then s := LeftPanelEngine.Path
               else s := RightPanelEngine.Path;
  s := ExcludeTrailingPathDelimiter(s);
  if Bookmarks.IndexOf(s) > -1 then begin
    Application.MessageBox(LANGTheCurrentDirectoryAlreadyExistsInTheBookmarksList, [mbOK], mbWarning);
    Exit;
  end;
  Bookmarks.Add(s);
  WriteBookmarks;
  RefreshBookmarksMenu;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miBookmarkClick(Sender: TObject);
var LeftPanel: boolean;
begin
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  
  //  Close VFS connections
  if LeftPanel then while (LeftPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True)
  else while (RightPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True);

  ChangingDir(LeftPanel, Bookmarks[Integer((Sender as TGTKMenuItem).Data)]);
end;

procedure TFMain.BookmarkPopupDeleteClick(Sender: TObject);
begin
  try
    Bookmarks.Delete(Integer((Sender as TGTKMenuItem).Data));
    WriteBookmarks;
    RefreshBookmarksMenu;
  except
    on E: Exception do
      DebugMsg(['*** Error deleting item: ', E.Message]);
  end;
end;

procedure TFMain.BookmarkItemMouseUp(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
begin
  if Button = mbRight then begin
    Accept := False;
    BookmarkPopup.PopUp;
    BookmarkPopupDelete.Data := (Sender as TGTKMenuItem).Data;
  end;
end;                       

(********************************************************************************************************************************)
function form_event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl;
begin
  Result := False;
  if event^._type = GDK_FOCUS_CHANGE then
    if event^.focus_change._in = 1 then FMain.HandleFormFocusIn
                                   else FileListTipsHide;
end;

procedure TFMain.HandleFormFocusIn;
var ChangedMainGUI, ChangedAssoc, ChangedBookmarks, ChangedMounter, ChangedConnMgr, APerformRefresh: boolean;
begin
  try
  if (csDestroying in ComponentState) or (not Assigned(FMain)) then Exit;
  if ApplicationShuttingDown then begin
    DebugMsg([' *** TFMain.HandleFormFocusIn called when ApplicationShuttingDown, possible bug caught ***']);
    Exit;
  end;
  if InternalLockUnlocked then begin
    APerformRefresh := ConfFocusRefresh;

    if CheckConfFilesMod(ChangedMainGUI, ChangedAssoc, ChangedBookmarks, ChangedMounter, ChangedConnMgr) then begin
      if ChangedBookmarks then begin
        DebugMsg(['Bookmark file changed ---> performing refresh']);
        ReadBookmarks;
        RefreshBookmarksMenu;
      end;
(*      if ChangedConnMgr then begin
        DebugMsg(['Connection manager file changed ---> performing refresh']);
        ReadConnections;
      end;  *)
      try
        InternalLock;
        if (ChangedMainGUI or ChangedAssoc or ChangedMounter) and (Application.MessageBox(LANGSomeOtherInstanceChanged,
            [mbYes, mbNo], mbWarning) = mbNo) then Exit;
        if ChangedMounter then begin
          DebugMsg(['Mounter file changed ---> performing refresh']);
          ReadMounter;
          FillMounterBar;
        end;
        if ChangedMainGUI then begin
          DebugMsg(['GUI file changed ---> performing refresh']);
          ReadMainGUISettings;
          ApplySettings(True, True, False);
          APerformRefresh := True;
        end;
        if ChangedAssoc then begin
          DebugMsg(['Assoc file changed ---> performing refresh']);
          ReadAssoc;
          LoadIcons;
          RemoveIconRefs(AssocList, False);
          RecreateIcons(AssocList);
          APerformRefresh := True;
        end;
      finally
        Application.ProcessMessages;
        InternalLockInit(False);
      end;
    end;
    if APerformRefresh then begin
      DebugMsg(['ANN: Form Focus ---> refresh']);
      DoRefresh(True, True, True);
      DoRefresh(False, True, True);
      if ConfFocusRefresh and ParamDebug then Beep;
    end;
  end;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TFMain.HandleFormFocusIn (', E.ClassName, '): ', E.Message]);
  end;
end;

(********************************************************************************************************************************)
function Max(Int1, Int2: integer): integer;
begin
  if Int1 > Int2 then Result := Int1
                 else Result := Int2;
end;

procedure menu_position_cb(menu: PGtkMenu; x, y: Pgint; push_in: pgboolean; user_data: gpointer); cdecl;
var menu_requisition: TGtkRequisition;
    max_x, max_y: integer;
begin
  (* Calculate our preferred position.  *)
  gdk_window_get_origin(PGtkWidget(user_data).Window, x, y);
  x^ := x^ + PGtkWidget(user_data).allocation.x + PGtkWidget(user_data).allocation.width;
  y^ := y^ + PGtkWidget(user_data).allocation.y + PGtkWidget(user_data).allocation.height;

  (* Now make sure we are on the screen.  *)
  gtk_widget_size_request(PGtkWidget(menu), @menu_requisition);
  max_x := Max(0, gdk_screen_width () - menu_requisition.width);
  max_y := Max(0, gdk_screen_height () - menu_requisition.height);
  x^ := x^ - menu_requisition.width;

  x^ := CLAMP(x^, 0, max_x);
  y^ := CLAMP(y^, 0, max_y);
end;

procedure TFMain.BookmarkButtonClick(Sender: TObject);
begin
  miAddBookmark.Visible := True;
  miEditBookmarks.Visible := False;
  miBookmarksSeparator.Visible := mnuBookmarks.Count > 3;
  gtk_menu_popup(PGtkMenu(mnuBookmarks.FMenu), nil, nil, menu_position_cb, (Sender as TGTKControl).FWidget, 1, GDK_CURRENT_TIME);
  if (Sender = LeftBookmarkButton) {and (not LeftLastFocused)} then LeftListView.SetFocus else
  if (Sender = RightBookmarkButton) {and LeftLastFocused} then RightListView.SetFocus;
end;

(********************************************************************************************************************************)
procedure file_popup_position_cb(menu: PGtkMenu; x, y: Pgint; push_in: pgboolean; user_data: gpointer); cdecl;
var menu_requisition: TGtkRequisition;
    max_x, max_y: integer;
    TreePath: PGtkTreePath;
    Iter: TGtkTreeIter;
    Rect: TGdkRectangle;
    TreeView: PGtkTreeView;
begin
  TreeView := PGtkTreeView(TGTKListView(user_data).FWidget); 
  if not gtk_tree_selection_get_selected(gtk_tree_view_get_selection(TreeView), nil, @Iter) then Exit;
  TreePath := gtk_tree_model_get_path(gtk_tree_view_get_model(TreeView), @iter);
  if not Assigned(TreePath) then Exit;
  gtk_tree_view_get_background_area(TreeView, TreePath, nil, @Rect);

  gdk_window_get_origin(gtk_tree_view_get_bin_window(TreeView), x, y);
  y^ := y^ + Rect.y + Rect.height;

  gtk_widget_size_request(PGtkWidget(menu), @menu_requisition);
  if y^ > gdk_screen_height - menu_requisition.height then begin
    gdk_window_get_origin(gtk_tree_view_get_bin_window(TreeView), x, y);
    y^ := y^ + Rect.y - menu_requisition.height;
  end;

  max_x := Max(0, gdk_screen_width () - menu_requisition.width);
  max_y := Max(0, gdk_screen_height () - menu_requisition.height);

  x^ := CLAMP(x^, 0, max_x);
  y^ := CLAMP(y^, 0, max_y);
end;

procedure TFMain.PopupFileMenuPos;
var AListView: TGTKListView;
    LeftPanel: boolean;
begin
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then AListView := LeftListView
               else AListView := RightListView;
  FilePopupMenuPopup(AListView);
  if not Application.GTKVersion_2_0_5_Up then FilePopupMenu.PopUp else
  gtk_menu_popup(PGtkMenu(FilePopupMenu.FMenu), nil, nil, file_popup_position_cb, AListView, 0, gtk_get_current_event_time());
end;

(********************************************************************************************************************************)
procedure TFMain.miShowDirectorySizesClick(Sender: TObject);
begin
  DoGetDirSize(True);
end;

procedure TFMain.miTargetSourceClick(Sender: TObject);
begin
  if Sender = LeftEqualButton then SwitchOtherPanel(True, True) else
  if Sender = RightEqualButton then SwitchOtherPanel(False, True) else
  SwitchOtherPanel(LeftLastFocused, True);
end;

procedure TFMain.SwitchPanelCtrlLeftRight(LeftPanel, LeftArrowPressed: boolean);
var AListView: TGTKListView;
    SrcEngine, OrigSrcEngine, TargetEngine: TPanelEngine;
    s: string;
    DontShowAgain: boolean;
begin
  if LeftPanel then begin
    AListView := LeftListView;
    SrcEngine := LeftPanelEngine;
    TargetEngine := RightPanelEngine;
  end else begin
    AListView := RightListView;
    SrcEngine := RightPanelEngine;
    TargetEngine := LeftPanelEngine;
  end;
  OrigSrcEngine := SrcEngine;

  if ((not (SrcEngine is TLocalTreeEngine)) and (LeftPanel <> LeftArrowPressed)) or
     ((not (TargetEngine is TLocalTreeEngine)) and (LeftPanel = LeftArrowPressed)) then
  begin
    if ConfSwitchOtherPanelBehaviour < 0 then begin
      MessageBoxShowOnce(PGtkWindow(FWidget), LANGSwitchOtherPanelWarning, LANGDontShowAgain, DontShowAgain, [mbOK], mbWarning, mbOK, mbOK);
      if DontShowAgain then begin
        ConfSwitchOtherPanelBehaviour := 1;
        WriteMainGUISettings;
      end;
    end;
  end;

  //  don't change dir in VFS engines
  while not (SrcEngine is TLocalTreeEngine) do SrcEngine := SrcEngine.ParentEngine;
  while not (TargetEngine is TLocalTreeEngine) do TargetEngine := TargetEngine.ParentEngine;

  if LeftPanel <> LeftArrowPressed then begin
    s := ExcludeTrailingPathDelimiter(SrcEngine.Path);
    if (OrigSrcEngine = SrcEngine) and Assigned(AListView.Selected) and Assigned(AListView.Selected.Data) and
       (not PDataItem(AListView.Selected.Data)^.UpDir) and PDataItem(AListView.Selected.Data)^.IsDir
      then s := IncludeTrailingPathDelimiter(IncludeTrailingPathDelimiter(s) + PDataItem(AListView.Selected.Data)^.FName);
    if IncludeTrailingPathDelimiter(TargetEngine.Path) <> s then begin
      if LeftPanel then RightPanelEngine := TargetEngine
                   else LeftPanelEngine := TargetEngine;
      ChangingDir(not LeftPanel, s);
    end;
  end else begin
    //  Close opened VFS engines
    if LeftPanel then begin
      while (LeftPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True);
    end else
      while (RightPanelEngine is TVFSEngine) do CloseVFS(LeftPanel, True);
    ChangingDir(LeftPanel, TargetEngine.Path);
  end;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.FillMounterBar;

  procedure ClearButtons(List: TList; Table: TGTKTable);
  var i: integer;
  begin
    if List.Count > 0 then
      for i := List.Count - 1 downto 0 do
        Table.RemoveControl(TGTKControl(List[i]));
    List.Clear;
  end;

  procedure FillTableButtons(List: TList; Table: TGTKTable);
  var i: integer;
      Button: TGTKButton;
      Pixmap: TGDKPixbuf;
      b: boolean;
      Sep: TGTKVSeparator;
  begin
    if MounterList.Count > 0 then 
      for i := 0 to MounterList.Count - 1 do
        with TMounterItem(MounterList[i]) do begin
          if ConfMounterPushDown then begin
            Button := TGTKImageToggleButton.Create(Self);
            if Length(DisplayText) > 0 then (Button as TGTKImageToggleButton).Caption := DisplayText else
              (Button as TGTKImageToggleButton).Caption := Copy(MountPath, LastDelimiter(PathDelim, ExcludeTrailingPathDelimiter(MountPath)) + 1,
                                     Length(ExcludeTrailingPathDelimiter(MountPath)) - LastDelimiter(PathDelim, ExcludeTrailingPathDelimiter(MountPath)));
          end else begin
            Button := TGTKImageButton.Create(Self);
            if Length(DisplayText) > 0 then (Button as TGTKImageButton).Caption := DisplayText else
              (Button as TGTKImageButton).Caption := Copy(MountPath, LastDelimiter(PathDelim, ExcludeTrailingPathDelimiter(MountPath)) + 1,
                                     Length(ExcludeTrailingPathDelimiter(MountPath)) - LastDelimiter(PathDelim, ExcludeTrailingPathDelimiter(MountPath)));
          end;
          Button.CanFocus := False;
//          DebugMsg(['Int64(MounterList[i]) = ', Int64(MounterList[i])]);
//          DebugMsg(['Integer(MounterList[i]) = ', Integer(MounterList[i])]);
{$IFDEF CPU64}
          Button.Tag := QWORD(MounterList[i]);
{$ELSE}
          Button.Tag := Longint(MounterList[i]);
{$ENDIF}
          Button.Tooltip := Format(LANGMountPointDevice, [StrToUTF8(MountPath), StrToUTF8(Device)]);
          Button.BorderStyle := bsNone;
          Button.PopupMenu := MounterButtonPopupMenu;
          Button.OnMouseDown := MounterButtonMouseDown;

          //  Check the icon
          b := FileExists(IconPath);
          Pixmap := nil;
          if b then begin
            Pixmap := TGDKPixbuf.Create(Self);
            Pixmap.LoadFromFile(IconPath);
            b := Pixmap.FPixbuf <> nil;
            if b then Pixmap.ScaleSimple(16, 16);
          end;
          if not b then
            case DeviceType of
              0 : Pixmap := MounterHDD;
              1 : Pixmap := MounterRemovable;
              2 : Pixmap := MounterCD;
              3 : Pixmap := MounterFloppy;
              4 : Pixmap := MounterNetwork;
            end;
          if ConfMounterPushDown then begin
            (Button as TGTKImageToggleButton).Icon := Pixmap;
            (Button as TGTKImageToggleButton).Checked := Mounted;
          end else (Button as TGTKImageButton).Icon := Pixmap;

          Button.OnClick := MounterButtonClick;  //  It has to be here because setting the Checked property causes the signal emitting

          Table.AddControlEx(2*i + 1, 0, 1, 1, Button, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 0, 1);
          List.Add(Button);
          if i < MounterList.Count - 1 then begin
            Sep := TGTKVSeparator.Create(Self);
            Table.AddControlEx(2*(i + 1), 0, 1, 1, Sep, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 6);
            List.Add(Sep);
          end;
      end;
  end;

  
var Lab: TGTKLabel;
begin
  if ConfMounterUseFSTab then FillDefaultFstabMounterItems;

  //  Clean all items
  ClearButtons(MounterTableList, MounterBarTable);
  ClearButtons(MounterTableListLeft, LeftMounterTable);
  ClearButtons(MounterTableListRight, RightMounterTable);

  //  Fill the new items
  Lab := TGTKLabel.Create(Self);
  Lab.Caption := Format('<span weight="bold">%s</span>', [LANGMountSC]);
  Lab.UseMarkup := True;
  MounterTableList.Add(Lab);
  MounterBarTable.AddControlEx(0, 0, 1, 1, Lab, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 1);
  case ConfShowMounterBar of
    1: FillTableButtons(MounterTableList, MounterBarTable);
    2: begin
         FillTableButtons(MounterTableListLeft, LeftMounterTable);
         FillTableButtons(MounterTableListRight, RightMounterTable);
       end;
  end;
end;

procedure TFMain.MounterButtonClick(Sender: TObject);
var Item: TMounterItem;
    LeftPanel, b: boolean;
    Engine: TPanelEngine;
begin
  if not (Sender is TGTKButton) then Exit;
  try
    Item := Pointer((Sender as TGTKButton).Tag);
    if (not Assigned(Item)) or (Item.MountPath = '') then DebugMsg(['*** Error in mounter button: incorrect data']) else begin
      if MounterTableListLeft.IndexOf(Sender) > -1 then LeftPanel := True else
      if MounterTableListRight.IndexOf(Sender) > -1 then LeftPanel := False else LeftPanel := LeftLastFocused;
      if LeftPanel then Engine := LeftPanelEngine
                   else Engine := RightPanelEngine;
      try
        b := Item.Mounted;
      except
        b := False;
      end;

      if ConfMounterPushDown then begin
        if not b then b := Item.Mount else
          if Pos(Item.MountPath, Engine.Path) = 1 then b := Item.Eject;
        (Sender as TGTKToggleButton).OnClick := nil;
        (Sender as TGTKToggleButton).Checked := Item.Mounted;
        (Sender as TGTKToggleButton).OnClick := MounterButtonClick;
      end else if not b then b := Item.Mount;

      if b then begin
        if Engine is TVFSEngine then CloseVFS(LeftPanel, True);
        ChangingDir(LeftPanel, Item.MountPath);
        DoRefresh(LeftPanel, True, True);
      end;
      if LeftPanel then LeftListView.SetFocus
                   else RightListView.SetFocus;
    end;
  except
    on E: Exception do DebugMsg(['*** Exception raised in FMain.MounterButtonClick:  (', E.ClassName, '): ', E.Message]);
  end;
end;

procedure TFMain.MounterButtonPopupMenuPopup(Sender: TObject);
begin
//  DebugMsg(['aaaa']);
  try
//  DebugMsg(['aaaa']);
    if (not Assigned(LastMounterButton)) or (LastMounterButton.Tag = 0) then begin
//  DebugMsg(['aaaa']);
      MounterButtonPopupMenu.PopDown;
//  DebugMsg(['aaaa']);
      Exit;
    end;
//  DebugMsg(['aaaaX']);
//  DebugMsg(['sizeof(LastMounterButton.Tag) = ', sizeof(LastMounterButton.Tag)]);
//  DebugMsg(['LastMounterButton.Tag = ', Int64(LastMounterButton.Tag)]);
//  DebugMsg(['TMounterItem(Pointer(QWord(LastMounterButton.Tag))).Device = ', TMounterItem(Pointer(QWord(LastMounterButton.Tag))).Device]);

//  DebugMsg(['TMounterItem(LastMounterButton.Tag).ClassName = ', TMounterItem(LastMounterButton.Tag).ClassName]);

    miMount.Enabled := not TMounterItem(LastMounterButton.Tag).Mounted;
//  DebugMsg(['aaaaX']);
    miUmount.Enabled := not miMount.Enabled;
//  DebugMsg(['aaaaX']);
    miEject.Enabled := not miMount.Enabled;
  except
    on E: Exception do begin
      DebugMsg(['*** Exception raised in FMain.MounterButtonClick:  (', E.ClassName, '): ', E.Message]);
      MounterButtonPopupMenu.PopDown;
    end;
  end;
end;

procedure TFMain.MounterButtonMouseDown(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
begin
  LastMounterButton := nil;
  if (not (Sender is TGTKButton)) or ((Sender as TGTKButton).Tag = 0) then Exit;
  LastMounterButton := Sender as TGTKButton;
end;

procedure TFMain.miMountClick(Sender: TObject);
begin
  if Assigned(LastMounterButton) then TMounterItem(LastMounterButton.Tag).Mount;
  DoRefresh(LeftLastFocused, True, True);
  DoRefresh(not LeftLastFocused, True, True);
  if ConfMounterPushDown then begin
    (LastMounterButton as TGTKToggleButton).OnClick := nil;
    (LastMounterButton as TGTKToggleButton).Checked := TMounterItem(LastMounterButton.Tag).Mounted;
    (LastMounterButton as TGTKToggleButton).OnClick := MounterButtonClick;
  end;
end;

procedure TFMain.miUmountClick(Sender: TObject);
begin
  if Assigned(LastMounterButton) then TMounterItem(LastMounterButton.Tag).Umount;
  DoRefresh(LeftLastFocused, True, True);
  DoRefresh(not LeftLastFocused, True, True);
  if ConfMounterPushDown then begin
    (LastMounterButton as TGTKToggleButton).OnClick := nil;
    (LastMounterButton as TGTKToggleButton).Checked := TMounterItem(LastMounterButton.Tag).Mounted;
    (LastMounterButton as TGTKToggleButton).OnClick := MounterButtonClick;
  end;
end;

procedure TFMain.miEjectClick(Sender: TObject);
begin
  if Assigned(LastMounterButton) then TMounterItem(LastMounterButton.Tag).Eject;
  DoRefresh(LeftLastFocused, True, True);
  DoRefresh(not LeftLastFocused, True, True);
  if ConfMounterPushDown then begin
    (LastMounterButton as TGTKToggleButton).OnClick := nil;
    (LastMounterButton as TGTKToggleButton).Checked := TMounterItem(LastMounterButton.Tag).Mounted;
    (LastMounterButton as TGTKToggleButton).OnClick := MounterButtonClick;
  end;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.miMounterSettingsClick(Sender: TObject);
var x: TList;
begin
  try
    InternalLock;
    FMounterPrefs := TFMounterPrefs.Create(Self);
    ReadMounter;
    FMounterPrefs.AssignAssocList(MounterList);
    FMounterPrefs.FillList;
    if FMounterPrefs.Run = mbOK then begin
      FMounterPrefs.CleanItems;
      x := MounterList;
      MounterList := FMounterPrefs.InternalMounterList;
      FMounterPrefs.InternalMounterList := x;
      ConfMounterUseFSTab := FMounterPrefs.UseFSTabDefaultsCheckBox.Checked;
      ConfMounterPushDown := FMounterPrefs.ToggleModeCheckBox.Checked;
      WriteMounter;
    end;
    FillMounterBar;
  finally
    FMounterPrefs.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miShowMounterBarClick(Sender: TObject);
begin
  if miShowOneMounterBar.Checked then ConfShowMounterBar := 1 else
  if miShowTwoMounterBar.Checked then ConfShowMounterBar := 2 else ConfShowMounterBar := 0;
  MounterBarHandleBox.Visible := ConfShowMounterBar = 1;
  LeftMounterTable.Visible := ConfShowMounterBar = 2;
  RightMounterTable.Visible := ConfShowMounterBar = 2;
  FillMounterBar;
end;

procedure TFMain.miColumnsClick(Sender: TObject);
begin
  try
    InternalLock;
    FColumns := TFColumns.Create(Self);
//    FColumns.Show;
    if FColumns.Run = mbOK then begin
      FColumns.ApplyColumnList;
      RebuildListViews(True);
    end;
  finally
    FColumns.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.ListViewColumnsChanged(Sender: TObject);
var SourceListView: TGTKListView;
    i, j, k, ColIdx, ColumnID: integer;
    Column: PGtkTreeViewColumn;
    TempIDs, TempArrayI: array[1..ConstNumPanelColumns] of integer;
    TempArrayB: array[1..ConstNumPanelColumns] of boolean;
begin
  DebugMsg(['*** Columns reordered ---> performing listview rebuild']);
  LeftListView.OnMouseUp := nil;
  RightListView.OnMouseUp := nil;
  LeftListView.OnColumnsChanged := nil;
  RightListView.OnColumnsChanged := nil; 
  SourceListView := Sender as TGTKListView;

  //  Copy the old items
  for i := 1 to ConstNumPanelColumns do begin
   TempIDs[i] := ConfColumnIDs[i];
   TempArrayI[i] := ConfColumnSizes[i];
   TempArrayB[i] := ConfColumnVisible[i];
  end;

  ColIdx := 1;
  //  Search for moved columns
  for i := 0 to SourceListView.Columns.Count - 1 do begin
    Column := gtk_tree_view_get_column(PGtkTreeView(SourceListView.FWidget), i);
    ColumnID := ColumnSortIDs[gtk_tree_view_column_get_sort_column_id(column) + 1];
    for j := ColIdx to ConstNumPanelColumns do
      if TempArrayB[j] then begin
        for k := 1 to ConstNumPanelColumns do
          if ColumnID = TempIDs[k] then begin
            DebugMsg(['moving from ', k, ' to ', j]);
            ConfColumnIDs[j] := TempIDs[k];
            ConfColumnSizes[j] := TempArrayI[k];
            ConfColumnVisible[j] := TempArrayB[k];
            Break;
          end;
        ColIdx := j + 1;
        Break;
      end;
  end;
  Application.ProcessMessages;

{  RebuildListViewsTimer.Interval := 100;
  RebuildListViewsTimer.Enabled := True; }
  RebuildListViewsTimerTimer(Self);
  gtk_tree_view_set_headers_visible(PGtkTreeView(SourceListView.FWidget), False);
  gtk_tree_view_set_headers_visible(PGtkTreeView(SourceListView.FWidget), True);
end;

procedure TFMain.RebuildListViews(DoRefresh: boolean);
begin
  LeftListView.OnColumnsChanged := nil;
  RightListView.OnColumnsChanged := nil;
  ConfMainWindowLeftSortColumn := LeftListView.SortColumnID;
  ConfMainWindowLeftSortType := Integer(LeftListView.SortOrder);
  ConfMainWindowRightSortColumn := RightListView.SortColumnID;
  ConfMainWindowRightSortType := Integer(RightListView.SortOrder);
  LeftListView.Items.Clear;
  RightListView.Items.Clear;
  LeftListView.Columns.Clear;
  RightListView.Columns.Clear;
  ConstructColumns(LeftListView);
  ConstructColumns(RightListView);
  InactiveItemsTimer.Enabled := False;
  if DoRefresh then begin
    FMain.DoRefresh(True, True, True);
    FMain.DoRefresh(False, True, True);
    LeftListView.SetSortInfo(ConfMainWindowLeftSortColumn, TGTKTreeViewSortOrder(ConfMainWindowLeftSortType));
    RightListView.SetSortInfo(ConfMainWindowRightSortColumn, TGTKTreeViewSortOrder(ConfMainWindowRightSortType));
  end;
  LeftListView.OnColumnsChanged := ListViewColumnsChanged;
  RightListView.OnColumnsChanged := ListViewColumnsChanged;
end;

procedure TFMain.RebuildListViewsTimerTimer(Sender: TObject);
begin
  RebuildListViewsTimer.Enabled := False;
  RebuildListViews(True);
  LeftListView.OnMouseUp := ListViewMouseUp;
  RightListView.OnMouseUp := ListViewMouseUp;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.FillPluginMenu;
var i: integer;
    MenuItem1, MenuItem2: TGTKMenuItem;
begin
  if PluginList.Count = 0 then begin
    MenuItem1 := TGTKMenuItem.CreateTyped(Self, itLabel);
    MenuItem1.Caption := LANGNoPluginsFound;
    MenuItem1.Enabled := False;
    mnuPlugins.Add(MenuItem1);
  end else
    for i := 0 to PluginList.Count - 1 do begin
      MenuItem1 := TGTKMenuItem.CreateTyped(Self, itImageText);
      MenuItem1.Caption := TVFSPlugin(PluginList[i]).ModuleName;
      MenuItem2 := TGTKMenuItem.CreateTyped(Self, itImageText);
      MenuItem2.Caption := LANGPluginAbout;
      MenuItem2.Tag := i;
      MenuItem2.OnClick := miPluginAboutClick;
      mnuPlugins.Add(MenuItem1);
      MenuItem1.Add(MenuItem2);
    end;
end;

procedure TFMain.miTestPluginClick(Sender: TObject);
var Engine: TVFSEngine;
    b: boolean;
begin
  try
    InternalLock;
    FTestPlugin := TFTestPlugin.Create(Self);
    if (FTestPlugin.Run = mbOK) and (PluginList.Count > 0) then begin
      Engine := TVFSEngine.Create(PluginList[FTestPlugin.PluginOptionMenu.ItemIndex]);
      if not Engine.VFSOpenURI(FTestPlugin.CommandEntry.Text, nil, nil, nil, nil) then begin
        Application.MessageBox(LANGCouldntOpenURI, [mbOK], mbError, mbOK, mbOK);
        Exit;
      end;
      b := True;
{      if not FTestPlugin.AnonymousCheckButton.Checked then
        b := HandleLogin(FTestPlugin, Engine, FTestPlugin.UserEntry.Text, FTestPlugin.PasswordEntry.Text); }
      if b then begin
        if LeftLastFocused then LeftPanelEngine := Engine
                           else RightPanelEngine := Engine;
        DoRefresh(LeftLastFocused, False, False);
      end;
    end;
  finally
    FTestPlugin.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miPluginAboutClick(Sender: TObject);
const Authors : array[0..1] of PChar = ('', nil);
var AboutBox: PGtkWidget;
    VFSItem: TVFSPlugin;
begin
  VFSItem := PluginList[(Sender as TGTKMenuItem).Tag];
  InternalLock; 
  if (libGnomeUI2Handle = nil) or (@gnome_about_new = nil) then
    Application.MessageBox(Format(LANGPluginAboutInside, [VFSItem.ModuleName, VFSItem.ModuleAbout, VFSItem.ModuleCopyright]))
    else begin
           AboutBox := gnome_about_new(PChar(VFSItem.ModuleName), nil, PChar(VFSItem.ModuleCopyright), PChar(VFSItem.ModuleAbout), @Authors, nil, nil, AppIcon64.FPixbuf);
           gtk_window_set_transient_for(GTK_WINDOW(AboutBox), GTK_WINDOW(FMain.FWidget));
           gtk_dialog_run(GTK_DIALOG(AboutBox));
    end;
  Application.ProcessMessages;
  InternalLockInit(False);
end;

procedure TFMain.miSavePositionClick(Sender: TObject);
begin
  WriteMainSettings;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.NewTabInternal(LeftPanel: boolean; _Engine: TPanelEngine; _Path: string; NewTabPosition: integer; SwitchToNewTab: boolean);
var AListView: TGTKListView;
    AEngine: TPanelEngine;
    ANotebook: TEphyNotebook;
    ATabList: TStringList;
    AVBoxList: TList;
    APath, APathSave: string;
    VBox: TGTKVBox;
    DockedToNotebook: boolean;
    PathsHighlight: TStringList;
    TabEngines: TList;
    TabSortIDs: TList;
    TabSortTypes: TList;
    i, InsertPos: integer;
    ForceReparent: boolean;
begin
  if LeftPanel then begin
    AListView := LeftListView;
    AEngine := LeftPanelEngine;
    ANotebook := LeftPanelNotebook;
    ATabList := LeftPanelTabs;
    AVBoxList := LeftNotebookBoxList;
    PathsHighlight := LeftPathsHighlight;
    TabEngines := LeftTabEngines;
    TabSortIDs := LeftTabSortIDs;
    TabSortTypes := LeftTabSortTypes;
  end else begin
    AListView := RightListView;
    AEngine := RightPanelEngine;
    ANotebook := RightPanelNotebook;
    ATabList := RightPanelTabs;
    AVBoxList := RightNotebookBoxList;
    PathsHighlight := RightPathsHighlight;
    TabEngines := RightTabEngines;
    TabSortIDs := RightTabSortIDs;
    TabSortTypes := RightTabSortTypes;
  end;

  DockedToNotebook := ANotebook.Visible;

  VBox := nil;
  for i := 0 to 0 + Ord(not DockedToNotebook) do begin
    InsertPos := 0;
    if (i = 1) or DockedToNotebook then begin
      APath := _Path;
      AEngine := _Engine;
      InsertPos := NewTabPosition;
      if (i = 1) and (InsertPos > 1) then InsertPos := 1;
    end else APath := AEngine.Path;

    ATabList.Insert(InsertPos, APath);
    APath := ExtractFileName(ExcludeTrailingPathDelimiter(APath));
    if APath = '' then APath := '/';
    if i = 0 then APathSave := APath;
    VBox := TGTKVBox.Create(Self);
    AVBoxList.Insert(InsertPos, VBox);
    PathsHighlight.Insert(InsertPos, PDataItem(AListView.Selected.Data)^.FName);
    TabSortIDs.Insert(InsertPos, Pointer(AListView.SortColumnID));
    TabSortTypes.Insert(InsertPos, Pointer(Integer(AListView.SortOrder)));
    TabEngines.Insert(InsertPos, AEngine);
  end;

  if not DockedToNotebook then begin
    ANotebook.InsertPage(0, AVBoxList[0], StrToUTF8(APathSave));
    SetTabLabel(ANotebook, 0, StrToUTF8(APathSave), StrToUTF8(ATabList[0]));
  end;

  InsertPos := NewTabPosition;
  if InsertPos > ATabList.Count then InsertPos := ATabList.Count;
  ForceReparent := not ANotebook.Visible;
  if not ANotebook.Visible then ANotebook.Visible := True;
  i := ANotebook.InsertPage(InsertPos, VBox, StrToUTF8(APath));
  SetTabLabel(ANotebook, i, StrToUTF8(APath), StrToUTF8(ATabList[InsertPos]));
  if SwitchToNewTab then ANotebook.PageIndex := i else
    if ForceReparent then SwitchTab(0, LeftPanel, True);   //  We use first page as there were no tabs before
end;

procedure TFMain.NewTab(LeftPanel, SendSelectedDirToBg: boolean; CustomPath: string = '');
var AEngine: TPanelEngine;
    ATabList: TStringList;
    ANotebook: TEphyNotebook;
    APath: string;
    DontShowAgain: boolean;
    NewTabPosition: integer;
begin
  if LeftPanel then begin
    AEngine := LeftPanelEngine;
    ATabList := LeftPanelTabs;
    ANotebook := LeftPanelNotebook;
  end else begin
    AEngine := RightPanelEngine;
    ATabList := RightPanelTabs;
    ANotebook := RightPanelNotebook;
  end;

  if Length(Trim(CustomPath)) > 0
    then APath := CustomPath
    else APath := AEngine.Path;
  if AEngine is TVFSEngine then begin
    while AEngine is TVFSEngine do begin
      APath := AEngine.SavePath;
      AEngine := AEngine.ParentEngine;
    end;
    //  Show warning that we couldn't duplicate the VFS location
    if ConfDuplicateTabWarning then begin
      MessageBoxShowOnce(PGtkWindow(FWidget), LANGDuplicateTabWarning, LANGDontShowAgain, DontShowAgain, [mbOK], mbInfo, mbOK, mbOK);
      if DontShowAgain then begin
        ConfDuplicateTabWarning := False;
        WriteMainGUISettings;
      end;
    end;
  end;

  NewTabPosition := ATabList.Count + Ord(ATabList.Count = 0);  //  Append to end by default
  if SendSelectedDirToBg and ANotebook.Visible then NewTabPosition := ANotebook.PageIndex + 1;
  NewTabInternal(LeftPanel, AEngine, APath, NewTabPosition, not SendSelectedDirToBg);
end;

procedure TFMain.SwitchTab(TabNo: integer; LeftPanel, SetFocus: boolean);
var ANotebook: TEphyNotebook;
    AListView: TGTKListView;
    AVBoxList: TList;
    ListBox: TGTKVBox;
    AScrolledWindow: TGTKScrolledWindow;
    i: integer;
begin
  if LeftPanel then begin
    ANotebook := LeftPanelNotebook;
    AVBoxList := LeftNotebookBoxList;
    ListBox := LeftListBox;
    AScrolledWindow := LeftScrolledWindow;
    AListView := LeftListView;
  end else begin
    ANotebook := RightPanelNotebook;
    AVBoxList := RightNotebookBoxList;
    ListBox := RightListBox;
    AScrolledWindow := RightScrolledWindow;
    AListView := RightListView;
  end;

  if (AVBoxList.Count < TabNo) or (TabNo < -1) then Exit;

  //  Remove any objects
  g_object_ref(AScrolledWindow.FWidget);
  if ListBox.ChildrenCount > 0 then ListBox.RemoveControl(AScrolledWindow);
  if AVBoxList.Count > 0 then
    for i := 0 to AVBoxList.Count - 1 do
      if TGTKVBox(AVBoxList[i]).ChildrenCount > 0 then TGTKVBox(AVBoxList[i]).RemoveControl(AScrolledWindow);

  if TabNo >= 0 then begin       //  Reparent to the tab
    TGTKVBox(AVBoxList[TabNo]).AddControlEx(AScrolledWindow, True, True, 0);
    ListBox.Visible := False;
    ANotebook.Visible := True;
  end else begin                 //  Reparent back to the panel
    ListBox.AddControlEx(AScrolledWindow, True, True, 0);
    ListBox.Visible := True;
    ANotebook.Visible := False;
  end;
  g_object_unref(AScrolledWindow.FWidget);
  if SetFocus then AListView.SetFocus;
end;

procedure TFMain.TabNotebookSwitchPage(Sender: TObject; const NewTabNum: integer; const ShouldFocus: boolean);
var LeftPanel: boolean;
    ATabList: TStringList;
    PathsHighlight: TStringList;
    TabEngines: TList;
    AListView: TGTKListView;
    TabSortIDs: TList;
    TabSortTypes: TList;
begin
  DebugMsg(['Switch page']);
  LeftPanel := (Sender as TEphyNotebook) = LeftPanelNotebook;
  if LeftPanel then begin
    ATabList := LeftPanelTabs;
    PathsHighlight := LeftPathsHighlight;
    TabEngines := LeftTabEngines;
    TabSortIDs := LeftTabSortIDs;
    TabSortTypes := LeftTabSortTypes;
    AListView := LeftListView;
  end else begin
    ATabList := RightPanelTabs;
    PathsHighlight := RightPathsHighlight;
    TabEngines := RightTabEngines;
    TabSortIDs := RightTabSortIDs;
    TabSortTypes := RightTabSortTypes;
    AListView := RightListView;
  end;
  SwitchTab(NewTabNum, LeftPanel, ShouldFocus);
  if LeftPanel then LeftPanelEngine := TabEngines[NewTabNum]
               else RightPanelEngine := TabEngines[NewTabNum];
  ChangingDir(LeftPanel, ATabList[NewTabNum], PathsHighlight[NewTabNum], '', False, True);
  AListView.SetSortInfo(Integer(TabSortIDs[NewTabNum]),
                        TGTKTreeViewSortOrder(Integer(TabSortTypes[NewTabNum])));
end;

procedure TFMain.miDuplicateTabClick(Sender: TObject);
begin
  if not CommandLineCombo.Entry.Focused then
  if LeftListView.Focused then NewTab(True, False) else
  if RightListView.Focused then NewTab(False, False) else
  if (Sender is TGTKMenuItem) and LeftTabPopup then NewTab(True, False) else
  if (Sender is TGTKMenuItem) and (not LeftTabPopup) then NewTab(False, False) else
  DebugMsg(['Couldn''t duplicate tab: No listview focused.']);
end;

procedure TFMain.miCloseTabClick(Sender: TObject);
begin
  if not CommandLineCombo.Entry.Focused then
  if LeftListView.Focused then CloseTab(LeftPanelNotebook.PageIndex, True, True) else
  if RightListView.Focused then CloseTab(RightPanelNotebook.PageIndex, False, True) else
  if (Sender is TGTKMenuItem) and LeftTabPopup then CloseTab(LeftPanelNotebook.PageIndex, True, True) else
  if (Sender is TGTKMenuItem) and (not LeftTabPopup) then CloseTab(RightPanelNotebook.PageIndex, False, True) else
  DebugMsg(['Couldn''t close tab: No listview focused.']);
end;

procedure TFMain.miCloseAllTabsClick(Sender: TObject);
begin
  if Application.MessageBox(LANGAreYouSureCloseAllTabs, [mbYes, mbNo], mbQuestion, mbYes, mbNo) = mbYes then
  if (Sender is TGTKMenuItem) and LeftTabPopup then CloseTab(-1, True, True) else
  if (Sender is TGTKMenuItem) and (not LeftTabPopup) then CloseTab(-1, False, True) else
  DebugMsg(['Couldn''t close all tabs: No listview focused.']);
end;

procedure TFMain.CloseTab(TabNo: integer; LeftPanel, CloseVFSEngine: boolean);
var ATabList: TStringList;
    PathsHighlight: TStringList;
    ANotebook: TEphyNotebook;
    AVBoxList: TList;
    i, NewPageIndex: integer;
    TabEngines: TList;
    Engine, xEngine: TPanelEngine;
    TabSortIDs: TList;
    TabSortTypes: TList;
    AListView: TGTKListView;
begin
  if LeftPanel then begin
    ATabList := LeftPanelTabs;
    PathsHighlight := LeftPathsHighlight;
    ANotebook := LeftPanelNotebook;
    AVBoxList := LeftNotebookBoxList;
    TabEngines := LeftTabEngines;
    TabSortIDs := LeftTabSortIDs;
    TabSortTypes := LeftTabSortTypes;
    AListView := LeftListView;
  end else begin
    ATabList := RightPanelTabs;
    PathsHighlight := RightPathsHighlight;
    ANotebook := RightPanelNotebook;
    AVBoxList := RightNotebookBoxList;
    TabEngines := RightTabEngines;
    TabSortIDs := RightTabSortIDs;
    TabSortTypes := RightTabSortTypes;
    AListView := RightListView;
  end;
  DebugMsg(['Close tab, TabNo = ', TabNo, ', PageIndex = ', ANotebook.PageIndex]);

  if (ANotebook.ChildrenCount < TabNo) or (TabNo < -1) or (not ANotebook.Visible) then begin
    DebugMsg(['Couldn''t close tab: wrong TabNo']);
    Exit;
  end;

  if TabNo > 0 then begin
    Engine := TabEngines[TabNo];
    if (Engine is TVFSEngine) and CloseVFSEngine and (not CheckForUnsavedConnection(Engine as TVFSEngine, True)) then Exit;
  end;

  if (ANotebook.ChildrenCount > 2) and (TabNo >= 0) then begin
    //  Close one tab, leave tab bar visible
    Engine := TabEngines[TabNo];
// !!!!!!!!
    NewPageIndex := ANotebook.PageIndex - Ord((TabNo = ANotebook.PageIndex) and (TabNo = ANotebook.ChildrenCount - 1))
                                        + Ord((TabNo = ANotebook.PageIndex) and (ANotebook.ChildrenCount > TabNo + 1));
//    if (ANotebook.PageIndex = 0) and (NewPageIndex = 0) then NewPageIndex := 1;
    ANotebook.PageIndex := NewPageIndex;
    //  Before remove, ensure some other page is active
    ANotebook.RemovePage(TabNo);
    ATabList.Delete(TabNo);
    PathsHighlight.Delete(TabNo);
    TabEngines.Delete(TabNo);
//    g_object_unref(TGTKVBox(AVBoxList[TabNo]).FWidget);
    AVBoxList.Delete(TabNo);
    TabSortIDs.Delete(TabNo);
    TabSortTypes.Delete(TabNo);
    //  Try to close the VFS engine
    if CloseVFSEngine then
      while Engine is TVFSEngine do
      try
        xEngine := Engine;
        Engine := xEngine.ParentEngine;
        if not TVFSEngine(xEngine).VFSClose then DebugMsg(['Error closing the engine...']);
        xEngine.Free;
      except end;

  end else begin  //  Close last/all tabs, hide the tab bar
    //  Change dir to the opposite
    if (TabNo >= 0) and (TabNo = ANotebook.PageIndex) then begin    //  we should not change directory while closing all tabs...
      i := Ord(not Boolean(ANotebook.PageIndex));
      if LeftPanel then LeftPanelEngine := TabEngines[i]
                   else RightPanelEngine := TabEngines[i];
      ChangingDir(LeftPanel, ATabList[i], PathsHighlight[i]);
      AListView.SetSortInfo(Integer(TabSortIDs[i]), TGTKTreeViewSortOrder(Integer(TabSortTypes[i])));
    end;
    //  Remove the tabs
    SwitchTab(-1, LeftPanel, False);
    for i := ANotebook.ChildrenCount - 1 downto 0 do ANotebook.RemovePage(i);
    ATabList.Clear;
    PathsHighlight.Clear;
    for i := 0 to TabEngines.Count - 1 do
      if (TPanelEngine(TabEngines[i]) is TVFSEngine) and CloseVFSEngine and ((LeftPanel and (LeftPanelEngine <> TabEngines[i])) or
                                                          ((not LeftPanel) and (RightPanelEngine <> TabEngines[i])))
        then begin
          Engine := TabEngines[i];
          while Engine is TVFSEngine do
          try
            if (i <> TabNo) and (not CheckForUnsavedConnection(Engine as TVFSEngine, False)) then Exit;
            xEngine := Engine;
            Engine := xEngine.ParentEngine;
            if not TVFSEngine(xEngine).VFSClose then DebugMsg(['Error closing the engine...']);
            xEngine.Free;
          except end;
        end;

    TabEngines.Clear;
{    for i := 0 to AVBoxList.Count - 1 do
      g_object_unref(TGTKVBox(AVBoxList[i]).FWidget); }
    AVBoxList.Clear;
    TabSortIDs.Clear;
    TabSortTypes.Clear;
    AListView.SetFocus;
  end;
end;

procedure TFMain.TabPopupMenuPopup(Sender: TObject);
begin
  LeftTabPopup := Sender = LeftPanelNotebook;
end;

procedure TFMain.AddTabs(LeftPanel: boolean; TabList: TStringList; TabSortIDs, TabSortTypes: TList; SetTabActive: integer);
var ANotebook: TEphyNotebook;
    ATabList: TStringList;
    AVBoxList: TList;
    APath: string;
    VBox: TGTKVBox;
    PathsHighlight: TStringList;
    TabEngines: TList;
    i: integer;
    ATabSortIDs: TList;
    ATabSortTypes: TList;
    OldEvent: TEphyNotebookTabSwitchedEvent;
begin
  if LeftPanel then begin
    ANotebook := LeftPanelNotebook;
    ATabList := LeftPanelTabs;
    AVBoxList := LeftNotebookBoxList;
    PathsHighlight := LeftPathsHighlight;
    TabEngines := LeftTabEngines;
    ATabSortIDs := LeftTabSortIDs;
    ATabSortTypes := LeftTabSortTypes;
  end else begin
    ANotebook := RightPanelNotebook;
    ATabList := RightPanelTabs;
    AVBoxList := RightNotebookBoxList;
    PathsHighlight := RightPathsHighlight;
    TabEngines := RightTabEngines;
    ATabSortIDs := RightTabSortIDs;
    ATabSortTypes := RightTabSortTypes;
  end;

  if (not Assigned(TabList)) or (TabList.Count < 1) or (not Assigned(TabSortIDs)) or (TabSortIDs.Count < 1) or
     (not Assigned(TabSortTypes)) or (TabSortTypes.Count < 1) then Exit;

  for i := 0 to TabList.Count - 1 do begin
    ATabList.Add(TabList[i]);
    APath := ExtractFileName(ExcludeTrailingPathDelimiter(TabList[i]));
    if APath = '' then APath := '/';
    VBox := TGTKVBox.Create(Self);
    AVBoxList.Add(VBox);
    PathsHighlight.Add('');
    ATabSortIDs.Add(TabSortIDs[i]);
    ATabSortTypes.Add(TabSortTypes[i]);
    if LeftPanel then TabEngines.Add(LeftPanelEngine)
                 else TabEngines.Add(RightPanelEngine);
    ANotebook.AppendPage(VBox, StrToUTF8(APath));
    SetTabLabel(ANotebook, ANotebook.ChildrenCount - 1, StrToUTF8(APath), StrToUTF8(TabList[i]));
  end;

  if not ANotebook.Visible then ANotebook.Visible := True;
  OldEvent := ANotebook.OnTabSwitched;
  ANotebook.OnTabSwitched := nil;
  ANotebook.PageIndex := SetTabActive;
  ANotebook.OnTabSwitched := OldEvent;
  TabNotebookSwitchPage(ANotebook, SetTabActive, True);
end;

procedure TFMain.SetTabLabel(Notebook: TEphyNotebook; PageIndex: integer; ALabel, Tooltip: string);
var g: PChar;
begin
  if (ConfTabMaxLength > 0) and (g_utf8_strlen(PChar(ALabel), -1) > ConfTabMaxLength) then begin
    g := malloc(Length(ALabel) + 4);
    memset(g, 0, Length(ALabel) + 4);
    g_utf8_strncpy(g, PChar(ALabel), ConfTabMaxLength);
    ALabel := g + '...';
    libc_free(g);
  end;
  Notebook.SetTabCaption(PageIndex, ALabel);
  Notebook.SetTabTooltip(PageIndex, Tooltip);
end;

procedure TFMain.NotebookReordered(Sender: TObject; const Source, Dest: integer);
var ANotebook: TEphyNotebook;
    ATabList: TStringList;
    AVBoxList: TList;
    PathsHighlight: TStringList;
    TabEngines: TList;
    ATabSortIDs: TList;
    ATabSortTypes: TList;
begin
  ANotebook := Sender as TEphyNotebook;
  if ANotebook = LeftPanelNotebook then begin
    ATabList := LeftPanelTabs;
    AVBoxList := LeftNotebookBoxList;
    PathsHighlight := LeftPathsHighlight;
    TabEngines := LeftTabEngines;
    ATabSortIDs := LeftTabSortIDs;
    ATabSortTypes := LeftTabSortTypes;
  end else begin
    ATabList := RightPanelTabs;
    AVBoxList := RightNotebookBoxList;
    PathsHighlight := RightPathsHighlight;
    TabEngines := RightTabEngines;
    ATabSortIDs := RightTabSortIDs;
    ATabSortTypes := RightTabSortTypes;
  end;

  ATabList.Move(Source, Dest);
  AVBoxList.Move(Source, Dest);
  PathsHighlight.Move(Source, Dest);
  TabEngines.Move(Source, Dest);
  ATabSortIDs.Move(Source, Dest);
  ATabSortTypes.Move(Source, Dest);
end;

procedure TFMain.NotebookTabClosed(Sender: TObject; const TabNum: integer; var CanClose: boolean);
begin
  CloseTab(TabNum, Sender = LeftPanelNotebook, True);
  CanClose := False;
end;

procedure TFMain.NotebookTabDoubleClick(Sender: TObject; const TabNum: integer);
begin
  CloseTab(TabNum, Sender = LeftPanelNotebook, True);
end;

function TFMain.NotebookFindNotebookAtPointerEvent(Sender: TObject; const AbsX, AbsY: integer): TEphyNotebook;
var wx, wy: Integer;
begin
//  DebugMsg([' *** NotebookFindNotebookAtPointerEvent(AbsX = ', AbsX, ', AbsY = ', AbsY, ')']);
  Result := nil;

  if LeftPanelNotebook.Visible then begin
    gtk_widget_get_pointer(LeftPanelNotebook.FWidget, @wx, @wy);
    if (wx > 0) and (wy > 0) and (wx < LeftPanelNotebook.FWidget^.allocation.width) and
       (wy < LeftPanelNotebook.FWidget^.allocation.height) then Result := LeftPanelNotebook;
  end else begin
    gtk_widget_get_pointer(LeftScrolledWindow.FWidget, @wx, @wy);
    if (wx > 0) and (wy > 0) and (wx < LeftScrolledWindow.FWidget^.allocation.width) and
       (wy < LeftScrolledWindow.FWidget^.allocation.height) then Result := LeftPanelNotebook;
  end;

  if Result = nil then begin
    if RightPanelNotebook.Visible then begin
      gtk_widget_get_pointer(RightPanelNotebook.FWidget, @wx, @wy);
      if (wx > 0) and (wy > 0) and (wx < RightPanelNotebook.FWidget^.allocation.width) and
         (wy < RightPanelNotebook.FWidget^.allocation.height) then Result := RightPanelNotebook;
    end else begin
      gtk_widget_get_pointer(RightScrolledWindow.FWidget, @wx, @wy);
      if (wx > 0) and (wy > 0) and (wx < RightScrolledWindow.FWidget^.allocation.width) and
         (wy < RightScrolledWindow.FWidget^.allocation.height) then Result := RightPanelNotebook;
    end;
  end;
  
{
  if Result = LeftPanelNotebook then DebugMsg([' *** NotebookFindNotebookAtPointerEvent: inside LeftPanelNotebook']) else
  if Result = RightPanelNotebook then DebugMsg([' *** NotebookFindNotebookAtPointerEvent: inside RightPanelNotebook']);
  }
end;

function TFMain.NotebookMoveTabToAnotherNotebook(Sender: TObject; Destination: TEphyNotebook; const SourceTabNo, DestTabNo: integer): boolean;
var LeftPanel: boolean;
    ATargetEngine, ASrcEngine: TPanelEngine;
    ATargetTabList, ASrcTabList: TStringList;
    ATargetPathsHighlight, ASrcPathsHighlight: TStringList;
    ATargetTabEngines, ASrcTabEngines: TList;
    ATargetTabSortIDs, ASrcTabSortIDs: TList;
    ATargetTabSortTypes, ASrcTabSortTypes: TList;
    APath: string;
    DontShowAgain: boolean;
    dst_no: integer;
begin
  Result := False;
  DontShowAgain := False;
  DebugMsg([' *** NotebookMoveTabToAnotherNotebook(SourceTabNo = ', SourceTabNo, ', DestTabNo = ', DestTabNo, ')']);
  LeftPanel := Sender = LeftPanelNotebook;
  if LeftPanel then begin
    ATargetEngine := RightPanelEngine;
    ATargetTabList := RightPanelTabs;
    ASrcEngine := LeftPanelEngine;
    ASrcTabList := LeftPanelTabs;
    ATargetPathsHighlight := RightPathsHighlight;
    ATargetTabEngines := RightTabEngines;
    ATargetTabSortIDs := RightTabSortIDs;
    ATargetTabSortTypes := RightTabSortTypes;
    ASrcPathsHighlight := LeftPathsHighlight;
    ASrcTabEngines := LeftTabEngines;
    ASrcTabSortIDs := LeftTabSortIDs;
    ASrcTabSortTypes := LeftTabSortTypes;
  end else begin
    ATargetEngine := LeftPanelEngine;
    ATargetTabList := LeftPanelTabs;
    ASrcEngine := RightPanelEngine;
    ASrcTabList := RightPanelTabs;
    ATargetPathsHighlight := LeftPathsHighlight;
    ATargetTabEngines := LeftTabEngines;
    ATargetTabSortIDs := LeftTabSortIDs;
    ATargetTabSortTypes := LeftTabSortTypes;
    ASrcPathsHighlight := RightPathsHighlight;
    ASrcTabEngines := RightTabEngines;
    ASrcTabSortIDs := RightTabSortIDs;
    ASrcTabSortTypes := RightTabSortTypes;
  end;

  if (SourceTabNo < 0) or (SourceTabNo > ASrcTabList.Count - 1) then begin
    DebugMsg([' *** NotebookMoveTabToAnotherNotebook: invalid SourceTabNo']);
    Exit;
  end;

  APath := TPanelEngine(ASrcTabEngines[SourceTabNo]).Path;

(*  --  disabled, we allow to _move_ (not duplicate) non-local engine
  if TPanelEngine(ASrcTabEngines[SourceTabNo]) is TVFSEngine then begin
    while TPanelEngine(ASrcTabEngines[SourceTabNo]) is TVFSEngine do begin
      APath := TPanelEngine(ASrcTabEngines[SourceTabNo]).SavePath;
      ASrcTabEngines[SourceTabNo] := TPanelEngine(ASrcTabEngines[SourceTabNo]).ParentEngine;
    end;
    //  Show warning that we couldn't duplicate the VFS location
    if ConfDuplicateTabWarning then begin
      MessageBoxShowOnce(LANGDuplicateTabWarning, LANGDontShowAgain, DontShowAgain, [mbOK], mbInfo, mbOK, mbOK);
      if DontShowAgain then begin
        ConfDuplicateTabWarning := False;
        WriteMainGUISettings;
      end;
    end;
  end;

  while ATargetEngine is TVFSEngine do
    ATargetEngine := ATargetEngine.ParentEngine;
*)

  dst_no := DestTabNo;
  if dst_no < 0 then dst_no := ATargetTabList.Count + Ord(ATargetTabList.Count = 0);
  CloseTab(SourceTabNo, LeftPanel, False);
  if not (ASrcEngine is TLocalTreeEngine)
    then NewTabInternal(not LeftPanel, ASrcEngine, APath, dst_no, True)
    else NewTabInternal(not LeftPanel, ATargetEngine, APath, dst_no, True);

  Result := True;
end;

procedure TFMain.NotebookTabFocusOnlyEvent(Sender: TObject; const TabNum: integer);
begin
  if Sender = LeftPanelNotebook then LeftListView.SetFocus else
  if Sender = RightPanelNotebook then RightListView.SetFocus
  else Exit;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
function TFMain.HandleVFSArchive(LeftPanel: boolean; const FullPath, HighlightItem, TargetPath: string): boolean;
var Plugin: TVFSPlugin;
    Engine, NewEngine: TPanelEngine;
    NewPath: string;
begin
  Plugin := FindVFSPlugin(ExtractFileName(FullPath));
  Result := Plugin <> nil;

  if Result then begin
    if LeftPanel then Engine := LeftPanelEngine
                 else Engine := RightPanelEngine;

    if Engine is TLocalTreeEngine then begin
      DebugMsg(['Found plugin ''', Plugin.ModuleID, ''', trying to open the file ''', FullPath, '''']);
      ChangingDir(LeftPanel, TargetPath, FullPath, HighlightItem, False, False, Plugin);
    end else begin
      DebugMsg(['Found plugin ''', Plugin.ModuleID, ''', archive is nested in another archive, extracting first.']);
      NewPath := '';
      Result := ExtractFromArchive(NewPath, Engine, FullPath, False);
      if Result then begin
        DebugMsg(['Extract OK, trying to open the file ''', NewPath, '''']);
        ChangingDir(LeftPanel, TargetPath, NewPath, HighlightItem, False, False, Plugin);
        if LeftPanel then NewEngine := LeftPanelEngine
                     else NewEngine := RightPanelEngine;
        if (NewEngine is TVFSEngine) and (NewEngine.ParentEngine = Engine) then
          (NewEngine as TVFSEngine).RemoveFileOnClose := NewPath;
      end;
    end;
  end;
end;

function TFMain.CloseVFS(LeftPanel, SuppressRefresh: boolean): string;
var Engine: TPanelEngine;
begin
  if LeftPanel then Engine := LeftPanelEngine
               else Engine := RightPanelEngine;
  if (not Assigned(Engine.ParentEngine)) or (not (Engine is TVFSEngine)) then begin
    if LeftPanel then begin
      LeftPanelEngine := LeftLocalEngine;
      Result := LeftPanelEngine.Path;
    end else begin
      RightPanelEngine := RightLocalEngine;
      Result := RightPanelEngine.Path;
    end;
    Exit;
  end;

  if not CheckForUnsavedConnection(Engine as TVFSEngine, (not SuppressRefresh) and (not ApplicationShuttingDown)) then Exit;

  if LeftPanel then LeftPanelEngine := Engine.ParentEngine
               else RightPanelEngine := Engine.ParentEngine;

  Result := Engine.SavePath;
  if not SuppressRefresh then
    ChangingDir(LeftPanel, Engine.SavePath, Engine.ParentEngine.LastHighlightItem, Engine.ParentEngine.LastHighlightItem, False, True);

  if not TVFSEngine(Engine).VFSClose then DebugMsg(['Error closing the engine...']);
  Engine.Free;
end;

function TFMain.CheckForUnsavedConnection(Engine: TVFSEngine; AllowCancel: boolean): boolean;  //  Returns False to Cancel
var Buttons: TMessageButtons;
    CancelButton: TMessageButton;
    AFConnectionProperties: TFConnectionProperties;
    URI: string;
    ConnMgrItem: TConnMgrItem;
    i: integer;
    DontShowAgain: boolean;
    res: TMessageButton;
begin
  Result := True;
  URI := Engine.GetPathURI;
  if Engine.OpenedFromQuickConnect and (Length(Trim(URI)) > 0) then begin
    Buttons := [mbYes, mbNo];
    CancelButton := mbNo;
    if AllowCancel then begin
      Include(Buttons, mbCancel);
      CancelButton := mbCancel;
    end;

    if ConfWarnUnsavedConnection then begin
      DontShowAgain := False;
      res := MessageBoxShowOnce(PGtkWindow(FWidget), PChar(Format(LANGTheActiveConnectionHasNotBeenSaved, [Engine.GetPathURI])), LANGDontShowAgain, DontShowAgain, Buttons, mbWarning, mbNo, CancelButton);
      if DontShowAgain then begin
        ConfWarnUnsavedConnection := False;
        WriteMainGUISettings;
      end;
      case res of
        mbYes: begin
                 AFConnectionProperties := TFConnectionProperties.Create(Self);
                 try
                   AFConnectionProperties.URIEntry.Text := URI;
                   if (Engine.CustomPluginIDSave <> '') and (AFConnectionProperties.PluginOptionMenu.Items.Count > 0) then
                     for i := 0 to PluginList.Count - 1 do
                       if TVFSPlugin(PluginList[i]).ModuleID = Engine.CustomPluginIDSave
                         then AFConnectionProperties.PluginOptionMenu.ItemIndex := i + 1;
                   if AFConnectionProperties.Run = mbOK then begin
                     ReadConnections;
                     ConnMgrItem := TConnMgrItem.Create;
                     ConnMgrItem.ConnectionName := AFConnectionProperties.NameEntry.Text;
                     ConnMgrItem.ServiceType := AFConnectionProperties.GetService;
                     ConnMgrItem.Server := AFConnectionProperties.ServerEntry.Text;
                     ConnMgrItem.Username := AFConnectionProperties.UserNameEntry.Text;
                     ConnMgrItem.Password := AFConnectionProperties.PasswordEntry.Text;
                     ConnMgrItem.TargetDir := AFConnectionProperties.TargetDirEntry.Text;
                     ConnMgrItem.PluginID := '';
                     if AFConnectionProperties.PluginOptionMenu.ItemIndex <> 0 then
                       ConnMgrItem.PluginID := TVFSPlugin(PluginList[AFConnectionProperties.PluginOptionMenu.ItemIndex - 1]).ModuleID;
                     ConfConnMgrActiveItem := ConnectionMgrList.Add(ConnMgrItem);
                     WriteConnections;
                   end;
                 finally
                   AFConnectionProperties.Free;
                 end;
                 Result := True;
               end;
        mbNo: Result := True;
        mbCancel: Result := False;
        else {Cancel?} Result := not AllowCancel;
      end;
    end else Result := True;
  end;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.ShowBookmarkQuick(LeftPanel: boolean);
var SenderControl: TGTKControl;
begin
  if LeftPanel then SenderControl := LeftBookmarkButton
               else SenderControl := RightBookmarkButton;
  miAddBookmark.Visible := False;
  miEditBookmarks.Visible := False;
  miBookmarksSeparator.Visible := False;

  gtk_menu_popup(PGtkMenu(mnuBookmarks.FMenu), nil, nil, menu_position_cb, SenderControl.FWidget, 1, GDK_CURRENT_TIME);
  if LeftPanel then LeftListView.SetFocus else RightListView.SetFocus;
end;

procedure TFMain.mnuBookmarksPopup(Sender: TObject);
begin
  miAddBookmark.Visible := True;
  miEditBookmarks.Visible := False;
  miBookmarksSeparator.Visible := mnuBookmarks.Count > 3;
end;


(********************************************************************************************************************************)
procedure TFMain.miSearchClick(Sender: TObject);
var Engine: TPanelEngine;
    DataList: TList;
    AListView: TGTKListView;
    i: integer;
begin
  if LeftLastFocused then Engine := LeftPanelEngine
                     else Engine := RightPanelEngine;

  try
    FSearch := TFSearch.Create(Self);
    FSearch.ParentForm := FMain;
    FSearch.Engine := Engine;
    FSearch.SearchInEntry.Text := StrToUTF8(ExcludeTrailingPathDelimiter(Engine.Path));
    if Length(FSearch.SearchInEntry.Text) < 1 then FSearch.SearchInEntry.Text := '/';
    FSearch.CaseSensitiveMatchCheckButton.Checked := ConfSearchFilterCaseSensitive;
    FSearch.StayCurrentFSCheckButton.Checked := ConfSearchOtherFS;
    FSearch.SearchArchivesCheckButton.Checked := ConfSearchArchives;
    FSearch.CaseSensitiveCheckButton.Checked := ConfSearchTextCaseSensitive;

    case FSearch.Run of
      mbOK: ;
      mbApply: begin
                 DebugMsg(['TFMain.miSearchClick: FSearch.GoToFileArchive = "', FSearch.GoToFileArchive, '", FSearch.GoToFile = "', FSearch.GoToFile, '"']);
                 if Length(FSearch.GoToFileArchive) > 0 then begin
                   HandleVFSArchive(LeftLastFocused, FSearch.GoToFileArchive, ExtractFileName(FSearch.GoToFileArchive), ExtractFilePath(FSearch.GoToFile));

                   if LeftLastFocused then begin
                     Engine := LeftPanelEngine;
                     DataList := LeftPanelData;
                     AListView := LeftListView;
                   end else begin
                     Engine := RightPanelEngine;
                     DataList := RightPanelData;
                     AListView := RightListView;
                   end;

                   if Engine is TVFSEngine then begin
                     Engine.SavePath := ExtractFilePath(FSearch.GoToFileArchive);
//                     (Engine as TVFSEngine).ParentEngine.LastHighlightItem := ExtractFileName(FSearch.GoToFileArchive);
                     for i := 0 to DataList.Count - 1 do
                       if WideCompareText(string(PDataItem(DataList[i])^.FName), ExtractFileName(FSearch.GoToFile)) = 0 then begin
                         AListView.Items[i].Selected := True;
                         AListView.Items[i].SetCursor(0, False, not Application.GTKVersion_2_2_0_Up, 0.5, 0);
                         Break;
                       end;
                   end;
                 end else ChangingDir(LeftLastFocused, ExtractFilePath(FSearch.GoToFile), ExtractFileName(FSearch.GoToFile));
               end;
    end;
  finally
    ConfSearchFilterCaseSensitive := FSearch.CaseSensitiveMatchCheckButton.Checked;
    ConfSearchOtherFS := FSearch.StayCurrentFSCheckButton.Checked;
    ConfSearchArchives := FSearch.SearchArchivesCheckButton.Checked;
    ConfSearchTextCaseSensitive := FSearch.CaseSensitiveCheckButton.Checked;
    FSearch.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TFMain.miOpenConnectionClick(Sender: TObject);
var b: boolean;
begin
  try
    InternalLock;
    ReadConnections;
    FConnectionManager := TFConnectionManager.Create(Self);
    if LeftLastFocused then FConnectionManager.SourcePanelEngine := LeftPanelEngine
                       else FConnectionManager.SourcePanelEngine := RightPanelEngine;
    b := FConnectionManager.Run = mbOK;
    WriteConnections;      //  Save connection manager data

    if b and (FConnectionManager.ConnectedEngine <> nil) then begin
      while FConnectionManager.ConnectedEngine.ParentEngine is TVFSEngine do
        FConnectionManager.ConnectedEngine.ParentEngine := FConnectionManager.ConnectedEngine.ParentEngine.ParentEngine;
      if FConnectionManager.SourcePanelEngine is TVFSEngine then CloseVFS(LeftLastFocused, True);
      if LeftLastFocused then LeftPanelEngine := FConnectionManager.ConnectedEngine
                         else RightPanelEngine := FConnectionManager.ConnectedEngine;
      DoRefresh(LeftLastFocused, False, False);
    end;
  finally
    FConnectionManager.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miQuickConnectClick(Sender: TObject);
var b: boolean;
begin
  try
    InternalLock;
    ReadConnections;
    FQuickConnect := TFQuickConnect.Create(Self);
    if LeftLastFocused then FQuickConnect.SourcePanelEngine := LeftPanelEngine
                       else FQuickConnect.SourcePanelEngine := RightPanelEngine;
    b := FQuickConnect.Run = mbOK;
    WriteConnections;      //  Save connection manager data

    if b and (FQuickConnect.ConnectedEngine <> nil) then begin
      while FQuickConnect.ConnectedEngine.ParentEngine is TVFSEngine do
        FQuickConnect.ConnectedEngine.ParentEngine := FQuickConnect.ConnectedEngine.ParentEngine.ParentEngine;
      if FQuickConnect.SourcePanelEngine is TVFSEngine then CloseVFS(LeftLastFocused, True);
      if LeftLastFocused then LeftPanelEngine := FQuickConnect.ConnectedEngine
                         else RightPanelEngine := FQuickConnect.ConnectedEngine;
      DoRefresh(LeftLastFocused, False, False);
    end;
  finally
    FQuickConnect.Free;
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

procedure TFMain.miDisconnectClick(Sender: TObject);
begin
  CloseVFS(LeftLastFocused, False);
end;

procedure TFMain.DisconnectButtonClick(Sender: TObject);
begin
  CloseVFS(Sender = LeftDisconnectButton, False);
  if (Sender = LeftDisconnectButton) and (not LeftListView.Focused) then LeftListView.SetFocus else
  if (Sender = RightDisconnectButton) and (not RightListView.Focused) then RightListView.SetFocus;
end;

procedure TFMain.LeaveArchiveButtonClick(Sender: TObject);
begin
  CloseVFS(Sender = LeftLeaveArchiveButton, False);
  if (Sender = LeftLeaveArchiveButton) and (not LeftListView.Focused) then LeftListView.SetFocus else
  if (Sender = RightLeaveArchiveButton) and (not RightListView.Focused) then RightListView.SetFocus;
end;

procedure TFMain.OpenTerminalButtonClick(Sender: TObject);
var CurrentPath: string;
    AEngine: TPanelEngine;
    Error: integer;
begin
  Error := 0;
  if LeftLastFocused then AEngine := LeftPanelEngine
                     else AEngine := RightPanelEngine;
  while not (AEngine is TLocalTreeEngine) do AEngine := AEngine.ParentEngine;
  CurrentPath := AEngine.Path;
  libc_chdir(PChar(CurrentPath));
  ExecuteProgram('bash', CurrentPath, False, True, Error);
  libc_chdir('/');
end;


(********************************************************************************************************************************)
procedure TFMain.miFilePropertiesClick(Sender: TObject);
var LeftPanel: boolean;
    AListView: TGTKListView;
    Engine: TPanelEngine;
    DataList: TList;
    i: integer;
    SelCount: longint;
    AFile, NextItem1, NextItem2: string;
    Stat: PDataItemSL;
{    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress; }
begin
  try
  InternalLock;
  if LeftListView.Focused then LeftPanel := True else
  if RightListView.Focused then LeftPanel := False else
  LeftPanel := LeftLastFocused;
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) {or PDataItem(AListView.Selected.Data)^.UpDir}) then begin
    Application.MessageBox(LANGNoFilesSelected, [mbOK], mbInfo, mbNone, mbOK);
    Exit;
  end;

  AFile := '';
  if SelCount = 0 then AFile := PDataItem(AListView.Selected.Data)^.FName else
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then begin
          AFile := FName;
          Break;
        end;

  if AFile <> '' then
  try
    FProperties := TFProperties.Create(Self);
    Stat := Engine.GetFileInfoSL(IncludeTrailingPathDelimiter(Engine.Path) + AFile);
    if not Assigned(Stat) then Exit;
//    FProperties.AssignMode(Stat^.Mode, AFile, Stat^.UID, Stat^.GID);
    FProperties.DisplayFileName := AFile;
    if FProperties.Run = mbOK then begin
{      FindNextSelected(AListView, DataList, NextItem1, NextItem2);

      AWorkingThread := TWorkerThread.Create;
      AFProgress := TFProgress.Create(Self);
      try
        AFProgress.SetNumBars(False);
        AFProgress.ProgressBar.Value := 0;
        AFProgress.Label1.Caption := LANGChownProgress;
        AWorkingThread.ProgressForm := AFProgress;
        if Assigned(AListView.Selected) then AWorkingThread.SelectedItem := AListView.Selected.Data;
        AWorkingThread.ParamBool1 := FChown.RecursiveCheckButton.Checked;
        AWorkingThread.ParamCardinal1 := FChown.LastUID;
        AWorkingThread.ParamCardinal2 := FChown.LastGID;
        AWorkingThread.Engine := Engine;
        AWorkingThread.LeftPanel := LeftPanel;
        AWorkingThread.DataList := DataList;
        AWorkingThread.WorkerProcedure := ChownFilesWorker;
        AWorkingThread.Resume;
        AFProgress.ParentForm := FMain;
        if (SelCount > 1) or FChown.RecursiveCheckButton.Checked then AFProgress.ShowModal;
        ProcessProgressThread(AWorkingThread, AFProgress);
        AFProgress.Close;
      finally
        AFProgress.Free;
        AWorkingThread.Free;
      end; }

      NextItem1 := '';
      NextItem2 := '';
      ChangingDir(LeftPanel, Engine.Path, NextItem1, NextItem2);
      DoRefresh(not LeftPanel, True, True);
    end;
  finally
    FreeDataItem(Stat);
    FProperties.Free;
  end;
  finally
    Application.ProcessMessages;
    InternalLockInit(False);
  end;
end;

(********************************************************************************************************************************)
procedure TFMain.CopyFilenamesToClipboard(FullPaths, LeftPanel: boolean);
var DataList: TList;
    Engine: TPanelEngine;
    AListView: TGTKListView;
    i, x: longint;
    SelCount: longint;
    clip: PGtkClipboard;
    s: string;
begin
  if LeftPanel then begin
    AListView := LeftListView;
    Engine := LeftPanelEngine;
    DataList := LeftPanelData;
  end else begin
    AListView := RightListView;
    Engine := RightPanelEngine;
    DataList := RightPanelData;
  end;

  clip := gtk_clipboard_get(gdk_atom_intern('CLIPBOARD', False));

  SelCount := 0;
  if DataList.Count > 0 then
    for i := 0 to DataList.Count - 1 do
      with PDataItem(DataList[i])^ do
        if Selected and (not UpDir) then Inc(SelCount);
  if (SelCount = 0) and ((not Assigned(AListView.Selected)) or PDataItem(AListView.Selected.Data)^.UpDir) then begin
    //  Empty the clipboard
    gtk_clipboard_clear(clip);
    Exit;
  end;

  if SelCount = 0 then begin
    s := PDataItem(AListView.Selected.Data)^.FDisplayName;
    if FullPaths then s := IncludeTrailingPathDelimiter(Engine.GetPath) + s;
  end else begin
    s := '';
    if DataList.Count > 0 then
      for i := 0 to DataList.Count - 1 do begin
        x := AListView.ConvertFromSorted(i);
        if (x >= 0) and (x < DataList.Count) and PDataItem(DataList[x])^.Selected then begin
          if FullPaths then s := s + IncludeTrailingPathDelimiter(Engine.GetPath);
          s := s + PDataItem(DataList[x])^.FDisplayName + #10;
        end;
      end;
    end;
      
  gtk_clipboard_set_text(clip, PChar(s), Length(s));
end;

procedure TFMain.miPathBoxCopyPathClick(Sender: TObject);
var clip: PGtkClipboard;
    s: string;
begin
  if LeftLastFocused then s := LeftPanelEngine.Path
                     else s := RightPanelEngine.Path;
  clip := gtk_clipboard_get(gdk_atom_intern('CLIPBOARD', False));
  gtk_clipboard_set_text(clip, PChar(StrToUTF8(s)), Length(StrToUTF8(s)));
end;

procedure TFMain.miCopyNamesClick(Sender: TObject);
begin
  if LeftListView.Focused then CopyFilenamesToClipboard(Sender = miCopyFullPaths, True) else
  if RightListView.Focused then CopyFilenamesToClipboard(Sender = miCopyFullPaths, False);
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
function TFMain.HandleRunFromArchive(var APath: string; Engine: TPanelEngine; Command, FileTypeDesc: string; BypassDialog: boolean): boolean;
var Res: TMessageButton;
    Stat: PDataItemSl;
    s: string;
    AListView: TGTKListView;
begin
  Result := False;
  try
    if not BypassDialog then begin
      Stat := Engine.GetFileInfoSL(APath);
      FRunFromVFS := TFRunFromVFS.Create(Self);
      FRunFromVFS.FileNameLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [StrToUTF8(APath)]);
      if FileTypeDesc = '' then FileTypeDesc := LANGHandleRunFromArchive_FileTypeDesc_Unknown;
      FRunFromVFS.FileTypeLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [FileTypeDesc]);
      if Assigned(Stat) then begin
        if (ConfSizeFormat < 5) or (Stat^.Size < 1024) then s := Format(' %s', [LANGHandleRunFromArchive_Bytes]);
        FRunFromVFS.SizeLabel2.Caption := Format('%s%s<span weight="ultrabold"> </span>', [FormatSize(Stat^.Size, 0), s]);
        if (ConfSizeFormat < 5) or (Stat^.Size < 1024) then s := Format(' %s', [LANGHandleRunFromArchive_Bytes]);
        if Stat^.PackedSize >= 0 then begin
          FRunFromVFS.PackedSizeLabel2.Caption := Format('%s%s<span weight="ultrabold"> </span>', [FormatSize(Stat^.PackedSize, 0), s]);
        end else begin
          FRunFromVFS.PackedSizeLabel2.Visible := False;
          FRunFromVFS.PackedSizeLabel.Visible := False;
        end;
        FRunFromVFS.DateLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [FormatDate(Stat^.ModifyTime, True, True)]);
        if (Command = '') and (not Stat^.IsExecutable) then begin
          FRunFromVFS.OpensWithLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [LANGHandleRunFromArchive_NotAssociated]);
          FRunFromVFS.ExecuteButton.Enabled := False;
          FRunFromVFS.ExecuteAllButton.Enabled := False;
        end else begin
          if Command = '' then Command := LANGHandleRunFromArchive_SelfExecutable;
          FRunFromVFS.OpensWithLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [Command]);
        end;
        FreeDataItem(Stat);
      end else begin
        FRunFromVFS.SizeLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', ['??']);
        FRunFromVFS.PackedSizeLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', ['??']);
        FRunFromVFS.DateLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', ['??']);
          FRunFromVFS.OpensWithLabel2.Caption := Format('%s<span weight="ultrabold"> </span>', [Command]);
      end;
      FRunFromVFS.FileNameLabel2.UseMarkup := True;
      FRunFromVFS.FileTypeLabel2.UseMarkup := True;
      FRunFromVFS.SizeLabel2.UseMarkup := True;
      FRunFromVFS.PackedSizeLabel2.UseMarkup := True;
      FRunFromVFS.DateLabel2.UseMarkup := True;
      FRunFromVFS.OpensWithLabel2.UseMarkup := True;

      Res := FRunFromVFS.Run;
      FRunFromVFS.Close;
      FRunFromVFS.Free;
      Application.ProcessMessages;
    end else Res := mbYes;
    if Res in [mbYes, mbNo] then begin
      Result := False;
      if Res = mbYes then DebugMsg(['(II) HandleRunFromArchive: Selected extract and execute single item'])
                     else DebugMsg(['(II) HandleRunFromArchive: Selected extract all and execute']);

      if LeftLastFocused then AListView := LeftListView
                         else AListView := RightListView;
      Engine.Path := ExtractFilePath(APath);
      //  Extract the files
      Result := ExtractFromArchive(APath, Engine, IncludeTrailingPathDelimiter(Engine.Path) + string(PDataItem(AListView.Selected.Data)^.FName), Res = mbNo);
    end;

  finally
    Application.ProcessMessages;
  end;
end;

function TFMain.ExtractFromArchive(var NewPath: string; Engine: TPanelEngine; const FilePath: string; ExtractAll: boolean): boolean;
var s: string;
    AWorkingThread: TWorkerThread;
    AFProgress: TFProgress;
    tmp: PChar;
    LocalEngine: TLocalTreeEngine;
    DataList: TList;
    err: integer;
begin
  Result := False;
  s := IncludeTrailingPathDelimiter(ConfTempPath) + 'tuxcmd-XXXXXX';
  tmp := strdup(PChar(s));
  tmp := mkdtemp(tmp);
  if tmp = nil then begin
    err := errno;
    DebugMsg(['(EE) ExtractFromArchive: Couldn''t create temporary directory: ', strerror(err)]);
    Application.MessageBox(PChar(Format(LANGHandleRunFromArchive_CouldntCreateTemporaryDirectory, [s, string(strerror(err))])), [mbOK], mbError, mbOK, mbOK);
    Result := False;
    Exit;
  end;
  DebugMsg(['(II) ExtractFromArchive: Using temporary directory: ', tmp]);
  UsedTempPaths.Add(string(tmp));

  if LeftLastFocused then DataList := LeftPanelData
                     else DataList := RightPanelData;

  LocalEngine := TLocalTreeEngine.Create;
  AFProgress := TFProgress.Create(Self);
  AWorkingThread := TWorkerThread.Create;
  try
    DebugMsg(['TFMain.ExtractFromArchive: Creating thread...']);
    AFProgress.Label1.Caption := LANGCopySC;
    AFProgress.SetNumBars(True);
    AFProgress.ProgressBar.Value := 0;
    AWorkingThread.ProgressForm := AFProgress;
    LocalEngine.SetPath(tmp);
    AWorkingThread.ExtractFromVFSMode := True;
    AWorkingThread.DestEngine := LocalEngine;
    AWorkingThread.SrcEngine := Engine;
    AWorkingThread.ExtractFromVFSAll := ExtractAll;
    AWorkingThread.LeftPanel := LeftLastFocused;
    AWorkingThread.DataList := DataList;
    AWorkingThread.WorkerProcedure := CopyFilesWorker;
    AWorkingThread.ParamBool3 := True;
    AWorkingThread.ParamBool4 := False;
    AWorkingThread.ParamBool5 := True;
    AWorkingThread.ParamString1 := string(tmp);
    AWorkingThread.ParamString2 := FilePath;
    AWorkingThread.ParamDataItem1 := nil;
    DebugMsg(['*** Copy: AWorkingThread.Resume']);
    AWorkingThread.Resume;
    DebugMsg(['*** Copy: AWorkingThread.Resumed.']);
    AFProgress.ParentForm := FMain;
    AFProgress.ShowModal;
    ProcessProgressThread(AWorkingThread, AFProgress);
    AFProgress.Close;
    Result := (not AWorkingThread.FCancelled) and (not AWorkingThread.ErrorHappened);
  finally
    DebugMsg(['TFMain.ExtractFromArchive: Freeing thread...']);
    LocalEngine.Free;
    AFProgress.Free;
    AWorkingThread.Free;
  end;

  DebugMsg(['(II) ExtractFromArchive: Old path = ', FilePath]);
  if Result then begin
    if not ExtractAll then NewPath := IncludeTrailingPathDelimiter(string(tmp)) + ExtractFileName(FilePath)
                      else NewPath := ExcludeTrailingPathDelimiter(string(tmp)) + FilePath;
    DebugMsg(['(II) ExtractFromArchive: New path = ', NewPath]);

    //  Test for read access to the new file
    if (not ExtractAll) and (access(PChar(NewPath), R_OK) <> 0) then begin
      Result := False;
      DebugMsg(['(EE) ExtractFromArchive: access test to the new file failed.']);
    end;
  end;

  libc_free(tmp);
  DebugMsg(['(II) ExtractFromArchive: Copy OK, Result = ', Result]);
end;

(********************************************************************************************************************************)
procedure TFMain.PasswordButtonClick(Sender: TObject);
var Engine: TPanelEngine;
    Password: PChar;
begin
  if Sender = LeftPasswordButton then Engine := LeftPanelEngine else
  if Sender = RightPasswordButton then Engine := RightPanelEngine
  else Exit;

  if not (Engine is TVFSEngine) then Exit;
  try
    Password := nil;
    if HandleVFSAskPasswordCallback(FWidget, PChar(LANGTheArchiveIsEncryptedAndRequiresPassword),
                                    nil, nil, PChar(TVFSEngine(Engine).Password),
                                    VFS_ASK_PASSWORD_NEED_PASSWORD or VFS_ASK_PASSWORD_ARCHIVE_MODE,
                                    nil, @Password, nil, nil, nil) then
      if Password <> nil then begin
        TVFSEngine(Engine).Password := string(Password);
        TVFSEngine(Engine).PasswordUsed := False;
        TVFSEngine(Engine).ResetPassword;
        g_free(Password);
      end;
  except
    on E: Exception do DebugMsg(['*** Exception raised in FMain.HandleSetPassword:  (', E.ClassName, '): ', E.Message]);
  end;
end;


end.

