lazarus/ide/searchresultview.pp

1875 lines
56 KiB
ObjectPascal

{
/***************************************************************************
searchresultviewView.pp - SearchResult view
-------------------------------------------
TSearchResultsView is responsible for displaying the
Search Results of a find operation.
Initial Revision : Sat Nov 8th 2003
***************************************************************************/
***************************************************************************
* *
* This source 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 code 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. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
}
unit SearchResultView;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Math, StrUtils, AVL_Tree,
// LCL
LCLProc, LCLType, Forms, Controls, Graphics, ComCtrls, Menus, Clipbrd,
ActnList, ExtCtrls, Dialogs, LCLIntf,
// LazControls
TreeFilterEdit, ExtendedNotebook,
// LazUtils
LazUTF8, LazUtilities, LazFileUtils, LazLoggerBase, LazTracer, LazStringUtils,
// IdeIntf
IDEImagesIntf, IDECommands, InputHistory,
// IdeConfig
IDEOptionDefs,
// IDE
LazarusIDEStrConsts, Project, MainIntf, EnvGuiOptions;
type
{ TLazSearchMatchPos }
TLazSearchMatchPos = class(TObject)
private
FFileEndPos: TPoint;
FFilename: string;
FFileStartPos: TPoint;
fMatchStart: integer;
fMatchLen: integer;
FNextInThisLine: TLazSearchMatchPos;
FShownFilename: string;
FTheText: string;
public
property MatchStart: integer read fMatchStart write fMatchStart;// start in TheText
property MatchLen: integer read fMatchLen write fMatchLen; // length in TheText
property Filename: string read FFilename write FFilename;
property FileStartPos: TPoint read FFileStartPos write FFileStartPos;
property FileEndPos: TPoint read FFileEndPos write FFileEndPos;
property TheText: string read FTheText write FTheText;
property ShownFilename: string read FShownFilename write FShownFilename;
property NextInThisLine: TLazSearchMatchPos read FNextInThisLine write FNextInThisLine;
destructor Destroy; override;
end;
{ TLazSearch }
TLazSearch = Class(TObject)
private
FReplaceText: string;
fSearchString: string;
fSearchOptions: TLazFindInFileSearchOptions;
fSearchDirectories: string;
fSearchMask: string;
ftabEllipsed: boolean;
public
property SearchString: string read fSearchString write fSearchString;
property ReplaceText: string read FReplaceText write FReplaceText;
property SearchOptions: TLazFindInFileSearchOptions read fSearchOptions
write fSearchOptions;
property SearchDirectories: string read fSearchDirectories
write fSearchDirectories;
property SearchMask: string read fSearchMask write fSearchMask;
property TabEllipsed: boolean read ftabEllipsed write ftabEllipsed;
end;
{ TLazSearchResultTV }
TLazSearchResultTV = class(TCustomTreeView)
private
fSearchObject: TLazSearch;
FSkipped: integer;
fUpdateStrings: TStringList;
fUpdating: boolean;
fUpdateCount: integer;
FSearchInListPhrases: string;
fFiltered: Boolean;
fFilenameToNode: TAvlTree; // TTreeNode sorted for Text
procedure SetSkipped(const AValue: integer);
procedure AddNode(Line: string; MatchPos: TLazSearchMatchPos);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property SearchObject: TLazSearch read fSearchObject write fSearchObject;
procedure BeginUpdate;
procedure EndUpdate;
procedure ShortenPaths;
function BeautifyLineAt(SearchPos: TLazSearchMatchPos): string;
property Filtered: Boolean read fFiltered write fFiltered;
property SearchInListPhrases: string read FSearchInListPhrases write FSearchInListPhrases;
property UpdateItems: TStringList read fUpdateStrings write fUpdateStrings;
property Updating: boolean read fUpdating;
property Skipped: integer read FSkipped write SetSkipped;
function ItemsAsStrings: TStrings;
property ShowLines;
property ScrolledLeft;
property ScrolledTop;
end;
TSVCloseButtonsState = (
svcbNone,
svcbEnable,
svcbDisable
);
{ TSearchResultsView }
TSearchResultsView = class(TForm)
actClosePage: TAction;
actCloseLeft: TAction;
actCloseOthers: TAction;
actCloseRight: TAction;
actCloseAll: TAction;
ActionList: TActionList;
ClosePageButton: TToolButton;
ControlBar1: TPanel;
MenuItem1: TMenuItem;
mniPathAbsolute: TMenuItem;
mniPathRelative: TMenuItem;
mniPathFileName: TMenuItem;
mniCollapseAll: TMenuItem;
mniExpandAll: TMenuItem;
mniCopySelected: TMenuItem;
mniCopyAll: TMenuItem;
mniCopyItem: TMenuItem;
pnlToolBars: TPanel;
popList: TPopupMenu;
popShowPath: TPopupMenu;
ResultsNoteBook: TExtendedNotebook;
tbbCloseLeft: TToolButton;
tbbCloseOthers: TToolButton;
tbbCloseRight: TToolButton;
PageToolBar: TToolBar;
CloseTabs: TToolBar;
RefreshButton: TToolButton;
SearchAgainButton: TToolButton;
SearchInListEdit: TTreeFilterEdit;
ShowPathButton: TToolButton;
ToolButton1: TToolButton;
ToolButton2: TToolButton;
ToolButton3: TToolButton;
tbbCloseAll: TToolButton;
procedure RefreshButtonClick(Sender: TObject);
procedure ResultsNoteBookMouseMove(Sender: TObject; {%H-}Shift: TShiftState; X,
Y: Integer);
procedure SearchAgainButtonClick(Sender: TObject);
procedure ClosePageButtonClick(Sender: TObject);
procedure ResultsNoteBookResize(Sender: TObject);
procedure mniShowPathClick(Sender: TObject);
procedure tbbCloseAllClick(Sender: TObject);
procedure tbbCloseLeftClick(Sender: TObject);
procedure tbbCloseOthersClick(Sender: TObject);
procedure tbbCloseRightClick(Sender: TObject);
procedure Form1Create(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
procedure mniCopyAllClick(Sender: TObject);
procedure mniCopyItemClick(Sender: TObject);
procedure mniCopySelectedClick(Sender: TObject);
procedure mniExpandAllClick(Sender: TObject);
procedure mniCollapseAllClick(Sender: TObject);
procedure ResultsNoteBookChanging(Sender: TObject; var {%H-}AllowChange: Boolean);
procedure ResultsNoteBookMouseDown(Sender: TObject; Button: TMouseButton;
{%H-}Shift: TShiftState; X, Y: Integer);
procedure ResultsNoteBookCloseTabClick(Sender: TObject);
procedure TreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
var {%H-}PaintImages, {%H-}DefaultDraw: Boolean);
procedure LazTVShowHint(Sender: TObject; {%H-}HintInfo: PHintInfo);
procedure LazTVMousemove(Sender: TObject; {%H-}Shift: TShiftState;
X, Y: Integer);
Procedure LazTVMouseWheel(Sender: TObject; Shift: TShiftState;
{%H-}WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
procedure ResultsNoteBookPageChanged (Sender: TObject );
procedure SearchInListChange(Sender: TObject );
procedure TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
type
TOnSide = (osLeft, osOthers, osRight); { Handling of multi tab closure }
private
FAsyncUpdateCloseButtons: TSVCloseButtonsState;
FMaxItems: integer;
FFocusTreeViewInOnChange: Boolean;
FFocusTreeViewInEndUpdate: Boolean;
FOnSelectionChanged: TNotifyEvent;
FMouseOverIndex: integer;
FClosingTabs: boolean;
FNoteBookTab: integer;
function IsBackup(const aFullFilePath: string): boolean;
function BeautifyPageName(const APageName: string; out aoTabEllipsed : boolean): string;
function GetPageIndex(const APageName: string): integer;
function GetTreeView(APageIndex: integer): TLazSearchResultTV;
function GetCurrentTree: TLazSearchResultTV;
procedure SetAsyncUpdateCloseButtons(const AValue: TSVCloseButtonsState);
procedure SetItems(Index: Integer; Value: TStrings);
function GetItems(Index: integer): TStrings;
procedure SetMaxItems(const AValue: integer);
procedure UpdateToolbar;
function GetPagesOnActiveLine(aOnSide : TOnSide = osOthers):TFPlist;
procedure ClosePageOnSides(aOnSide : TOnSide);
procedure ClosePageBegin;
procedure ClosePageEnd;
procedure DoAsyncUpdateCloseButtons({%H-}Data: PtrInt);
procedure NoteBookShowHint(Sender: TObject; {%H-}HintInfo: PHintInfo);
protected
procedure Loaded; override;
procedure ActivateControl(aWinControl: TWinControl);
procedure UpdateShowing; override;
property AsyncUpdateCloseButtons: TSVCloseButtonsState read FAsyncUpdateCloseButtons write SetAsyncUpdateCloseButtons;
public
function AddSearch(const aSearchText: string;
const aReplaceText: string;
const aDirectories: string;
const aFileMask: string;
const aOptions: TLazFindInFileSearchOptions): TTabSheet;
function GetSourcePositon: TPoint;
function GetSourceFileName: string;
function GetSelectedText: string;
function GetSelectedMatchPos: TLazSearchMatchPos;
function SelectNextMatchPos(DirectionDown: boolean): boolean;
procedure AddMatch(const APageIndex: integer;
const Filename: string; const StartPos, EndPos: TPoint;
const TheText: string;
const MatchStart: integer; const MatchLen: integer);
procedure BeginUpdate(APageIndex: integer);
procedure EndUpdate(APageIndex: integer; APageName: string = '');
procedure ClosePage(PageIndex: integer);
property MaxItems: integer read FMaxItems write SetMaxItems;
property OnSelectionChanged: TNotifyEvent read fOnSelectionChanged
write fOnSelectionChanged;
property Items[Index: integer]: TStrings read GetItems write SetItems;
function GetResultsPage(aIndex: integer): TTabSheet;
end;
var
SearchResultsView: TSearchResultsView = nil;
OnSearchResultsViewSelectionChanged: TNotifyEvent = nil;
implementation
{$R *.lfm}
function CompareTVNodeTextAsFilename(Node1, Node2: Pointer): integer;
var
TVNode1: TTreeNode absolute Node1;
TVNode2: TTreeNode absolute Node2;
begin
Result:=CompareFilenames(TVNode1.Text,TVNode2.Text);
end;
function CompareFilenameWithTVNode(Filename, Node: Pointer): integer;
var
aFilename: String;
TVNode: TTreeNode absolute Node;
begin
aFilename:=String(Filename);
Result:=CompareFilenames(aFilename,TVNode.Text);
end;
function GetTreeSelectedItemsAsText(ATreeView: TCustomTreeView): string;
var
lList: TStringList;
lNode: TTreeNode;
begin
lList := TStringList.Create;
try
lNode := ATreeView.GetFirstMultiSelected;
while lNode <> nil do
begin
lList.Add(lNode.Text);
lNode := lNode.GetNextMultiSelected;
end;
result := lList.Text;
finally
lList.Free;
end;
end;
{ TSearchResultsView }
procedure TSearchResultsView.Form1Create(Sender: TObject);
var
CloseCommand: TIDECommand;
begin
Name := NonModalIDEWindowNames[nmiwSearchResultsView];
FMaxItems := 50000;
ResultsNoteBook.Options := ResultsNoteBook.Options + [nboShowCloseButtons];
ResultsNoteBook.Update;
CloseCommand := IDECommandList.FindIDECommand(ecClose);
if CloseCommand <> nil then
begin
if CloseCommand.AsShortCut <> 0 then
actClosePage.ShortCut := CloseCommand.AsShortCut;
if (CloseCommand.ShortcutB.Key1 <> 0) and (CloseCommand.ShortcutB.Key2 = 0) then
actClosePage.SecondaryShortCuts.Append(
ShortCutToText(
ShortCut(CloseCommand.ShortcutB.Key1, CloseCommand.ShortcutB.Shift1)
)
);
end;
fOnSelectionChanged:= nil;
fMouseOverIndex:= -1;
// hints
ShowHint:= True;
// Notebook
FNoteBookTab := -1;
ResultsNoteBook.OnMouseMove := @ResultsNoteBookMouseMove;
ResultsNoteBook.OnShowHint := @NoteBookShowHint;
RefreshButton .Hint := rsRefreshTheSearch;
SearchAgainButton.Hint := rsNewSearchWithSameCriteria;
ClosePageButton .Hint := rsCloseCurrentPage;
SearchInListEdit .Hint := rsFilterTheListWithString;
ShowPathButton .Hint := rsShowPathMode;
actCloseLeft .Hint := rsCloseLeft;
actCloseRight .Hint := rsCloseRight;
actCloseOthers .Hint := rsCloseOthers;
actCloseAll .Hint := rsCloseAll;
// caption
Caption := lisMenuViewSearchResults;
mniCopyItem .Caption := lisCopyItemToClipboard;
mniCopySelected.Caption := lisCopySelectedItemToClipboard;
mniCopyAll .Caption := lisCopyAllItemsToClipboard;
mniExpandAll .Caption := lisExpandAll;
mniCollapseAll .Caption := lisCollapseAll;
mniPathAbsolute.Caption := rsShowAbsPath;
mniPathRelative.Caption := rsShowRelPath;
mniPathFileName.Caption := rsShowFileName;
// images lists
PageToolBar.Images := IDEImages.Images_16;
ActionList .Images := IDEImages.Images_16;
CloseTabs .Images := IDEImages.Images_16;
// images
RefreshButton .ImageIndex := IDEImages.LoadImage('laz_refresh');
SearchAgainButton.ImageIndex := IDEImages.LoadImage('menu_new_search');
ClosePageButton .ImageIndex := IDEImages.LoadImage('menu_close');
ShowPathButton .ImageIndex := IDEImages.LoadImage('menu_search_openfile_atcursor');
actClosePage .ImageIndex := IDEImages.LoadImage('menu_close');
actCloseLeft .ImageIndex := IDEImages.LoadImage('tab_close_L');
actCloseOthers .ImageIndex := IDEImages.LoadImage('tab_close_LR');
actCloseRight .ImageIndex := IDEImages.LoadImage('tab_close_R');
actCloseAll .ImageIndex := IDEImages.LoadImage('tab_close_All');
// load path style
//mniPathRelative.Checked := true; // default
case EnvironmentGuiOpts.SearchResultViewPathStyle of
mwfsShort : mniPathFileName.Checked := true;
mwfsRelative: mniPathRelative.Checked := true;
mwfsFull : mniPathAbsolute.Checked := true;
end;
mniShowPathClick(Sender);
end;
procedure TSearchResultsView.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
lTree: TLazSearchResultTV;
begin
// select
if (Key = VK_RETURN) and (Shift = []) then
begin
Key := 0;
if assigned(FOnSelectionChanged) then
FOnSelectionChanged(self);
end
// close
else if (Key = VK_ESCAPE) and (Shift = []) then
begin
Key := 0;
Close;
end
// line scroll
else if (Key = VK_DOWN) and (Shift = [ssCtrl]) then
begin
Key := 0;
lTree := GetCurrentTree;
if lTree <> nil then
lTree.ScrolledTop := lTree.ScrolledTop + lTree.DefaultItemHeight;
end
else if (Key = VK_UP) and (Shift = [ssCtrl]) then
begin
Key := 0;
lTree := GetCurrentTree;
if lTree <> nil then
lTree.ScrolledTop := lTree.ScrolledTop - lTree.DefaultItemHeight;
end
// full expand/collapse
else if (Key = VK_LEFT) and (Shift = [ssAlt, ssShift]) then
begin
Key := 0;
mniCollapseAllClick(Sender);
end
else if (Key = VK_RIGHT) and (Shift = [ssAlt, ssShift]) then
begin
Key := 0;
mniExpandAllClick(Sender);
end
// set focus in filter
else if (Key = VK_F) and (Shift = [ssCtrl]) then
begin
Key := 0;
if SearchInListEdit.CanSetFocus then
SearchInListEdit.SetFocus;
end
// toggle path display style
else if (Key = VK_P) and (Shift = [ssCtrl]) then
begin
Key := 0;
// change path style
if mniPathAbsolute.Checked then
mniPathRelative.Checked := true
else if mniPathRelative.Checked then
mniPathFileName.Checked := true
else
mniPathAbsolute.Checked := true;
mniShowPathClick(Sender);
end
// new search
else if (Key = VK_N) and (Shift = [ssCtrl]) then
begin
Key := 0;
SearchAgainButtonClick(Sender);
end
// refresh
else if (Key = VK_F5) and (Shift = []) then
begin
Key := 0;
RefreshButtonClick(Sender);
end
// next tab
else if (Key = VK_TAB) and (Shift = [ssCtrl]) then
begin
Key := 0;
ResultsNoteBook.SelectNextPage(true);
end
else if (Key = VK_TAB) and (Shift = [ssShift, ssCtrl]) then
begin
Key := 0;
ResultsNoteBook.SelectNextPage(false);
end;
end;
procedure TSearchResultsView.mniCopyAllClick(Sender: TObject);
var
lList: TStrings;
begin
lList := TLazSearchResultTV(popList.PopupComponent).ItemsAsStrings;
try
Clipboard.AsText := lList.Text;
finally
lList.Free;
end;
end;
procedure TSearchResultsView.mniCopyItemClick(Sender: TObject);
var
lTree: TCustomTreeView;
lNode: TTreeNode;
begin
lTree := TCustomTreeView(popList.PopupComponent);
with lTree.ScreenToClient(popList.PopupPoint) do
lNode := lTree.GetNodeAt(X, Y);
if lNode <> nil then
Clipboard.AsText := lNode.Text;
end;
procedure TSearchResultsView.mniCopySelectedClick(Sender: TObject);
begin
Clipboard.AsText := GetTreeSelectedItemsAsText(TCustomTreeView(popList.PopupComponent));
end;
procedure TSearchResultsView.mniExpandAllClick(Sender: TObject);
var
lTree: TLazSearchResultTV;
lOldCursor : TCursor;
begin
lTree := GetCurrentTree;
if lTree = nil then exit;
// expand
lOldCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
lTree.FullExpand;
Screen.Cursor := lOldCursor;
end;
procedure TSearchResultsView.mniCollapseAllClick(Sender: TObject);
var
lTree: TLazSearchResultTV;
lNode: TTreeNode;
lOldCursor : TCursor;
begin
lTree := GetCurrentTree;
if lTree = nil then exit;
// collapse
lOldCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
lTree.FullCollapse;
// selection
lTree.ClearSelection;
lNode := lTree.Items.GetFirstVisibleNode;
if lNode <> nil then
lNode.Selected := true;
Screen.Cursor := lOldCursor;
end;
procedure TSearchResultsView.ResultsNoteBookMouseDown(
Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
lIndex: LongInt;
begin
if Button = mbMiddle then
begin
lIndex := ResultsNoteBook.IndexOfPageAt(Point(X,Y));
if lIndex >= 0 then
ResultsNoteBookCloseTabClick(ResultsNoteBook.Page[lIndex]);
end;
end;
procedure TSearchResultsView.RefreshButtonClick(Sender: TObject);
var
lTree: TLazSearchResultTV;
lNode: TTreeNode;
lNodeText: String;
lOldScroll: TPoint;
lDeltaScrollY: integer;
begin
if not RefreshButton.Enabled then exit;
lTree := GetCurrentTree;
if lTree = nil then exit; // this also check ResultsNoteBook.PageIndex
// store selection and scrolling
TCustomTreeView(lTree).BeginUpdate; // TCustomTreeView, but not TLazSearchResultTV!
try
lNode := lTree.Selected;
lNodeText := '';
if lNode <> nil then
begin
lNodeText := lNode.Text;
lOldScroll.X := lTree.ScrolledLeft;
lOldScroll.Y := lTree.ScrolledTop;
lTree.MoveEnd;
lNode.MakeVisible;
lDeltaScrollY := lTree.ScrolledTop - lOldScroll.Y;
end;
finally
lTree.ScrolledTop := lOldScroll.Y; // go back while searching
TCustomTreeView(lTree).EndUpdate;
end;
// update tree
with lTree.SearchObject do
MainIDEInterface.FindInFiles(
Project1, SearchString,
SearchOptions, SearchMask, SearchDirectories,
false, ResultsNoteBook.PageIndex
);
// restore selection and scrolling
TCustomTreeView(lTree).BeginUpdate;
try
lNode := lTree.Items.FindNodeWithText(lNodeText);
if lNode <> nil then
begin
lTree.MoveEnd;
lTree.ClearSelection;
lNode.Selected := true;
lTree.ScrolledTop := lTree.ScrolledTop - lDeltaScrollY; // restore scrolling relative to node
end else
lTree.ScrolledTop := lOldScroll.Y; // just restore old scrolling
lTree.ScrolledLeft := lOldScroll.X;
finally
TCustomTreeView(lTree).EndUpdate;
end;
end;
procedure TSearchResultsView.ResultsNoteBookMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
var
lPageIx: integer;
begin
if Sender = ResultsNoteBook then begin
with ResultsNoteBook do begin
lPageIx := IndexOfTabAt(Point(x, y));
if lPageIx <> FNoteBookTab then begin
FNoteBookTab := lPageIx;
Application.CancelHint;
end;
end;
end;
end;
procedure TSearchResultsView.SearchAgainButtonClick(Sender: TObject);
var
lTree: TLazSearchResultTV;
begin
if not SearchAgainButton.Enabled then exit;
lTree := GetCurrentTree;
if lTree = nil then
MainIDEInterface.FindInFiles(Project1)
else
with lTree.SearchObject do
MainIDEInterface.FindInFiles(Project1, SearchString,
SearchOptions, SearchMask, SearchDirectories);
end;
procedure TSearchResultsView.ClosePageButtonClick(Sender: TObject);
begin
ClosePage(ResultsNoteBook.PageIndex);
end;
procedure TSearchResultsView.ResultsNoteBookResize(Sender: TObject);
begin
if ResultsNoteBook.PageCount > 0 then
AsyncUpdateCloseButtons := svcbEnable
else
AsyncUpdateCloseButtons := svcbDisable;
end;
procedure TSearchResultsView.mniShowPathClick(Sender: TObject);
var
lTree: TLazSearchResultTV;
begin
// store in config
if mniPathFileName.Checked then
EnvironmentGuiOpts.SearchResultViewPathStyle := mwfsShort
else if mniPathRelative.Checked then
EnvironmentGuiOpts.SearchResultViewPathStyle := mwfsRelative
else // mniPathAbsolute.Checked
EnvironmentGuiOpts.SearchResultViewPathStyle := mwfsFull;
lTree := GetCurrentTree;
if lTree = nil then exit;
lTree.Invalidate;
end;
{ Handling of tabs closure. Only tabs on pages at the level of active page in
multiline ResultsNoteBook will be closed by Left / Others and Right }
procedure TSearchResultsView.ClosePageOnSides(aOnSide: TOnSide);
var
lvPageList: TFPList = nil;
lCurTabSheet, lTabSheet: TTabSheet;
ix: integer;
lNeedsRefresh : boolean = false;
begin
lvPageList := GetPagesOnActiveLine(aOnSide);
if lvPageList = Nil then Exit;
ClosePageBegin;
lCurTabSheet := ResultsNoteBook.ActivePage;
if aOnSide = osLeft then
ix := lvPageList.IndexOf(lCurTabSheet)-1
else
ix := lvPageList.Count-1;
while ix >= 0 do begin
lTabSheet := TTabSheet(lvPageList[ix]);
if lTabSheet = lCurTabSheet then begin
if aOnSide = osRight then
break;
end
else begin
ClosePage(lTabSheet.TabIndex);
lNeedsRefresh := True;
end;
Dec(ix);
end;
lvPageList.Free;
ClosePageEnd;
if lNeedsRefresh then { Force resizing of the active TabSheet }
lCurTabSheet.Height := lCurTabSheet.Height+1;
UpdateToolBar;
end;
procedure TSearchResultsView.ClosePageBegin;
begin
FClosingTabs := True;
end;
procedure TSearchResultsView.ClosePageEnd;
begin
FClosingTabs := False;
end;
procedure TSearchResultsView.tbbCloseLeftClick(Sender: TObject);
begin
ClosePageOnSides(osLeft);
end;
procedure TSearchResultsView.tbbCloseOthersClick(Sender: TObject);
begin
ClosePageOnSides(osOthers);
end;
procedure TSearchResultsView.tbbCloseRightClick(Sender: TObject);
begin
ClosePageOnSides(osRight);
end;
procedure TSearchResultsView.tbbCloseAllClick(Sender: TObject);
var
lPageIx : integer;
begin
with ResultsNoteBook do begin
lPageIx := PageCount;
while lPageIx > 0 do begin
Dec(lPageIx);
if lPageIx < PageCount then
ClosePage(lPageIx);
end;
end;
end;
{Keeps track of the Index of the Item the mouse is over, Sets Show to true
if the Item length is longer than the TreeView client width.}
procedure TSearchResultsView.LazTVMousemove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var
Node: TTreeNode;
begin
if Sender is TLazSearchResultTV then
with TLazSearchResultTV(Sender) do
begin
Node := GetNodeAt(X, Y);
if Assigned(Node) then
fMouseOverIndex:=Node.Index
else begin
fMouseOverIndex:=-1;
Application.CancelHint;
end;
end;//with
end;
{Keep track of the mouse position over the treeview when the wheel is used}
procedure TSearchResultsView.LazTVMouseWheel(Sender: TObject;
Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
var Handled: Boolean);
begin
LazTVMouseMove(Sender,Shift,MousePos.X, MousePos.Y);
Handled:= false;
end;
procedure TSearchResultsView.ResultsNoteBookPageChanged(Sender: TObject);
var
lTree: TLazSearchResultTV;
begin
lTree := GetCurrentTree;
if assigned(lTree) and not (csDestroying in lTree.ComponentState) then
begin
SearchInListEdit.SetTreeFilterSilently(lTree, lTree.SearchInListPhrases);
if FFocusTreeViewInOnChange then
ActivateControl(lTree);
end;
UpdateToolbar;
end;
procedure TSearchResultsView.SearchInListChange (Sender: TObject );
var
lTree: TLazSearchResultTV;
begin
lTree := GetCurrentTree;
if assigned(lTree) then
lTree.SearchInListPhrases := SearchInListEdit.Text;
end;
procedure TSearchResultsView.TreeViewMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
TV: TCustomTreeView;
Node: TTreeNode;
begin
if Button<>mbLeft then exit;
TV:=Sender as TCustomTreeView;
Node:=TV.GetNodeAt(X,Y);
if Node=nil then exit;
if x<Node.DisplayTextLeft then exit;
//debugln(['TSearchResultsView.TreeViewMouseDown single=',([ssDouble,ssTriple,ssQuad]*Shift=[]),' Option=',EnvironmentOptions.MsgViewDblClickJumps]);
if EnvironmentGuiOpts.MsgViewDblClickJumps then
begin
// double click jumps
if not (ssDouble in Shift) then exit;
end else begin
// single click jumps -> single selection
if ([ssDouble,ssTriple,ssQuad]*Shift<>[]) then exit;
TV.Items.SelectOnlyThis(Node);
end;
if Assigned(fOnSelectionChanged) then
fOnSelectionChanged(Self);
end;
function TSearchResultsView.IsBackup(const aFullFilePath: string): boolean;
begin
result := 0 = CompareText('backup', ExtractFileName(ExtractFileDir(aFullFilePath)));
end;
function TSearchResultsView.BeautifyPageName(const APageName: string; out
aoTabEllipsed: boolean): string;
const
MaxPageName = 25;
begin
aoTabEllipsed:= False;
Result:=Utf8EscapeControlChars(APageName, emHexPascal);
if UTF8Length(Result)>MaxPageName then begin
Result:=UTF8Copy(Result,1,MaxPageName-5)+'...';
aoTabEllipsed:= True;
end;
end;
procedure TSearchResultsView.AddMatch(const APageIndex: integer;
const Filename: string; const StartPos, EndPos: TPoint;
const TheText: string;
const MatchStart: integer; const MatchLen: integer);
var
CurrentTV: TLazSearchResultTV;
SearchPos: TLazSearchMatchPos;
ShownText: String;
LastPos: TLazSearchMatchPos;
begin
CurrentTV:=GetTreeView(APageIndex);
if Assigned(CurrentTV) then
begin
if CurrentTV.Updating then begin
if CurrentTV.UpdateItems.Count>=MaxItems then begin
CurrentTV.Skipped:=CurrentTV.Skipped+1;
exit;
end;
end else begin
if CurrentTV.Items.Count>=MaxItems then begin
CurrentTV.Skipped:=CurrentTV.Skipped+1;
exit;
end;
end;
SearchPos:= TLazSearchMatchPos.Create;
SearchPos.MatchStart:=MatchStart;
SearchPos.MatchLen:=MatchLen;
SearchPos.Filename:=Filename;
SearchPos.FileStartPos:=StartPos;
SearchPos.FileEndPos:=EndPos;
SearchPos.TheText:=TheText;
SearchPos.ShownFilename:=SearchPos.Filename;
ShownText:=CurrentTV.BeautifyLineAt(SearchPos);
LastPos:=nil;
if CurrentTV.Updating then begin
if (CurrentTV.UpdateItems.Count>0)
and (CurrentTV.UpdateItems.Objects[CurrentTV.UpdateItems.Count-1] is TLazSearchMatchPos) then
LastPos:=TLazSearchMatchPos(CurrentTV.UpdateItems.Objects[CurrentTV.UpdateItems.Count-1]);
end else
if (CurrentTV.Items.Count>0) and Assigned(CurrentTV.Items[CurrentTV.Items.Count-1].Data) then
LastPos:=TLazSearchMatchPos(CurrentTV.Items[CurrentTV.Items.Count-1].Data);
if (LastPos<>nil) and (LastPos.Filename=SearchPos.Filename) and
(LastPos.FFileStartPos.Y=SearchPos.FFileStartPos.Y) and
(LastPos.FFileEndPos.Y=SearchPos.FFileEndPos.Y) then
begin
while (LastPos.NextInThisLine<>nil) do
LastPos := LastPos.NextInThisLine;
LastPos.NextInThisLine:=SearchPos
end
else if CurrentTV.Updating then
CurrentTV.UpdateItems.AddObject(ShownText, SearchPos)
else
CurrentTV.AddNode(ShownText, SearchPos);
CurrentTV.ShortenPaths;
end;//if
end;//AddMatch
procedure TSearchResultsView.BeginUpdate(APageIndex: integer);
var
lTree: TLazSearchResultTV;
begin
lTree:= GetTreeView(APageIndex);
if assigned(lTree) then
lTree.BeginUpdate;
UpdateToolbar;
end;
procedure TSearchResultsView.EndUpdate(APageIndex: integer; APageName: string = '');
var
lTree: TLazSearchResultTV;
lNode: TTreeNode;
lPage: TTabSheet;
lEllipsed: boolean;
begin
lTree := GetTreeView(APageIndex);
if assigned(lTree) then
begin
// end update
lTree.EndUpdate; // this need before next collapse and selecting
// collapse results from backup folder
lNode := lTree.Items.GetFirstVisibleNode;
while lNode <> nil do
begin
if IsBackup(lNode.Text) then
lNode.Collapse(false);
lNode := lNode.GetNextVisibleSibling;
end;
// select first
lNode := lTree.Items.GetFirstVisibleNode;
if lNode <> nil then
lNode.Selected := true;
end;
// Page name
lPage := GetResultsPage(APageIndex); // this also check APageIndex range
if (lPage <> nil) and (APageName <> '') then
begin
lPage.Caption := BeautifyPageName(APageName, lEllipsed);
end;
// Count
lPage.Caption := lPage.Caption + ' (' + inttostr(lTree.Items.Count - lTree.Items.TopLvlCount) + ')';
UpdateToolbar;
if FFocusTreeViewInEndUpdate and assigned(lTree) then
ActivateControl(lTree)
else
if SearchInListEdit.CanSetFocus then
ActivateControl(SearchInListEdit);
end;
procedure TSearchResultsView.ResultsNoteBookChanging(Sender: TObject;
var AllowChange: Boolean);
var
lTree: TLazSearchResultTV;
begin
lTree := GetCurrentTree;
FFocusTreeViewInOnChange := assigned(lTree) and lTree.Focused;
end;
procedure TSearchResultsView.ClosePage(PageIndex: integer);
var
lTree: TLazSearchResultTV;
begin
if InRange(PageIndex, 0, ResultsNoteBook.PageCount - 1) then
begin
lTree := GetTreeView(PageIndex);
if assigned(lTree) and lTree.Updating then
exit;
ResultsNoteBook.Pages[PageIndex].Free;
end;
if ResultsNoteBook.PageCount = 0 then
Close
else
AsyncUpdateCloseButtons:=svcbEnable;
end;
function TSearchResultsView.GetResultsPage(aIndex: integer): TTabSheet;
begin
if InRange(aIndex, 0, ResultsNoteBook.PageCount - 1) then
Result := ResultsNoteBook.Pages[aIndex]
else
Result := nil;
end;
{Sets the Items from the treeview on the currently selected page in the TNoteBook}
procedure TSearchResultsView.SetItems(Index: Integer; Value: TStrings);
var
CurrentTV: TLazSearchResultTV;
begin
if Index > -1 then
begin
CurrentTV:= GetTreeView(Index);
if Assigned(CurrentTV) then
begin
if CurrentTV.Updating then
CurrentTV.UpdateItems.Assign(Value)
else
CurrentTV.Items.Assign(Value);
CurrentTV.Skipped:=0;
end;
end;
end;
function TSearchResultsView.GetItems(Index: integer): TStrings;
var
CurrentTV: TLazSearchResultTV;
begin
result:= nil;
CurrentTV:= GetTreeView(Index);
if Assigned(CurrentTV) then
begin
if CurrentTV.Updating then
result:= CurrentTV.UpdateItems
else
Result := CurrentTV.ItemsAsStrings;
end;
end;
procedure TSearchResultsView.SetMaxItems(const AValue: integer);
begin
if FMaxItems=AValue then exit;
FMaxItems:=AValue;
end;
procedure TSearchResultsView.UpdateToolbar;
var
lTree: TLazSearchResultTV;
lEnabled, lRenameIdResults: Boolean;
begin
lTree := GetCurrentTree;
lEnabled := Assigned(lTree) and (not lTree.Updating);
lRenameIdResults := Assigned(lTree) and
(lTree.SearchObject.SearchOptions - [fifReplace, fifReplaceAll] = []);
RefreshButton .Enabled := lEnabled and (not lRenameIdResults);
SearchAgainButton.Enabled := lEnabled and (not lRenameIdResults);
ClosePageButton .Enabled := lEnabled;
SearchInListEdit .Enabled := lEnabled;
if lEnabled then
AsyncUpdateCloseButtons:=svcbEnable;
end;
{ Returns a list of all pages (visible tabs) on the same line of Tabs as the ActivaPage }
function TSearchResultsView.GetPagesOnActiveLine(aOnSide: TOnSide {=osOthers}): TFPlist;
var
lActiveMidY, lActiveIndex, ix, hh: integer;
lActiveRect, lRect, lLastRect: TRect;
begin
Result := nil;
with ResultsNoteBook do begin
if ActivePage = Nil then Exit;
Result := TFPList.Create;
lActiveIndex := ResultsNoteBook.ActivePageIndex;
lActiveRect := TabRect(lActiveIndex);
hh := (lActiveRect.Bottom - lActiveRect.Top) div 2;
{ Some widgetsets returned a negative value from Bottom-Top calculation. }
if hh < 0 then begin // Do a sanity check.
DebugLn(['TSearchResultsView.GetPagesOnActiveLine: TabRect Bottom-Top calculation'+
' for ActivePage returned a negative value "', hh, '".']);
hh := -hh;
end;
lActiveMidY := lActiveRect.Top + hh;
{ Search closable tabs left of current tab }
if aOnSide in [osLeft, osOthers] then begin
lLastRect := lActiveRect;
for ix := lActiveIndex-1 downto 0 do begin
lRect := TabRect(ix);
if (lRect.Top >= lActiveMidY) or (lRect.Bottom <= lActiveMidY)
or (lRect.Right > lLastRect.Left) then
break;
Result.Insert(0, Pages[ix]);
lLastRect := lRect;
end;
end;
{ Current tab }
Result.Add(Pages[lActiveIndex]);
{ Search closable tabs right of current tab }
if aOnSide in [osOthers, osRight] then begin
lLastRect := lActiveRect;
for ix := lActiveIndex+1 to PageCount-1 do begin
lRect := TabRect(ix);
if (lRect.Top >= lActiveMidY) or (lRect.Bottom <= lActiveMidY)
or (lRect.Left < lLastRect.Right) then
break;
Result.Add(Pages[ix]);
lLastRect := lRect;
end;
end;
end;
end;
procedure TSearchResultsView.DoAsyncUpdateCloseButtons(Data: PtrInt);
var
lPageList: TFPlist = nil;
lActiveIx: integer = -1;
aEnable: Boolean;
begin
if FClosingTabs then
exit;
if FAsyncUpdateCloseButtons=svcbNone then exit;
aEnable:=FAsyncUpdateCloseButtons=svcbEnable;
FAsyncUpdateCloseButtons:=svcbNone;
if aEnable and (ResultsNoteBook.PageCount>0) then begin
lPageList := GetPagesOnActiveLine;
if Assigned(lPageList) and (lPageList.Count>0) then
repeat
inc(lActiveIx);
if lPageList[lActiveIx]=Pointer(ResultsNoteBook.ActivePage) then
break;
until lActiveIx>=lPageList.Count -1;
end;
aEnable := aEnable and Assigned(lPageList);
actCloseLeft.Enabled := aEnable and (lActiveIx>0);
if aEnable then begin
actCloseOthers.Enabled:= lPageList.Count>1;
actCloseRight.Enabled := lActiveIx<(lPageList.Count-1);
end
else begin
actCloseOthers.Enabled:= False;
actCloseRight.Enabled := False;
end;
actCloseAll.Enabled := aEnable;
lPageList.Free;
end;
procedure TSearchResultsView.NoteBookShowHint(Sender: TObject; HintInfo: PHintInfo);
const
cFifPlaces = [fifSearchDirectories, fifSearchProject, fifSearchProjectGroup, fifSearchOpen, fifSearchActive];
var
lTree: TLazSearchResultTV;
p: TPoint;
r: TRect;
h: integer;
//
function PlaceIs(aOption: TLazFindInFileSearchOption): boolean;
begin
result := lTree.SearchObject.SearchOptions * cFifPlaces = [aOption];
end;
//
function GetHintString: string;
var
lReplace: boolean;
begin
result := '';
lReplace := lTree.SearchObject.SearchOptions * [fifReplace, fifReplaceAll] <> [];
{ Line 1. Place }
if lTree.SearchObject.SearchOptions * cFifPlaces = [] then
begin
{ find or rename identifier }
result := lisFRIFindOrRenameIdentifier;
end else begin
{ find or replace string }
if PlaceIs(fifSearchProject) then result := RemoveAmpersands(lisFindFilesearchAllFilesInProject)
else if PlaceIs(fifSearchProjectGroup) then result := RemoveAmpersands(lisFindFilesSearchInProjectGroup)
else if PlaceIs(fifSearchOpen) then result := RemoveAmpersands(lisFindFilesearchAllOpenFiles)
else if PlaceIs(fifSearchActive) then result := RemoveAmpersands(lisFindFilesearchInActiveFile)
else if PlaceIs(fifSearchDirectories) then result := '"' + lTree.SearchObject.SearchDirectories + '"'
else
debugln('TSearchResultsView.NoteBookShowHint: An invalid combination of TLazFindInFileSearchOptions flags');
result := lisFindFileWhere + ': ' + result;
end;
{ Line 2. Search string }
if lTree.SearchObject.TabEllipsed or lReplace then
Result := Result + EndOfLine + RemoveAmpersands(dlgTextToFind) + ': "' + lTree.SearchObject.SearchString + '"';
{ Line 3. Replace string }
if lReplace then
Result := Result + EndOfLine + RemoveAmpersands(dlgReplaceWith) + ': "' + lTree.SearchObject.ReplaceText + '"'
end;
//
begin
if Sender <> ResultsNoteBook then
exit;
p := HintInfo^.CursorPos;
FNoteBookTab := ResultsNoteBook.IndexOfTabAt(p.X, p.Y);
if FNoteBookTab >= 0 then
begin
lTree := GetTreeView(FNoteBookTab);
if lTree = nil then exit;
if lTree.SearchObject = nil then exit;
r := ResultsNoteBook.TabRect(FNoteBookTab);
h := SearchInListEdit.Height; // Pick DPI independent value
if p.X > r.Right - 2 * h then
p.X := r.Right - 2 * h;
HintInfo^.HintStr := GetHintString;
HintInfo^.HintPos := ResultsNoteBook.ClientToScreen(Point(p.x, r.Bottom - (h div 10)));
end else
Application.CancelHint;
end;
procedure TSearchResultsView.ResultsNoteBookCloseTabClick(Sender: TObject);
begin
if Sender is TTabSheet then
ClosePage(TTabSheet(Sender).PageIndex);
end;
{ Add Result will create a tab in the Results view window with an new
treeview or focus an existing TreeView and update it's searchoptions.}
function TSearchResultsView.AddSearch(
const aSearchText: string;
const aReplaceText: string;
const aDirectories: string;
const aFileMask: string;
const aOptions: TLazFindInFileSearchOptions
): TTabSheet;
var
lNewTree: TLazSearchResultTV;
lTabEllipsed: boolean;
lTabCaption: String;
begin
result := nil;
if ResultsNoteBook = nil then
exit;
with ResultsNoteBook do
begin
FFocusTreeViewInEndUpdate := (ActivePage = nil) and SearchInListEdit.IsParentOf(ActivePage);
lTabCaption := BeautifyPageName(aSearchText, lTabEllipsed); // default page name
PageIndex := TCustomTabControl(ResultsNoteBook).Pages.Add(lTabCaption);
lNewTree := TLazSearchResultTV.Create(Page[PageIndex]);
with lNewTree do
begin
Parent := Page[PageIndex];
BorderSpacing.Around := 0;
Align := alClient;
ShowHint := False; // true; ~bk apparently OnShowHint messes up with ShowHint True
RowSelect := true;
ShowLines := false;
Options := Options + [tvoAllowMultiselect] - [tvoThemedDraw];
PopupMenu := popList;
lNewTree.Canvas.Brush.Color := clWhite;
// events
OnShowHint := @LazTVShowHint;
OnMouseMove := @LazTVMousemove;
OnMouseWheel := @LazTVMouseWheel;
OnMouseDown := @TreeViewMouseDown;
OnAdvancedCustomDrawItem := @TreeViewAdvancedCustomDrawItem;
end;
if lNewTree.SearchObject <> nil then
begin
lNewTree.SearchObject.SearchString := aSearchText;
lNewTree.SearchObject.ReplaceText := aReplaceText;
lNewTree.SearchObject.SearchDirectories := aDirectories;
lNewTree.SearchObject.SearchMask := aFileMask;
lNewTree.SearchObject.SearchOptions := aOptions;
lNewTree.SearchObject.TabEllipsed := lTabEllipsed;
end;
lNewTree.Skipped := 0;
result := Pages[PageIndex];
SearchInListEdit.ResetFilter;
SearchInListEdit.FilteredTreeview := lNewTree;
end;
end;
procedure TSearchResultsView.LazTVShowHint(Sender: TObject; HintInfo: PHintInfo);
var
MatchPos: TLazSearchMatchPos;
lThisTV: TLazSearchResultTV;
begin
if Sender is TLazSearchResultTV then
begin
lThisTV := Sender as TLazSearchResultTV;
With lThisTV do
begin
{ (Canvas.TextWidth(Items[fMouseOverIndex].Text) > Width) then}
if (fMouseOverIndex >= 0) and (fMouseOverIndex < Items.Count)
and (Canvas.TextWidth(Items[fMouseOverIndex].Text) > Width)
then begin
if Assigned(Items[fMouseOverIndex].Data) then
MatchPos:= TLazSearchMatchPos(Items[fMouseOverIndex].Data)
else
MatchPos:= nil;
if MatchPos<>nil then
HintInfo^.HintStr := MatchPos.Filename
+' ('+IntToStr(MatchPos.FileStartPos.Y)
+','+IntToStr(MatchPos.FileStartPos.X)+')'
+' '+MatchPos.TheText
else
HintInfo^.HintStr := Items[fMouseOverIndex].Text;
end //if
else
Application.CancelHint;
end;//with
end;//if
end;
procedure TSearchResultsView.Loaded;
begin
inherited Loaded;
ActiveControl := ResultsNoteBook;
end;
procedure TSearchResultsView.ActivateControl(aWinControl: TWinControl);
var
aForm: TCustomForm;
begin
if not aWinControl.CanFocus then exit;
if Parent=nil then
ActiveControl:=aWinControl
else begin
aForm:=GetParentForm(Self);
if aForm<>nil then aForm.ActiveControl:=aWinControl;
end;
end;
procedure TSearchResultsView.UpdateShowing;
begin
inherited UpdateShowing;
AsyncUpdateCloseButtons:=svcbDisable;
end;
procedure TSearchResultsView.TreeViewAdvancedCustomDrawItem(
Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState;
Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean);
var
lTree: TLazSearchResultTV;
lRect: TRect;
lMatch: TLazSearchMatchPos;
lPart, lRelPath: string;
lDrawnLength: integer;
lDigitWidth: integer;
lTextX: integer;
i: integer;
procedure DrawNextText(aText: string; aColor: TColor = clNone; aStyle: TFontStyles = []);
var
lOldColor: TColor;
begin
// store
lOldColor := lTree.Canvas.Font.Color;
// apply
if aColor <> clNone then
lTree.Canvas.Font.Color := aColor;
lTree.Canvas.Font.Style := aStyle;
lTree.Canvas.Brush.Style := bsClear;
// draw
lTree.Canvas.TextOut(lTextX, lRect.Top, aText);
inc(lTextX, lTree.Canvas.GetTextWidth(aText));
// restore
lTree.Canvas.Font.Color := lOldColor;
lTree.Canvas.Font.Style := [];
lTree.Canvas.Brush.Style := bsSolid;
end;
var
lOldBkMode : integer;
begin
if Stage <> cdPostPaint then exit;
if Sender is TLazSearchResultTV then // it also check nil
lTree := TLazSearchResultTV(Sender)
else
exit;
// clear
lRect := Node.DisplayRect(true);
lTree.Canvas.FillRect(lRect);
// transparent paint for very near bold text
lOldBkMode := SetBkMode(lTree.Canvas.Handle, TRANSPARENT);
if TObject(Node.Data) is TLazSearchMatchPos then
begin { search results }
lMatch := TLazSearchMatchPos(Node.Data);
// calculating the maximum width of a digit:
// not in monospace fonts, digits can have different widths
lDigitWidth := lTree.Canvas.GetTextWidth(':');
for i := 0 to 9 do
lDigitWidth := Max(lDigitWidth, lTree.Canvas.GetTextWidth(inttostr(i)));
lPart := inttostr(lMatch.FileStartPos.Y) + ':';
lTextX := lRect.Left + 6 * lDigitWidth - lTree.Canvas.GetTextWidth(lPart);
// draw line number ("99999: ")
if [cdsSelected, cdsMarked] * State <> [] then
DrawNextText(lPart)
else
DrawNextText(lPart, clGrayText);
lTextX := lRect.Left + 7 * lDigitWidth;
lDrawnLength := 0;
while assigned(lMatch) do
begin
//debugln(['TSearchResultsView.TreeViewAdvancedCustomDrawItem MatchPos.TheText="',lMatch.TheText,'" MatchPos.MatchStart=',lMatch.MatchStart,' MatchPos.MatchLen=',lMatch.MatchLen]);
// draw normal text
lPart := copy(lMatch.TheText, lDrawnLength + 1, lMatch.MatchStart - 1 - lDrawnLength);
lPart := Utf8EscapeControlChars(lPart, emHexPascal);
lDrawnLength := lMatch.MatchStart - 1;
// draw normal text
DrawNextText(lPart);
lPart := ShortDotsLine(copy(lMatch.TheText, lDrawnLength + 1, lMatch.MatchLen));
lDrawnLength := lDrawnLength + lMatch.MatchLen;
// draw found text
if [cdsSelected, cdsMarked] * State <> [] then
DrawNextText(lPart, clHighlightText, [fsBold])
else
DrawNextText(lPart, clHighlight, [fsBold]);
// remaining normal text
if lMatch.NextInThisLine = nil then
begin
lPart := copy(lMatch.TheText, lDrawnLength + 1, Length(lMatch.TheText));
lPart := Utf8EscapeControlChars(lPart, emHexPascal);
// draw normal text
DrawNextText(lPart);
end;
lMatch := lMatch.NextInThisLine;
end;
end else begin { filename }
lTextX := lRect.Left;
// get relative path (if needed)
if mniPathRelative.Checked or mniPathFileName.Checked then
lRelPath := ExtractRelativePath(
IncludeTrailingPathDelimiter(lTree.SearchObject.SearchDirectories),
Node.Text
);
if fifSearchDirectories in lTree.SearchObject.SearchOptions then
begin
if mniPathFileName.Checked then
// file name
DrawNextText(ExtractFileName(lRelPath), clNone, [fsBold])
else if mniPathRelative.Checked then
// relative path
DrawNextText(lRelPath, clNone, [fsBold])
else
// absolute path
DrawNextText(Node.Text, clNone, [fsBold]);
// also show relative path if row selected
if mniPathFileName.Checked then
if [cdsSelected,cdsMarked] * State <> [] then
DrawNextText(' (' + lRelPath + ')', clHighlightText);
end else
// absolute path
DrawNextText(Node.Text, clNone, [fsBold]);
// show a warning that this is a backup folder
// strip path delimiter and filename, then check if last directory is 'backup'
if IsBackup(Node.Text) then
DrawNextText(' [BACKUP]', clRed);
end;
SetBkMode(lTree.Canvas.Handle, lOldBkMode); // restore
end;
{Returns the Position within the source file from a properly formated search result}
function TSearchResultsView.GetSourcePositon: TPoint;
var
MatchPos: TLazSearchMatchPos;
begin
Result.x:= -1;
Result.y:= -1;
MatchPos:=GetSelectedMatchPos;
if MatchPos=nil then exit;
Result:=MatchPos.FileStartPos;
end;
{Returns The file name portion of a properly formated search result}
function TSearchResultsView.GetSourceFileName: string;
var
MatchPos: TLazSearchMatchPos;
begin
MatchPos:=GetSelectedMatchPos;
if MatchPos=nil then
Result:=''
else
Result:=MatchPos.Filename;
end;
{Returns the selected text in the currently active TreeView.}
function TSearchResultsView.GetSelectedText: string;
var
lPage: TTabSheet;
lTree: TLazSearchResultTV;
i: integer;
begin
result := '';
i := ResultsNoteBook.PageIndex;
if i < 0 then exit;
lPage:= ResultsNoteBook.Pages[i];
if not assigned(lPage) then exit;
lTree := GetTreeView(lPage.PageIndex);
if Assigned(lTree.Selected) then
Result := lTree.Selected.Text;
end;
function TSearchResultsView.GetSelectedMatchPos: TLazSearchMatchPos;
var
lPage: TTabSheet;
lTree: TLazSearchResultTV;
i: integer;
begin
result:= nil;
i := ResultsNoteBook.PageIndex;
if i < 0 then exit;
lPage := ResultsNoteBook.Pages[i];
if not Assigned(lPage) then exit;
lTree := GetTreeView(lPage.PageIndex);
if Assigned(lTree.Selected) then
Result := TLazSearchMatchPos(lTree.Selected.Data);
end;
function TSearchResultsView.SelectNextMatchPos(DirectionDown: boolean): boolean;
var
lPage: TTabSheet;
lTree: TLazSearchResultTV;
i: integer;
lSelNode, lNextNode: TTreeNode;
begin
result:= false;
i := ResultsNoteBook.PageIndex;
if i < 0 then exit;
lPage := ResultsNoteBook.Pages[i];
if not Assigned(lPage) then exit;
lTree := GetTreeView(lPage.PageIndex);
lSelNode := lTree.Selected;
if not Assigned(lSelNode) then Exit;
if Assigned(lSelNode.Data) then begin
// file pos node
if DirectionDown then
lNextNode := lSelNode.GetNextVisibleSibling
else
lNextNode := lSelNode.GetPrevVisibleSibling;
if not Assigned(lNextNode) then begin
// if no more siblings, find first/last child of parent's sibling
if DirectionDown then begin
lNextNode := lSelNode.Parent;
if Assigned(lNextNode) then begin
lNextNode := lNextNode.GetNextVisibleSibling;
if Assigned(lNextNode) then
lNextNode := lNextNode.GetFirstVisibleChild;
end;
end
else begin
lNextNode := lSelNode.Parent;
if Assigned(lNextNode) then begin
lNextNode := lNextNode.GetPrevVisibleSibling;
if Assigned(lNextNode) then
lNextNode := lNextNode.GetLastVisibleChild;
end;
end;
end;
end
else begin
// file node - need to use first child or last child of prev sibling
if DirectionDown then
lNextNode := lSelNode.GetFirstVisibleChild
else begin
lNextNode := lSelNode.GetPrevVisibleSibling;
if Assigned(lNextNode) then
lNextNode := lNextNode.GetLastVisibleChild;
end;
end;
if Assigned(lNextNode) then begin
lTree.Select(lNextNode);
result := true;
end;
end;
function TSearchResultsView.GetPageIndex(const APageName: string): integer;
var
Paren, i: integer;
PN: String;
begin
result := -1;
for i := 0 to ResultsNoteBook.PageCount - 1 do
begin
PN := ResultsNoteBook.Page[i].Caption;
Paren := Pos(' (', PN);
if (Paren > 0) and (PosEx(')', PN, Paren + 2) > 0) then
PN := LeftStr(PN, Paren-1);
if PN = APageName then
exit(i);
end;
end;
{Returns the TreeView control from a Tab if both the page and the TreeView exist,
else returns nil}
function TSearchResultsView.GetTreeView(APageIndex: integer): TLazSearchResultTV;
var
i: integer;
lPage: TTabSheet;
begin
result := nil;
lPage := GetResultsPage(APageIndex); // this also check APageIndex range
if not assigned(lPage) then exit;
for i:= 0 to lPage.ComponentCount - 1 do
if lPage.Components[i] is TLazSearchResultTV then
exit(TLazSearchResultTV(lPage.Components[i]));
end;
function TSearchResultsView.GetCurrentTree: TLazSearchResultTV;
begin
if ResultsNoteBook.PageIndex < 0 then
Result := nil
else
Result := GetTreeView(ResultsNoteBook.PageIndex);
end;
procedure TSearchResultsView.SetAsyncUpdateCloseButtons(const AValue: TSVCloseButtonsState);
var
Old: TSVCloseButtonsState;
begin
if FAsyncUpdateCloseButtons=AValue then Exit;
Old:=FAsyncUpdateCloseButtons;
FAsyncUpdateCloseButtons:=AValue;
if Old=svcbNone then
Application.QueueAsyncCall(@DoAsyncUpdateCloseButtons,0);
end;
procedure TLazSearchResultTV.SetSkipped(const AValue: integer);
var
SrcList: TStrings;
s: String;
HasSkippedLine: Boolean;
SkippedLine: String;
begin
if FSkipped=AValue then exit;
FSkipped:=AValue;
s:=rsFoundButNotListedHere;
if fUpdating then
SrcList:=fUpdateStrings
else
SrcList:=ItemsAsStrings;
if (SrcList.Count>0) and (copy(SrcList[SrcList.Count-1],1,length(s))=s) then
HasSkippedLine:=true
else
HasSkippedLine:=false;
SkippedLine:=s+IntToStr(FSkipped);
if FSkipped>0 then begin
if HasSkippedLine then begin
SrcList[SrcList.Count-1]:=SkippedLine;
end else begin
SrcList.add(SkippedLine);
end;
end else begin
if HasSkippedLine then
SrcList.Delete(SrcList.Count-1);
end;
end;
procedure TLazSearchResultTV.AddNode(Line: string; MatchPos: TLazSearchMatchPos);
var
Node: TTreeNode;
ChildNode: TTreeNode;
AVLNode: TAvlTreeNode;
begin
if MatchPos=nil then exit;
AVLNode:=fFilenameToNode.FindKey(PChar(MatchPos.FileName),@CompareFilenameWithTVNode);
if AVLNode<>nil then
Node := TTreeNode(AVLNode.Data)
else
Node := nil;
//enter a new file entry
if not Assigned(Node) then
begin
Node := Items.Add(Nil, MatchPos.FileName);
fFilenameToNode.Add(Node);
end;
ChildNode := Items.AddChild(Node, Line);
Node.Expanded := true;
ChildNode.Data := MatchPos;
end;
{******************************************************************************
TLazSearchResultTV
******************************************************************************}
Constructor TLazSearchResultTV.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ReadOnly := True;
fSearchObject:= TLazSearch.Create;
fUpdateStrings:= TStringList.Create;
fFilenameToNode:=TAvlTree.Create(@CompareTVNodeTextAsFilename);
fUpdating:= false;
fUpdateCount:= 0;
FSearchInListPhrases := '';
fFiltered := False;
end;//Create
Destructor TLazSearchResultTV.Destroy;
begin
if Assigned(fSearchObject) then
FreeThenNil(fSearchObject);
//if UpdateStrings is empty, the objects are stored in Items due to filtering
//filtering clears UpdateStrings
if (fUpdateStrings.Count = 0) then
Items.FreeAllNodeData;
FreeThenNil(fFilenameToNode);
Assert(Assigned(fUpdateStrings), 'fUpdateStrings = Nil');
fUpdateStrings.OwnsObjects := true; // set only right before Free!
FreeThenNil(fUpdateStrings);
inherited Destroy;
end;//Destroy
procedure TLazSearchResultTV.BeginUpdate;
var
s: TStrings;
begin
inc(fUpdateCount);
if (fUpdateCount = 1) then
begin
// save old treeview content
if Assigned(Items) then
begin
s := ItemsAsStrings;
fUpdateStrings.Assign(s);
s.Free;
end;
fUpdating:= true;
end;
end;
procedure TLazSearchResultTV.EndUpdate;
var
i: integer;
begin
if (fUpdateCount = 0) then
LazTracer.RaiseGDBException('TLazSearchResultTV.EndUpdate');
Dec(fUpdateCount);
if (fUpdateCount = 0) then
begin
ShortenPaths;
fUpdating:= false;
Items.FreeAllNodeData;
Items.BeginUpdate;
Items.Clear;
fFilenameToNode.Clear;
for i := 0 to fUpdateStrings.Count - 1 do
AddNode(fUpdateStrings[i], TLazSearchMatchPos(fUpdateStrings.Objects[i]));
Items.EndUpdate;
end;//if
end;//EndUpdate
procedure TLazSearchResultTV.ShortenPaths;
var
i: Integer;
AnObject: TObject;
SharedPath: String;
MatchPos: TLazSearchMatchPos;
SrcList: TStrings;
SharedLen: Integer;
ShownText: String;
FreeSrcList: Boolean;
begin
if fUpdateCount > 0 then exit;
FreeSrcList := not fUpdating;
if fUpdating then
SrcList := fUpdateStrings
else
SrcList := ItemsAsStrings;
try
// find shared path (the path of all filenames, that is the same)
SharedPath := '';
for i := 0 to SrcList.Count - 1 do
begin
AnObject := SrcList.Objects[i];
if AnObject is TLazSearchMatchPos then
begin
MatchPos := TLazSearchMatchPos(AnObject);
if i = 0 then
SharedPath := ExtractFilePath(MatchPos.Filename)
else if SharedPath <> '' then
begin
SharedLen := 0;
while
(SharedLen < length(MatchPos.Filename)) and
(SharedLen < length(SharedPath)) and
(MatchPos.Filename[SharedLen + 1] = SharedPath[SharedLen + 1])
do
inc(SharedLen);
while (SharedLen > 0) and (SharedPath[SharedLen] <> PathDelim) do
dec(SharedLen);
if SharedLen <> length(SharedPath) then
SharedPath := copy(SharedPath, 1, SharedLen);
end;
end;
end;
// shorten shown paths
SharedLen := length(SharedPath);
for i := 0 to SrcList.Count - 1 do
begin
AnObject := SrcList.Objects[i];
if AnObject is TLazSearchMatchPos then
begin
MatchPos := TLazSearchMatchPos(AnObject);
MatchPos.ShownFilename := copy(MatchPos.Filename, SharedLen + 1, length(MatchPos.Filename));
ShownText := BeautifyLineAt(MatchPos);
SrcList[i] := ShownText;
SrcList.Objects[i] := MatchPos;
end;
end;
finally
if FreeSrcList then FreeThenNil(SrcList);
end;
end;
function TLazSearchResultTV.BeautifyLineAt(SearchPos: TLazSearchMatchPos): string;
begin
with SearchPos do
Result:=BeautifyLineXY(ShownFilename, TheText, FileStartPos.X, FileStartPos.Y);
end;
function TLazSearchResultTV.ItemsAsStrings: TStrings;
var
i: integer;
begin
Result := TStringList.Create;
for i := 0 to Items.Count - 1 do
Result.AddObject(Items[i].Text,TObject(Items[i].Data));
end;
{ TLazSearchMatchPos }
destructor TLazSearchMatchPos.Destroy;
begin
FreeThenNil(FNextInThisLine);
inherited Destroy;
end;
end.