{
   Double Commander
   -------------------------------------------------------------------------
   Find dialog, with searching in thread

   Copyright (C) 2006-2023 Alexander Koblov (alexx2000@mail.ru)
   Copyright (C) 2003-2004 Radek Cervinka (radek.cervinka@centrum.cz)

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

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

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

unit fFindDlg;

{$mode objfpc}{$H+}
{$include calling.inc}

interface

uses
  Graphics, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ComCtrls,
  ExtCtrls, Menus, EditBtn, Spin, Buttons, DateTimePicker, KASComboBox,
  KASButton, fAttributesEdit, uDsxModule, DsxPlugin, uFindThread, uFindFiles,
  uRegExprU, uSearchTemplate, fSearchPlugin, uFileView, types, DCStrUtils,
  ActnList, uOSForms, uShellContextMenu, uExceptions, uFileSystemFileSource,
  uFormCommands, uHotkeyManager, LCLVersion, uWcxModule, uFileSource;

{$IF DEFINED(LCLGTK2) or DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
  {$DEFINE FIX_DEFAULT}
{$ENDIF}

const
  HotkeysCategory = 'Find files';

type
  { TfrmFindDlg }
  TfrmFindDlg = class(TModalForm, IFormCommands)
    actIntelliFocus: TAction;
    actCancel: TAction;
    actClose: TAction;
    actEdit: TAction;
    actGoToFile: TAction;
    actFeedToListbox: TAction;
    actCancelClose: TAction;
    actPagePrev: TAction;
    actPageNext: TAction;
    actPageResults: TAction;
    actPageLoadSave: TAction;
    actPagePlugins: TAction;
    actPageAdvanced: TAction;
    actPageStandard: TAction;
    actView: TAction;
    actLastSearch: TAction;
    actNewSearch: TAction;
    actStart: TAction;
    actList: TActionList;
    Bevel2: TBevel;
    Bevel3: TBevel;
    btnAddAttribute: TButton;
    btnAttrsHelp: TButton;
    btnClose: TButton;
    btnGoToPath: TButton;
    btnNewSearch: TButton;
    btnLastSearch: TButton;
    btnSaveTemplate: TButton;
    btnSearchDelete: TButton;
    btnSearchLoad: TButton;
    btnSearchSave: TButton;
    btnSearchSaveWithStartingPath: TButton;
    btnStart: TButton;
    btnUseTemplate: TButton;
    btnStop: TButton;
    btnView: TButton;
    btnEdit: TButton;
    btnWorkWithFound: TButton;
    cbFindText: TCheckBox;
    cbNotContainingText: TCheckBox;
    cbDateFrom: TCheckBox;
    cbNotOlderThan: TCheckBox;
    cbFileSizeFrom: TCheckBox;
    cbDateTo: TCheckBox;
    cbFileSizeTo: TCheckBox;
    cbReplaceText: TCheckBox;
    cbTimeFrom: TCheckBox;
    cbTimeTo: TCheckBox;
    cbPartialNameSearch: TCheckBox;
    cbFollowSymLinks: TCheckBox;
    cbUsePlugin: TCheckBox;
    cbSelectedFiles: TCheckBox;
    cbTextRegExp: TCheckBox;
    cbFindInArchive: TCheckBox;
    cbOpenedTabs: TCheckBox;
    cbOfficeXML: TCheckBox;
    chkDuplicateContent: TCheckBox;
    chkDuplicateSize: TCheckBox;
    chkDuplicateHash: TCheckBox;
    chkDuplicateName: TCheckBox;
    chkDuplicates: TCheckBox;
    chkHex: TCheckBox;
    cmbExcludeDirectories: TComboBoxWithDelItems;
    cmbNotOlderThanUnit: TComboBox;
    cmbFileSizeUnit: TComboBox;
    cmbEncoding: TComboBox;
    cmbSearchDepth: TComboBox;
    cbRegExp: TCheckBox;
    cmbPlugin: TComboBox;
    cmbReplaceText: TComboBoxWithDelItems;
    cmbFindText: TComboBoxWithDelItems;
    cmbExcludeFiles: TComboBoxWithDelItems;
    edtAttrib: TEdit;
    cmbFindPathStart: TComboBoxWithDelItems;
    frmContentPlugins: TfrmSearchPlugin;
    gbDirectories: TGroupBox;
    gbFiles: TGroupBox;
    btnEncoding: TKASButton;
    lblAttributes: TLabel;
    lblExcludeDirectories: TLabel;
    lblCurrent: TLabel;
    lblExcludeFiles: TLabel;
    lblFound: TLabel;
    lblStatus: TLabel;
    lblTemplateHeader: TLabel;
    lbSearchTemplates: TListBox;
    lblSearchContents: TPanel;
    lblSearchDepth: TLabel;
    lblEncoding: TLabel;
    lsFoundedFiles: TListBox;
    CheksPanel: TPanel;
    miOpenInNewTab: TMenuItem;
    miShowInEditor: TMenuItem;
    miShowAllFound: TMenuItem;
    miRemoveFromLlist: TMenuItem;
    pnlDuplicates: TPanel;
    pnlDirectoriesDepth: TPanel;
    pnlLoadSaveBottomButtons: TPanel;
    pnlLoadSaveBottom: TPanel;
    pnlButtons: TPanel;
    pnlResultsBottomButtons: TPanel;
    pnlResults: TPanel;
    pnlStatus: TPanel;
    pnlResultsBottom: TPanel;
    seNotOlderThan: TSpinEdit;
    seFileSizeFrom: TSpinEdit;
    seFileSizeTo: TSpinEdit;
    pnlFindFile: TPanel;
    pgcSearch: TPageControl;
    btnChooseFolder: TSpeedButton;
    tsPlugins: TTabSheet;
    tsResults: TTabSheet;
    tsLoadSave: TTabSheet;
    tsStandard: TTabSheet;
    lblFindPathStart: TLabel;
    lblFindFileMask: TLabel;
    cmbFindFileMask: TComboBoxWithDelItems;
    gbFindData: TGroupBox;
    cbCaseSens: TCheckBox;
    tsAdvanced: TTabSheet;
    PopupMenuFind: TPopupMenu;
    miShowInViewer: TMenuItem;
    ZVDateFrom: TDateTimePicker;
    ZVDateTo: TDateTimePicker;
    ZVTimeFrom: TDateTimePicker;
    ZVTimeTo: TDateTimePicker;
    actFreeFromMem: TAction;
    actFreeFromMemAllOthers: TAction;
    actConfigFileSearchHotKeys: TAction;
    actNewSearchClearFilters: TAction;
    mmMainMenu: TMainMenu;
    miNewSearchClearFilters: TMenuItem;
    miConfigFileSearchHotKeys: TMenuItem;
    miOptions: TMenuItem;
    miAction: TMenuItem;
    miNewSearch: TMenuItem;
    miLastSearch: TMenuItem;
    miStart: TMenuItem;
    miCancel: TMenuItem;
    miFreeFromMem: TMenuItem;
    miFreeFromMemAllOthers: TMenuItem;
    miSeparator1: TMenuItem;
    miCancelClose: TMenuItem;
    miClose: TMenuItem;
    miViewTab: TMenuItem;
    miPageStandard: TMenuItem;
    miPageAdvanced: TMenuItem;
    miPagePlugins: TMenuItem;
    miPageLoadSave: TMenuItem;
    miPageResults: TMenuItem;
    miSeparator2: TMenuItem;
    miResult: TMenuItem;
    miView: TMenuItem;
    miEdit: TMenuItem;
    miFeedToListbox: TMenuItem;
    miGoToFile: TMenuItem;
    procedure actExecute(Sender: TObject);
    procedure btnAddAttributeClick(Sender: TObject);
    procedure btnAttrsHelpClick(Sender: TObject);
    procedure btnEncodingClick(Sender: TObject);
    procedure btnNewSearchKeyDown(Sender: TObject; var Key: word;
      {%H-}Shift: TShiftState);
    procedure btnSearchDeleteClick(Sender: TObject);
    procedure btnSearchLoadClick(Sender: TObject);
    procedure btnSearchSaveWithStartingPathClick(Sender: TObject);
    procedure btnSearchSaveClick(Sender: TObject);
    procedure cbCaseSensChange(Sender: TObject);
    procedure cbDateFromChange(Sender: TObject);
    procedure cbDateToChange(Sender: TObject);
    procedure cbFindInArchiveChange(Sender: TObject);
    procedure cbOfficeXMLChange(Sender: TObject);
    procedure cbOpenedTabsChange(Sender: TObject);
    procedure cbPartialNameSearchChange(Sender: TObject);
    procedure cbRegExpChange(Sender: TObject);
    procedure cbTextRegExpChange(Sender: TObject);
    procedure cbSelectedFilesChange(Sender: TObject);
    procedure chkDuplicateContentChange(Sender: TObject);
    procedure chkDuplicateHashChange(Sender: TObject);
    procedure chkDuplicatesChange(Sender: TObject);
    procedure chkDuplicateSizeChange(Sender: TObject);
    procedure chkHexChange(Sender: TObject);
    procedure cmbEncodingSelect(Sender: TObject);
    procedure cbFindTextChange(Sender: TObject);
    procedure cbUsePluginChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnSelDirClick(Sender: TObject);
    procedure cbFileSizeFromChange(Sender: TObject);
    procedure cbFileSizeToChange(Sender: TObject);
    procedure cbNotOlderThanChange(Sender: TObject);
    procedure cbReplaceTextChange(Sender: TObject);
    procedure cbTimeFromChange(Sender: TObject);
    procedure cbTimeToChange(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormDestroy(Sender: TObject);
{$IF DEFINED(FIX_DEFAULT)}
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
{$ENDIF}
    procedure frmFindDlgClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
    procedure frmFindDlgShow(Sender: TObject);
    procedure gbDirectoriesResize(Sender: TObject);
    procedure lbSearchTemplatesDblClick(Sender: TObject);
    procedure lbSearchTemplatesSelectionChange(Sender: TObject; {%H-}User: boolean);
    procedure lsFoundedFilesDblClick(Sender: TObject);
    procedure lsFoundedFilesKeyDown(Sender: TObject;
      var Key: word; Shift: TShiftState);
    procedure lsFoundedFilesMouseDown(Sender: TObject; Button: TMouseButton;
      {%H-}Shift: TShiftState; X, Y: integer);
    procedure lsFoundedFilesMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: integer);
    procedure lsFoundedFilesMouseWheelDown(Sender: TObject; Shift: TShiftState;
      {%H-}MousePos: TPoint; var Handled: boolean);
    procedure lsFoundedFilesMouseWheelUp(Sender: TObject; Shift: TShiftState;
      {%H-}MousePos: TPoint; var Handled: boolean);
    procedure miOpenInNewTabClick(Sender: TObject);
    procedure miRemoveFromLlistClick(Sender: TObject);
    procedure miShowAllFoundClick(Sender: TObject);
    procedure miShowInEditorClick(Sender: TObject);
    procedure miShowInViewerClick(Sender: TObject);
    procedure pgcSearchChange(Sender: TObject);
    procedure seFileSizeFromChange(Sender: TObject);
    procedure seFileSizeToChange(Sender: TObject);
    procedure seNotOlderThanChange(Sender: TObject);
    procedure tsLoadSaveShow(Sender: TObject);
    procedure tsStandardEnter(Sender: TObject);
    procedure ZVDateFromChange(Sender: TObject);
    procedure ZVDateToChange(Sender: TObject);
    procedure ZVTimeFromChange(Sender: TObject);
    procedure ZVTimeToChange(Sender: TObject);
    procedure PopupMenuFindPopup(Sender: TObject);
    function GetTextSearchOptions: UIntPtr;
    procedure CancelCloseAndFreeMem;
    procedure LoadHistory;
    procedure SaveHistory;
    procedure LoadPlugins;
  private
    FSelectedFiles: TStringList;
    FFindThread: TFindThread;
    FTimeSearch: string;
    DsxPlugins: TDSXModuleList;
    FSearchingActive: boolean;
    FFrmAttributesEdit: TfrmAttributesEdit;
    FLastTemplateName: string;
    FLastSearchTemplate: TSearchTemplate;
    FUpdateTimer: TTimer;
    FUpdating: boolean;
    FRButtonPanelSender: TObject; // last focused button on Right Panel (pnlButtons)
    FCommands: TFormCommands;
    FSearchWithDSXPluginInProgress: boolean;
    FSearchWithWDXPluginInProgress: boolean;
    FFreeOnClose: boolean;
    FAtLeastOneSearchWasDone: boolean;
    FFileSource: IFileSource;
    FWcxModule: TWcxModule;

    property Commands: TFormCommands read FCommands implements IFormCommands;

    procedure DisableControlsForTemplate;
    procedure StopSearch;
    procedure AfterSearchStopped;  //update button states after stop search(ThreadTerminate call this method)
    procedure AfterSearchFocus;     //set correct focus after search stopped

    procedure UpdateEncodings;
    function GetEncodings(AList: TCustomComboBox): String;
    procedure SetEncodings(const AEncodings: String; AList: TCustomComboBox);

    procedure FindInArchive(AFileView: TFileView);
    procedure FillFindOptions(out FindOptions: TSearchTemplateRec; SetStartPath: boolean);
    procedure FindOptionsToDSXSearchRec(const AFindOptions: TSearchTemplateRec;
                                        out SRec: TDsxSearchRecord);
    procedure FoundedStringCopyAdded(Sender: TObject);
    procedure FoundedStringCopyChanged(Sender: TObject);
    procedure LoadTemplate(const Template: TSearchTemplateRec);
    procedure LoadSelectedTemplate;
    procedure SaveTemplate(SaveStartingPath: boolean);
    procedure SelectTemplate(const ATemplateName: string);
    procedure UpdateTemplatesList;
    procedure OnUpdateTimer(Sender: TObject);
    procedure OnAddAttribute(Sender: TObject);
    function InvalidRegExpr: Boolean;
    procedure SetWindowCaption(AWindowCaptionStyle: byte);
    function ObjectType(Index: Integer): TCheckBoxState;
    function GetFileMask: String;
  public
    FoundedStringCopy: TStringList;
    class function Instance: TfrmFindDlg;
  public
    LastClickResultsPath: string;
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure ClearFilter(bClearSearchLocation: boolean = True);
    procedure ClearResults;
    procedure ThreadTerminate(Sender: TObject);
    procedure EnableControls(AEnabled: Boolean);
    procedure FocusOnResults(Sender: TObject); // if press VK_LEFT or VK_RIGHT when on any button on left panel  - focus on results and remember button in FRButtonPanelSender
  published
    procedure cm_IntelliFocus(const {%H-}Params: array of string);
    procedure cm_Start(const {%H-}Params: array of string);
    procedure cm_CancelClose(const {%H-}Params: array of string);
    procedure cm_Cancel(const {%H-}Params: array of string);
    procedure cm_Close(const {%H-}Params: array of string);
    procedure cm_NewSearch(const {%H-}Params: array of string);
    procedure cm_LastSearch(const {%H-}Params: array of string);
    procedure cm_View(const {%H-}Params: array of string);
    procedure cm_Edit(const {%H-}Params: array of string);
    procedure cm_GoToFile(const {%H-}Params: array of string);
    procedure cm_FeedToListbox(const {%H-}Params: array of string);
    procedure cm_PageNext(const Params: array of string);
    procedure cm_PagePrev(const Params: array of string);
    procedure cm_PageStandard(const {%H-}Params: array of string);
    procedure cm_PageAdvanced(const {%H-}Params: array of string);
    procedure cm_PagePlugins(const {%H-}Params: array of string);
    procedure cm_PageLoadSave(const {%H-}Params: array of string);
    procedure cm_PageResults(const {%H-}Params: array of string);
    procedure cm_NewSearchClearFilters(const {%H-}Params: array of string);
    procedure cm_FreeFromMem(const {%H-}Params: array of string);
    procedure cm_FreeFromMemAllOthers(const {%H-}Params: array of string);
    procedure cm_ConfigFileSearchHotKeys(const {%H-}Params: array of string);
  end;

{en
   Shows the find files dialog.
   Cannot store FileView reference as it might get destroyed while Find Dialog is running.
   We can store FileSource though, if needed in future (as it is reference counted).
   @param(FileView
          For which file view the find dialog is executed,
          to get file source, current path and a list of selected files.)
}

  { TListOffrmFindDlgInstance }
  TListOffrmFindDlgInstance = class(TList)
  private
    function GetfrmFindDlgInstance(Index: integer): TfrmFindDlg;
  public
    constructor Create;
    procedure Clear; override;
    function Add(AfrmFindDlg: TfrmFindDlg): integer;
    property frmFindDlgInstance[Index: integer]: TfrmFindDlg read GetfrmFindDlgInstance;
  end;

var
  // [ ListOffrmFindDlgInstance ]
  // This list will hold in memory pointers to our find dialog forms.
  ListOffrmFindDlgInstance: TListOffrmFindDlgInstance;
  frmFindDlgUsingPluginDSX: TfrmFindDlg = nil;
  frmFindDlgUsingPluginWDX: TfrmFindDlg = nil;

procedure ShowFindDlg(FileView: TFileView; const TemplateName: string; bCreateNewFindDlg: boolean = False);
function ShowDefineTemplateDlg(var TemplateName: string): boolean;
function ShowUseTemplateDlg(var Template: TSearchTemplate): boolean;

implementation

{$R *.lfm}

uses
  LCLProc, LCLType, LConvEncoding, StrUtils, HelpIntfs, fViewer, fMain,
  uLng, uGlobs, uShowForm, uDCUtils, uFileSourceUtil, uOfficeXML,
  uSearchResultFileSource, uFile, uFileProperty, uColumnsFileView,
  uFileViewNotebook, uKeyboard, uOSUtils, uArchiveFileSourceUtil,
  DCOSUtils, uRegExprA, uRegExprW, uDebug, uShowMsg, uConvEncoding,
  uColumns, uFileFunctions, uFileSorting, uWcxArchiveFileSource,
  DCConvertEncoding, WcxPlugin, fChooseEncoding, dmCommonData
{$IFDEF DARKWIN}
  , uDarkStyle
{$ENDIF}
  ;

const
  TimeUnitToComboIndex: array[TTimeUnit] of integer = (0, 1, 2, 3, 4, 5, 6);
  ComboIndexToTimeUnit: array[0..6] of TTimeUnit = (tuSecond, tuMinute, tuHour, tuDay, tuWeek, tuMonth, tuYear);
  FileSizeUnitToComboIndex: array[TFileSizeUnit] of integer = (0, 1, 2, 3, 4);
  ComboIndexToFileSizeUnit: array[0..4] of TFileSizeUnit = (suBytes, suKilo, suMega, suGiga, suTera);

  wcs_NewSearch = $01;
  wcs_StartSearch = $0A;
  wcs_EndSearch = $1B;

type
  { TStringListTemp }
  TStringListTemp = class(TStringList)
  public
    function AddObject(const S: string; AObject: TObject): integer; override;
  end;

var
  gSearchWithDSXPluginInProgress: boolean = False;
  gSearchWithWDXPluginInProgress: boolean = False;

{ TListOffrmFindDlgInstance.Create }
constructor TListOffrmFindDlgInstance.Create;
begin
  inherited Create;
end;

{ TListOffrmFindDlgInstance.Clear }
procedure TListOffrmFindDlgInstance.Clear;
var
  i: integer;
begin
  for i := pred(Count) downto 0 do
    if frmFindDlgInstance[i] <> nil then
      frmFindDlgInstance[i].Free;
  inherited Clear;
end;

{ TListOffrmFindDlgInstance.Add }
function TListOffrmFindDlgInstance.Add(AfrmFindDlg: TfrmFindDlg): integer;
begin
  Result := inherited Add(AfrmFindDlg);
end;

{ TListOffrmFindDlgInstance.GetfrmFindDlgInstance }
function TListOffrmFindDlgInstance.GetfrmFindDlgInstance(Index: integer): TfrmFindDlg;
begin
  Result := TfrmFindDlg(Items[Index]);
end;

procedure SAddFileProc({%H-}PlugNr: integer; FoundFile: PChar); dcpcall;
var
  s: string;
begin
  s := string(FoundFile);
  if s = '' then
  begin
    TfrmFindDlg.Instance.AfterSearchStopped;
    TfrmFindDlg.Instance.btnStart.Default := True;
  end
  else
  begin
    TfrmFindDlg.Instance.FoundedStringCopy.Add(s);
    Application.ProcessMessages;
  end;
end;

procedure SUpdateStatusProc({%H-}PlugNr: integer; CurrentFile: PChar; FilesScanned: integer); dcpcall;
var
  sCurrentFile: string;
begin
  sCurrentFile := string(CurrentFile);
  TfrmFindDlg.Instance.lblStatus.Caption := Format(rsFindScanned, [FilesScanned]) + TfrmFindDlg.Instance.FTimeSearch;
  if sCurrentFile = '' then
    TfrmFindDlg.Instance.lblCurrent.Caption := ''
  else
    TfrmFindDlg.Instance.lblCurrent.Caption := rsFindScanning + ': ' + sCurrentFile;
  Application.ProcessMessages;
end;

{ ShowFindDlg }
procedure ShowFindDlg(FileView: TFileView; const TemplateName: string; bCreateNewFindDlg: boolean = False);
var
  ASelectedFiles: TFiles = nil;
  I: integer;
  AfrmFindDlgInstance: TfrmFindDlg;
  bFirstFindDlg: boolean;
begin
  if not Assigned(FileView) then
    raise Exception.Create('ShowFindDlg: FileView=nil');

  bFirstFindDlg := (ListOffrmFindDlgInstance.Count = 0);

  // 1. We create a new form: if it's the first search we do OR if we've been instructed to do so (cm_AddNewSearch)
  if bFirstFindDlg or bCreateNewFindDlg then
  begin
    AfrmFindDlgInstance := TfrmFindDlg.Create(nil);
    ListOffrmFindDlgInstance.add(AfrmFindDlgInstance);
  end
  else
  begin
    AfrmFindDlgInstance := ListOffrmFindDlgInstance.frmFindDlgInstance[pred(ListOffrmFindDlgInstance.Count)];
  end;

  // 2. If we don't have a search in progress, then clear and set a few things.
  if not AfrmFindDlgInstance.FSearchingActive then
  begin
    with AfrmFindDlgInstance do
    begin
      // Prepare window for search files
      LoadHistory;
      LoadPlugins;
      ClearFilter;
      // SetWindowCaption(wcs_NewSearch);
      cmbFindPathStart.Text := FileView.CurrentPath;

      // Get paths of selected files, if any.
      FSelectedFiles.Clear;
      ASelectedFiles := FileView.CloneSelectedFiles;
      if Assigned(ASelectedFiles) then
        try
          if ASelectedFiles.Count > 0 then
          begin
            for I := 0 to ASelectedFiles.Count - 1 do
              FSelectedFiles.Add(ASelectedFiles[I].FullPath);
          end;
        finally
          FreeAndNil(ASelectedFiles);
        end;

      FindInArchive(FileView);

      if Length(TemplateName) > 0 then
      begin
        FUpdating := True;
        UpdateTemplatesList;
        SelectTemplate(TemplateName);
        LoadSelectedTemplate;
        FUpdating := False;
      end;

    end;
  end;

  AfrmFindDlgInstance.ShowOnTop;
end;

{ ShowDefineTemplateDlg }
function ShowDefineTemplateDlg(var TemplateName: string): boolean;
var
  AIndex: integer;
  AForm: TfrmFindDlg;
begin
  AForm := TfrmFindDlg.Create(nil);
  try
    with AForm do
    begin
      // Prepare window for define search template
      LoadHistory;
      LoadPlugins;
      Caption := rsFindDefineTemplate;
      DisableControlsForTemplate;
      btnSaveTemplate.Visible := True;
      btnSaveTemplate.Default := True;
      BorderIcons := [biSystemMenu, biMaximize];
      if Length(TemplateName) > 0 then
      begin
        UpdateTemplatesList;
        AIndex := lbSearchTemplates.Items.IndexOf(TemplateName);
        if AIndex >= 0 then
        begin
          lbSearchTemplates.ItemIndex := AIndex;
          AForm.LoadSelectedTemplate;
        end;
      end;
      Result := (ShowModal = mrOk);
      if Result and (lbSearchTemplates.Count > 0) then
      begin
        TemplateName := FLastTemplateName;
      end;
    end;
  finally
    AForm.Free;
  end;
end;

{ ShowUseTemplateDlg }
function ShowUseTemplateDlg(var Template: TSearchTemplate): boolean;
var
  AForm: TfrmFindDlg;
  SearchRec: TSearchTemplateRec;
begin
  AForm := TfrmFindDlg.Create(nil);
  try
    with AForm do
    begin
      // Prepare window for define search template
      LoadHistory;
      LoadPlugins;
      Caption := rsFindDefineTemplate;
      DisableControlsForTemplate;
      btnUseTemplate.Visible := True;
      btnUseTemplate.Default := True;
      BorderIcons := [biSystemMenu, biMaximize];
      if Assigned(Template) then
        AForm.LoadTemplate(Template.SearchRecord);
      Result := (ShowModal = mrOk);
      if Result then
      begin
        if not Assigned(Template) then
          Template := TSearchTemplate.Create;
        try
          Template.TemplateName := AForm.FLastTemplateName;
          AForm.FillFindOptions(SearchRec, False);
          Template.SearchRecord := SearchRec;
        except
          FreeAndNil(Template);
          raise;
        end;
      end;
    end;
  finally
    AForm.Free;
  end;
end;

{ TStringListTemp }

{ TStringListTemp.AddObject }
function TStringListTemp.AddObject(const S: string; AObject: TObject): integer;
begin
  Result := Count;
  InsertItem(Result, S, AObject);
end;

{ TfrmFindDlg }

{ TfrmFindDlg.FormCreate }
procedure TfrmFindDlg.FormCreate(Sender: TObject);
var
  I: integer;
  HMFindFiles: THMForm;
begin
  if not gShowMenuBarInFindFiles then FreeAndNil(mmMainMenu);
  DsxPlugins := TDSXModuleList.Create;
  FoundedStringCopy := TStringListTemp.Create;
  FoundedStringCopy.OwnsObjects := True;
  FFreeOnClose := False;
  FAtLeastOneSearchWasDone := False;
  FSearchWithDSXPluginInProgress := False;
  FSearchWithWDXPluginInProgress := False;

  // load language
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitSecond);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitMinute);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitHour);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitDay);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitWeek);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitMonth);
  cmbNotOlderThanUnit.Items.Add(rsTimeUnitYear);
  cmbFileSizeUnit.Items.Add(rsSizeUnitBytes);
  cmbFileSizeUnit.Items.Add(rsSizeUnitKBytes);
  cmbFileSizeUnit.Items.Add(rsSizeUnitMBytes);
  cmbFileSizeUnit.Items.Add(rsSizeUnitGBytes);
  cmbFileSizeUnit.Items.Add(rsSizeUnitTBytes);

  cbOfficeXML.Hint := StripHotkey(cbOfficeXML.Caption) + ' ' + OFFICE_FILTER;

  // fill search depth combobox
  cmbSearchDepth.Items.Add(rsFindDepthAll);
  cmbSearchDepth.Items.Add(rsFindDepthCurDir);
  for I := 1 to 100 do
    cmbSearchDepth.Items.Add(Format(rsFindDepth, [IntToStr(I)]));
  cmbSearchDepth.ItemIndex := 0;
  // fill encoding combobox
  cmbEncoding.Clear;
  GetSupportedEncodings(cmbEncoding.Items);
  I := cmbEncoding.Items.IndexOf('UTF-8BOM');
  if I >= 0 then cmbEncoding.Items.Delete(I);
  cmbEncoding.Items.Insert(0, 'Default');
  cmbEncoding.ItemIndex := 0;
  cmbEncoding.Items.Objects[0]:= TObject(PtrInt(True));

  // gray disabled fields
  cbUsePluginChange(Sender);
  cbFindTextChange(Sender);
  cbReplaceTextChange(Sender);
  cbNotOlderThanChange(Sender);
  cbFileSizeFromChange(Sender);
  cbFileSizeToChange(Sender);
  ZVDateFrom.DateTime := Now();
  ZVDateTo.DateTime := Now();
  ZVTimeFrom.DateTime := Now();
  ZVTimeTo.DateTime := Now();
  cbDateFrom.Checked := False;
  cbDateTo.Checked := False;
  cbTimeFrom.Checked := False;
  cbTimeTo.Checked := False;

  btnStart.Default := True;

  cmbNotOlderThanUnit.ItemIndex := 3; // Days
  cmbFileSizeUnit.ItemIndex := 1; // Kilobytes
  cbPartialNameSearch.Checked := gPartialNameSearch;
  FontOptionsToFont(gFonts[dcfSearchResults], lsFoundedFiles.Font);

  InitPropStorage(Self);

  HMFindFiles := HotMan.Register(Self, HotkeysCategory);
  HMFindFiles.RegisterActionList(actList);

  CloneMainAction(frmMain.actAddNewSearch, actList, miViewTab, -1);
  CloneMainAction(frmMain.actViewSearches, actList, miViewTab, -1);
  CloneMainAction(frmMain.actDeleteSearches, actList, miAction, -1);
  CloneMainAction(frmMain.actConfigSearches, actList, miOptions, 0);

{$IF DEFINED(FIX_DEFAULT)}
  if (ListOffrmFindDlgInstance.Count = 0) then
    Application.AddOnKeyDownBeforeHandler(@FormKeyDown);
{$ENDIF}
end;

{ TfrmFindDlg.cbUsePluginChange }
procedure TfrmFindDlg.cbUsePluginChange(Sender: TObject);
begin
  EnableControl(cmbPlugin, cbUsePlugin.Checked);

  if not FUpdating and cmbPlugin.Enabled and cmbPlugin.CanSetFocus and (Sender = cbUsePlugin) then
  begin
    cmbPlugin.SetFocus;
    cmbPlugin.SelectAll;
  end;
end;

{ TfrmFindDlg.cmbEncodingSelect }
procedure TfrmFindDlg.cmbEncodingSelect(Sender: TObject);
var
  Index, ItemIndex: Integer;
begin
  if (cmbEncoding.Tag = 1) then
  begin
    ItemIndex:= cmbEncoding.ItemIndex;
    for Index:= 0 to cmbEncoding.Items.Count - 1 do
    begin
      cmbEncoding.Items.Objects[Index]:= TObject(PtrInt((ItemIndex = Index)));
    end;
  end;
  UpdateEncodings;
end;

{ TfrmFindDlg.Create }
constructor TfrmFindDlg.Create(TheOwner: TComponent);
var
  C: TPortableNetworkGraphic;
begin
  FSelectedFiles := TStringList.Create;
  inherited Create(TheOwner);
  FUpdateTimer := TTimer.Create(Self);
  FUpdateTimer.Interval := 100;
  FUpdateTimer.Enabled := False;
  FUpdateTimer.OnTimer := @OnUpdateTimer;

  try
    C := TPortableNetworkGraphic.Create;
    C.LoadFromResourceName(hInstance, ResBtnSelDir);
    btnChooseFolder.Glyph.Assign(C);
  finally
    C.Free;
  end;

  dmComData.ilEditorImages.GetBitmap(44, btnEncoding.Glyph);

  FCommands := TFormCommands.Create(Self, actList);
end;

{ TfrmFindDlg.Destroy }
destructor TfrmFindDlg.Destroy;
begin
  inherited Destroy;
  FSelectedFiles.Free;
  FLastSearchTemplate.Free;
end;

{ TfrmFindDlg.DisableControlsForTemplate }
procedure TfrmFindDlg.DisableControlsForTemplate;
begin
  lblFindPathStart.Visible := False;
  cmbFindPathStart.Visible := False;
  cbFollowSymLinks.Visible := False;
  cbSelectedFiles.Visible := False;
  cbOpenedTabs.Visible := False;
  btnStart.Visible := False;
  btnStop.Visible := False;
  btnNewSearch.Visible := False;
  btnLastSearch.Visible := False;
  btnSearchSaveWithStartingPath.Visible := False;
  gbFindData.Visible := False;
  tsResults.TabVisible := False;
  actPageResults.Enabled := False;
  chkDuplicates.Visible:= False;
  pnlDuplicates.Visible:= False;
  if mmMainMenu <> nil then FreeAndNil(mmMainMenu);
end;

{ TfrmFindDlg.cbFindTextChange }
procedure TfrmFindDlg.cbFindTextChange(Sender: TObject);
begin
  EnableControl(chkHex, cbFindText.Checked);
  EnableControl(cmbFindText, cbFindText.Checked);
  EnableControl(cmbEncoding, cbFindText.Checked);
  EnableControl(cbCaseSens, cbFindText.Checked);
  EnableControl(cbReplaceText, cbFindText.Checked and not (cbFindInArchive.Checked or chkHex.Checked or cbOfficeXML.Checked));
  EnableControl(cbNotContainingText, cbFindText.Checked);
  EnableControl(cbTextRegExp, cbFindText.Checked);
  EnableControl(cbOfficeXML, cbFindText.Checked);
  lblEncoding.Enabled := cbFindText.Checked;
  cbReplaceText.Checked := False;
  UpdateEncodings;

  if not FUpdating and cmbFindText.Enabled and cmbFindText.CanSetFocus and (Sender = cbFindText) then
  begin
    cmbFindText.SetFocus;
    cmbFindText.SelectAll;
  end;
end;

{ TfrmFindDlg.ClearFilter }
procedure TfrmFindDlg.ClearFilter(bClearSearchLocation: boolean = True);
var
  FreezeTime: TDateTime;
begin
  FUpdating := True;

  FLastTemplateName := '';
  if bClearSearchLocation then
  begin
    cmbFindPathStart.Text := '';
    cmbExcludeDirectories.Text := '';
  end;

  if gInitiallyClearFileMask then
    cmbFindFileMask.Text := ''
  else if glsMaskHistory.Count > 0 then begin
    cmbFindFileMask.Text:= glsMaskHistory[0];
  end;

  // If we already search text then use last searched text
  if not gFirstTextSearch then
  begin
    if glsSearchHistory.Count > 0 then
      cmbFindText.Text := glsSearchHistory[0];
    end;

  cmbSearchDepth.ItemIndex := 0;
  cmbExcludeFiles.Text := '';
  cbPartialNameSearch.Checked := gPartialNameSearch;
  cbRegExp.Checked := False;

  // attributes
  edtAttrib.Text := '';

  // file date/time
  FreezeTime := Now;
  ZVDateFrom.DateTime := FreezeTime;
  ZVDateTo.DateTime := FreezeTime;
  ZVTimeFrom.DateTime := FreezeTime;
  ZVTimeTo.DateTime := FreezeTime;
  cbDateFrom.Checked := False;
  cbDateTo.Checked := False;
  cbTimeFrom.Checked := False;
  cbTimeTo.Checked := False;

  // not older then
  cbNotOlderThan.Checked := False;
  seNotOlderThan.Value := 1;
  cmbNotOlderThanUnit.ItemIndex := 3; // Days

  // file size
  cbFileSizeFrom.Checked := False;
  cbFileSizeTo.Checked := False;
  seFileSizeFrom.Value := 0;
  seFileSizeTo.Value := 10;
  cmbFileSizeUnit.ItemIndex := 1; // Kilobytes

  // find/replace text
  // do not clear search/replace text just clear checkbox
  chkHex.Checked := False;
  cbFindText.Checked := False;
  cbReplaceText.Checked := False;
  cbCaseSens.Checked := False;
  cbOfficeXML.Checked := False;
  cbNotContainingText.Checked := False;
  cmbEncoding.ItemIndex := 0;
  cmbEncoding.Tag := 1;
  cmbEncodingSelect(nil);

  // duplicates
  chkDuplicates.Checked:= False;

  // plugins
  cbUsePlugin.Checked:= False;
  frmContentPlugins.chkUsePlugins.Checked:= False;

  FUpdating := False;
end;

{ TfrmFindDlg.ClearResults }
procedure TfrmFindDlg.ClearResults;
begin
  lsFoundedFiles.Clear;
  lsFoundedFiles.Tag := 0;
  lsFoundedFiles.ScrollWidth := 0;
  FoundedStringCopy.Clear;
  EnableControls(False);
end;

{ TfrmFindDlg.btnSearchLoadClick }
procedure TfrmFindDlg.btnSearchLoadClick(Sender: TObject);
begin
  LoadSelectedTemplate;
end;

{ TfrmFindDlg.btnSearchSaveWithStartingPathClick }
procedure TfrmFindDlg.btnSearchSaveWithStartingPathClick(Sender: TObject);
begin
  SaveTemplate(True);
end;

{ TfrmFindDlg.btnSearchDeleteClick }
procedure TfrmFindDlg.btnSearchDeleteClick(Sender: TObject);
var
  OldIndex: integer;
begin
  OldIndex := lbSearchTemplates.ItemIndex;
  if OldIndex < 0 then Exit;
  gSearchTemplateList.DeleteTemplate(OldIndex);
  lbSearchTemplates.Items.Delete(OldIndex);
  if OldIndex < lbSearchTemplates.Count then
    lbSearchTemplates.ItemIndex := OldIndex
  else if lbSearchTemplates.Count > 0 then
    lbSearchTemplates.ItemIndex := lbSearchTemplates.Count - 1;
end;

{ TfrmFindDlg.btnAttrsHelpClick }
procedure TfrmFindDlg.btnAttrsHelpClick(Sender: TObject);
begin
  ShowHelpOrErrorForKeyword('', edtAttrib.HelpKeyword);
end;

procedure TfrmFindDlg.btnEncodingClick(Sender: TObject);
var
  I, Index, ACount: Integer;
begin
  if ChooseEncoding(Self, cmbEncoding.Items) then
  begin
    I:= 0;
    ACount:= 0;
    for Index:= 0 to cmbEncoding.Items.Count - 1 do
    begin
      if (PtrInt(cmbEncoding.Items.Objects[Index]) <> 0) then
      begin
        I:= Index;
        Inc(ACount);
      end;
    end;
    if ACount > 1 then
    begin
      I:= 0;
    end
    else if (ACount = 0) then
    begin
      I:= 0;
      ACount:= 1;
      cmbEncoding.Items.Objects[I]:= TObject(PtrInt(True));
    end;
    cmbEncoding.Tag:= ACount;
    cmbEncoding.ItemIndex:= I;
    cmbEncoding.Enabled:= (ACount <= 1);
    UpdateEncodings;
  end;
end;

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

{ TfrmFindDlg.btnAddAttributeClick }
procedure TfrmFindDlg.btnAddAttributeClick(Sender: TObject);
begin
  if not Assigned(FFrmAttributesEdit) then
  begin
    FFrmAttributesEdit := TfrmAttributesEdit.Create(Self);
    FFrmAttributesEdit.OnOk := @OnAddAttribute;
  end;
  FFrmAttributesEdit.Reset;
{$IFDEF DARKWIN}
  if g_darkModeEnabled then
    FFrmAttributesEdit.ShowModal
  else
{$ENDIF}
  if not (fsModal in FormState) then
    FFrmAttributesEdit.Show
  else
  begin
    FFrmAttributesEdit.ShowModal;
  end;
end;

{ TfrmFindDlg.btnSearchSaveClick }
procedure TfrmFindDlg.btnSearchSaveClick(Sender: TObject);
begin
  SaveTemplate(False);
end;

{ TfrmFindDlg.cbCaseSensChange }
procedure TfrmFindDlg.cbCaseSensChange(Sender: TObject);
begin
  if cbCaseSens.Checked then cbTextRegExp.Checked := False;
end;

{ TfrmFindDlg.cbDateFromChange }
procedure TfrmFindDlg.cbDateFromChange(Sender: TObject);
begin
  UpdateColor(ZVDateFrom, cbDateFrom.Checked);
end;

{ TfrmFindDlg.cbDateToChange }
procedure TfrmFindDlg.cbDateToChange(Sender: TObject);
begin
  UpdateColor(ZVDateTo, cbDateTo.Checked);
end;

{ TfrmFindDlg.cbFindInArchiveChange }
procedure TfrmFindDlg.cbFindInArchiveChange(Sender: TObject);
begin
  EnableControl(cbReplaceText, cbFindText.Checked and not cbFindInArchive.Checked);
  if cbReplaceText.Checked then cbReplaceText.Checked := cbReplaceText.Enabled;
  actView.Enabled := pnlResultsBottom.Enabled and (not cbFindInArchive.Checked);
  actEdit.Enabled := pnlResultsBottom.Enabled and (not cbFindInArchive.Checked);
  actFeedToListbox.Enabled := pnlResultsBottom.Enabled and (not cbFindInArchive.Checked);
  cbReplaceTextChange(cbReplaceText);
end;

procedure TfrmFindDlg.cbOfficeXMLChange(Sender: TObject);
begin
  if cbOfficeXML.Checked then
  begin
    chkHex.Checked:= False;
    cbReplaceText.Checked:= False;
  end;
  cbReplaceText.Enabled:= not (chkHex.Checked or cbOfficeXML.Checked);
end;

{ TfrmFindDlg.cbOpenedTabsChange }
procedure TfrmFindDlg.cbOpenedTabsChange(Sender: TObject);
begin
  cbSelectedFiles.Enabled := not cbOpenedTabs.Checked AND (FSelectedFiles.Count > 0);
  cbFollowSymLinks.Enabled := not cbOpenedTabs.Checked;
  cmbFindPathStart.Enabled := not cbOpenedTabs.Checked;
end;

{ TfrmFindDlg.cbPartialNameSearchChange }
procedure TfrmFindDlg.cbPartialNameSearchChange(Sender: TObject);
begin
  if cbPartialNameSearch.Checked then cbRegExp.Checked := False;
end;

{ TfrmFindDlg.cbRegExpChange }
procedure TfrmFindDlg.cbRegExpChange(Sender: TObject);
begin
  if cbRegExp.Checked then cbPartialNameSearch.Checked := False;
end;

{ TfrmFindDlg.cbTextRegExpChange }
procedure TfrmFindDlg.cbTextRegExpChange(Sender: TObject);
begin
  if cbTextRegExp.Checked then
  begin
    if cbCaseSens.Enabled then
    begin
      cbCaseSens.Tag := Integer(cbCaseSens.Checked);
    end;
  end
  else if not cbCaseSens.Enabled then
  begin
    cbCaseSens.Checked := Boolean(cbCaseSens.Tag);
  end;
  UpdateEncodings;
end;

{ TfrmFindDlg.cbSelectedFilesChange }
procedure TfrmFindDlg.cbSelectedFilesChange(Sender: TObject);
begin
  cmbFindPathStart.Enabled := not cbSelectedFiles.Checked;
end;

procedure TfrmFindDlg.chkDuplicateContentChange(Sender: TObject);
begin
  if chkDuplicateContent.Checked then
  begin
    chkDuplicateSize.Checked:= True;
    chkDuplicateHash.Checked:= False;
  end;
end;

procedure TfrmFindDlg.chkDuplicateHashChange(Sender: TObject);
begin
  if chkDuplicateHash.Checked then
  begin
    chkDuplicateSize.Checked:= True;
    chkDuplicateContent.Checked:= False;
  end;
end;

procedure TfrmFindDlg.chkDuplicatesChange(Sender: TObject);
begin
  pnlDuplicates.Enabled:= chkDuplicates.Checked;
  if chkDuplicates.Checked then
  begin
    if not (chkDuplicateName.Checked or chkDuplicateSize.Checked) then
      chkDuplicateName.Checked:= True;
  end;
end;

procedure TfrmFindDlg.chkDuplicateSizeChange(Sender: TObject);
begin
  if not chkDuplicateSize.Checked then
  begin
    chkDuplicateHash.Checked:= False;
    chkDuplicateContent.Checked:= False;
  end;
end;

procedure TfrmFindDlg.chkHexChange(Sender: TObject);
begin
  if chkHex.Checked then
  begin
    cmbEncoding.ItemIndex:= 0;
    if cbCaseSens.Enabled then
    begin
      cbCaseSens.Tag := Integer(cbCaseSens.Checked);
    end;
    cbOfficeXML.Checked:= False;
    cbReplaceText.Checked:= False;
  end
  else if not cbCaseSens.Enabled then
  begin
    cbCaseSens.Checked := Boolean(cbCaseSens.Tag);
  end;
  cbReplaceText.Enabled:= not (chkHex.Checked or cbOfficeXML.Checked);
  UpdateEncodings;
end;

{ TfrmFindDlg.btnSelDirClick }
procedure TfrmFindDlg.btnSelDirClick(Sender: TObject);
var
  S, AFolder: String;
begin
  S := cmbFindPathStart.Text;
  AFolder:= ExtractFilePath(ExcludeTrailingBackslash(S));
  if not mbDirectoryExists(AFolder) then AFolder := EmptyStr;
  if SelectDirectory(rsFindWhereBeg, AFolder, S, gShowSystemFiles) then
    cmbFindPathStart.Text := S;
end;

{ TfrmFindDlg.btnNewSearchKeyDown }
procedure TfrmFindDlg.btnNewSearchKeyDown(Sender: TObject; var Key: word;
  Shift: TShiftState);
begin
  if ((Key = VK_LEFT) or (Key = VK_RIGHT)) and (lsFoundedFiles.Count > 0) then FocusOnResults(Sender);
end;

{ TfrmFindDlg.FillFindOptions }
procedure TfrmFindDlg.FillFindOptions(out FindOptions: TSearchTemplateRec; SetStartPath: boolean);
begin
  with FindOptions do
  begin
    if SetStartPath then
      StartPath := cmbFindPathStart.Text
    else
      StartPath := '';
    ExcludeDirectories := cmbExcludeDirectories.Text;
    FilesMasks := GetFileMask;
    ExcludeFiles := cmbExcludeFiles.Text;
    SearchDepth := cmbSearchDepth.ItemIndex - 1;
    RegExp := cbRegExp.Checked;
    IsPartialNameSearch := cbPartialNameSearch.Checked;
    FollowSymLinks := cbFollowSymLinks.Checked;
    FindInArchives := cbFindInArchive.Checked;

    { File attributes }
    AttributesPattern := edtAttrib.Text;

    { Date/time }
    DateTimeFrom := 0;
    DateTimeTo := 0;
    IsDateFrom := False;
    IsDateTo := False;
    IsTimeFrom := False;
    IsTimeTo := False;
    if cbDateFrom.Checked then
    begin
      IsDateFrom := True;
      DateTimeFrom := ZVDateFrom.Date;
    end;
    if cbDateTo.Checked then
    begin
      IsDateTo := True;
      DateTimeTo := ZVDateTo.Date;
    end;
    if cbTimeFrom.Checked then
    begin
      IsTimeFrom := True;
      DateTimeFrom := DateTimeFrom + ZVTimeFrom.Time;
    end;
    if cbTimeTo.Checked then
    begin
      IsTimeTo := True;
      DateTimeTo := DateTimeTo + ZVTimeTo.Time;
    end;

    { Not Older Than }
    IsNotOlderThan := cbNotOlderThan.Checked;
    NotOlderThan := seNotOlderThan.Value;
    NotOlderThanUnit := ComboIndexToTimeUnit[cmbNotOlderThanUnit.ItemIndex];

    { File size }
    IsFileSizeFrom := cbFileSizeFrom.Checked;
    IsFileSizeTo := cbFileSizeTo.Checked;
    FileSizeFrom := seFileSizeFrom.Value;
    FileSizeTo := seFileSizeTo.Value;
    FileSizeUnit := ComboIndexToFileSizeUnit[cmbFileSizeUnit.ItemIndex];

    { Find/replace text }
    IsFindText := cbFindText.Checked;
    FindText := cmbFindText.Text;
    IsReplaceText := cbReplaceText.Checked;
    ReplaceText := cmbReplaceText.Text;
    HexValue := chkHex.Checked;
    CaseSensitive := cbCaseSens.Checked;
    NotContainingText := cbNotContainingText.Checked;
    TextRegExp := cbTextRegExp.Checked;
    TextEncoding := GetEncodings(cmbEncoding);
    OfficeXML := cbOfficeXML.Checked;
    { Duplicates }
    Duplicates:= chkDuplicates.Checked;
    DuplicateName:= chkDuplicateName.Checked;
    DuplicateSize:= chkDuplicateSize.Checked;
    DuplicateHash:= chkDuplicateHash.Checked;
    DuplicateContent:= chkDuplicateContent.Checked;
    { Plugins }
    if not cbUsePlugin.Checked then
      SearchPlugin := EmptyStr
    else begin
      SearchPlugin := cmbPlugin.Text;
    end;
    frmContentPlugins.Save(FindOptions);
  end;
end;

{ TfrmFindDlg.FindOptionsToDSXSearchRec }
procedure TfrmFindDlg.FindOptionsToDSXSearchRec(
  const AFindOptions: TSearchTemplateRec;
  out SRec: TDsxSearchRecord);
begin
  with AFindOptions do
  begin
    FillByte(SRec{%H-}, SizeOf(SRec), 0);

    SRec.StartPath := Copy(StartPath, 1, SizeOf(SRec.StartPath));

    if IsPartialNameSearch then
      SRec.FileMask := '*' + Copy(FilesMasks, 1, SizeOf(SRec.FileMask) - 2) + '*'
    else
      SRec.FileMask := Copy(FilesMasks, 1, SizeOf(SRec.FileMask));

    SRec.Attributes := faAnyFile;  // AttrStrToFileAttr?
    SRec.AttribStr := Copy(AttributesPattern, 1, SizeOf(SRec.AttribStr));

    SRec.CaseSensitive := CaseSensitive;
    {Date search}
    SRec.IsDateFrom := IsDateFrom;
    SRec.IsDateTo := IsDateTo;
    SRec.DateTimeFrom := DateTimeFrom;
    SRec.DateTimeTo := DateTimeTo;
    {Time search}
    SRec.IsTimeFrom := IsTimeFrom;
    SRec.IsTimeTo := IsTimeTo;
    (* File size search *)
    SRec.IsFileSizeFrom := IsFileSizeFrom;
    SRec.IsFileSizeTo := IsFileSizeTo;
    SRec.FileSizeFrom := FileSizeFrom;
    SRec.FileSizeTo := FileSizeTo;
    (* Find text *)
    SRec.NotContainingText := NotContainingText;
    SRec.IsFindText := IsFindText;
    SRec.FindText := Copy(FindText, 1, SizeOf(SRec.FindText));
    (* Replace text *)
    SRec.IsReplaceText := IsReplaceText;
    SRec.ReplaceText := Copy(ReplaceText, 1, SizeOf(SRec.ReplaceText));
  end;
end;

{ TfrmFindDlg.StopSearch }
procedure TfrmFindDlg.StopSearch;
begin
  if FSearchingActive then
  begin
    if (cbUsePlugin.Checked) and (cmbPlugin.ItemIndex <> -1) then
    begin
      if FSearchWithDSXPluginInProgress then
      begin
        DSXPlugins.GetDSXModule(cmbPlugin.ItemIndex).CallStopSearch;
        DSXPlugins.GetDSXModule(cmbPlugin.ItemIndex).CallFinalize;
      end;
      AfterSearchStopped;
      AfterSearchFocus;
    end;

    if Assigned(FFindThread) then
    begin
      FFindThread.Terminate;
      FFindThread := nil;
    end;
  end;
end;

{ TfrmFindDlg.Instance }
class function TfrmFindDlg.Instance: TfrmFindDlg;
begin
  Result:=frmFindDlgUsingPluginDSX;
end;

{ TfrmFindDlg.lbSearchTemplatesDblClick }
procedure TfrmFindDlg.lbSearchTemplatesDblClick(Sender: TObject);
begin
  LoadSelectedTemplate;
end;

{ TfrmFindDlg.AfterSearchStopped }
procedure TfrmFindDlg.AfterSearchStopped;
begin
  actCancel.Enabled := False;
  actStart.Enabled := True;;
  actClose.Enabled := True;
  actNewSearch.Enabled := True;
  actNewSearchClearFilters.Enabled := True;
  actLastSearch.Enabled := True;
  FSearchingActive := False;
  if FSearchWithDSXPluginInProgress then
  begin
    FSearchWithDSXPluginInProgress := False;
    gSearchWithDSXPluginInProgress := False;
  end;
  if FSearchWithWDXPluginInProgress then
  begin
    FSearchWithWDXPluginInProgress := False;
    gSearchWithWDXPluginInProgress := False;
  end;
end;

{ TfrmFindDlg.AfterSearchFocus }
procedure TfrmFindDlg.AfterSearchFocus;
var
  LastButton: TButton;
begin
  if Assigned(Self) and Visible then
  begin
    if FRButtonPanelSender <> nil then  // if user press a keys while search - keep focus on it
    begin
      LastButton := (FRButtonPanelSender as TButton);
      if LastButton.Enabled then LastButton.SetFocus else btnNewSearch.SetFocus;
    end
    else
    begin                          // if user don't press anything - focus on results
      if (pgcSearch.ActivePage = tsResults) and (lsFoundedFiles.Count > 0) then
      begin
        btnGoToPath.SetFocus;
        lsFoundedFiles.SetFocus;
        if lsFoundedFiles.ItemIndex<0 then lsFoundedFiles.ItemIndex:=0;
        lsFoundedFiles.Selected[lsFoundedFiles.ItemIndex] := True;
      end
      else
      begin
        if actNewSearch.Enabled then
          btnNewSearch.SetFocus
        else
          btnStart.SetFocus;
      end;
    end;
  end;
end;

procedure TfrmFindDlg.UpdateEncodings;
var
  Index: Integer;
  Encoding: String;
  SupportedEncoding: Boolean;
begin
  SupportedEncoding:= True;

  for Index:= 0 to cmbEncoding.Items.Count - 1 do
  begin
    if (PtrInt(cmbEncoding.Items.Objects[Index]) <> 0) then
    begin
      Encoding:= cmbEncoding.Items[Index];
      SupportedEncoding:= SingleByteEncoding(Encoding);
      if (not SupportedEncoding) and TRegExprU.AvailableNew then
      begin
        Encoding := NormalizeEncoding(Encoding);
        if Encoding = EncodingDefault then Encoding := GetDefaultTextEncoding;
        SupportedEncoding := Encoding = EncodingUTF8;
      end;
      if not SupportedEncoding then Break;
    end;
  end;

  btnEncoding.Visible:= not cbReplaceText.Checked;
  btnEncoding.Enabled:= cbFindText.Checked and (not chkHex.Checked);
  cmbEncoding.Enabled:= btnEncoding.Enabled and (cmbEncoding.Tag < 2);

  cbTextRegExp.Enabled := cbFindText.Checked and SupportedEncoding and (not chkHex.Checked);
  if not cbTextRegExp.Enabled then cbTextRegExp.Checked := False;

  cbCaseSens.Enabled:= cbFindText.Checked and (not cbReplaceText.Checked) and (not chkHex.Checked) and (not cbTextRegExp.Checked);
  if cbFindText.Checked and (not cbCaseSens.Enabled) then cbCaseSens.Checked := not cbTextRegExp.Checked;
end;

function TfrmFindDlg.GetEncodings(AList: TCustomComboBox): String;
var
  Index: Integer;
begin
  Result:= EmptyStr;

  for Index:= 0 to AList.Items.Count - 1 do
  begin
    if (PtrInt(AList.Items.Objects[Index]) <> 0) then
    begin
      Result+= AList.Items[Index] + '|';
    end;
  end;

  if Length(Result) = 0 then Result:= AList.Text;
end;

procedure TfrmFindDlg.SetEncodings(const AEncodings: String;
  AList: TCustomComboBox);
var
  S: TStringArray;
  I, Index, ACount: Integer;
begin
  ACount:= 0;
  S:= SplitString(AEncodings, '|');

  for Index:= 0 to High(S) do
  begin
    I:= AList.Items.IndexOf(S[Index]);
    if (I >= 0) then
    begin
      Inc(ACount);
      AList.Items.Objects[I]:= TObject(PtrInt(True));
    end;
  end;
  AList.Tag:= ACount;
  if ACount = 1 then AList.ItemIndex:= I;
end;

procedure TfrmFindDlg.FindInArchive(AFileView: TFileView);
var
  AEnabled: Boolean;
  AFileSource: IWcxArchiveFileSource;
begin
  FFileSource:= aFileView.FileSource;
  AEnabled:= FFileSource.IsClass(TWcxArchiveFileSource);

  cbOpenedTabs.Visible:= not AEnabled;
  cbSelectedFiles.Visible:= not AEnabled;
  cbFindInArchive.Enabled:= not AEnabled;
  cbReplaceText.Enabled:= (not AEnabled) and (cbFindText.Checked);
  cmbFindPathStart.Enabled:= not AEnabled;
  btnChooseFolder.Enabled:= not AEnabled;
  chkDuplicates.Enabled:= not AEnabled;
  cmbSearchDepth.Enabled:=  not AEnabled;
  tsPlugins.TabVisible:= not AEnabled;
  actPagePlugins.Enabled:= not AEnabled;
  cbFollowSymLinks.Enabled:= not AEnabled;

  if not AEnabled then
    FWcxModule:= nil
  else begin
    AFileSource:= (FFileSource as IWcxArchiveFileSource);
    FWcxModule:= AFileSource.WcxModule;
  end;

  cbFindText.Enabled:= (AEnabled = False) or (AFileSource.PluginCapabilities and PK_CAPS_SEARCHTEXT <> 0);

  if AEnabled then
  begin
    cmbSearchDepth.ItemIndex:= 0;
    cbFindInArchive.Checked:= True;
    cbReplaceText.Checked:= False;
    chkDuplicates.Checked:= False;
    cbOpenedTabs.Checked:= False;
    cbSelectedFiles.Checked:= False;
    cbFollowSymLinks.Checked:= False;
    cmbFindPathStart.Text:= aFileView.CurrentAddress;
  end;
end;

procedure TfrmFindDlg.FoundedStringCopyAdded(Sender: TObject);
begin
  if FoundedStringCopy.Count > 0 then
  begin
    EnableControls(True);
    FoundedStringCopyChanged(Sender);
    FoundedStringCopy.OnChange:= @FoundedStringCopyChanged;
  end;
end;

{ TfrmFindDlg.FoundedStringCopyChanged }
procedure TfrmFindDlg.FoundedStringCopyChanged(Sender: TObject);
var
  sText: string;
  iTemp: integer;
begin
  if FoundedStringCopy.Count > 0 then
  begin
    iTemp := FoundedStringCopy.Count - 1;
    Sender := FoundedStringCopy.Objects[iTemp];
    sText := FoundedStringCopy[iTemp];
    iTemp := Length(sText);
    if iTemp > lsFoundedFiles.Tag then
    begin
      lsFoundedFiles.Tag := iTemp;
      iTemp := lsFoundedFiles.Canvas.TextWidth(sText);
      if iTemp > lsFoundedFiles.ScrollWidth then
        lsFoundedFiles.ScrollWidth := iTemp + 32;
    end;
    lsFoundedFiles.Items.AddObject(sText, Sender);
{$IF DEFINED(LCLQT) or DEFINED(LCLQT5) or DEFINED(LCLQT6)}
    Application.ProcessMessages;
{$ENDIF}
  end;
end;

{ TfrmFindDlg.cbFileSizeFromChange }
procedure TfrmFindDlg.cbFileSizeFromChange(Sender: TObject);
begin
  UpdateColor(seFileSizeFrom, cbFileSizeFrom.Checked);
  EnableControl(cmbFileSizeUnit, cbFileSizeFrom.Checked or cbFileSizeTo.Checked);
end;

{ TfrmFindDlg.cbFileSizeToChange }
procedure TfrmFindDlg.cbFileSizeToChange(Sender: TObject);
begin
  UpdateColor(seFileSizeTo, cbFileSizeTo.Checked);
  EnableControl(cmbFileSizeUnit, cbFileSizeFrom.Checked or cbFileSizeTo.Checked);
end;

{ TfrmFindDlg.cbNotOlderThanChange }
procedure TfrmFindDlg.cbNotOlderThanChange(Sender: TObject);
begin
  UpdateColor(seNotOlderThan, cbNotOlderThan.Checked);
  EnableControl(cmbNotOlderThanUnit, cbNotOlderThan.Checked);
end;

{ TfrmFindDlg.cbReplaceTextChange }
procedure TfrmFindDlg.cbReplaceTextChange(Sender: TObject);
var
  Index: Integer;
begin
  EnableControl(cmbReplaceText, cbReplaceText.Checked and cbFindText.Checked);
  cbNotContainingText.Checked := False;
  cbNotContainingText.Enabled := (not cbReplaceText.Checked and cbFindText.Checked);

  if cmbReplaceText.Enabled and (cmbEncoding.Tag > 1) then
  begin
    for Index:= cmbEncoding.Items.Count - 1 downto 0 do
    begin
      if (PtrInt(cmbEncoding.Items.Objects[Index]) <> 0) then
      begin
        if cmbEncoding.Tag = 1 then
        begin
          cmbEncoding.ItemIndex:= Index;
          Break;
        end;
        cmbEncoding.Tag:= cmbEncoding.Tag - 1;
        cmbEncoding.Items.Objects[Index]:= nil;
      end;
    end;
  end;

  UpdateEncodings;

  if not FUpdating and cmbReplaceText.Enabled and cmbReplaceText.CanSetFocus then
  begin
    cmbReplaceText.SetFocus;
    cmbReplaceText.SelectAll;
  end;
end;

{ TfrmFindDlg.cbTimeFromChange }
procedure TfrmFindDlg.cbTimeFromChange(Sender: TObject);
begin
  UpdateColor(ZVTimeFrom, cbTimeFrom.Checked);
end;

{ TfrmFindDlg.cbTimeToChange }
procedure TfrmFindDlg.cbTimeToChange(Sender: TObject);
begin
  UpdateColor(ZVTimeTo, cbTimeTo.Checked);
end;

{ TfrmFindDlg.ThreadTerminate }
procedure TfrmFindDlg.ThreadTerminate(Sender: TObject);
begin
  FFindThread := TFindThread(Sender);
  if FFindThread.TimeOfScan <> 0 then FTimeSearch := ' , ' + rsFindTimeOfScan + formatdatetime('hh:nn:ss.zzz', FFindThread.TimeOfScan);
  FUpdateTimer.OnTimer(FUpdateTimer);
  FUpdateTimer.Enabled := False;
  FFindThread := nil;
  SetWindowCaption(wcs_EndSearch);
  AfterSearchStopped;
  AfterSearchFocus;
end;

procedure TfrmFindDlg.EnableControls(AEnabled: Boolean);
begin
  actView.Enabled:= AEnabled;
  actEdit.Enabled:= AEnabled;
  actGoToFile.Enabled:= AEnabled;
  actFeedToListbox.Enabled:= AEnabled;
  pnlResultsBottom.Enabled:= AEnabled;
end;

{ TfrmFindDlg.FocusOnResults }
procedure TfrmFindDlg.FocusOnResults(Sender: TObject);
begin
  FRButtonPanelSender := Sender;

  if pgcSearch.ActivePage = tsResults then
  begin
    btnStart.Default := False;
    if lsFoundedFiles.SelCount = 0 then lsFoundedFiles.ItemIndex := 0;
    lsFoundedFiles.SetFocus;
    lsFoundedFiles.Selected[lsFoundedFiles.ItemIndex] := True;

  end;
end;

{ TfrmFindDlg.cm_IntelliFocus }
procedure TfrmFindDlg.cm_IntelliFocus(const Params: array of string);
begin
  if FFindThread <> nil then
  begin
    FFindThread.OnTerminate := nil;
    FFindThread.Terminate;
    FUpdateTimer.OnTimer(FUpdateTimer);
    FUpdateTimer.Enabled := False;
    FFindThread := nil;
  end;

  AfterSearchStopped;

  btnStart.Default := True;

  if cmbFindText.Focused then // if F7 on already focused textSearch field- disable text search and set focun on file mask
  begin
    cbFindText.Checked := False;
    cmbFindFileMask.SetFocus;
    cmbFindFileMask.SelectAll;
    exit;
  end
  else
  begin
    pgcSearch.PageIndex := 0;
    cbFindText.Checked := True;
    cmbFindText.SetFocus;
    cmbFindText.SelectAll;
  end;
end;

{ TfrmFindDlg.cm_Start }
procedure TfrmFindDlg.cm_Start(const Params: array of string);
var
  sPath: String;
  sr: TDsxSearchRecord;
  SearchTemplate, TmpTemplate: TSearchTemplateRec;
  PassedSelectedFiles: TStringList = nil;
begin
  cm_Cancel([]);
  Self.Repaint;
  Application.ProcessMessages;

  if cbFindInArchive.Enabled then
  begin
    if (cmbFindPathStart.Text = '') then begin
      cmbFindPathStart.Text:= mbGetCurrentDir;
    end;
    for sPath in SplitPath(cmbFindPathStart.Text) do
    begin
      if not mbDirectoryExists(sPath) then
      begin
        ShowMessage(Format(rsFindDirNoEx, [sPath]));
        Exit;
      end;
    end;
  end;

  if (cbFindText.Checked and chkHex.Checked) then
  try
    HexToBin(cmbFindText.Text);
  except
    on E: EConvertError do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0, mbOK);
      Exit;
    end;
  end;

  SaveHistory;
  FAtLeastOneSearchWasDone := True;

  if cbSelectedFiles.Checked and (FSelectedFiles.Count = 0) then
  begin
    ShowMessage(rsMsgNoFilesSelected);
    cbSelectedFiles.Checked := False;
    Exit;
  end;

  if not (chkDuplicateName.Checked or chkDuplicateSize.Checked) then
    chkDuplicates.Checked:= False;

  // Show search results page
  pgcSearch.ActivePage := tsResults;

  if lsFoundedFiles.CanSetFocus then
    lsFoundedFiles.SetFocus;

  ClearResults;
  miShowAllFound.Enabled := False;

  FSearchingActive := True;
  actCancel.Enabled := True;

  btnStop.Default := True;

  actStart.Enabled := False;
  actClose.Enabled := False;
  actNewSearch.Enabled := False;
  actNewSearchClearFilters.Enabled := False;
  actLastSearch.Enabled := False;

  try
    if (frmContentPlugins.chkUsePlugins.Checked) and (gSearchWithWDXPluginInProgress) then
      raise EConvertError.Create(rsSearchWithWDXPluginInProgress);

    FillFindOptions(SearchTemplate, True);
    if frmContentPlugins.chkUsePlugins.Checked then
    begin
      gSearchWithWDXPluginInProgress := True;
      FSearchWithWDXPluginInProgress := True;
      frmFindDlgUsingPluginWDX := Self;
    end;

    if not Assigned(FLastSearchTemplate) then
      FLastSearchTemplate := TSearchTemplate.Create;
    TmpTemplate := SearchTemplate;
    TmpTemplate.StartPath := ''; // Don't remember starting path.
    FLastSearchTemplate.SearchRecord := TmpTemplate;

    FoundedStringCopy.OnChange:= @FoundedStringCopyAdded;

    if (cbUsePlugin.Checked) and (cmbPlugin.ItemIndex <> -1) then
    begin
      if not gSearchWithDSXPluginInProgress then
      begin
        gSearchWithDSXPluginInProgress := True;
        FSearchWithDSXPluginInProgress := True;
        frmFindDlgUsingPluginDSX := Self;
        if DSXPlugins.LoadModule(cmbPlugin.ItemIndex) then
        begin
          FindOptionsToDSXSearchRec(SearchTemplate, sr);
          DSXPlugins.GetDSXModule(cmbPlugin.ItemIndex).CallInit(@SAddFileProc, @SUpdateStatusProc);
          DSXPlugins.GetDSXModule(cmbPlugin.ItemIndex).CallStartSearch(sr);
        end
        else
          StopSearch;
      end
      else
      begin
        MsgError(rsSearchWithDSXPluginInProgress);
        StopSearch;
      end;
    end
    else
    begin
      if cbSelectedFiles.Checked then PassedSelectedFiles := FSelectedFiles;

      if cbOpenedTabs.Checked then
      begin
        frmMain.GetListOpenedPaths(FSelectedFiles);
        PassedSelectedFiles := FSelectedFiles;
      end;

      FFindThread := TFindThread.Create(SearchTemplate, PassedSelectedFiles);
      with FFindThread do
      begin
        Archive := FWcxModule;
        Items := FoundedStringCopy;
        OnTerminate := @ThreadTerminate; // will update the buttons after search is finished
      end;

      SetWindowCaption(wcs_StartSearch);

      FTimeSearch := '';
      FFindThread.Start;
      FUpdateTimer.Enabled := True;
      FUpdateTimer.OnTimer(FUpdateTimer);

      FRButtonPanelSender := nil;
    end;
  except
    on E: Exception do
    begin
      if (E is EConvertError) then
        msgError(E.Message)
      else
        raise;
      StopSearch;
      AfterSearchStopped;
      AfterSearchFocus;
    end;
  end;
end; //cm_Start

{ TfrmFindDlg.cm_CancelClose }
procedure TfrmFindDlg.cm_CancelClose(const Params: array of string);
begin
  if FSearchingActive then
    StopSearch
  else
    Close;
end;

{ TfrmFindDlg.cm_Cancel }
procedure TfrmFindDlg.cm_Cancel(const Params: array of string);
begin
  StopSearch;
  AfterSearchStopped;
  AfterSearchFocus;
end;

{ TfrmFindDlg.cm_NewSearch }
procedure TfrmFindDlg.cm_NewSearch(const Params: array of string);
var
  Param: string;
  sActionWithFilters: string = '';
begin
  StopSearch;

  if length(Params) = 0 then
  begin
    case gNewSearchClearFiltersAction of
      fonsClear: sActionWithFilters := 'clear';
      fonsPrompt: if msgYesNo(rsClearFiltersOrNot) then sActionWithFilters := 'clear';
    end;
  end;
  for Param in Params do
    GetParamValue(Param, 'filters', sActionWithFilters);
  if sActionWithFilters = 'clear' then ClearFilter(False);

  pgcSearch.PageIndex := 0;
  ClearResults;
  miShowAllFound.Enabled := False;
  lblStatus.Caption := EmptyStr;
  lblCurrent.Caption := EmptyStr;
  lblFound.Caption := EmptyStr;
  SetWindowCaption(wcs_NewSearch);
  if pgcSearch.ActivePage = tsStandard then cmbFindFileMask.SetFocus;
end;

{ TfrmFindDlg.cm_LastSearch }
procedure TfrmFindDlg.cm_LastSearch(const Params: array of string);
begin
  if Assigned(FLastSearchTemplate) then
  begin
    LoadTemplate(FLastSearchTemplate.SearchRecord);
    pgcSearch.ActivePage := tsStandard;
    cmbFindFileMask.SetFocus;
  end;
end;

{ TfrmFindDlg.cm_View }
procedure TfrmFindDlg.cm_View(const Params: array of string);
begin
  if pgcSearch.ActivePage = tsResults then
    if lsFoundedFiles.ItemIndex <> -1 then
    begin
      if (ObjectType(lsFoundedFiles.ItemIndex) = cbChecked) then
        msgError(rsMsgErrNotSupported)
      else
        ShowViewerByGlob(lsFoundedFiles.Items[lsFoundedFiles.ItemIndex]);
    end;
end;

{ TfrmFindDlg.cm_Edit }
procedure TfrmFindDlg.cm_Edit(const Params: array of string);
var
  FileName: String;
begin
  if pgcSearch.ActivePage = tsResults then
    if lsFoundedFiles.ItemIndex <> -1 then
    begin
      if (ObjectType(lsFoundedFiles.ItemIndex) = cbChecked) then
        msgError(rsMsgErrNotSupported)
      else begin
        FileName:= lsFoundedFiles.Items[lsFoundedFiles.ItemIndex];
        if mbFileExists(FileName) then
          ShowEditorByGlob(FileName)
        else
          msgError(Format(rsMsgFileNotFound, [FileName]));
      end;
    end;
end;

{ TfrmFindDlg.cm_GoToFile }
procedure TfrmFindDlg.cm_GoToFile(const Params: array of string);
var
  AFile: TFile = nil;
  TargetFile: string;
  ArchiveFile: string;
  FileSource: IFileSource;
begin
  if lsFoundedFiles.ItemIndex <> -1 then
    try
      StopSearch;
      TargetFile := lsFoundedFiles.Items[lsFoundedFiles.ItemIndex];
      if (ObjectType(lsFoundedFiles.ItemIndex) = cbChecked) then
      begin
        ArchiveFile := ExtractWord(1, TargetFile, [ReversePathDelim]);
        TargetFile := PathDelim + ExtractWord(2, TargetFile, [ReversePathDelim]);
        AFile := TFileSystemFileSource.CreateFileFromFile(ArchiveFile);
        try
          FileSource:= GetArchiveFileSource(TFileSystemFileSource.GetFileSource, AFile, EmptyStr, False, False);
        finally
          AFile.Free;
        end;
        if Assigned(FileSource) then
        begin
          frmMain.ActiveFrame.AddFileSource(FileSource, ExtractFilePath(TargetFile));
          frmMain.ActiveFrame.SetActiveFile(ExtractFileName(TargetFile));
        end;
      end
      else
      begin
        if not mbFileSystemEntryExists(TargetFile) then begin
          msgError(rsMsgObjectNotExists + LineEnding + TargetFile);
          Exit;
        end;
        SetFileSystemPath(frmMain.ActiveFrame, ExtractFilePath(TargetFile));
        frmMain.ActiveFrame.SetActiveFile(ExtractFileName(TargetFile));
      end;
      frmMain.RestoreWindow;
      Close;
    except
      on E: Exception do MessageDlg(E.Message, mtError, [mbOK], 0);
    end;
end;

{ TfrmFindDlg.cm_FeedToListbox }
procedure TfrmFindDlg.cm_FeedToListbox(const Params: array of string);
const
  PluginDuplicate = 'Plugin().Duplicate{}';
var
  I: integer;
  sFileName: string;
  SearchResultFS: ISearchResultFileSource;
  FileList: TFileTree;
  aFile: TFile;
  Notebook: TFileViewNotebook;
  NewPage: TFileViewPage;
  DCFunc: String;
  AProperty: TFileVariantProperty;
  ANewSet: TPanelColumnsClass;
  NewSorting: TFileSortings;
  AHeader: TWCXHeader;
begin
  StopSearch;

  FileList := TFileTree.Create;
  for i := 0 to lsFoundedFiles.Items.Count - 1 do
  begin
    if Assigned(FWcxModule) then
    begin
      AHeader:= TWCXHeader(lsFoundedFiles.Items.Objects[I]);
      aFile := TWcxArchiveFileSource.CreateFile(ExtractFilePath(AHeader.FileName), AHeader);
      FileList.AddSubNode(aFile);
    end
    else try
      sFileName := lsFoundedFiles.Items[I];
      aFile := TFileSystemFileSource.CreateFileFromFile(sFileName);
      if FLastSearchTemplate.SearchRecord.Duplicates then
      begin
        AProperty:= TFileVariantProperty.Create(PluginDuplicate);
        AProperty.Value:= TDuplicate(lsFoundedFiles.Items.Objects[I]).Index;
        aFile.Properties[fpVariant]:= AProperty;
      end;
      FileList.AddSubNode(aFile);
    except
      on EFileNotFound do ;
    end;
  end;

  // Create search result file source.
  // Currently only searching FileSystem is supported.
  SearchResultFS := TSearchResultFileSource.Create;
  SearchResultFS.AddList(FileList, FFileSource);

  // Add new tab for search results.
  Notebook := frmMain.ActiveNotebook;
  NewPage := Notebook.NewPage(Notebook.ActiveView);

  if FLastSearchTemplate.SearchRecord.Duplicates then
  begin
    if not (NewPage.FileView is TColumnsFileView) then
    begin
      NewPage.FileView:= TColumnsFileView.Create(NewPage, NewPage.FileView, EmptyStr);
      NewPage.FileView.SetFocus;
    end;

    ANewSet:= TPanelColumnsClass.Create;
    DCFunc := '[' + sFuncTypeDC + '().%s{}]';
    I:= Notebook.ActiveView.ClientWidth;
    ANewSet.Add(rsFuncName, Format(DCFunc, [TFileFunctionStrings[fsfName]]), I * 40 div 100, taLeftJustify);
    ANewSet.Add(rsFuncGroup, '[' + PluginDuplicate + ']', I * 20 div 100, taCenter);
    ANewSet.Add(rsFuncPath, Format(DCFunc, [TFileFunctionStrings[fsfPath]]), I * 40 div 100, taLeftJustify);

    TColumnsFileView(NewPage.FileView).isSlave:= True;
    TColumnsFileView(NewPage.FileView).ActiveColmSlave:= ANewSet;

    TColumnsFileView(NewPage.FileView).UpdateColumnsView;

    SetLength(NewSorting, 1);
    SetLength(NewSorting[0].SortFunctions, 2);
    NewSorting[0].SortFunctions[0] := fsfVariant;
    NewSorting[0].SortFunctions[1] := fsfName;
    NewSorting[0].SortDirection := sdAscending;

    NewPage.FileView.Sorting:= NewSorting;
  end;

  NewPage.FileView.AddFileSource(SearchResultFS, SearchResultFS.GetRootDir);
  NewPage.FileView.FlatView := True;
  NewPage.MakeActive;

  Close;
end;

procedure TfrmFindDlg.cm_PageNext(const Params: array of string);
begin
  with pgcSearch do
  begin
    if PageIndex = PageCount - 1 then
      ActivePage := Pages[0]
    else
      ActivePage := Pages[PageIndex + 1];
  end;
end;

procedure TfrmFindDlg.cm_PagePrev(const Params: array of string);
begin
  with pgcSearch do
  begin
    if PageIndex = 0 then
      ActivePage := Pages[PageCount - 1]
    else
      ActivePage := Pages[PageIndex - 1];
  end;
end;

{ TfrmFindDlg.cm_PageStandard }
procedure TfrmFindDlg.cm_PageStandard(const Params: array of string);
begin
  pgcSearch.ActivePage := tsStandard;
end;

{ TfrmFindDlg.cm_PageAdvanced }
procedure TfrmFindDlg.cm_PageAdvanced(const Params: array of string);
begin
  pgcSearch.ActivePage := tsAdvanced;
end;

{ TfrmFindDlg.cm_PagePlugins }
procedure TfrmFindDlg.cm_PagePlugins(const Params: array of string);
begin
  pgcSearch.ActivePage := tsPlugins;
end;

{ TfrmFindDlg.cm_PageLoadSave }
procedure TfrmFindDlg.cm_PageLoadSave(const Params: array of string);
begin
  pgcSearch.ActivePage := tsLoadSave;
end;

{ TfrmFindDlg.cm_PageResults }
procedure TfrmFindDlg.cm_PageResults(const Params: array of string);
begin
  pgcSearch.ActivePage := tsResults;
end;

{ TfrmFindDlg.FormCloseQuery }
procedure TfrmFindDlg.FormCloseQuery(Sender: TObject; var CanClose: boolean);
begin
  if FFindThread <> nil then  // We can't call StopSearch because it method will set focus on unavailable field
  begin
    FFindThread.OnTerminate := nil;
    FFindThread.Terminate;
    FUpdateTimer.OnTimer(FUpdateTimer);
    FUpdateTimer.Enabled := False;
    FFindThread := nil;
  end;

  AfterSearchStopped;

  btnStart.Default := True;

  CanClose := not Assigned(FFindThread);
end;

{ TfrmFindDlg.FormDestroy }
procedure TfrmFindDlg.FormDestroy(Sender: TObject);
begin
{$IF DEFINED(FIX_DEFAULT)}
  if ListOffrmFindDlgInstance.Count = 0 then
    Application.RemoveOnKeyDownBeforeHandler(@FormKeyDown);
{$ENDIF}
  FreeAndNil(FoundedStringCopy);
  FreeAndNil(DsxPlugins);
end;

{$IF DEFINED(FIX_DEFAULT)}
procedure TfrmFindDlg.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  AParentForm: TCustomForm;
begin
  if Key = VK_RETURN then
  begin
    if Sender is TControl then
    begin
      AParentForm := GetParentForm(TControl(Sender));
      if (AParentForm is TfrmFindDlg) then
      begin
        if (Sender = TfrmFindDlg(AParentForm).lsFoundedFiles) then
          TCustomListBox(Sender).OnKeyDown(Sender, Key, Shift)
        else if (Sender is TCustomButton) then
        begin
          TCustomButton(Sender).Click;
          Key:= 0;
        end
{$if (lcl_fullversion < 1090000) and defined(lclgtk2)}
        else begin
          Key := 0;
          if btnStart.Enabled then
            btnStart.Click
          else
            btnStop.Click;
        end;
{$endif}
      end;
    end;
  end;
end;
{$ENDIF}

{ TfrmFindDlg.FormClose }
procedure TfrmFindDlg.frmFindDlgClose(Sender: TObject; var CloseAction: TCloseAction);
const
  CLOSETAG: longint = $233528DE;
var
  iSearchingForm: integer;
begin
  if Assigned(FFrmAttributesEdit) then
  begin
    FFrmAttributesEdit.Close;
    FreeAndNil(FFrmAttributesEdit);
  end;

  // Remove the whole thing from memory if no search was made at all.
  // We remove it also if we've been asked to remove it.
  // We ned to remove it from our list of instances "ListOffrmFindDlgInstance".
  // We'll use the trick to give current form a magic tag number and then pass the list to delete the matching one.
  if (not FAtLeastOneSearchWasDone) or FFreeOnClose then
  begin
    tag := CLOSETAG;
    for iSearchingForm := pred(ListOffrmFindDlgInstance.Count) downto 0 do
      if ListOffrmFindDlgInstance.frmFindDlgInstance[iSearchingForm].Tag = CLOSETAG then
        ListOffrmFindDlgInstance.Delete(iSearchingForm);

    CloseAction := caFree; // This will destroy the from on next step in the flow.
  end;
{$IFDEF DARKWIN}
  if g_darkModeEnabled and (CloseAction <> caFree) then DestroyHandle;
{$ENDIF}
end;

{ TfrmFindDlg.SetWindowCaption }
procedure TfrmFindDlg.SetWindowCaption(AWindowCaptionStyle: byte);
var
  sBuildingCaptionName: string;
begin
  sBuildingCaptionName := rsFindSearchFiles;

  case (AWindowCaptionStyle and $07) of
    2: sBuildingCaptionName := sBuildingCaptionName + ' - ' + rsFindScanning;
    3: sBuildingCaptionName := sBuildingCaptionName + ' - ' + rsOperFinished;
  end;

  if (AWindowCaptionStyle and $10) <> 0 then
    sBuildingCaptionName := sBuildingCaptionName + ' - ' + lblFound.Caption;

  if (AWindowCaptionStyle and $08) <> 0 then
  begin
    sBuildingCaptionName := sBuildingCaptionName + ' - Files: ' + GetFileMask;
    if cbFindText.Checked then
      sBuildingCaptionName := sBuildingCaptionName + ' - Text: ' + cmbFindText.Text;
  end;

  Caption := sBuildingCaptionName;
end;

function TfrmFindDlg.ObjectType(Index: Integer): TCheckBoxState;
var
  ATemp: TObject;
begin
  ATemp:= lsFoundedFiles.Items.Objects[Index];
  if (ATemp = nil) then
    Result:= cbUnchecked
  else if (ATemp is TWcxHeader) then
    Result:= cbChecked
  else
    Result:= cbGrayed;
end;

function TfrmFindDlg.GetFileMask: String;
begin
  if Length(cmbFindFileMask.Text) = 0 then
    Result := AllFilesMask
  else begin
    Result := cmbFindFileMask.Text;
  end;
end;

{ TfrmFindDlg.LoadHistory }
procedure TfrmFindDlg.LoadHistory;
begin
  cmbFindFileMask.Items.Assign(glsMaskHistory);
  cmbFindPathStart.Items.Assign(glsSearchDirectories);
  cmbExcludeDirectories.Items.Assign(glsSearchExcludeDirectories);
  cmbExcludeFiles.Items.Assign(glsSearchExcludeFiles);
  cmbFindText.Items.Assign(glsSearchHistory);
  cmbReplaceText.Items.Assign(glsReplaceHistory);
end;

{ TfrmFindDlg.SaveHistory }
procedure TfrmFindDlg.SaveHistory;
begin
  // 1. Add to find mask history
  InsertFirstItem(cmbFindFileMask.Text, cmbFindFileMask);
  glsMaskHistory.Assign(cmbFindFileMask.Items);

  // 1. Add to find directory history
  InsertFirstItem(cmbFindPathStart.Text, cmbFindPathStart);
  glsSearchDirectories.Assign(cmbFindPathStart.Items);

  // 2. Add to exclude directories history
  InsertFirstItem(cmbExcludeDirectories.Text, cmbExcludeDirectories);
  glsSearchExcludeFiles.Assign(cmbExcludeFiles.Items);

  // 3. Add to exclude files history
  InsertFirstItem(cmbExcludeFiles.Text, cmbExcludeFiles);
  glsSearchExcludeDirectories.Assign(cmbExcludeDirectories.Items);

  // 4. Add to search text history
  if cbFindText.Checked then
  begin
    InsertFirstItem(cmbFindText.Text, cmbFindText, GetTextSearchOptions);
    // Update search history, so it can be used in
    // Viewer/Editor opened from find files dialog
    gFirstTextSearch := False;
    glsSearchHistory.Assign(cmbFindText.Items);
  end;

  // 5. Add to replace text history
  if cbReplaceText.Checked then
  begin
    InsertFirstItem(cmbReplaceText.Text, cmbReplaceText);
    // Update replace history, so it can be used in
    // Editor opened from find files dialog (issue 0000539)
    glsReplaceHistory.Assign(cmbReplaceText.Items);
  end;
end;

procedure TfrmFindDlg.LoadPlugins;
var
  I: Integer;
  AModule: TDsxModule;
begin
  cmbPlugin.Clear;
  DSXPlugins.Assign(gDSXPlugins);
  for I := 0 to DSXPlugins.Count - 1 do
  begin
    AModule:= DSXPlugins.GetDSXModule(I);
    if Length(AModule.Descr) = 0 then
      cmbPlugin.Items.Add(AModule.Name)
    else
      cmbPlugin.Items.Add(AModule.Name + ' (' + AModule.Descr + ')');
  end;
  cbUsePlugin.Enabled := (cmbPlugin.Items.Count > 0);
  if (cbUsePlugin.Enabled) then cmbPlugin.ItemIndex := 0;
end;

{ TfrmFindDlg.FormShow }
procedure TfrmFindDlg.frmFindDlgShow(Sender: TObject);
begin
  {$IFDEF LCLCOCOA}
  pgcSearch.DoAdjustClientRectChange();
  {$ENDIF}

  pgcSearch.PageIndex := 0;

  if cmbFindFileMask.Visible then
    cmbFindFileMask.SelectAll;

  lsFoundedFiles.Canvas.Font := lsFoundedFiles.Font;

  if pgcSearch.ActivePage = tsStandard then
    if cmbFindFileMask.CanSetFocus then
      cmbFindFileMask.SetFocus;

  cbSelectedFiles.Checked := FSelectedFiles.Count > 0;
  cbSelectedFiles.Enabled := cbSelectedFiles.Checked;
end;

{ TfrmFindDlg.gbDirectoriesResize }
procedure TfrmFindDlg.gbDirectoriesResize(Sender: TObject);
begin
  pnlDirectoriesDepth.Width := gbDirectories.Width div 3;
end;

{ TfrmFindDlg.lbSearchTemplatesSelectionChange }
procedure TfrmFindDlg.lbSearchTemplatesSelectionChange(Sender: TObject; User: boolean);
begin
  if lbSearchTemplates.ItemIndex < 0 then
    lblSearchContents.Caption := ''
  else
  begin
    with gSearchTemplateList.Templates[lbSearchTemplates.ItemIndex].SearchRecord do
    begin
      if StartPath <> '' then
        lblSearchContents.Caption := '"' + FilesMasks + '" -> "' + StartPath + '"'
      else begin
        lblSearchContents.Caption := '"' + FilesMasks + '"';
      end;
      pnlLoadSaveBottom.Enabled:= cbFindInArchive.Enabled or
                                  ((Duplicates = False) and (ContentPlugin = False) and
                                   (SearchPlugin = EmptyStr) and (IsReplaceText = False));
    end;
  end;
end;

{ TfrmFindDlg.LoadSelectedTemplate }
procedure TfrmFindDlg.LoadSelectedTemplate;
var
  SearchTemplate: TSearchTemplate;
begin
  if lbSearchTemplates.ItemIndex < 0 then Exit;
  SearchTemplate := gSearchTemplateList.Templates[lbSearchTemplates.ItemIndex];
  if Assigned(SearchTemplate) then
  begin
    FLastTemplateName := SearchTemplate.TemplateName;
    LoadTemplate(SearchTemplate.SearchRecord);
  end;
end;

{ TfrmFindDlg.LoadTemplate }
procedure TfrmFindDlg.LoadTemplate(const Template: TSearchTemplateRec);
begin
  with Template do
  begin
    if cbFindInArchive.Enabled then
    begin
      if StartPath <> '' then
        cmbFindPathStart.Text := StartPath;
      cbFollowSymLinks.Checked := FollowSymLinks;
      cbFindInArchive.Checked := FindInArchives;
      if (SearchDepth + 1 >= 0) and (SearchDepth + 1 < cmbSearchDepth.Items.Count) then
        cmbSearchDepth.ItemIndex := SearchDepth + 1
      else
        cmbSearchDepth.ItemIndex := 0;
    end;
    cmbExcludeDirectories.Text := ExcludeDirectories;
    cmbFindFileMask.Text := FilesMasks;
    cmbExcludeFiles.Text := ExcludeFiles;

    cbRegExp.Checked := RegExp;
    cbPartialNameSearch.Checked := IsPartialNameSearch;

    // attributes
    edtAttrib.Text := AttributesPattern;
    // file date/time
    cbDateFrom.Checked := IsDateFrom;
    if IsDateFrom then
      ZVDateFrom.Date := DateTimeFrom;

    cbDateTo.Checked := IsDateTo;
    if IsDateTo then
      ZVDateTo.Date := DateTimeTo;

    cbTimeFrom.Checked := IsTimeFrom;
    if IsTimeFrom then
      ZVTimeFrom.Time := DateTimeFrom;

    cbTimeTo.Checked := IsTimeTo;
    if IsTimeTo then
      ZVTimeTo.Time := DateTimeTo;

    // not older then
    cbNotOlderThan.Checked := IsNotOlderThan;
    seNotOlderThan.Value := NotOlderThan;
    cmbNotOlderThanUnit.ItemIndex := TimeUnitToComboIndex[NotOlderThanUnit];
    // file size
    cbFileSizeFrom.Checked := IsFileSizeFrom;
    cbFileSizeTo.Checked := IsFileSizeTo;
    seFileSizeFrom.Value := FileSizeFrom;
    seFileSizeTo.Value := FileSizeTo;
    cmbFileSizeUnit.ItemIndex := FileSizeUnitToComboIndex[FileSizeUnit];
    // find/replace text
    cbFindText.Checked := IsFindText;
    cmbFindText.Text := FindText;
    if cbFindInArchive.Enabled then
    begin
      cbReplaceText.Checked := IsReplaceText;
      cmbReplaceText.Text := ReplaceText;
    end;
    chkHex.Checked := HexValue;
    cbCaseSens.Checked := CaseSensitive;
    cbNotContainingText.Checked := NotContainingText;
    cbTextRegExp.Checked := TextRegExp;
    SetEncodings(TextEncoding, cmbEncoding);
    cbOfficeXML.Checked := OfficeXML;
    UpdateEncodings;

    if cbFindInArchive.Enabled then
    begin
      // duplicates
      chkDuplicates.Checked := Duplicates;
      chkDuplicateName.Checked := DuplicateName;
      chkDuplicateSize.Checked := DuplicateSize;
      chkDuplicateHash.Checked := DuplicateHash;
      chkDuplicateContent.Checked := DuplicateContent;
      // plugins
      if cbUsePlugin.Enabled then
      begin
        cmbPlugin.Tag := cmbPlugin.Items.IndexOf(SearchPlugin);
        cbUsePlugin.Checked:= (cmbPlugin.Tag >= 0);
        if cbUsePlugin.Checked then
          cmbPlugin.ItemIndex := cmbPlugin.Tag;
      end;
      frmContentPlugins.Load(Template);
    end;

    //Let's switch to the most pertinent tab after having load the template.
    //If we would just load and no switching, user has not a real feedback visually he loaded something.
    //1. If we're using at least plug in, switch to it.
    //2. If not but we're using at least something from the "Advanced" tab, switch to it.
    //3. If nothing above, at least switch to "Standard" tab.
    pgcSearch.Options:= pgcSearch.Options + [nboDoChangeOnSetIndex];
    if (cbUsePlugin.Checked OR frmContentPlugins.chkUsePlugins.Checked) then
      pgcSearch.ActivePage := tsPlugins
    else
      if (cbNotOlderThan.Checked OR cbFileSizeFrom.Checked OR cbFileSizeTo.Checked OR cbDateFrom.Checked OR cbDateTo.Checked OR cbTimeFrom.Checked OR cbTimeTo.Checked OR (edtAttrib.Text<>'')) then
        pgcSearch.ActivePage := tsAdvanced
      else begin
        pgcSearch.ActivePage := tsStandard;
      end;
    pgcSearch.Options:= pgcSearch.Options - [nboDoChangeOnSetIndex];
  end;
end;

{ TfrmFindDlg.lsFoundedFilesDblClick }
procedure TfrmFindDlg.lsFoundedFilesDblClick(Sender: TObject);
begin
  cm_GoToFile([]);
end;

{ TfrmFindDlg.lsFoundedFilesKeyDown }
procedure TfrmFindDlg.lsFoundedFilesKeyDown(Sender: TObject;
  var Key: word; Shift: TShiftState);
begin
  if (Shift = []) and (lsFoundedFiles.ItemIndex <> -1) then
  begin
    case Key of
      VK_DELETE:
      begin
        miRemoveFromLlistClick(Sender);
        Key := 0;
      end;

      VK_RETURN:
      begin
        if not FSearchingActive then
        begin
          cm_GotoFile([]);
          Key := 0;
        end;
      end;

      VK_RIGHT, VK_LEFT:
      begin
        if not FSearchingActive then
        begin
          if FRButtonPanelSender <> nil then (FRButtonPanelSender as TButton).SetFocus else btnNewSearch.SetFocus;
          Key := 0;
        end
        else
        begin
          Key := 0;
          btnStop.SetFocus;
        end;
      end;

    end;

  end;
end;

{ TfrmFindDlg.lsFoundedFilesMouseDown }
procedure TfrmFindDlg.lsFoundedFilesMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
  i: integer;
begin
  i := lsFoundedFiles.ItemAtPos(Point(X, Y), False);

  if (i >= 0) then
  begin
    LastClickResultsPath := GetDeepestExistingPath(lsFoundedFiles.Items[i]);

    if (Button = mbRight) and (lsFoundedFiles.Selected[i] <> True) then
    begin
      lsFoundedFiles.ClearSelection;
      lsFoundedFiles.Selected[i] := True;
    end;
  end;
end;

{ TfrmFindDlg.lsFoundedFilesMouseUp }
procedure TfrmFindDlg.lsFoundedFilesMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
  i: integer;
  sPath: string;
  AFile: TFile;
  AFiles: TFiles;
  pt: TPoint;
begin
  if Button = mbRight then
  begin

    if Shift = [ssCtrl] then // Show System context menu
    begin

      {$IF DEFINED(MSWINDOWS)}
      try
        AFiles := TFiles.Create(LastClickResultsPath);
        AFiles.Path := LastClickResultsPath;

        i := 0;
        while i < lsFoundedFiles.Count do
        begin
          if lsFoundedFiles.Selected[i] then
          begin
            sPath := lsFoundedFiles.Items[i];
            AFile := TFileSystemFileSource.CreateFile(sPath);
            AFiles.Add(aFile);
          end;
          Inc(i);
        end;

        try
          pt.X := X;
          pt.Y := Y;
          pt := ClientToScreen(pt);
          ShowContextMenu(lsFoundedFiles, AFiles, pt.X, pt.Y, False, nil);
        finally
          FreeAndNil(AFiles);
        end;


      except
        on E: EContextMenuException do
          ShowException(E)
        else;
      end;

      {$ENDIF}

    end
    else
    begin
      PopupMenuFind.PopUp;  // Show DC menu
    end;

  end;
end;

{ TfrmFindDlg.lsFoundedFilesMouseWheelDown }
procedure TfrmFindDlg.lsFoundedFilesMouseWheelDown(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: boolean);
begin
  if (Shift = [ssCtrl]) and (gFonts[dcfSearchResults].Size > gFonts[dcfSearchResults].MinValue) then
  begin
    dec(gFonts[dcfSearchResults].Size);
    lsFoundedFiles.Font.Size := gFonts[dcfSearchResults].Size;
    Handled := True;
  end;
end;

{ TfrmFindDlg.lsFoundedFilesMouseWheelUp }
procedure TfrmFindDlg.lsFoundedFilesMouseWheelUp(Sender: TObject;
  Shift: TShiftState; MousePos: TPoint; var Handled: boolean);
begin
  if (Shift = [ssCtrl]) and (gFonts[dcfSearchResults].Size < gFonts[dcfSearchResults].MaxValue) then
  begin
    inc(gFonts[dcfSearchResults].Size);
    lsFoundedFiles.Font.Size := gFonts[dcfSearchResults].Size;
    Handled := True;
  end;
end;

{ TfrmFindDlg.miOpenInNewTabClick }
procedure TfrmFindDlg.miOpenInNewTabClick(Sender: TObject);
var
  i: integer;
  sPath: string;

  Notebook: TFileViewNotebook;
  NewPage: TFileViewPage;

begin
  Notebook := frmMain.ActiveNotebook;

  i := 0;
  while i < lsFoundedFiles.Count do
  begin
    if lsFoundedFiles.Selected[i] then
    begin
      sPath := lsFoundedFiles.Items[i];
      sPath := GetDeepestExistingPath(sPath);

      NewPage := Notebook.NewPage(Notebook.ActiveView);
      NewPage.FileView.CurrentPath := sPath;
      NewPage.FileView.SetActiveFile(ExtractFileName(lsFoundedFiles.Items[i]));
    end;
    Inc(i);
  end;

end;

{ TfrmFindDlg.miRemoveFromLlistClick }
procedure TfrmFindDlg.miRemoveFromLlistClick(Sender: TObject);
var
  i: integer;
begin
  if lsFoundedFiles.ItemIndex = -1 then Exit;
  if lsFoundedFiles.SelCount = 0 then Exit;

  for i := lsFoundedFiles.Items.Count - 1 downto 0 do
    if lsFoundedFiles.Selected[i] then
      lsFoundedFiles.Items.Delete(i);

  miShowAllFound.Enabled := True;
end;

{ TfrmFindDlg.miShowAllFoundClick }
procedure TfrmFindDlg.miShowAllFoundClick(Sender: TObject);
begin
  lsFoundedFiles.Clear;
  lsFoundedFiles.Items.AddStrings(FoundedStringCopy);

  miShowAllFound.Enabled := False;
end;

{ TfrmFindDlg.miShowInEditorClick }
procedure TfrmFindDlg.miShowInEditorClick(Sender: TObject);
begin
  if lsFoundedFiles.ItemIndex >= 0 then
    ShowEditorByGlob(lsFoundedFiles.Items[lsFoundedFiles.ItemIndex]);
end;

{ TfrmFindDlg.miShowInViewerClick }
procedure TfrmFindDlg.miShowInViewerClick(Sender: TObject);
var
  sl: TStringList;
  i: integer;
begin
  if lsFoundedFiles.ItemIndex = -1 then Exit;

  sl := TStringList.Create;
  try
    for i := 0 to lsFoundedFiles.Items.Count - 1 do
      if lsFoundedFiles.Selected[i] then
        sl.Add(lsFoundedFiles.Items[i]);
    ShowViewer(sl);
  finally
    sl.Free;
  end;
end;

{ TfrmFindDlg.seFileSizeFromChange }
procedure TfrmFindDlg.seFileSizeFromChange(Sender: TObject);
begin
  if not FUpdating then
    cbFileSizeFrom.Checked := (seFileSizeFrom.Value > 0);
end;

{ TfrmFindDlg.seFileSizeToChange }
procedure TfrmFindDlg.seFileSizeToChange(Sender: TObject);
begin
  if not FUpdating then
    cbFileSizeTo.Checked := (seFileSizeTo.Value > 0);
end;

{ TfrmFindDlg.SelectTemplate }
procedure TfrmFindDlg.SelectTemplate(const ATemplateName: string);
var
  i: integer;
begin
  for i := 0 to lbSearchTemplates.Count - 1 do
    if lbSearchTemplates.Items[i] = ATemplateName then
    begin
      lbSearchTemplates.ItemIndex := i;
      Break;
    end;
end;

{ TfrmFindDlg.seNotOlderThanChange }
procedure TfrmFindDlg.seNotOlderThanChange(Sender: TObject);
begin
  if not FUpdating then
    cbNotOlderThan.Checked := (seNotOlderThan.Value > 0);
end;

{ TfrmFindDlg.tsLoadSaveShow }
procedure TfrmFindDlg.tsLoadSaveShow(Sender: TObject);
begin
  UpdateTemplatesList;
  if (lbSearchTemplates.Count > 0) and (lbSearchTemplates.ItemIndex = -1) then
    lbSearchTemplates.ItemIndex := 0;
end;

{ TfrmFindDlg.tsStandardEnter }
procedure TfrmFindDlg.tsStandardEnter(Sender: TObject);
begin
  btnStart.Default := True;
end;

{ TfrmFindDlg.UpdateTemplatesList }
procedure TfrmFindDlg.UpdateTemplatesList;
var
  OldIndex: integer;
begin
  OldIndex := lbSearchTemplates.ItemIndex;
  gSearchTemplateList.LoadToStringList(lbSearchTemplates.Items);
  if OldIndex <> -1 then
    lbSearchTemplates.ItemIndex := OldIndex;
end;

{ TfrmFindDlg.OnUpdateTimer }
procedure TfrmFindDlg.OnUpdateTimer(Sender: TObject);
begin
  if Assigned(FFindThread) then
  begin
    lblStatus.Caption := Format(rsFindScanned, [FFindThread.FilesScanned]) + FTimeSearch;
    lblFound.Caption := Format(rsFindFound, [FFindThread.FilesFound]);
    lblCurrent.Caption := rsFindScanning + ': ' + FFindThread.CurrentDir;
  end;
end;

{ TfrmFindDlg.ZVDateFromChange }
procedure TfrmFindDlg.ZVDateFromChange(Sender: TObject);
begin
  if not FUpdating then
    cbDateFrom.Checked := True;
end;

{ TfrmFindDlg.ZVDateToChange }
procedure TfrmFindDlg.ZVDateToChange(Sender: TObject);
begin
  if not FUpdating then
    cbDateTo.Checked := True;
end;

{ TfrmFindDlg.ZVTimeFromChange }
procedure TfrmFindDlg.ZVTimeFromChange(Sender: TObject);
begin
  if not FUpdating then
    cbTimeFrom.Checked := True;
end;

{ TfrmFindDlg.ZVTimeToChange }
procedure TfrmFindDlg.ZVTimeToChange(Sender: TObject);
begin
  if not FUpdating then
    cbTimeTo.Checked := True;
end;

procedure TfrmFindDlg.PopupMenuFindPopup(Sender: TObject);
begin
  if (lsFoundedFiles.ItemIndex <> -1) then
  begin
    miShowInViewer.Enabled:= (ObjectType(lsFoundedFiles.ItemIndex) <> cbChecked);
    miShowInEditor.Enabled:= (ObjectType(lsFoundedFiles.ItemIndex) <> cbChecked);
  end;
end;

{ TfrmFindDlg.OnAddAttribute }
procedure TfrmFindDlg.OnAddAttribute(Sender: TObject);
var
  sAttr: string;
begin
  sAttr := edtAttrib.Text;
  if edtAttrib.SelStart > 0 then
    // Insert at caret position.
    Insert((Sender as TfrmAttributesEdit).AttrsAsText, sAttr, edtAttrib.SelStart + 1)
  else
    sAttr := sAttr + (Sender as TfrmAttributesEdit).AttrsAsText;
  edtAttrib.Text := sAttr;
end;

{ TfrmFindDlg.InvalidRegExpr }
function TfrmFindDlg.InvalidRegExpr: Boolean;
var
  sMsg, sEncoding: String;
begin
  Result:= False;
  try
    if cbRegExp.Checked then
    begin
      uRegExprW.ExecRegExpr(CeUtf8ToUtf16(cmbFindFileMask.Text), '');
    end;
    if cbTextRegExp.Checked then
    begin
      sMsg:= cmbFindText.Text;
      sEncoding:= NormalizeEncoding(cmbEncoding.Text);
      if sEncoding = EncodingDefault then sEncoding:= GetDefaultTextEncoding;
      // Use correct RegExp engine
      if TRegExprU.Available and (sEncoding = EncodingUTF8) then
        uRegExprU.ExecRegExpr(sMsg, '')
      else if SingleByteEncoding(sEncoding) then
        uRegExprA.ExecRegExpr(sMsg, '')
      else if (sEncoding = EncodingUTF16LE) then
        uRegExprW.ExecRegExpr(CeUtf8ToUtf16(sMsg), '');
    end;
  except
    on E: Exception do
    begin
      Result:= True;
      sMsg:= StringReplace(cbRegExp.Caption, '&', '', [rfReplaceAll]);
      MessageDlg(sMsg + ': ' + E.Message, mtError, [mbOK], 0);
    end;
  end;
end;

{ TfrmFindDlg.pgcSearchChange }
procedure TfrmFindDlg.pgcSearchChange(Sender: TObject);
begin
  if pgcSearch.ActivePage = tsStandard then
  begin
    if (not cmbFindFileMask.Focused) and (cmbFindFileMask.CanSetFocus) then
      cmbFindFileMask.SetFocus;
  end
  else
  if pgcSearch.ActivePage = tsResults then
  begin
    if (not lsFoundedFiles.Focused) and (lsFoundedFiles.CanSetFocus) then
      lsFoundedFiles.SetFocus;
  end;
end;

{ TfrmFindDlg.SaveTemplate }
procedure TfrmFindDlg.SaveTemplate(SaveStartingPath: boolean);
var
  sName: string;
  SearchTemplate: TSearchTemplate;
  SearchRec: TSearchTemplateRec;
begin
  if InvalidRegExpr then Exit;

  sName := FLastTemplateName;
  if not InputQuery(rsFindSaveTemplateCaption, rsFindSaveTemplateTitle, sName) then
  begin
    ModalResult := mrCancel;
    Exit;
  end;

  FLastTemplateName := sName;
  SearchTemplate := gSearchTemplateList.TemplateByName[sName];
  if Assigned(SearchTemplate) then
  begin
    // TODO: Ask for overwriting existing template.
    FillFindOptions(SearchRec, SaveStartingPath);
    SearchTemplate.SearchRecord := SearchRec;
    Exit;
  end;

  SearchTemplate := TSearchTemplate.Create;
  try
    SearchTemplate.TemplateName := sName;
    FillFindOptions(SearchRec, SaveStartingPath);
    SearchTemplate.SearchRecord := SearchRec;
    gSearchTemplateList.Add(SearchTemplate);
  except
    FreeAndNil(SearchTemplate);
    raise;
  end;
  UpdateTemplatesList;
  SelectTemplate(FLastTemplateName);
end;

function TfrmFindDlg.GetTextSearchOptions: UIntPtr;
var
  Options: TTextSearchOptions absolute Result;
begin
  Result:= 0;
  if cbCaseSens.Checked then
    Include(Options, tsoMatchCase);
  if cbTextRegExp.Checked then
    Include(Options, tsoRegExpr);
  if chkHex.Checked then
    Include(Options, tsoHex);
end;

procedure TfrmFindDlg.CancelCloseAndFreeMem;
begin
  cm_FreeFromMem([]);
end;

{ TfrmFindDlg.cm_Close }
procedure TfrmFindDlg.cm_Close(const Params: array of string);
begin
  Hide;
  Close;
end;

{ TfrmFindDlg.cm_NewSearchClearFilters }
procedure TfrmFindDlg.cm_NewSearchClearFilters(const Params: array of string);
begin
  cm_NewSearch(['filters=clear']);
end;


{ TfrmFindDlg.cm_FreeFromMem }
// We will set the flag "FFreeOnClose" to "true" (it was to "false" since "FormCreate".
// This flag will be checked in "FormClose" to set "CloseAction" to "caFree" so form will be destroy.
// But we need to remove the pointer to that form from our "ListOffrmFindDlgInstance".
// To determine which one to remove, we set the tag to a magic number, then scan our list and delete the one pointing the form with that magic number.
// We just delete the pointer. The actual form will be destroyed properly because of the "CloseAction" set to "caFree".
procedure TfrmFindDlg.cm_FreeFromMem(const {%H-}Params: array of string);
var
  iSearchingForm: integer;
const
  CLOSETAG: longint = $233528DE;
begin
  if FSearchingActive then StopSearch;

  // Remove our pointer from our list of forms
  tag := CLOSETAG;
  for iSearchingForm := pred(ListOffrmFindDlgInstance.Count) downto 1 do
  begin
    if ListOffrmFindDlgInstance.frmFindDlgInstance[iSearchingForm].Tag = CLOSETAG then
      ListOffrmFindDlgInstance.Delete(iSearchingForm);
  end;

  FFreeOnClose := True; // Prepare the "free mem"

  // Do the "close"
  Close;
end;

{ TfrmFindDlg.cm_FreeFromMemAllOthers }
// We set the tag of our actual current form to a magic number and then scan
// all forms in our list to close all the ones that does not have that magic
// tag number.
procedure TfrmFindDlg.cm_FreeFromMemAllOthers(const {%H-}Params: array of string);
const
  KEEPOPENTAG: longint = $270299;
var
  iIndex: integer;
begin
  if ListOffrmFindDlgInstance.Count > 1 then
  begin
    tag := KEEPOPENTAG;
    try
      for iIndex := pred(ListOffrmFindDlgInstance.Count) downto 0 do
        if ListOffrmFindDlgInstance.frmFindDlgInstance[iIndex].Tag <> KEEPOPENTAG then
          ListOffrmFindDlgInstance.frmFindDlgInstance[iIndex].CancelCloseAndFreeMem;
    finally
      tag := 0; // Don't forget to set back tag to 0!!!
    end;
  end
  else
  begin
    msgOK(rsNoOtherFindFilesWindowToClose);
  end;
end;

{ TfrmFindDlg.cm_ConfigFileSearchHotKeys }
procedure TfrmFindDlg.cm_ConfigFileSearchHotKeys(const {%H-}Params: array of string);
begin
  frmMain.Commands.cm_ConfigHotKeys([Format('category=%s', [rsHotkeyCategoryFindFiles])]);
end;

initialization
  TFormCommands.RegisterCommandsForm(TfrmFindDlg, HotkeysCategory, @rsHotkeyCategoryFindFiles);
  ListOffrmFindDlgInstance := TListOffrmFindDlgInstance.Create;

finalization
  ListOffrmFindDlgInstance.Destroy; // "Destroy" does call the "Clear" who will free the forms.
end.
