(* Tux Commander - USearch - Search dialog Copyright (C) 2008 Tomas Bzatek 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 SyncObjs, SysUtils, Classes, lazglib2, lazgdk3, lazgtk3, GTKControls, GTKForms, GTKStdCtrls, GTKExtCtrls, GTKView, UEngines, ULibc; type TFSearch = class(TGTKDialog) BottomBox: TGTKVBox; HBox, HBox2: TGTKHBox; ButtonBox: TGTKHButtonBox; FileList: TGTKListView; FileListScrolledWindow: TGTKScrolledWindow; Notebook: TGTKNotebook; Grid1, Grid2: TGTKGrid; ResultsLabel, StatusLabel: TGTKLabel; Label1, Label2, Label3, Label4, Label5, Label6: TGTKLabel; FileMaskEntry, FindTextEntry: TGTKComboBoxEntry; SearchInEntry: TGTKEntry; ViewButton, NewSearchButton, GoToFileButton, FeedToListboxButton: TGTKButton; SearchArchivesCheckButton, CaseSensitiveCheckButton, StayCurrentFSCheckButton, CaseSensitiveMatchCheckButton: TGTKCheckButton; FindButton, StopButton, CloseButton: TGTKButton; BiggerThanCheckButton, SmallerThanCheckButton: TGTKCheckButton; BiggerThanEntry, SmallerThanEntry: TGTKSpinEdit; BiggerThanComboBox, SmallerThanComboBox, ModifiedLastComboBox, ModifiedNotLastComboBox: TGTKComboBoxText; ModifiedBetweenRadioButton, NotModifiedAfterRadioButton, ModifiedLastRadioButton, ModifiedNotLastRadionButton: TGTKCheckButton; ModifiedLastSpinEdit, ModifiedNotLastSpinEdit: TGTKSpinEdit; ModifiedBetweenEntry1, ModifiedBetweenEntry2, NotModifiedAfterEntry: TGTKEntry; 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: 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; FFinished: boolean; procedure DoRecurse(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 CancelIt: boolean; CurrentDir: string; constructor Create(Engine: TPanelEngine); destructor Destroy; override; end; var FSearch: TFSearch; implementation uses Math, UMain, ULocale, UCoreUtils, UCore, DateUtils, UConfig, UVFSCore, UFileAssoc; var SizeUnits: array[0..2] of string; DayUnits: array[0..3] of string; procedure TFSearch.FormCreate(Sender: TObject); var i: integer; HSeparator: TGTKHSeparator; VBox: TGTKVBox; 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 List := TList.Create; List.Clear; Processing := False; Stop := False; GoToFile := ''; GoToFileArchive := ''; SetDefaultSize(650, 600); Caption := LANGSearch_Caption; Buttons := []; Notebook := TGTKNotebook.Create(Self); Notebook.BorderWidth := 10; Grid1 := TGTKGrid.Create(Self); Grid1.BorderWidth := 10; Grid2 := TGTKGrid.Create(Self); Grid2.BorderWidth := 10; Notebook.AppendPage(Grid1, LANGSearch_General); Notebook.AppendPage(Grid2, LANGSearch_Advanced); ConstructViews; ResultsLabel := TGTKLabel.Create(Self); ResultsLabel.XAlign := 0; ResultsLabel.Caption := Format('%s', [LANGSearch_SearchResults]); ResultsLabel.FocusControl := FileList; ResultsLabel.UseMarkup := True; ResultsLabel.UseUnderline := True; ClientArea.AddControlEx(Notebook, False, False, 0); Label1 := TGTKLabel.Create(Self); Label1.Caption := Format('%s', [LANGSearch_SearchFor]); Label1.XAlign := 0; Label1.MarginStart := 5; Label1.MarginEnd := 5; Label1.MarginTop := 2; Label1.MarginBottom := 2; FileMaskEntry := TGTKComboBoxEntry.Create(Self); FileMaskEntry.Tooltip := LANGSearch_FileMaskEntryTooltip; FileMaskEntry.Entry.OnKeyDown := @FileMaskEntryKeyDown; if SearchHistory.Count > 0 then for i := 0 to SearchHistory.Count - 1 do FileMaskEntry.AppendItem(SearchHistory[i]); FileMaskEntry.Entry.Text := ''; FileMaskEntry.MarginStart := 5; FileMaskEntry.MarginEnd := 5; FileMaskEntry.MarginTop := 2; FileMaskEntry.MarginBottom := 2; Label1.FocusControl := FileMaskEntry.Entry; Label1.UseMarkup := True; Label1.UseUnderline := True; Label2 := TGTKLabel.Create(Self); Label2.Caption := LANGSearch_SearchIn; Label2.XAlign := 0; Label2.MarginStart := 5; Label2.MarginEnd := 5; Label2.MarginTop := 2; Label2.MarginBottom := 2; SearchInEntry := TGTKEntry.Create(Self); SearchInEntry.MarginStart := 5; SearchInEntry.MarginEnd := 5; SearchInEntry.MarginTop := 2; SearchInEntry.MarginBottom := 2; Label2.FocusControl := SearchInEntry; Label2.UseMarkup := True; Label2.UseUnderline := True; SearchArchivesCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_SearchArchivesCheckButton); SearchArchivesCheckButton.Enabled := PluginList.Count > 0; SearchArchivesCheckButton.MarginStart := 5; SearchArchivesCheckButton.MarginEnd := 5; SearchArchivesCheckButton.MarginTop := 2; SearchArchivesCheckButton.MarginBottom := 2; Label3 := TGTKLabel.Create(Self); Label3.Caption := LANGSearch_FindText; Label3.XAlign := 0; Label3.MarginStart := 5; Label3.MarginEnd := 5; Label3.MarginTop := 2; Label3.MarginBottom := 2; FindTextEntry := TGTKComboBoxEntry.Create(Self); FindTextEntry.Tooltip := LANGSearch_FindTextEntryTooltip; FindTextEntry.Entry.OnKeyDown := @FindTextEntryKeyDown; if SearchTextHistory.Count > 0 then for i := 0 to SearchTextHistory.Count - 1 do FindTextEntry.AppendItem(SearchTextHistory[i]); FindTextEntry.Entry.Text := ''; FindTextEntry.MarginStart := 5; FindTextEntry.MarginEnd := 5; FindTextEntry.MarginTop := 2; FindTextEntry.MarginBottom := 2; Label3.FocusControl := FindTextEntry.Entry; Label3.UseMarkup := True; Label3.UseUnderline := True; CaseSensitiveCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_CaseSensitiveCheckButton); CaseSensitiveCheckButton.MarginStart := 5; CaseSensitiveCheckButton.MarginEnd := 5; CaseSensitiveCheckButton.MarginTop := 2; CaseSensitiveCheckButton.MarginBottom := 2; StayCurrentFSCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_StayCurrentFSCheckButton); StayCurrentFSCheckButton.MarginStart := 5; StayCurrentFSCheckButton.MarginEnd := 5; StayCurrentFSCheckButton.MarginTop := 2; StayCurrentFSCheckButton.MarginBottom := 2; CaseSensitiveMatchCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_CaseSensitiveMatchCheckButton); CaseSensitiveMatchCheckButton.MarginStart := 5; CaseSensitiveMatchCheckButton.MarginEnd := 5; CaseSensitiveMatchCheckButton.MarginTop := 2; CaseSensitiveMatchCheckButton.MarginBottom := 2; HSeparator := TGTKHSeparator.Create(Self); HSeparator.MarginStart := 5; HSeparator.MarginEnd := 5; HSeparator.MarginTop := 4; HSeparator.MarginBottom := 4; VBox := TGTKVBox.Create(Self); VBox.MarginStart := 5; VBox.MarginEnd := 5; VBox.MarginTop := 2; VBox.MarginBottom := 2; Grid1.AddControl(0, 0, 1, 1, Label1); Grid1.AddControl(1, 0, 1, 1, FileMaskEntry); Grid1.AddControl(2, 0, 1, 1, CaseSensitiveMatchCheckButton); Grid1.AddControl(0, 1, 1, 1, Label2); Grid1.AddControl(1, 1, 1, 1, SearchInEntry); Grid1.AddControl(2, 1, 1, 1, StayCurrentFSCheckButton); Grid1.AddControl(1, 2, 2, 1, SearchArchivesCheckButton); Grid1.AddControl(0, 3, 3, 1, HSeparator); Grid1.AddControl(0, 4, 1, 1, Label3); Grid1.AddControl(1, 4, 2, 1, FindTextEntry); Grid1.AddControl(1, 5, 2, 1, CaseSensitiveCheckButton); Grid1.AddControl(0, 6, 3, 1, VBox); Label4 := TGTKLabel.Create(Self); Label4.Caption := Format('%s', [LANGSearch_Size]); Label4.UseMarkup := True; Label4.XAlign := 0; Label4.MarginStart := 5; Label4.MarginEnd := 5; Label5 := TGTKLabel.Create(Self); Label5.Caption := Format('%s', [LANGSearch_Date]); Label5.UseMarkup := True; Label5.XAlign := 0; Label5.MarginStart := 5; Label5.MarginEnd := 5; Label5.MarginTop := 1; Label5.MarginBottom := 1; BiggerThanCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_BiggerThan); BiggerThanCheckButton.MarginStart := 2; BiggerThanCheckButton.MarginEnd := 2; SmallerThanCheckButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_SmallerThan); SmallerThanCheckButton.MarginStart := 5; SmallerThanCheckButton.MarginEnd := 5; BiggerThanEntry := TGTKSpinEdit.Create(Self); BiggerThanEntry.Min := 0; BiggerThanEntry.Max := 999999999; BiggerThanEntry.Digits := 0; BiggerThanEntry.IncrementStep := 1; BiggerThanEntry.IncrementPage := 10; BiggerThanEntry.MarginStart := 5; BiggerThanEntry.MarginEnd := 5; SmallerThanEntry := TGTKSpinEdit.Create(Self); SmallerThanEntry.Min := 0; SmallerThanEntry.Max := 999999999; SmallerThanEntry.Digits := 0; SmallerThanEntry.IncrementStep := 1; SmallerThanEntry.IncrementPage := 10; SmallerThanEntry.MarginStart := 5; SmallerThanEntry.MarginEnd := 5; BiggerThanComboBox := TGTKComboBoxText.Create(Self); SmallerThanComboBox := TGTKComboBoxText.Create(Self); for i := Low(SizeUnits) to High(SizeUnits) do begin BiggerThanComboBox.AppendItem(SizeUnits[i]); SmallerThanComboBox.AppendItem(SizeUnits[i]); end; BiggerThanComboBox.ItemIndex := 0; SmallerThanComboBox.ItemIndex := 0; ModifiedBetweenRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedBetweenRadioButton); ModifiedBetweenRadioButton.MarginStart := 2; ModifiedBetweenRadioButton.MarginEnd := 2; ModifiedBetweenRadioButton.MarginTop := 2; ModifiedBetweenRadioButton.MarginBottom := 2; NotModifiedAfterRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_NotModifiedAfterRadioButton); NotModifiedAfterRadioButton.MarginStart := 2; NotModifiedAfterRadioButton.MarginEnd := 2; NotModifiedAfterRadioButton.MarginTop := 2; NotModifiedAfterRadioButton.MarginBottom := 2; ModifiedLastRadioButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedLastRadioButton); ModifiedLastRadioButton.MarginStart := 2; ModifiedLastRadioButton.MarginEnd := 2; ModifiedLastRadioButton.MarginTop := 2; ModifiedLastRadioButton.MarginBottom := 2; ModifiedNotLastRadionButton := TGTKCheckButton.CreateWithLabel(Self, LANGSearch_ModifiedNotLastRadionButton); ModifiedNotLastRadionButton.MarginStart := 5; ModifiedNotLastRadionButton.MarginEnd := 5; ModifiedNotLastRadionButton.MarginTop := 2; ModifiedNotLastRadionButton.MarginBottom := 2; ModifiedLastSpinEdit := TGTKSpinEdit.Create(Self); ModifiedLastSpinEdit.Min := 0; ModifiedLastSpinEdit.Max := 10000; ModifiedLastSpinEdit.Digits := 0; ModifiedLastSpinEdit.IncrementStep := 1; ModifiedLastSpinEdit.IncrementPage := 10; ModifiedLastSpinEdit.MarginStart := 5; ModifiedLastSpinEdit.MarginEnd := 5; ModifiedLastSpinEdit.MarginTop := 2; ModifiedLastSpinEdit.MarginBottom := 2; ModifiedNotLastSpinEdit := TGTKSpinEdit.Create(Self); ModifiedNotLastSpinEdit.Min := 0; ModifiedNotLastSpinEdit.Max := 10000; ModifiedNotLastSpinEdit.Digits := 0; ModifiedNotLastSpinEdit.IncrementStep := 1; ModifiedNotLastSpinEdit.IncrementPage := 10; ModifiedNotLastSpinEdit.MarginStart := 5; ModifiedNotLastSpinEdit.MarginEnd := 5; ModifiedNotLastSpinEdit.MarginTop := 2; ModifiedNotLastSpinEdit.MarginBottom := 2; ModifiedLastComboBox := TGTKComboBoxText.Create(Self); ModifiedNotLastComboBox := TGTKComboBoxText.Create(Self); for i := Low(DayUnits) to High(DayUnits) do begin ModifiedLastComboBox.AppendItem(DayUnits[i]); ModifiedNotLastComboBox.AppendItem(DayUnits[i]); end; ModifiedLastComboBox.MarginStart := 2; ModifiedLastComboBox.MarginEnd := 2; ModifiedLastComboBox.MarginTop := 2; ModifiedLastComboBox.MarginBottom := 2; ModifiedNotLastComboBox.MarginTop := 2; ModifiedNotLastComboBox.MarginBottom := 2; Label6 := TGTKLabel.Create(Self); Label6.Caption := LANGSearch_And; Label6.UseMarkup := True; Label6.XAlign := 0; Label6.MarginStart := 5; Label6.MarginEnd := 5; Label6.MarginTop := 2; Label6.MarginBottom := 2; ModifiedBetweenEntry1 := TGTKEntry.Create(Self); ModifiedBetweenEntry1.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date); ModifiedBetweenEntry1.MarginStart := 5; ModifiedBetweenEntry1.MarginEnd := 5; ModifiedBetweenEntry1.MarginTop := 2; ModifiedBetweenEntry1.MarginBottom := 2; ModifiedBetweenEntry2 := TGTKEntry.Create(Self); ModifiedBetweenEntry2.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date); ModifiedBetweenEntry2.MarginStart := 5; ModifiedBetweenEntry2.MarginEnd := 5; ModifiedBetweenEntry2.MarginTop := 2; ModifiedBetweenEntry2.MarginBottom := 2; NotModifiedAfterEntry := TGTKEntry.Create(Self); NotModifiedAfterEntry.Tooltip := SysUtils.FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date); NotModifiedAfterEntry.MarginStart := 5; NotModifiedAfterEntry.MarginEnd := 5; NotModifiedAfterEntry.MarginTop := 2; NotModifiedAfterEntry.MarginBottom := 2; Grid2.AddControl(2, 5, 2, 1, ModifiedBetweenEntry1); Grid2.AddControl(5, 5, 2, 1, ModifiedBetweenEntry2); Grid2.AddControl(2, 6, 2, 1, NotModifiedAfterEntry); VBox := TGTKVBox.Create(Self); VBox.MarginStart := 8; VBox.MarginEnd := 8; Grid2.AddControl(0, 0, 3, 1, Label4); Grid2.AddControl(0, 1, 1, 1, VBox); Grid2.AddControl(1, 1, 1, 1, BiggerThanCheckButton); Grid2.AddControl(2, 1, 1, 1, BiggerThanEntry); Grid2.AddControl(3, 1, 1, 1, BiggerThanComboBox); Grid2.AddControl(5, 1, 1, 1, SmallerThanCheckButton); Grid2.AddControl(6, 1, 1, 1, SmallerThanEntry); Grid2.AddControl(7, 1, 1, 1, SmallerThanComboBox); VBox := TGTKVBox.Create(Self); VBox.MarginTop := 6; VBox.MarginBottom := 6; Grid2.AddControl(1, 3, 3, 1, VBox); Grid2.AddControl(0, 4, 3, 1, Label5); Grid2.AddControl(1, 5, 1, 1, ModifiedBetweenRadioButton); Grid2.AddControl(4, 5, 1, 1, Label6); Grid2.AddControl(1, 6, 1, 1, NotModifiedAfterRadioButton); Grid2.AddControl(1, 7, 1, 1, ModifiedLastRadioButton); Grid2.AddControl(2, 7, 1, 1, ModifiedLastSpinEdit); Grid2.AddControl(3, 7, 1, 1, ModifiedLastComboBox); Grid2.AddControl(4, 7, 2, 1, ModifiedNotLastRadionButton); Grid2.AddControl(6, 7, 1, 1, ModifiedNotLastSpinEdit); Grid2.AddControl(7, 7, 1, 1, ModifiedNotLastComboBox); 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.Caption := Format('%s %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.CreateFromIconName(Self, GTK_STOCK_FIND); FindButton.Caption := 'Find'; FindButton.Default := True; StopButton := TGTKButton.CreateFromIconName(Self, GTK_STOCK_STOP); StopButton.Caption := 'Stop'; StopButton.Visible := False; StopButton.Default := True; CloseButton := TGTKButton.CreateFromIconName(Self, GTK_STOCK_CLOSE); CloseButton.Caption := '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_KEY_F3: if FileList.Focused and Assigned(FileList.Selected) then begin Accept := False; ViewButtonClick(Sender); end; GDK_KEY_Return, GDK_KEY_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_KEY_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); FileMaskEntry.ClearItems(); if SearchHistory.Count > 0 then for i := 0 to SearchHistory.Count - 1 do FileMaskEntry.AppendItem(SearchHistory[i]); end; s := Trim(FindTextEntry.Entry.Text); if Length(s) > 0 then begin SaveItemToHistory(s, SearchTextHistory); FindTextEntry.ClearItems(); if SearchTextHistory.Count > 0 then for i := 0 to SearchTextHistory.Count - 1 do FindTextEntry.AppendItem(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; BiggerThanComboBox.Enabled := BiggerThanCheckButton.Checked; SmallerThanEntry.Enabled := SmallerThanCheckButton.Checked; SmallerThanComboBox.Enabled := SmallerThanCheckButton.Checked; Label6.Enabled := ModifiedBetweenRadioButton.Checked; ModifiedLastSpinEdit.Enabled := ModifiedLastRadioButton.Checked; ModifiedLastComboBox.Enabled := ModifiedLastRadioButton.Checked; ModifiedNotLastSpinEdit.Enabled := ModifiedNotLastRadionButton.Checked; ModifiedNotLastComboBox.Enabled := ModifiedNotLastRadionButton.Checked; ModifiedBetweenEntry1.Enabled := ModifiedBetweenRadioButton.Checked; ModifiedBetweenEntry2.Enabled := ModifiedBetweenRadioButton.Checked; NotModifiedAfterEntry.Enabled := NotModifiedAfterRadioButton.Checked; 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('%s %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, nil); end; end; end else begin AEngine := TLocalTreeEngine.Create; b := True; end; if b then FMain.EditViewFileInternal(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(nil); 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_KEY_Up, GDK_KEY_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_KEY_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_KEY_Up, GDK_KEY_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_KEY_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('%s %s', [LANGSearch_StatusSC, LANGSearch_PreparingToSearch]); StatusLabel.UseMarkup := True; // Disable the UI Grid1.Enabled := False; Grid2.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, BiggerThanComboBox.ItemIndex)); FBiggerThan := i; end; if not SmallerThanCheckButton.Checked then FSmallerThan := -1 else begin i := StrToIntDef(SmallerThanEntry.Text, -1); i := i * Trunc(Power(1024, SmallerThanComboBox.ItemIndex)); FSmallerThan := i; end; FModifiedBetween1 := 0; FModifiedBetween2 := 0; FNotModifiedAfter := 0; 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); if not ModifiedLastRadioButton.Checked then FModifiedLast := -1 else begin i := StrToIntDef(ModifiedLastSpinEdit.Text, -1); case ModifiedLastComboBox.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 ModifiedNotLastComboBox.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('%s %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.FFinished; if not Stop then StatusLabel.Caption := Format('%s (' + LANGSearch_FilesFound + ')', [LANGSearch_SearchFinished, FSearchThread.FList.Count]) else StatusLabel.Caption := Format('%s', [LANGSearch_UserCancelled]); StatusLabel.UseMarkup := True; // Save the list x := List; List := FSearchThread.FList; FSearchThread.FList := TList(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 not Engine.ChangeDir(OldDir, nil) then DebugMsg(['DoSearch: cannot change back to saved directory']); // Enable the UI Grid1.Enabled := True; Grid2.Enabled := True; end; end; (********************************************************************************************************************************) constructor TSearchThread.Create(Engine: TPanelEngine); begin inherited Create(True); FreeOnTerminate := False; GUIMutex := TCriticalSection.Create; CancelIt := False; FFinished := 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; DoRecurse(ExcludeTrailingPathDelimiter(FStartPath)); SetLength(Wilds, 0); finally FFinished := True; end; end; procedure TSearchThread.DoRecurse(StartDir: string); var LocalList: TList; i, j: integer; Matches, b: boolean; FileName: string; Data: PDataItem; Plugin: TVFSPlugin; xEngine: TVFSEngine; VFSOpenResult: boolean; begin try if CancelIt then Exit; if Length(StartDir) < 1 then StartDir := '/'; // DebugMsg(['++ Entering directory ', StartDir]); GUIMutex.Acquire; CurrentDir := StartDir; GUIMutex.Release; if not FEngine.ChangeDir(StartDir, nil) then Exit; LocalList := TList.Create; if FEngine.GetListing(LocalList, StartDir, True, True, False, nil) 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, True); // 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^.mtime <= Now) and (Data^.mtime >= Now - FModifiedLast); if Matches and (FModifiedNotLast > 0) then Matches := Matches and ((Data^.mtime > Now) or (Data^.mtime <= Now - FModifiedNotLast)); if Matches and (FNotModifiedAfter > 0) then Matches := Matches and (Data^.mtime <= FNotModifiedAfter); if Matches and (FModifiedBetween1 > 0) and (FModifiedBetween2 > 0) then Matches := Matches and (Data^.mtime >= FModifiedBetween1) and (Data^.mtime <= 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(DuplicateDataItem(Data)); libc_free(PDataItem(FList[FList.Count - 1])^.FDisplayName); PDataItem(FList[FList.Count - 1])^.FDisplayName := g_strdup(PChar(StartDir + FileName)); if Assigned(PDataItem(FList[FList.Count - 1])^.LnkPointTo) then begin libc_free(PDataItem(FList[FList.Count - 1])^.LnkPointTo); PDataItem(FList[FList.Count - 1])^.LnkPointTo := nil; end; if (FEngine is TVFSEngine) and (FRootEngine <> FEngine) then PDataItem(FList[FList.Count - 1])^.LnkPointTo := g_strdup(PChar((FEngine as TVFSEngine).SavePath)); GUIMutex.Release; end; if Data^.IsDir and ((not FDontLeaveFS) or (FDontLeaveFS and FEngine.IsOnSameFS(FStartPath, StartDir + FileName, True))) then DoRecurse(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, nil); if VFSOpenResult and (not CancelIt) then DoRecurse('/'); FEngine := FEngine.ParentEngine; if not (xEngine as TVFSEngine).VFSClose(nil) then DebugMsg(['Error closing the engine...']); xEngine.Free; end; end; FreeDataItem(PDataItem(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, Read, Pos: integer; Buffer: PByteArray; x: boolean; begin Result := False; try Buffer := libc_malloc(BlockSize); if Buffer = nil then Exit; memset(Buffer, 0, BlockSize); fd := FEngine.OpenFile(FileName, omRead, nil); if fd = nil then Exit; Pos := 1; repeat Read := FEngine.ReadFile(fd, Buffer, BlockSize, nil); 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; Break; end; end else Pos := 1; end; // DebugMsg(['Read : ', Read, ' bytes.']); if CancelIt then Break; until (Read < BlockSize) or Result; FEngine.CloseFile(fd, nil); libc_free(Buffer); except end; end; end.