(*
    Tux Commander - USearch - Search dialog
    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 USearch;

interface

uses
  glib2, gdk2, gtk2, SyncObjs, SysUtils, Types, Classes, Variants, GTKControls, GTKForms, GTKStdCtrls, GTKExtCtrls, GTKConsts, GTKView,
  GTKMenus,
  UEngines, UGnome, ULibc;

type
  TFSearch = class(TGTKDialog)
    BottomBox: TGTKVBox;
    HBox, HBox2: TGTKHBox;
    ButtonBox: TGTKHButtonBox;
    FileList: TGTKListView;
    FileListScrolledWindow: TGTKScrolledWindow;

    Notebook: TGTKNotebook;
    Table1, Table2: TGTKTable;
    ResultsLabel, StatusLabel: TGTKLabel;
    Label1, Label2, Label3, Label4, Label5, Label6: TGTKLabel;
    FileMaskEntry, FindTextEntry: TGTKCombo;
    SearchInEntry: TGTKEntry;
    ViewButton, NewSearchButton, GoToFileButton, FeedToListboxButton: TGTKButton;
    SearchArchivesCheckButton, CaseSensitiveCheckButton, StayCurrentFSCheckButton, CaseSensitiveMatchCheckButton: TGTKCheckButton;
    FindButton, StopButton, CloseButton: TGTKButton;
    BiggerThanCheckButton, SmallerThanCheckButton: TGTKCheckButton;
    BiggerThanEntry, SmallerThanEntry: TGTKSpinEdit;
    BiggerThanOptionMenu, SmallerThanOptionMenu, ModifiedLastOptionMenu, ModifiedNotLastOptionMenu: TGTKOptionMenu;
    ModifiedBetweenRadioButton, NotModifiedAfterRadioButton, ModifiedLastRadioButton, ModifiedNotLastRadionButton: TGTKCheckButton;
    ModifiedLastSpinEdit, ModifiedNotLastSpinEdit: TGTKSpinEdit;
    ModifiedBetweenEntry1, ModifiedBetweenEntry2, NotModifiedAfterEntry: TGTKEntry;
    ModifiedBetweenEntry1G, ModifiedBetweenEntry2G, NotModifiedAfterEntryG: TGnomeDateEdit;
    procedure FormCreate(Sender: TObject); override;
    procedure FormDestroy(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormResponse(Sender: TObject; const ResponseID: integer);
    procedure FileListSelectionChanged(Sender: TObject);
    procedure FindButtonClick(Sender: TObject);
    procedure StopButtonClick(Sender: TObject);
    procedure CloseButtonClick(Sender: TObject);
    procedure BiggerThanCheckButtonToggled(Sender: TObject);
    procedure NewSearchButtonClick(Sender: TObject);
    procedure ViewButtonClick(Sender: TObject);
    procedure GoToFileButtonClick(Sender: TObject);
    procedure FileListDblClick(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
    procedure FileMaskEntryKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
    procedure FindTextEntryKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
  private
    Processing, Stop, FUseGnomeWidgets: boolean;
    SavedData: string;
    procedure ConstructViews;
    procedure DoSearch;
  public
    Engine: TPanelEngine;
    AListView: TGTKListView;
    List: TList;
    GoToFile, GoToFileArchive: string;
  end;

  TSearchThread = class(TThread)
  private
    FEngine: TPanelEngine;
    FRootEngine: TPanelEngine;
    Wilds: array of string;
    GUIMutex: TCriticalSection;
    procedure Rekurze(StartDir: string);
    function FindText(FileName: string): boolean;
  protected
    FStartPath, FFileMask, FStringFind: string;
    FDontLeaveFS, FCaseSensitiveMask, FCaseSensitiveStrings, FSearchArchives: boolean;
    FBiggerThan, FSmallerThan: integer;   //  -1 = skip
    FModifiedBetween1, FModifiedBetween2, FNotModifiedAfter: time_t;
    FModifiedLast, FModifiedNotLast: integer;
    FList: TList;
    procedure Execute; override;
  public
    Finished, CancelIt: boolean;
    CurrentDir: string;
    constructor Create(Engine: TPanelEngine);
    destructor Destroy; override;
  end;

var
  FSearch: TFSearch;

implementation

uses Math, UMain, ULocale, UCoreUtils, UCore, DateUtils, UViewer, UConfig, UVFSCore, UFileAssoc;

var SizeUnits: array[0..2] of string;
    DayUnits: array[0..3] of string;


procedure TFSearch.FormCreate(Sender: TObject);
var MenuItem: TGTKMenuItem;
    i: integer;
begin
  //  Set the constants
  SizeUnits[0] := LANGSearch_Bytes;
  SizeUnits[1] := LANGSearch_kB;
  SizeUnits[2] := LANGSearch_MB;
  DayUnits[0] := LANGSearch_days;
  DayUnits[1] := LANGSearch_weeks;
  DayUnits[2] := LANGSearch_months;
  DayUnits[3] := LANGSearch_years;

  //  Initialization
//  WindowTypeHint := whNormal;
  List := TList.Create;
  List.Clear;
  Processing := False;
  Stop := False;
  GoToFile := '';
  GoToFileArchive := '';
  SetDefaultSize(650, 600);
  Caption := LANGSearch_Caption;
  Buttons := [];
  ShowSeparator := False;
  FUseGnomeWidgets := Assigned(@gnome_date_edit_new) and Assigned(@gnome_date_edit_set_time) and Assigned(@gnome_date_edit_get_time);

  Notebook := TGTKNotebook.Create(Self);
  Notebook.BorderWidth := 10;
  Table1 := TGTKTable.Create(Self);
  Table1.BorderWidth := 10;
  Table2 := TGTKTable.Create(Self);
  Table2.BorderWidth := 10;
  Notebook.AppendPage(Table1, LANGSearch_General);
  Notebook.AppendPage(Table2, LANGSearch_Advanced);

  ConstructViews;

  ResultsLabel := TGTKLabel.Create(Self);
  ResultsLabel.XAlign := 0;
  ResultsLabel.XPadding := 0;
  ResultsLabel.Caption := Format('<span weight="ultrabold">%s</span>', [LANGSearch_SearchResults]);
  ResultsLabel.FocusControl := FileList;
  ResultsLabel.UseMarkup := True;
  ResultsLabel.UseUnderline := True;

  ClientArea.AddControlEx(Notebook, False, False, 0);


  Label1 := TGTKLabel.Create(Self);
  Label1.XAlign := 0;
  Label1.XPadding := 0;
  Label1.Caption := Format('<span weight="ultrabold">%s</span>', [LANGSearch_SearchFor]);
  FileMaskEntry := TGTKCombo.Create(Self);
  FileMaskEntry.Tooltip := LANGSearch_FileMaskEntryTooltip; 
  FileMaskEntry.Entry.OnKeyDown := FileMaskEntryKeyDown;
  if SearchHistory.Count > 0 then
    for i := 0 to SearchHistory.Count - 1 do
      FileMaskEntry.Items.Append(SearchHistory[i]);
  FileMaskEntry.Entry.Text := '';
  Label1.FocusControl := FileMaskEntry.Entry;
  Label1.UseMarkup := True;
  Label1.UseUnderline := True;
  Label2 := TGTKLabel.Create(Self);
  Label2.XAlign := 0;
  Label2.XPadding := 0;
  Label2.Caption := LANGSearch_SearchIn;
  SearchInEntry := TGTKEntry.Create(Self);
  Label2.FocusControl := SearchInEntry;
  Label2.UseMarkup := True;
  Label2.UseUnderline := True;
  SearchArchivesCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_SearchArchivesCheckButton);
  SearchArchivesCheckButton.Enabled := PluginList.Count > 0;
  Label3 := TGTKLabel.Create(Self);
  Label3.XAlign := 0;
  Label3.XPadding := 0;
  Label3.Caption := LANGSearch_FindText;
  FindTextEntry := TGTKCombo.Create(Self);
  FindTextEntry.Tooltip := LANGSearch_FindTextEntryTooltip;
  FindTextEntry.Entry.OnKeyDown := FindTextEntryKeyDown;
  if SearchTextHistory.Count > 0 then
    for i := 0 to SearchTextHistory.Count - 1 do
      FindTextEntry.Items.Append(SearchTextHistory[i]);
  FindTextEntry.Entry.Text := '';
  Label3.FocusControl := FindTextEntry.Entry;
  Label3.UseMarkup := True;
  Label3.UseUnderline := True;
  CaseSensitiveCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_CaseSensitiveCheckButton);
  StayCurrentFSCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_StayCurrentFSCheckButton);
  CaseSensitiveMatchCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_CaseSensitiveMatchCheckButton);

  Table1.AddControlEx(0, 0, 1, 1, Label1, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(1, 0, 1, 1, FileMaskEntry, [taoExpand, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(2, 0, 1, 1, CaseSensitiveMatchCheckButton, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(0, 1, 1, 1, Label2, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(1, 1, 1, 1, SearchInEntry, [taoExpand, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(2, 1, 1, 1, StayCurrentFSCheckButton, [taoShrink], [taoShrink], 5, 2);
  Table1.AddControlEx(1, 2, 2, 1, SearchArchivesCheckButton, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(0, 3, 3, 1, TGTKHSeparator.Create(Self), [taoExpand, taoFill], [taoShrink], 5, 4);
  Table1.AddControlEx(0, 4, 1, 1, Label3, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(1, 4, 2, 1, FindTextEntry, [taoExpand, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(1, 5, 2, 1, CaseSensitiveCheckButton, [taoShrink, taoFill], [taoShrink], 5, 2);
  Table1.AddControlEx(0, 6, 3, 1, TGTKVBox.Create(Self), [taoShrink, taoFill], [taoExpand, taoFill], 5, 2);



  Label4 := TGTKLabel.Create(Self);
  Label4.XAlign := 0;
  Label4.XPadding := 0;
  Label4.Caption := Format('<span weight="ultrabold">%s</span>', [LANGSearch_Size]);
  Label4.UseMarkup := True;
  Label5 := TGTKLabel.Create(Self);
  Label5.XAlign := 0;
  Label5.XPadding := 0;
  Label5.Caption := Format('<span weight="ultrabold">%s</span>', [LANGSearch_Date]);
  Label5.UseMarkup := True;
  BiggerThanCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_BiggerThan);
  SmallerThanCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_SmallerThan);
  BiggerThanEntry := TGTKSpinEdit.Create(Self);
  BiggerThanEntry.Min := 0;
  BiggerThanEntry.Max := 999999999;
  BiggerThanEntry.Digits := 0;
  BiggerThanEntry.IncrementStep := 1;
  BiggerThanEntry.IncrementPage := 10;
  SmallerThanEntry := TGTKSpinEdit.Create(Self);
  SmallerThanEntry.Min := 0;
  SmallerThanEntry.Max := 999999999;
  SmallerThanEntry.Digits := 0;
  SmallerThanEntry.IncrementStep := 1;
  SmallerThanEntry.IncrementPage := 10;
  BiggerThanOptionMenu := TGTKOptionMenu.Create(Self);
  for i := Low(SizeUnits) to High(SizeUnits) do begin
    MenuItem := TGTKMenuItem.Create(Self);
    MenuItem.Caption := SizeUnits[i];
    BiggerThanOptionMenu.Items.Add(MenuItem);
  end;
  SmallerThanOptionMenu := TGTKOptionMenu.Create(Self);
  for i := Low(SizeUnits) to High(SizeUnits) do begin
    MenuItem := TGTKMenuItem.Create(Self);
    MenuItem.Caption := SizeUnits[i];
    SmallerThanOptionMenu.Items.Add(MenuItem);
  end;
  ModifiedBetweenRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedBetweenRadioButton);
  NotModifiedAfterRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_NotModifiedAfterRadioButton);
  ModifiedLastRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedLastRadioButton);
  ModifiedNotLastRadionButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedNotLastRadionButton);
  ModifiedLastSpinEdit := TGTKSpinEdit.Create(Self);
  ModifiedLastSpinEdit.Min := 0;
  ModifiedLastSpinEdit.Max := 10000;
  ModifiedLastSpinEdit.Digits := 0;
  ModifiedLastSpinEdit.IncrementStep := 1;
  ModifiedLastSpinEdit.IncrementPage := 10;
  ModifiedNotLastSpinEdit := TGTKSpinEdit.Create(Self);
  ModifiedNotLastSpinEdit.Min := 0;
  ModifiedNotLastSpinEdit.Max := 10000;
  ModifiedNotLastSpinEdit.Digits := 0;
  ModifiedNotLastSpinEdit.IncrementStep := 1;
  ModifiedNotLastSpinEdit.IncrementPage := 10;
  ModifiedLastOptionMenu := TGTKOptionMenu.Create(Self);
  for i := Low(DayUnits) to High(DayUnits) do begin
    MenuItem := TGTKMenuItem.Create(Self);
    MenuItem.Caption := DayUnits[i];
    ModifiedLastOptionMenu.Items.Add(MenuItem);
  end;
  ModifiedNotLastOptionMenu := TGTKOptionMenu.Create(Self);
  for i := Low(DayUnits) to High(DayUnits) do begin
    MenuItem := TGTKMenuItem.Create(Self);
    MenuItem.Caption := DayUnits[i];
    ModifiedNotLastOptionMenu.Items.Add(MenuItem);
  end;
  Label6 := TGTKLabel.Create(Self);
  Label6.XAlign := 0;
  Label6.XPadding := 0;
  Label6.Caption := LANGSearch_And;
  Label6.UseMarkup := True;

  if not FUseGnomeWidgets then begin
    ModifiedBetweenEntry1 := TGTKEntry.Create(Self);
    ModifiedBetweenEntry1.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date);
    ModifiedBetweenEntry2 := TGTKEntry.Create(Self);
    ModifiedBetweenEntry2.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date);
    NotModifiedAfterEntry := TGTKEntry.Create(Self);
    NotModifiedAfterEntry.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date);
    Table2.AddControlEx(2, 5, 2, 1, ModifiedBetweenEntry1, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
    Table2.AddControlEx(5, 5, 2, 1, ModifiedBetweenEntry2, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
    Table2.AddControlEx(2, 6, 2, 1, NotModifiedAfterEntry, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
  end else begin
    ModifiedBetweenEntry1G := TGnomeDateEdit.Create(Self);
    ModifiedBetweenEntry2G := TGnomeDateEdit.Create(Self);
    NotModifiedAfterEntryG := TGnomeDateEdit.Create(Self);
    Table2.AddControlEx(2, 5, 2, 1, ModifiedBetweenEntry1G, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
    Table2.AddControlEx(5, 5, 2, 1, ModifiedBetweenEntry2G, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
    Table2.AddControlEx(2, 6, 2, 1, NotModifiedAfterEntryG, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
  end;

  Table2.AddControlEx(0, 0, 3, 1, Label4, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 0);
  Table2.AddControlEx(0, 1, 1, 1, TGTKVBox.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 8, 0);
  Table2.AddControlEx(1, 1, 1, 1, BiggerThanCheckButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 0);
  Table2.AddControlEx(2, 1, 1, 1, BiggerThanEntry, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 0);
  Table2.AddControlEx(3, 1, 1, 1, BiggerThanOptionMenu, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  Table2.AddControlEx(5, 1, 1, 1, SmallerThanCheckButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 0);
  Table2.AddControlEx(6, 1, 1, 1, SmallerThanEntry, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 0);
  Table2.AddControlEx(7, 1, 1, 1, SmallerThanOptionMenu, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 0, 0);
  Table2.AddControlEx(1, 3, 3, 1, TGTKVBox.Create(Self), [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 0, 6);
  Table2.AddControlEx(0, 4, 3, 1, Label5, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 1);
  Table2.AddControlEx(1, 5, 1, 1, ModifiedBetweenRadioButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 2);
  Table2.AddControlEx(4, 5, 1, 1, Label6, [taoShrink], [taoShrink, taoExpand, taoFill], 5, 2);
  Table2.AddControlEx(1, 6, 1, 1, NotModifiedAfterRadioButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 2);
  Table2.AddControlEx(1, 7, 1, 1, ModifiedLastRadioButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 2);
  Table2.AddControlEx(2, 7, 1, 1, ModifiedLastSpinEdit, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
  Table2.AddControlEx(3, 7, 1, 1, ModifiedLastOptionMenu, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 2, 2);
  Table2.AddControlEx(4, 7, 2, 1, ModifiedNotLastRadionButton, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
  Table2.AddControlEx(6, 7, 1, 1, ModifiedNotLastSpinEdit, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 5, 2);
  Table2.AddControlEx(7, 7, 1, 1, ModifiedNotLastOptionMenu, [taoShrink, taoFill], [taoShrink, taoExpand, taoFill], 0, 2);


  BottomBox := TGTKVBox.Create(Self);
  BottomBox.Homogeneous := False;
  BottomBox.BorderWidth := 10;
  FileListScrolledWindow := TGTKScrolledWindow.Create(Self);
  FileListScrolledWindow.HorizScrollBarPolicy := sbAutomatic;
  FileListScrolledWindow.VertScrollBarPolicy := sbAutomatic;
  FileListScrolledWindow.ShadowType := stShadowIn;
  FileListScrolledWindow.AddControl(FileList);
  FileListScrolledWindow.SetSizeRequest(100, -1);
  FileList.SetSizeRequest(100, -1); 
  HBox := TGTKHBox.Create(Self);
  HBox2 := TGTKHBox.Create(Self);
  HBox2.Homogeneous := False;
  HBox.Homogeneous := True;
  ViewButton := TGTKButton.Create(Self);
  ViewButton.Caption := LANGSearch_ViewButtonCaption;
  ViewButton.Visible := True;
  NewSearchButton := TGTKButton.Create(Self);
  NewSearchButton.Caption := LANGSearch_NewSearchButtonCaption;
  GoToFileButton := TGTKButton.Create(Self);
  GoToFileButton.Caption := LANGSearch_GoToFileButtonCaption;
  FeedToListboxButton := TGTKButton.Create(Self);
  FeedToListboxButton.Caption := LANGSearch_FeedToListboxButtonCaption;
  FeedToListboxButton.Enabled := False;
  FeedToListboxButton.Visible := True;
  StatusLabel := TGTKLabel.Create(Self);
  StatusLabel.XAlign := 0;
  StatusLabel.XPadding := 0;
  StatusLabel.Caption := Format('<span weight="ultrabold">%s</span> %s', [LANGSearch_StatusSC, LANGSearch_Ready]);
  StatusLabel.UseMarkup := True;
  StatusLabel.SetSizeRequest(100, -1);
  HBox.AddControlEx(ViewButton, True, True, 3);
  HBox.AddControlEx(NewSearchButton, True, True, 3);
  HBox.AddControlEx(GoToFileButton, True, True, 3);
  HBox.AddControlEx(FeedToListboxButton, True, True, 3);
  HBox2.AddControlEx(TGTKVBox.Create(Self), True, True, 20);
  HBox2.AddControlEx(HBox, False, False, 0);

  BottomBox.AddControlEx(ResultsLabel, False, False, 5);
  BottomBox.AddControlEx(FileListScrolledWindow, True, True, 0);
  BottomBox.AddControlEx(StatusLabel, False, False, 5);
  BottomBox.AddControlEx(HBox2, False, False, 2);
  ClientArea.AddControlEx(BottomBox, True, True, 5);

  FindButton := TGTKButton.CreateFromStock(Self, GTK_STOCK_FIND);
  FindButton.Default := True;
  StopButton := TGTKButton.CreateFromStock(Self, GTK_STOCK_STOP);
  StopButton.Visible := False;
  StopButton.Default := True;
  CloseButton := TGTKButton.CreateFromStock(Self, GTK_STOCK_CLOSE);
  CloseButton.Default := True;
  Default := FindButton;
  ButtonBox := TGTKHButtonBox.Create(Self);
  ButtonBox.Layout := blEnd;
  ButtonBox.Spacing := 10;
  ButtonBox.BorderWidth := 10;
  ButtonBox.AddControlEnd(FindButton);
  ButtonBox.AddControlEnd(StopButton);
  ButtonBox.AddControlEnd(CloseButton);

  ActionArea.AddControlEx(ButtonBox, False, False, 0);

  OnKeyDown := FormKeyDown;
  OnCloseQuery := FormCloseQuery;
  OnResponse := FormResponse;
  OnDestroy := FormDestroy;
  FileList.OnSelectionChanged := FileListSelectionChanged;
  FindButton.OnClick := FindButtonClick;
  StopButton.OnClick := StopButtonClick;
  CloseButton.OnClick := CloseButtonClick;
  BiggerThanCheckButton.OnToggled := BiggerThanCheckButtonToggled;
  SmallerThanCheckButton.OnToggled := BiggerThanCheckButtonToggled;
  ModifiedBetweenRadioButton.OnToggled := BiggerThanCheckButtonToggled;
  NotModifiedAfterRadioButton.OnToggled := BiggerThanCheckButtonToggled;
  ModifiedLastRadioButton.OnToggled := BiggerThanCheckButtonToggled;
  ModifiedNotLastRadionButton.OnToggled := BiggerThanCheckButtonToggled;
  NewSearchButton.OnClick := NewSearchButtonClick;
  ViewButton.OnClick := ViewButtonClick;
  GoToFileButton.OnClick := GoToFileButtonClick;
  FileList.OnDblClick := FileListDblClick;

  Notebook.PageIndex := 0;
  FileMaskEntry.Entry.SetFocus;
  BiggerThanCheckButtonToggled(Sender);
  FileListSelectionChanged(Sender);
end;

procedure TFSearch.ConstructViews;
var Column: TGTKTreeViewColumn;
    i: integer;
begin
  FileList := TGTKListView.CreateTyped(Self, True, [lcPointer, lcText]);
  FileList.SelectionMode := smSingle;
  FileList.RulesHint := True;
  FileList.ShowHeaders := False;
  Column := FileList.Columns.Add;
  Column.Caption := LANGFilenameColumnCaption;
  Column.AddAttribute('text', 1);
  Column.SortID := 0;
  for i := 0 to FileList.Columns.Count - 1 do begin
    FileList.Columns[i].SizingMode := smAutoSize;
    FileList.Columns[i].Resizable := True;
    FileList.Columns[i].SetProperty('ypad', 0);
    FileList.Columns[i].SetProperty('yalign', 0.5);
  end;
end;

procedure TFSearch.FormDestroy(Sender: TObject);
 var i: integer;
begin
  Application.ProcessMessages;
  try
    if List.Count > 0 then
      for i := 0 to List.Count - 1 do
        FreeDataItem(PDataItem(List[i]));
    List.Free;
  except end;
end;

procedure TFSearch.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  Stop := True;
end;

procedure TFSearch.FormResponse(Sender: TObject; const ResponseID: integer);
begin
  Stop := True;
end;

procedure TFSearch.FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
begin
  case Key of
    GDK_F3: if FileList.Focused and Assigned(FileList.Selected) then begin
              Accept := False;
              ViewButtonClick(Sender);
            end;
    GDK_RETURN, GDK_KP_ENTER: if FileList.Focused and Assigned(FileList.Selected) then begin
                                Accept := False;
                                GoToFileButtonClick(Sender);
                              end else
                              if FindButton.Visible then begin
                                FindButtonClick(Sender);
                                Accept := False;
                              end;
    GDK_ESCAPE: begin
                  Accept := False;
                  if Processing then Stop := True
                                else ModalResult := mbCancel;
                end;
  end;
end;

procedure TFSearch.FindButtonClick(Sender: TObject);
var s: string;
    i: integer;
begin
  s := Trim(FileMaskEntry.Entry.Text);
  if Length(s) > 0 then begin
    SaveItemToHistory(s, SearchHistory);
    if FileMaskEntry.Items.Count > 0 then
      for i := FileMaskEntry.Items.Count - 1 downto 0 do
        FileMaskEntry.Items.Delete(i);
    if SearchHistory.Count > 0 then
      for i := 0 to SearchHistory.Count - 1 do
        FileMaskEntry.Items.Append(SearchHistory[i]);
  end;

  s := Trim(FindTextEntry.Entry.Text);
  if Length(s) > 0 then begin
    SaveItemToHistory(s, SearchTextHistory);
    if FindTextEntry.Items.Count > 0 then
      for i := FindTextEntry.Items.Count - 1 downto 0 do
        FindTextEntry.Items.Delete(i);
    if SearchTextHistory.Count > 0 then
      for i := 0 to SearchTextHistory.Count - 1 do
        FindTextEntry.Items.Append(SearchTextHistory[i]);
  end;

  DoSearch;
end;

procedure TFSearch.StopButtonClick(Sender: TObject);
begin
  Stop := True;
end;

procedure TFSearch.CloseButtonClick(Sender: TObject);
begin
  if Processing then Stop := True;
  ModalResult := mbClose;
end;

procedure TFSearch.BiggerThanCheckButtonToggled(Sender: TObject);
begin
  BiggerThanEntry.Enabled := BiggerThanCheckButton.Checked;
  BiggerThanOptionMenu.Enabled := BiggerThanCheckButton.Checked;
  SmallerThanEntry.Enabled := SmallerThanCheckButton.Checked;
  SmallerThanOptionMenu.Enabled := SmallerThanCheckButton.Checked;
  Label6.Enabled := ModifiedBetweenRadioButton.Checked;
  ModifiedLastSpinEdit.Enabled := ModifiedLastRadioButton.Checked;
  ModifiedLastOptionMenu.Enabled := ModifiedLastRadioButton.Checked;
  ModifiedNotLastSpinEdit.Enabled := ModifiedNotLastRadionButton.Checked;
  ModifiedNotLastOptionMenu.Enabled := ModifiedNotLastRadionButton.Checked;
  if not FUseGnomeWidgets then begin
    ModifiedBetweenEntry1.Enabled := ModifiedBetweenRadioButton.Checked;
    ModifiedBetweenEntry2.Enabled := ModifiedBetweenRadioButton.Checked;
    NotModifiedAfterEntry.Enabled := NotModifiedAfterRadioButton.Checked;
  end else begin
    ModifiedBetweenEntry1G.Enabled := ModifiedBetweenRadioButton.Checked;
    ModifiedBetweenEntry2G.Enabled := ModifiedBetweenRadioButton.Checked;
    NotModifiedAfterEntryG.Enabled := NotModifiedAfterRadioButton.Checked;
  end;
end;

procedure TFSearch.FileListSelectionChanged(Sender: TObject);
begin
  GoToFileButton.Enabled := Assigned(FileList.Selected) and Assigned(FileList.Selected.AsPointer(0));
  ViewButton.Enabled := Assigned(FileList.Selected) and Assigned(FileList.Selected.AsPointer(0));
end;

procedure TFSearch.NewSearchButtonClick(Sender: TObject);
var i: integer;
begin
  try
    FileMaskEntry.Entry.Text := '';
    FileMaskEntry.Entry.SetFocus;
    FileList.Items.Clear;
    if List.Count > 0 then
      for i := List.Count - 1 downto 0 do
        FreeDataItem(PDataItem(List[i]));
    List.Clear;
    StatusLabel.Caption := Format('<span weight="ultrabold">%s</span> %s', [LANGSearch_StatusSC, LANGSearch_Ready]);
    StatusLabel.UseMarkup := True;
  except
  end;
end;

procedure TFSearch.ViewButtonClick(Sender: TObject);
var AEngine: TPanelEngine;
    Plugin: TVFSPlugin;
    b: boolean;
    archive, s: string;
    i, j: integer;
begin
  if not (Assigned(FileList.Selected) and Assigned(FileList.Selected.AsPointer(0))) then Exit;
  AEngine := nil;
  if Assigned(PDataItem(FileList.Selected.AsPointer(0))^.LnkPointTo) then begin
    archive := string(PDataItem(FileList.Selected.AsPointer(0))^.LnkPointTo);
    b := False;
    s := ANSIUpperCase(Trim(Copy(archive, LastDelimiter('.', archive) + 1, Length(archive) - LastDelimiter('.', archive))));
    if (Length(s) > 1) and (s[1] = '.') then Delete(s, 1, 1);
    if PluginList.Count > 0 then begin
      Plugin := nil;
      for i := 0 to PluginList.Count - 1 do begin
        Plugin := TVFSPlugin(PluginList[i]);
        if Length(Plugin.Extensions) > 0 then
          for j := 0 to Length(Plugin.Extensions) - 1 do
            if AnsiCompareText(Plugin.Extensions[j], s) = 0 then begin
              b := True;
              Break;
            end;
        if b then Break;
      end;
      if b then begin
        DebugMsg(['Found plugin ''', Plugin.ModuleID, ''', trying to open the archive ''', archive, '''']);
        AEngine := TVFSEngine.Create(Plugin);
        b := b and ((AEngine as TVFSEngine).VFSOpenEx(archive, nil, nil, nil, nil) = 0);
      end;
    end;
  end else begin
    AEngine := TLocalTreeEngine.Create;
    b := True;
  end;
  if b then FMain.EditViewFileInternal(Self, string(PDataItem(FileList.Selected.AsPointer(0))^.FDisplayName), AEngine, True, False)
       else Application.MessageBox(Format(LANGCannotLoadFile, [string(PDataItem(FileList.Selected.AsPointer(0))^.FDisplayName)]), [mbOK], mbError, mbNone, mbOK);
  if AEngine is TVFSEngine then (AEngine as TVFSEngine).VFSClose;
  AEngine.Free;
end;

procedure TFSearch.GoToFileButtonClick(Sender: TObject);
begin
  if not (Assigned(FileList.Selected) and Assigned(FileList.Selected.AsPointer(0))) then Exit;
  GoToFile := string(PDataItem(FileList.Selected.AsPointer(0))^.FDisplayName);
  if Assigned(PDataItem(FileList.Selected.AsPointer(0))^.LnkPointTo) then
    GoToFileArchive := string(PDataItem(FileList.Selected.AsPointer(0))^.LnkPointTo);
  ModalResult := mbApply;
end;

procedure TFSearch.FileListDblClick(Sender: TObject; Button: TGDKMouseButton; Shift: TShiftState; X, Y: Integer; var Accept: boolean);
begin
  if Assigned(FileList.Selected) and Assigned(FileList.Selected.AsPointer(0)) then GoToFileButtonClick(Sender);
end;

procedure TFSearch.FileMaskEntryKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
var Orig, s: string;
    i: integer;
begin
  case Key of
    GDK_UP, GDK_DOWN: if Shift = [] then begin
                        Accept := False;
                        if SearchHistory.Count > 0 then begin
                          Orig := Trim(FileMaskEntry.Entry.Text);
                          i := SearchHistory.IndexOf(Orig);

                          if Key = GDK_DOWN then begin
                            if i < 0 then begin
                              SavedData := Orig;
                              i := 0;
                            end else
                            if SearchHistory.Count > i + 1 then Inc(i);
                            s := SearchHistory[i];
                          end else begin
                            if i < 0 then Exit else
                            if i = 0 then begin
                              s := SavedData;
                              SavedData := '';
                            end else
                            if SearchHistory.Count > i then s := SearchHistory[i - 1];
                          end;

                          FileMaskEntry.Entry.Text := s;
                          FileMaskEntry.Entry.SetFocus;
                          FileMaskEntry.Entry.SelectAll;
                        end;
                      end;
  end;
end;

procedure TFSearch.FindTextEntryKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
var Orig, s: string;
    i: integer;
begin
  case Key of
    GDK_UP, GDK_DOWN: if Shift = [] then begin
                        Accept := False;
                        if SearchTextHistory.Count > 0 then begin
                          Orig := Trim(FindTextEntry.Entry.Text);
                          i := SearchTextHistory.IndexOf(Orig);

                          if Key = GDK_DOWN then begin
                            if i < 0 then begin
                              SavedData := Orig;
                              i := 0;
                            end else
                            if SearchTextHistory.Count > i + 1 then Inc(i);
                            s := SearchTextHistory[i];
                          end else begin
                            if i < 0 then Exit else
                            if i = 0 then begin
                              s := SavedData;
                              SavedData := '';
                            end else
                            if SearchTextHistory.Count > i then s := SearchTextHistory[i - 1];
                          end;

                          FindTextEntry.Entry.Text := s;
                          FindTextEntry.Entry.SetFocus;
                          FindTextEntry.Entry.SelectAll;
                        end;
                      end;
  end;
end;


(********************************************************************************************************************************)
procedure TFSearch.DoSearch;
var FSearchThread: TSearchThread;
    i, LastItems: integer;
    ListItem: TGTKListItem;
    x: Pointer;
    OldDir: string;
begin
  try
    Processing := True;
    Stop := False;
    FindButton.Visible := False;
    StopButton.Visible := True;
    StopButton.SetFocus;
    Default := StopButton;
    StatusLabel.Caption := Format('<span weight="ultrabold">%s</span> %s', [LANGSearch_StatusSC, LANGSearch_PreparingToSearch]);
    StatusLabel.UseMarkup := True;

    //  Disable the UI
    Table1.Enabled := False;
    Table2.Enabled := False;

    //  Clear the items
    FileList.Items.Clear;
    if List.Count > 0 then
      for i := List.Count - 1 downto 0 do
        FreeDataItem(PDataItem(List[i]));
    List.Clear;

    //  Save current directory
    OldDir := Engine.GetPath;

    //  Set the parameters
    FSearchThread := TSearchThread.Create(Engine);
    with FSearchThread do begin
      FStartPath := UTF8ToStr(SearchInEntry.Text);
      FFileMask := UTF8ToStr(FileMaskEntry.Entry.Text);
      FStringFind := FindTextEntry.Entry.Text;
      FDontLeaveFS := not StayCurrentFSCheckButton.Checked;
      FCaseSensitiveMask := CaseSensitiveMatchCheckButton.Checked;
      FCaseSensitiveStrings := CaseSensitiveCheckButton.Checked;
      FSearchArchives := SearchArchivesCheckButton.Checked;
      if not BiggerThanCheckButton.Checked then FBiggerThan := -1 else begin
        i := StrToIntDef(BiggerThanEntry.Text, -1);
        i := i * Trunc(Power(1024, BiggerThanOptionMenu.ItemIndex));
        FBiggerThan := i;
      end;
      if not SmallerThanCheckButton.Checked then FSmallerThan := -1 else begin
        i := StrToIntDef(SmallerThanEntry.Text, -1);
        i := i * Trunc(Power(1024, SmallerThanOptionMenu.ItemIndex));
        FSmallerThan := i;
      end;
      FModifiedBetween1 := 0;
      FModifiedBetween2 := 0;
      FNotModifiedAfter := 0;
      if not FUseGnomeWidgets then begin
        if ModifiedBetweenRadioButton.Checked then begin
          FModifiedBetween1 := StrTotimetDef(ModifiedBetweenEntry1.Text, 0);
          FModifiedBetween2 := StrTotimetDef(ModifiedBetweenEntry2.Text, 0);
        end;
        if NotModifiedAfterRadioButton.Checked then
          FNotModifiedAfter := StrTotimetDef(NotModifiedAfterEntry.Text, 0);
      end else begin
        if ModifiedBetweenRadioButton.Checked then begin
          FModifiedBetween1 := ModifiedBetweenEntry1G.Time;
          FModifiedBetween2 := ModifiedBetweenEntry2G.Time;
        end;
        if NotModifiedAfterRadioButton.Checked then
          FNotModifiedAfter := NotModifiedAfterEntryG.Time;
      end;
      if not ModifiedLastRadioButton.Checked then FModifiedLast := -1 else begin
        i := StrToIntDef(ModifiedLastSpinEdit.Text, -1);
        case ModifiedLastOptionMenu.ItemIndex of
          1: i := i * 7;   //  weeks
          2: i := i * 30;  //  months
          3: i := i * 365; //  years
        end;
        FModifiedLast := i;
      end;
      if not ModifiedNotLastRadionButton.Checked then FModifiedNotLast := -1 else begin
        i := StrToIntDef(ModifiedNotLastSpinEdit.Text, -1);
        case ModifiedNotLastOptionMenu.ItemIndex of
          1: i := i * 7;   //  weeks
          2: i := i * 30;  //  months
          3: i := i * 365; //  years
        end;
        FModifiedNotLast := i;
      end;
    end;

    //  UI loop
    FSearchThread.Resume;
    LastItems := 0;
    repeat
      Sleep(ConstInternalProgressTimer);

      FSearchThread.GUIMutex.Acquire;
      StatusLabel.Caption := Format('<span weight="ultrabold">%s</span> %s', [LANGSearch_SearchInProgress, StrToUTF8(FSearchThread.CurrentDir)]);
      FSearchThread.GUIMutex.Release;

      StatusLabel.UseMarkup := True;
      Application.ProcessMessages;

      FSearchThread.GUIMutex.Acquire;
      if Stop then FSearchThread.CancelIt := True;
      if LastItems < FSearchThread.FList.Count then begin
        for i := LastItems to FSearchThread.FList.Count - 1 do begin
          ListItem := FileList.Items.Add;
          ListItem.SetValue(0, FSearchThread.FList[i]);
          if PDataItem(FSearchThread.FList[i])^.LnkPointTo <> nil
            then ListItem.SetValue(1, Format(ConstFullPathFormatStr, [StrToUTF8(string(PDataItem(FSearchThread.FList[i])^.LnkPointTo)), StrToUTF8(string(PDataItem(FSearchThread.FList[i])^.FDisplayName))]))
            else ListItem.SetValue(1, StrToUTF8(string(PDataItem(FSearchThread.FList[i])^.FDisplayName)));
          if i mod 30 = 0 then Application.ProcessMessages;     //  Refresh UI only after some amount of items added
        end;
        LastItems := FSearchThread.FList.Count;
      end;
      FSearchThread.GUIMutex.Release;

    until FSearchThread.Finished;

    if not Stop then StatusLabel.Caption := Format('<span weight="ultrabold">%s</span>  (' + LANGSearch_FilesFound + ')',
                                                   [LANGSearch_SearchFinished, FSearchThread.FList.Count])
                else StatusLabel.Caption := Format('<span weight="ultrabold">%s</span>', [LANGSearch_UserCancelled]);
    StatusLabel.UseMarkup := True;

    //  Save the list
    x := List;
    List := FSearchThread.FList;
    FSearchThread.FList := x;
    FSearchThread.Free;
  finally
    StopButton.Visible := False;
    FindButton.Visible := True;
    Default := FindButton;
    Processing := False;
    Stop := False;
    if FileList.Items.Count > 0 then FileList.SetFocus;
    if Engine.ChangeDir(OldDir) <> 0 then DebugMsg(['DoSearch: cannot change back to saved directory']);

    //  Enable the UI
    Table1.Enabled := True;
    Table2.Enabled := True;
  end;
end;


(********************************************************************************************************************************)
constructor TSearchThread.Create(Engine: TPanelEngine);
begin
  inherited Create(True);
  FreeOnTerminate := False;
  GUIMutex := TCriticalSection.Create;
  CancelIt := False;
  Finished := False;
  CurrentDir := '';
  FSearchArchives := False;

  FEngine := Engine;
  FRootEngine := Engine;
  FList := TList.Create; 
end;

destructor TSearchThread.Destroy;
begin
  FList.Free;
  GUIMutex.Free;
  inherited Destroy;
end;

procedure TSearchThread.Execute;
var i: integer;
    s: string;
begin
  try
    //  Prepare the wildcards
    SetLength(Wilds, 0);
    if Length(FFileMask) > 0 then begin
      while LastDelimiter(ConfSelItemsDelim, FFileMask) > 0 do begin
        i := LastDelimiter(ConfSelItemsDelim, FFileMask);
        if i < Length(FFileMask) then begin
          s := Copy(FFileMask, i + 1, Length(FFileMask) - i);
          Delete(FFileMask, i, Length(FFileMask) - i + 1);
          SetLength(Wilds, Length(Wilds) + 1);
          Wilds[Length(Wilds) - 1] := s;
        end;
      end;
      if Length(FFileMask) > 0 then begin
        SetLength(Wilds, Length(Wilds) + 1);
        Wilds[Length(Wilds) - 1] := FFileMask;
      end;
      //  Check for strings with no jokers ---> allow searching by partial names
      if Length(Wilds) > 0 then
        for i := 0 to Length(Wilds) - 1 do
          if (Pos('*', Wilds[i]) < 1) and (Pos('?', Wilds[i]) < 1) then
            Wilds[i] := Format('*%s*', [Wilds[i]]);
    end;

    Rekurze(ExcludeTrailingPathDelimiter(FStartPath));
    SetLength(Wilds, 0);
  finally
    Finished := True;
  end;
end;

procedure TSearchThread.Rekurze(StartDir: string);
var LocalList: TList;
    i, j: integer;
    Matches, b: boolean;
    FileName: string;
    Data: PDataItem;
    Plugin: TVFSPlugin;
    xEngine: TVFSEngine;
    VFSOpenResult: integer;
begin
  try
    if CancelIt then Exit;
    Plugin := nil;
    if Length(StartDir) < 1 then StartDir := '/';
    DebugMsg(['++ Entering directory ', StartDir]);

    GUIMutex.Acquire;
    CurrentDir := StartDir;
    GUIMutex.Release;

    if FEngine.ChangeDir(StartDir) <> 0 then Exit;
    LocalList := TList.Create;
    if FEngine.GetListing(LocalList, True, StartDir) = 0 then begin
    
      //  Processing...
      StartDir := IncludeTrailingPathDelimiter(StartDir);
      if LocalList.Count > 0 then
        for i := 0 to LocalList.Count - 1 do begin
          if CancelIt then Break;
          Data := LocalList[i];
//          DebugMsg(['  --- found ', Data^.AName]);

          FileName := Data^.FName;

          Matches := True;
          //  Test if the file is on the same FS
          if Matches and FDontLeaveFS then
            Matches := Matches and FEngine.IsOnSameFS(FStartPath, StartDir + FileName);
          //  File mask test
          if Matches and (Length(Wilds) > 0) then begin
            b := False;
            for j := 0 to Length(Wilds) - 1 do
              b := b or IsWild(FileName, Wilds[j], not FCaseSensitiveMask) or IsWild(Data^.FDisplayName, Wilds[j], not FCaseSensitiveMask);
            Matches := Matches and b;
          end;
          if not Data^.IsDir then begin  //  Stop matching if file is directory
            //  Size limiting
            if Matches and (FSmallerThan > 0) then Matches := Matches and (Data^.Size <= FSmallerThan);
            if Matches and (FBiggerThan > 0) then Matches := Matches and (Data^.Size >= FBiggerThan);
            //  Date limiting
            if Matches and (FModifiedLast > 0) then
              Matches := Matches and (Data^.ModifyTime <= Now) and (Data^.ModifyTime >= Now - FModifiedLast);
            if Matches and (FModifiedNotLast > 0) then
              Matches := Matches and ((Data^.ModifyTime > Now) or (Data^.ModifyTime <= Now - FModifiedNotLast));
            if Matches and (FNotModifiedAfter > 0) then
              Matches := Matches and (Data^.ModifyTime <= FNotModifiedAfter);
            if Matches and (FModifiedBetween1 > 0) and (FModifiedBetween2 > 0) then
              Matches := Matches and (Data^.ModifyTime >= FModifiedBetween1) and (Data^.ModifyTime <= FModifiedBetween2);
            //  Find text in file
            if Matches and (Length(FStringFind) > 0) and (not (FEngine is TVFSEngine)) then begin
              GUIMutex.Acquire;
              CurrentDir := IncludeTrailingPathDelimiter(StartDir) + FileName;
              Matches := Matches and FindText(CurrentDir);
              CurrentDir := ExcludeTrailingPathDelimiter(StartDir);
              GUIMutex.Release;
            end;
          end else
            if (Length(FStringFind) > 0) then Matches := False;  //  When searching text in files, do not include directories

          //  Add the record to the list
          if Matches then begin
            GUIMutex.Acquire;
            FList.Add(LocalList[i]);
	    libc_free(Data^.FDisplayName);
            PDataItem(LocalList[i])^.FDisplayName := strdup(PChar(StartDir + FileName));
            if Assigned(PDataItem(LocalList[i])^.LnkPointTo) then begin
              libc_free(PDataItem(LocalList[i])^.LnkPointTo);
              PDataItem(LocalList[i])^.LnkPointTo := nil;
            end;
            if (FEngine is TVFSEngine) and (FRootEngine <> FEngine)
              then PDataItem(LocalList[i])^.LnkPointTo := strdup(PChar((FEngine as TVFSEngine).SavePath));
            GUIMutex.Release;
          end;

          if Data^.IsDir and ((not FDontLeaveFS) or (FDontLeaveFS and FEngine.IsOnSameFS(FStartPath, StartDir + FileName)))
            then Rekurze(IncludeTrailingPathDelimiter(StartDir) + FileName);

          //  Handle archives
          if (not Data^.IsDir) and FSearchArchives and (not (FEngine is TVFSEngine)) then begin
            Plugin := FindVFSPlugin(FileName);
            if Plugin <> nil then begin
              DebugMsg(['Found plugin ''', Plugin.ModuleID, ''', trying to open the archive ''', FileName, '''']);
              xEngine := TVFSEngine.Create(Plugin);
              xEngine.ParentEngine := FEngine;
              xEngine.SavePath := StartDir + FileName;
              FEngine := xEngine;
              VFSOpenResult := (FEngine as TVFSEngine).VFSOpenEx(IncludeTrailingPathDelimiter(StartDir) + FileName, nil, nil, nil, nil);
              if (VFSOpenResult = 0) and (not CancelIt) then Rekurze('/');
              FEngine := FEngine.ParentEngine;
              if not (xEngine as TVFSEngine).VFSClose then DebugMsg(['Error closing the engine...']);
              xEngine.Free;
            end;
          end;

          if not Matches then libc_free(LocalList[i]);
        end;
    end;
    LocalList.Free;
  finally
//    DebugMsg(['-- Leaving directory ', StartDir]);
  end;
end;

function TSearchThread.FindText(FileName: string): boolean;
const BlockSize = 65536;
var fd: TEngineFileDes;
    i, Error, Read, Pos: integer;
    Buffer: PByteArray;
    x: boolean;
begin
  Result := False;
  try
    Error := 0;
    Buffer := malloc(BlockSize);
    if Buffer = nil then Exit;
    memset(Buffer, 0, BlockSize);
    fd := FEngine.OpenFile(FileName, omRead, Error);
    if (fd = nil) or (Error <> 0) then Exit;

    Pos := 1;
    repeat
      Read := FEngine.ReadFile(fd, Buffer, BlockSize, Error);
      if Read > 0 then
        for i := 0 to Read - 1 do begin
          if FCaseSensitiveStrings then x := Buffer^[i] = byte(FStringFind[Pos])
                                   else x := ANSIUpperCase(char(Buffer^[i])) = ANSIUpperCase(FStringFind[Pos]);
          if x then begin
            Inc(Pos);
            if Pos > Length(FStringFind) then begin
              Result := True;
              FEngine.CloseFile(fd);
              libc_free(Buffer);
              Exit;
            end;
          end else Pos := 1;
        end;

//      DebugMsg(['Read : ', Read, ' bytes.']);
      if CancelIt then Break;
    until Read < BlockSize;
    FEngine.CloseFile(fd);
    libc_free(Buffer);
  except
  end;
end;







end.

