diff options
| author | Tomas Bzatek <tbzatek@users.sourceforge.net> | 2008-06-07 20:34:49 +0200 |
|---|---|---|
| committer | Tomas Bzatek <tbzatek@users.sourceforge.net> | 2008-06-07 20:34:49 +0200 |
| commit | ecde167da74c86bc047aaf84c5e548cf65a5da98 (patch) | |
| tree | a015dfda84f28a65811e3aa0d369f8f211ec8c60 /USearch.pas | |
| download | tuxcmd-0.6.36.tar.xz | |
Initial commitv0.6.36release-0.6.36-dev
Diffstat (limited to 'USearch.pas')
| -rw-r--r-- | USearch.pas | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/USearch.pas b/USearch.pas new file mode 100644 index 0000000..4cd49f1 --- /dev/null +++ b/USearch.pas @@ -0,0 +1,1030 @@ +(* + 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; + +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: TGTKCombo; + SearchInEntry, FindTextEntry: 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); + 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; + 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: TDateTime; + 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, Libc, UCore, DateUtils, UViewer, UConfig, UVFSCore; + +type TFileListItem = class + CRC: LongWord; + MD5: string; + Name, FullPath: string; + Status: byte; + IsMD5: boolean; + Size: Int64; + end; + +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 := TGTKEntry.Create(Self); + FindTextEntry.Tooltip := LANGSearch_FindTextEntryTooltip; + Label3.FocusControl := FindTextEntry; + 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 := FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date); + ModifiedBetweenEntry2 := TGTKEntry.Create(Self); + ModifiedBetweenEntry2.Tooltip := FormatDateTime(LANGSearch_ModifiedBetweenEntry1, Date); + NotModifiedAfterEntry := TGTKEntry.Create(Self); + NotModifiedAfterEntry.Tooltip := 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; + + 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 AViewer: TViewerThread; + 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; + 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 + 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.VFSName, ''', trying to open the archive ''', archive, '''']); + AEngine := TVFSEngine.Create(Plugin); + (AEngine as TVFSEngine).ArchiveMode := True; + b := b and ((AEngine as TVFSEngine).VFSOpenEx(archive) = 0); + end; + end else begin + AEngine := TLocalTreeEngine.Create; + b := True; + end; + if b then FMain.EditViewFileInternal(Self, string(PDataItem(FileList.Selected.AsPointer(0))^.AName), AEngine, True, False) + else Application.MessageBox(Format(LANGCannotLoadFile, [ANSIToUTF8(string(PDataItem(FileList.Selected.AsPointer(0))^.AName))]), [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))^.AName); + 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.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 := UTF8ToANSI(SearchInEntry.Text); + FFileMask := UTF8ToANSI(FileMaskEntry.Entry.Text); + FStringFind := UTF8ToANSI(FindTextEntry.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 := -1; + FModifiedBetween2 := -1; + FNotModifiedAfter := -1; + if not FUseGnomeWidgets then begin + if ModifiedBetweenRadioButton.Checked then begin + FModifiedBetween1 := StrToDateDef(ModifiedBetweenEntry1.Text, -1); + FModifiedBetween2 := StrToDateDef(ModifiedBetweenEntry2.Text, -1); + end; + if NotModifiedAfterRadioButton.Checked then + FNotModifiedAfter := StrToDateDef(NotModifiedAfterEntry.Text, -1); + 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, 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('%s%s%s', [string(PDataItem(FSearchThread.FList[i])^.LnkPointTo), ConstPathDelim, string(PDataItem(FSearchThread.FList[i])^.AName)])) + else ListItem.SetValue(1, string(PDataItem(FSearchThread.FList[i])^.AName)); + 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, False) <> 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; + 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, k: integer; + Matches, b: boolean; + FileName, s: 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, False) <> 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^.AName; + + 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); + 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^.AName); + PDataItem(LocalList[i])^.AName := 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 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 + b := False; + s := ANSIUpperCase(Trim(Copy(FileName, LastDelimiter('.', FileName) + 1, Length(FileName) - LastDelimiter('.', FileName)))); + if (Length(s) > 1) and (s[1] = '.') then Delete(s, 1, 1); + if PluginList.Count > 0 then + for k := 0 to PluginList.Count - 1 do begin + Plugin := TVFSPlugin(PluginList[k]); + 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.VFSName, ''', trying to open the archive ''', FileName, '''']); + xEngine := TVFSEngine.Create(Plugin); + xEngine.ParentEngine := FEngine; + xEngine.ArchiveMode := True; + xEngine.SavePath := StartDir + FileName; + FEngine := xEngine; + VFSOpenResult := (FEngine as TVFSEngine).VFSOpenEx(IncludeTrailingPathDelimiter(StartDir) + FileName); + 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 := Libc.malloc(BlockSize); + if Buffer = nil then Exit; + Libc.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. + |
