1{
2 /***************************************************************************
3                       searchresultviewView.pp - SearchResult view
4                       -------------------------------------------
5                   TSearchResultsView is responsible for displaying the
6                   Search Results of a find operation.
7
8
9                   Initial Revision  : Sat Nov 8th 2003
10
11
12 ***************************************************************************/
13
14 ***************************************************************************
15 *                                                                         *
16 *   This source is free software; you can redistribute it and/or modify   *
17 *   it under the terms of the GNU General Public License as published by  *
18 *   the Free Software Foundation; either version 2 of the License, or     *
19 *   (at your option) any later version.                                   *
20 *                                                                         *
21 *   This code is distributed in the hope that it will be useful, but      *
22 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
23 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
24 *   General Public License for more details.                              *
25 *                                                                         *
26 *   A copy of the GNU General Public License is available on the World    *
27 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
28 *   obtain it by writing to the Free Software Foundation,                 *
29 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
30 *                                                                         *
31 ***************************************************************************
32}
33unit SearchResultView;
34
35{$mode objfpc}{$H+}
36
37interface
38
39uses
40  Classes, SysUtils, strutils, Laz_AVL_Tree,
41  // LCL
42  LCLProc, LCLType, LCLIntf, Forms, Controls, Graphics, ComCtrls, Menus, Clipbrd,
43  ActnList, ExtCtrls, StdCtrls, Dialogs,
44  // LazControls
45  TreeFilterEdit, ExtendedNotebook,
46  // LazUtils
47  LazUTF8, LazFileUtils, LazLoggerBase, LazStringUtils,
48  // IdeIntf
49  IDEImagesIntf, IDECommands,
50  // IDE
51  IDEOptionDefs, LazarusIDEStrConsts, EnvironmentOpts, InputHistory, Project, MainIntf;
52
53
54type
55  { TLazSearchMatchPos }
56
57  TLazSearchMatchPos = class(TObject)
58  private
59    FFileEndPos: TPoint;
60    FFilename: string;
61    FFileStartPos: TPoint;
62    fMatchStart: integer;
63    fMatchLen: integer;
64    FNextInThisLine: TLazSearchMatchPos;
65    FShownFilename: string;
66    FTheText: string;
67  public
68    property MatchStart: integer read fMatchStart write fMatchStart;// start in TheText
69    property MatchLen: integer read fMatchLen write fMatchLen; // length in TheText
70    property Filename: string read FFilename write FFilename;
71    property FileStartPos: TPoint read FFileStartPos write FFileStartPos;
72    property FileEndPos: TPoint read FFileEndPos write FFileEndPos;
73    property TheText: string read FTheText write FTheText;
74    property ShownFilename: string read FShownFilename write FShownFilename;
75    property NextInThisLine: TLazSearchMatchPos read FNextInThisLine write FNextInThisLine;
76    destructor Destroy; override;
77  end;//TLazSearchMatchPos
78
79
80  { TLazSearch }
81
82  TLazSearch = Class(TObject)
83  private
84    FReplaceText: string;
85    fSearchString: string;
86    fSearchOptions: TLazFindInFileSearchOptions;
87    fSearchDirectories: string;
88    fSearchMask: string;
89  public
90    property SearchString: string read fSearchString write fSearchString;
91    property ReplaceText: string read FReplaceText write FReplaceText;
92    property SearchOptions: TLazFindInFileSearchOptions read fSearchOptions
93                                                        write fSearchOptions;
94    property SearchDirectories: string read fSearchDirectories
95                                     write fSearchDirectories;
96    property SearchMask: string read fSearchMask write fSearchMask;
97  end;//TLazSearch
98
99
100  { TLazSearchResultTV }
101
102  TLazSearchResultTV = class(TCustomTreeView)
103  private
104    fSearchObject: TLazSearch;
105    FSkipped: integer;
106    fUpdateStrings: TStrings;
107    fUpdating: boolean;
108    fUpdateCount: integer;
109    FSearchInListPhrases: string;
110    fFiltered: Boolean;
111    fFilenameToNode: TAvlTree; // TTreeNode sorted for Text
112    procedure SetSkipped(const AValue: integer);
113    procedure AddNode(Line: string; MatchPos: TLazSearchMatchPos);
114  public
115    constructor Create(AOwner: TComponent); override;
116    destructor Destroy; override;
117    property SearchObject: TLazSearch read fSearchObject write fSearchObject;
118    procedure BeginUpdate;
119    procedure EndUpdate;
120    procedure ShortenPaths;
121    procedure FreeObjectsTN(tnItems: TTreeNodes);
122    procedure FreeObjects(slItems: TStrings);
123    function BeautifyLineAt(SearchPos: TLazSearchMatchPos): string;
124    property Filtered: Boolean read fFiltered write fFiltered;
125    property SearchInListPhrases: string read FSearchInListPhrases write FSearchInListPhrases;
126    property UpdateItems: TStrings read fUpdateStrings write fUpdateStrings;
127    property Updating: boolean read fUpdating;
128    property Skipped: integer read FSkipped write SetSkipped;
129    function ItemsAsStrings: TStrings;
130  end;
131
132  TSVCloseButtonsState = (
133    svcbNone,
134    svcbEnable,
135    svcbDisable
136    );
137
138  { TSearchResultsView }
139
140  TSearchResultsView = class(TForm)
141    actClosePage: TAction;
142    actCloseLeft: TAction;
143    actCloseOthers: TAction;
144    actCloseRight: TAction;
145    actCloseAll: TAction;
146    actNextPage: TAction;
147    actPrevPage: TAction;
148    ActionList: TActionList;
149    ControlBar1: TPanel;
150    MenuItem1: TMenuItem;
151    mniCollapseAll: TMenuItem;
152    mniExpandAll: TMenuItem;
153    mniCopySelected: TMenuItem;
154    mniCopyAll: TMenuItem;
155    mniCopyItem: TMenuItem;
156    pnlToolBars: TPanel;
157    popList: TPopupMenu;
158    ResultsNoteBook: TExtendedNotebook;
159    tbbCloseLeft: TToolButton;
160    tbbCloseOthers: TToolButton;
161    tbbCloseRight: TToolButton;
162    PageToolBar: TToolBar;
163    CloseTabs: TToolBar;
164    RefreshButton: TToolButton;
165    SearchAgainButton: TToolButton;
166    ClosePageButton: TToolButton;
167    SearchInListEdit: TTreeFilterEdit;
168    ToolButton3: TToolButton;
169    tbbCloseAll: TToolButton;
170    procedure actNextPageExecute(Sender: TObject);
171    procedure actPrevPageExecute(Sender: TObject);
172    procedure RefreshButtonClick(Sender: TObject);
173    procedure SearchAgainButtonClick(Sender: TObject);
174    procedure ClosePageButtonClick(Sender: TObject);
175    procedure ResultsNoteBookResize(Sender: TObject);
176    procedure tbbCloseAllClick(Sender: TObject);
177    procedure tbbCloseLeftClick(Sender: TObject);
178    procedure tbbCloseOthersClick(Sender: TObject);
179    procedure tbbCloseRightClick(Sender: TObject);
180    procedure Form1Create(Sender: TObject);
181    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
182    procedure FormKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
183    procedure mniCopyAllClick(Sender: TObject);
184    procedure mniCopyItemClick(Sender: TObject);
185    procedure mniCopySelectedClick(Sender: TObject);
186    procedure mniExpandAllClick(Sender: TObject);
187    procedure mniCollapseAllClick(Sender: TObject);
188    procedure ResultsNoteBookChanging(Sender: TObject; var {%H-}AllowChange: Boolean);
189    procedure ResultsNoteBookMouseDown(Sender: TObject; Button: TMouseButton;
190      {%H-}Shift: TShiftState; X, Y: Integer);
191    procedure TreeViewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
192    procedure ResultsNoteBookClosetabclicked(Sender: TObject);
193    procedure TreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView;
194      Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
195      var {%H-}PaintImages, {%H-}DefaultDraw: Boolean);
196    procedure LazTVShowHint(Sender: TObject; {%H-}HintInfo: PHintInfo);
197    procedure LazTVMousemove(Sender: TObject; {%H-}Shift: TShiftState;
198                             X, Y: Integer);
199    Procedure LazTVMouseWheel(Sender: TObject; Shift: TShiftState;
200                   {%H-}WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
201    procedure TreeViewKeyPress(Sender: TObject; var Key: char);
202    procedure ResultsNoteBookPageChanged (Sender: TObject );
203    procedure SearchInListChange(Sender: TObject );
204    procedure TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
205      Shift: TShiftState; X, Y: Integer);
206  private
207    type
208      TOnSide = (osLeft, osOthers, osRight); { Handling of multi tab closure }
209  private
210    FAsyncUpdateCloseButtons: TSVCloseButtonsState;
211    FMaxItems: integer;
212    FFocusTreeViewInOnChange: Boolean;
213    FFocusTreeViewInEndUpdate: Boolean;
214    FWorkedSearchText: string;
215    FOnSelectionChanged: TNotifyEvent;
216    FMouseOverIndex: integer;
217    FClosingTabs: boolean;
218    function BeautifyPageName(const APageName: string): string;
219    function GetPageIndex(const APageName: string): integer;
220    function GetTreeView(APageIndex: integer): TLazSearchResultTV;
221    procedure SetAsyncUpdateCloseButtons(const AValue: TSVCloseButtonsState);
222    procedure SetItems(Index: Integer; Value: TStrings);
223    function GetItems(Index: integer): TStrings;
224    procedure SetMaxItems(const AValue: integer);
225    procedure UpdateToolbar;
226    function  GetPagesOnActiveLine(aOnSide : TOnSide = osOthers):TFPlist;
227    procedure ClosePageOnSides(aOnSide : TOnSide);
228    procedure ClosePageBegin;
229    procedure ClosePageEnd;
230    procedure DoAsyncUpdateCloseButtons(Data: PtrInt);
231  protected
232    procedure Loaded; override;
233    procedure ActivateControl(aWinControl: TWinControl);
234    procedure UpdateShowing; override;
235    property AsyncUpdateCloseButtons: TSVCloseButtonsState read FAsyncUpdateCloseButtons write SetAsyncUpdateCloseButtons;
236  public
237    function AddSearch(const ResultsName: string;
238                       const SearchText: string;
239                       const ReplaceText: string;
240                       const ADirectories: string;
241                       const AMask: string;
242                       const TheOptions: TLazFindInFileSearchOptions): TTabSheet;
243    function GetSourcePositon: TPoint;
244    function GetSourceFileName: string;
245    function GetSelectedText: string;
246    function GetSelectedMatchPos: TLazSearchMatchPos;
247    procedure AddMatch(const APageIndex: integer;
248                       const Filename: string; const StartPos, EndPos: TPoint;
249                       const TheText: string;
250                       const MatchStart: integer; const MatchLen: integer);
251    procedure BeginUpdate(APageIndex: integer);
252    procedure EndUpdate(APageIndex: integer);
253    procedure Parse_Search_Phrases(var slPhrases: TStrings);
254    procedure ClosePage(PageIndex: integer);
255
256    property MaxItems: integer read FMaxItems write SetMaxItems;
257    property WorkedSearchText: string read FWorkedSearchText;
258    property OnSelectionChanged: TNotifyEvent read fOnSelectionChanged
259                                              write fOnSelectionChanged;
260    property Items[Index: integer]: TStrings read GetItems write SetItems;
261  end;
262
263var
264  SearchResultsView: TSearchResultsView = nil;
265  OnSearchResultsViewSelectionChanged: TNotifyEvent = nil;
266  OnSearchAgainClicked: TNotifyEvent = nil;
267
268implementation
269
270{$R *.lfm}
271
272function CompareTVNodeTextAsFilename(Node1, Node2: Pointer): integer;
273var
274  TVNode1: TTreeNode absolute Node1;
275  TVNode2: TTreeNode absolute Node2;
276begin
277  Result:=CompareFilenames(TVNode1.Text,TVNode2.Text);
278end;
279
280function CompareFilenameWithTVNode(Filename, Node: Pointer): integer;
281var
282  aFilename: String;
283  TVNode: TTreeNode absolute Node;
284begin
285  aFilename:=String(Filename);
286  Result:=CompareFilenames(aFilename,TVNode.Text);
287end;
288
289function CopySearchMatchPos(var Src, Dest: TLazSearchMatchPos): Boolean;
290begin
291  Result := False;
292  if ((Src = nil) or (Dest = nil)) then Exit;
293  Dest.MatchStart := Src.MatchStart;
294  Dest.MatchLen := Src.MatchLen;
295  Dest.Filename := Src.Filename;
296  Dest.FileStartPos := Src.FileStartPos;
297  Dest.FileEndPos := Src.FileEndPos;
298  Dest.TheText := Src.TheText;
299  Dest.ShownFilename := Src.ShownFilename;
300  Result := True;
301end;
302
303function GetTreeSelectedItemsAsText(ATreeView: TCustomTreeView): string;
304var
305  sl: TStringList;
306  node: TTreeNode;
307begin
308  sl:=TStringList.Create;
309  node := ATreeView.GetFirstMultiSelected;
310  while assigned(node) do
311  begin
312    sl.Add(node.Text);
313    node := node.GetNextMultiSelected;
314  end;
315  Result:=sl.Text;
316  sl.Free;
317end;
318
319{ TSearchResultsView }
320
321procedure TSearchResultsView.Form1Create(Sender: TObject);
322var
323  CloseCommand: TIDECommand;
324begin
325  FMaxItems:=50000;
326  ResultsNoteBook.Options:= ResultsNoteBook.Options+[nboShowCloseButtons];
327  ResultsNoteBook.Update;
328
329  Name:=NonModalIDEWindowNames[nmiwSearchResultsView];
330  Caption:=lisMenuViewSearchResults;
331
332  RefreshButton.Hint:=rsRefreshTheSearch;
333  SearchAgainButton.Hint:=rsNewSearchWithSameCriteria;
334  ClosePageButton.Hint:=rsCloseCurrentPage;
335  SearchInListEdit.Hint:=rsFilterTheListWithString;
336  { Close tabs buttons }
337  actCloseLeft.Hint:=rsCloseLeft;
338  actCloseRight.Hint:=rsCloseRight;
339  actCloseOthers.Hint:=rsCloseOthers;
340  actCloseAll.Hint:=rsCloseAll;
341
342  CloseCommand := IDECommandList.FindIDECommand(ecClose);
343  if CloseCommand <> nil then
344  begin
345    if CloseCommand.AsShortCut <> 0 then
346      actClosePage.ShortCut:=CloseCommand.AsShortCut;
347    if (CloseCommand.ShortcutB.Key1 <> 0) and (CloseCommand.ShortcutB.Key2 = 0) then
348      actClosePage.SecondaryShortCuts.Append(ShortCutToText(
349        ShortCut(CloseCommand.ShortcutB.Key1, CloseCommand.ShortcutB.Shift1)));
350  end;
351  fOnSelectionChanged:= nil;
352  ShowHint:= True;
353  fMouseOverIndex:= -1;
354
355  mniCopyItem.Caption := lisCopyItemToClipboard;
356  mniCopySelected.Caption := lisCopySelectedItemToClipboard;
357  mniCopyAll.Caption := lisCopyAllItemsToClipboard;
358  mniExpandAll.Caption := lisExpandAll;
359  mniCollapseAll.Caption := lisCollapseAll;
360
361  PageToolBar.Images := IDEImages.Images_16;
362  RefreshButton.ImageIndex     := IDEImages.LoadImage('laz_refresh');
363  SearchAgainButton.ImageIndex := IDEImages.LoadImage('menu_new_search');
364  ClosePageButton.ImageIndex   := IDEImages.LoadImage('menu_close');
365  ActionList.Images := IDEImages.Images_16;
366  actClosePage.ImageIndex := IDEImages.LoadImage('menu_close');
367  { Close tabs buttons }
368  CloseTabs.Images := IDEImages.Images_16;
369  actCloseLeft.ImageIndex   := IDEImages.LoadImage('tab_close_L');
370  actCloseOthers.ImageIndex := IDEImages.LoadImage('tab_close_LR');
371  actCloseRight.ImageIndex  := IDEImages.LoadImage('tab_close_R');
372  actCloseAll.ImageIndex    := IDEImages.LoadImage('tab_close_All');
373end;
374
375procedure TSearchResultsView.FormClose(Sender: TObject; var CloseAction: TCloseAction);
376begin
377
378end;
379
380procedure TSearchResultsView.FormKeyDown(Sender: TObject; var Key: Word;
381  Shift: TShiftState);
382begin
383  if (Key = VK_ESCAPE) then
384  begin
385    Key := VK_UNKNOWN;
386    Close;
387  end;
388end;
389
390procedure TSearchResultsView.mniCopyAllClick(Sender: TObject);
391var
392  sl: TStrings;
393begin
394  sl := (popList.PopupComponent as TLazSearchResultTV).ItemsAsStrings;
395  Clipboard.AsText := sl.Text;
396  sl.Free;
397end;
398
399procedure TSearchResultsView.mniCopyItemClick(Sender: TObject);
400var
401  tv: TCustomTreeView;
402  Node: TTreeNode;
403begin
404  tv := popList.PopupComponent as TCustomTreeView;
405  with tv.ScreenToClient(popList.PopupPoint) do
406    Node := tv.GetNodeAt(X, Y);
407  if Node <> nil then
408    Clipboard.AsText := Node.Text;
409end;
410
411procedure TSearchResultsView.mniCopySelectedClick(Sender: TObject);
412begin
413  Clipboard.AsText := GetTreeSelectedItemsAsText(popList.PopupComponent as TCustomTreeView);
414end;
415
416procedure TSearchResultsView.mniExpandAllClick(Sender: TObject);
417var
418  CurrentTV: TLazSearchResultTV;
419  Key: Char = '*';
420begin
421  CurrentTV := GetTreeView(ResultsNoteBook.PageIndex);
422  if Assigned(CurrentTV) then
423    TreeViewKeyPress(CurrentTV, Key);
424end;
425
426procedure TSearchResultsView.mniCollapseAllClick(Sender: TObject);
427var
428  CurrentTV: TLazSearchResultTV;
429  Key: Char = '/';
430begin
431  CurrentTV := GetTreeView(ResultsNoteBook.PageIndex);
432  if Assigned(CurrentTV) then
433    TreeViewKeyPress(CurrentTV, Key);
434end;
435
436procedure TSearchResultsView.ResultsNoteBookMouseDown(Sender: TObject; Button: TMouseButton;
437  Shift: TShiftState; X, Y: Integer);
438var
439  TabIndex: LongInt;
440begin
441  if (Button = mbMiddle) then
442  begin
443    TabIndex := ResultsNoteBook.IndexOfPageAt(Point(X,Y));
444    if TabIndex >= 0 then
445      ResultsNoteBookClosetabclicked(ResultsNoteBook.Page[TabIndex]);
446  end;
447end;
448
449procedure TSearchResultsView.RefreshButtonClick(Sender: TObject);
450begin
451  ShowMessage('ToDo: Refresh the search in current page.');
452end;
453
454procedure TSearchResultsView.SearchAgainButtonClick(Sender: TObject);
455var
456  CurrentTV: TLazSearchResultTV;
457  SearchObj: TLazSearch;
458begin
459  CurrentTV:= GetTreeView(ResultsNoteBook.PageIndex);
460  if not Assigned(CurrentTV) then
461    MainIDEInterface.FindInFilesPerDialog(Project1)
462  else begin
463    SearchObj:= CurrentTV.SearchObject;
464    OnSearchAgainClicked(SearchObj);
465    MainIDEInterface.FindInFiles(Project1, SearchObj.SearchString);
466  end;
467end;
468
469procedure TSearchResultsView.ClosePageButtonClick(Sender: TObject);
470begin
471  ClosePage(ResultsNoteBook.PageIndex);
472end;
473
474procedure TSearchResultsView.actNextPageExecute(Sender: TObject);
475begin
476  ResultsNoteBook.SelectNextPage(True);
477end;
478
479procedure TSearchResultsView.actPrevPageExecute(Sender: TObject);
480begin
481  ResultsNoteBook.SelectNextPage(False);
482end;
483
484procedure TSearchResultsView.ResultsNoteBookResize(Sender: TObject);
485begin
486  if ResultsNoteBook.PageCount>0 then
487    AsyncUpdateCloseButtons:=svcbEnable
488  else
489    AsyncUpdateCloseButtons:=svcbDisable;
490end;
491
492{ Handling of tabs closure. Only tabs on pages at the level of active page in
493  multiline ResultsNoteBook will be closed by Left / Others and Right }
494procedure TSearchResultsView.ClosePageOnSides(aOnSide: TOnSide);
495var
496  lvPageList: TFPList = nil;
497  lCurTabSheet, lTabSheet: TTabSheet;
498  ix: integer;
499  lNeedsRefresh : boolean = false;
500begin
501  lvPageList := GetPagesOnActiveLine(aOnSide);
502  if lvPageList = Nil then Exit;
503  ClosePageBegin;
504  lCurTabSheet := ResultsNoteBook.ActivePage;
505  if aOnSide = osLeft then
506    ix := lvPageList.IndexOf(lCurTabSheet)-1
507  else
508    ix := lvPageList.Count-1;
509  while ix >= 0 do begin
510    lTabSheet := TTabSheet(lvPageList[ix]);
511    if lTabSheet = lCurTabSheet then begin
512      if aOnSide = osRight then
513        break;
514    end
515    else begin
516      ClosePage(lTabSheet.TabIndex);
517      lNeedsRefresh := True;
518    end;
519    Dec(ix);
520  end;
521  lvPageList.Free;
522  ClosePageEnd;
523  if lNeedsRefresh then { Force resizing of the active TabSheet }
524    lCurTabSheet.Height := lCurTabSheet.Height+1;
525  UpdateToolBar;
526end;
527
528procedure TSearchResultsView.ClosePageBegin;
529begin
530  FClosingTabs := True;
531end;
532
533procedure TSearchResultsView.ClosePageEnd;
534begin
535  FClosingTabs := False;
536end;
537
538procedure TSearchResultsView.tbbCloseLeftClick(Sender: TObject);
539begin
540  ClosePageOnSides(osLeft);
541end;
542
543procedure TSearchResultsView.tbbCloseOthersClick(Sender: TObject);
544begin
545  ClosePageOnSides(osOthers);
546end;
547
548procedure TSearchResultsView.tbbCloseRightClick(Sender: TObject);
549begin
550  ClosePageOnSides(osRight);
551end;
552
553procedure TSearchResultsView.tbbCloseAllClick(Sender: TObject);
554var
555  lPageIx : integer;
556begin
557  with ResultsNoteBook do begin
558    lPageIx := PageCount;
559    while lPageIx > 0 do begin
560      Dec(lPageIx);
561      if lPageIx < PageCount then
562        ClosePage(lPageIx);
563    end;
564  end;
565end;
566
567{Keeps track of the Index of the Item the mouse is over, Sets ShowHint to true
568if the Item length is longer than the TreeView client width.}
569procedure TSearchResultsView.LazTVMousemove(Sender: TObject; Shift: TShiftState;
570                                            X, Y: Integer);
571var
572  Node: TTreeNode;
573begin
574  if Sender is TLazSearchResultTV then
575    with TLazSearchResultTV(Sender) do
576    begin
577      Node := GetNodeAt(X, Y);
578      if Assigned(Node) then
579        fMouseOverIndex:=Node.Index
580      else
581        fMouseOverIndex:=-1;
582      if (fMouseOverIndex > -1) and (fMouseOverIndex < Items.Count)
583      and (Canvas.TextWidth(Items[fMouseOverIndex].Text) > Width) then
584        ShowHint:= True
585      else
586        ShowHint:= False;
587    end;//with
588end;//LazTVMousemove
589
590{Keep track of the mouse position over the treeview when the wheel is used}
591procedure TSearchResultsView.LazTVMouseWheel(Sender: TObject;
592  Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
593  var Handled: Boolean);
594begin
595  LazTVMouseMove(Sender,Shift,MousePos.X, MousePos.Y);
596  Handled:= false;
597end;
598
599procedure TSearchResultsView.TreeViewKeyPress(Sender: TObject; var Key: char);
600var
601  i: Integer;
602  Tree: TLazSearchResultTV;
603  Node: TTreeNode;
604  Collapse: Boolean;
605begin
606  if Key in ['/', '*'] then
607  begin
608    Collapse := Key = '/';
609    Tree := (Sender as TLazSearchResultTV);
610    for i := Tree.Items.TopLvlCount -1 downto 0 do
611    begin
612      Node := Tree.Items.TopLvlItems[i];
613      if Collapse then
614        Node.Collapse(False)
615      else
616        Node.Expand(False);
617    end;
618    Key := #0;
619  end else
620  if Key = Char(VK_RETURN) then  //SearchInListEdit passes only OnPress through
621  begin
622    Key := #0;
623    if Assigned(FOnSelectionChanged) then
624      FOnSelectionChanged(Self);
625  end;
626end;
627
628procedure TSearchResultsView.ResultsNoteBookPageChanged(Sender: TObject);
629var
630  CurrentTV: TLazSearchResultTV;
631begin
632  CurrentTV := GetTreeView(ResultsNoteBook.PageIndex);
633  if Assigned(CurrentTV) and not (csDestroying in CurrentTV.ComponentState) then begin
634    SearchInListEdit.FilteredTreeview := CurrentTV;
635    SearchInListEdit.Filter := CurrentTV.SearchInListPhrases;
636    if FFocusTreeViewInOnChange then
637      ActivateControl(CurrentTV);
638  end;
639  UpdateToolbar;
640end;
641
642procedure TSearchResultsView.SearchInListChange (Sender: TObject );
643var
644  CurrentTV: TLazSearchResultTV;
645begin
646  CurrentTV := GetTreeView(ResultsNoteBook.PageIndex);
647  if Assigned(CurrentTV) then
648    CurrentTV.SearchInListPhrases := SearchInListEdit.Text;
649end;
650
651procedure TSearchResultsView.TreeViewMouseDown(Sender: TObject;
652  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
653var
654  TV: TCustomTreeView;
655  Node: TTreeNode;
656begin
657  if Button<>mbLeft then exit;
658  TV:=Sender as TCustomTreeView;
659  Node:=TV.GetNodeAt(X,Y);
660  if Node=nil then exit;
661  if x<Node.DisplayTextLeft then exit;
662  //debugln(['TSearchResultsView.TreeViewMouseDown single=',([ssDouble,ssTriple,ssQuad]*Shift=[]),' Option=',EnvironmentOptions.MsgViewDblClickJumps]);
663  if EnvironmentOptions.MsgViewDblClickJumps then
664  begin
665    // double click jumps
666    if not (ssDouble in Shift) then exit;
667  end else begin
668    // single click jumps -> single selection
669    if ([ssDouble,ssTriple,ssQuad]*Shift<>[]) then exit;
670    TV.Items.SelectOnlyThis(Node);
671  end;
672  if Assigned(fOnSelectionChanged) then
673    fOnSelectionChanged(Self);
674end;
675
676function TSearchResultsView.BeautifyPageName(const APageName: string): string;
677const
678  MaxPageName = 25;
679begin
680  Result:=Utf8EscapeControlChars(APageName, emHexPascal);
681  if UTF8Length(Result)>MaxPageName then
682    Result:=UTF8Copy(Result,1,MaxPageName-5)+'...';
683end;
684
685procedure TSearchResultsView.AddMatch(const APageIndex: integer;
686  const Filename: string; const StartPos, EndPos: TPoint;
687  const TheText: string;
688  const MatchStart: integer; const MatchLen: integer);
689var
690  CurrentTV: TLazSearchResultTV;
691  SearchPos: TLazSearchMatchPos;
692  ShownText: String;
693  LastPos: TLazSearchMatchPos;
694begin
695  CurrentTV:=GetTreeView(APageIndex);
696  if Assigned(CurrentTV) then
697  begin
698    if CurrentTV.Updating then begin
699      if CurrentTV.UpdateItems.Count>=MaxItems then begin
700        CurrentTV.Skipped:=CurrentTV.Skipped+1;
701        exit;
702      end;
703    end else begin
704      if CurrentTV.Items.Count>=MaxItems then begin
705        CurrentTV.Skipped:=CurrentTV.Skipped+1;
706        exit;
707      end;
708    end;
709
710    SearchPos:= TLazSearchMatchPos.Create;
711    SearchPos.MatchStart:=MatchStart;
712    SearchPos.MatchLen:=MatchLen;
713    SearchPos.Filename:=Filename;
714    SearchPos.FileStartPos:=StartPos;
715    SearchPos.FileEndPos:=EndPos;
716    SearchPos.TheText:=TheText;
717    SearchPos.ShownFilename:=SearchPos.Filename;
718    ShownText:=CurrentTV.BeautifyLineAt(SearchPos);
719    LastPos:=nil;
720    if CurrentTV.Updating then begin
721      if (CurrentTV.UpdateItems.Count>0)
722      and (CurrentTV.UpdateItems.Objects[CurrentTV.UpdateItems.Count-1] is TLazSearchMatchPos) then
723        LastPos:=TLazSearchMatchPos(CurrentTV.UpdateItems.Objects[CurrentTV.UpdateItems.Count-1]);
724    end else
725      if (CurrentTV.Items.Count>0) and Assigned(CurrentTV.Items[CurrentTV.Items.Count-1].Data) then
726        LastPos:=TLazSearchMatchPos(CurrentTV.Items[CurrentTV.Items.Count-1].Data);
727    if (LastPos<>nil) and (LastPos.Filename=SearchPos.Filename) and
728       (LastPos.FFileStartPos.Y=SearchPos.FFileStartPos.Y) and
729       (LastPos.FFileEndPos.Y=SearchPos.FFileEndPos.Y) then
730    begin
731      while (LastPos.NextInThisLine<>nil) do
732        LastPos := LastPos.NextInThisLine;
733      LastPos.NextInThisLine:=SearchPos
734    end
735    else if CurrentTV.Updating then
736      CurrentTV.UpdateItems.AddObject(ShownText, SearchPos)
737    else
738      CurrentTV.AddNode(ShownText, SearchPos);
739    CurrentTV.ShortenPaths;
740  end;//if
741end;//AddMatch
742
743procedure TSearchResultsView.BeginUpdate(APageIndex: integer);
744var
745  CurrentTV: TLazSearchResultTV;
746begin
747  CurrentTV:= GetTreeView(APageIndex);
748  if Assigned(CurrentTV) then
749    CurrentTV.BeginUpdate;
750  UpdateToolbar;
751end;
752
753procedure TSearchResultsView.EndUpdate(APageIndex: integer);
754var
755  CurrentTV: TLazSearchResultTV;
756begin
757  CurrentTV:= GetTreeView(APageIndex);
758  if Assigned(CurrentTV) then
759  begin
760    CurrentTV.EndUpdate;
761    if CurrentTV.Items.Count>0 then begin
762      CurrentTV.Items[0].Selected:=True;
763    end;
764  end;
765  UpdateToolbar;
766  if FFocusTreeViewInEndUpdate and Assigned(CurrentTV) then
767    ActivateControl(CurrentTV)
768  else
769  if SearchInListEdit.CanFocus then
770    ActivateControl(SearchInListEdit);
771end;
772
773procedure TSearchResultsView.Parse_Search_Phrases(var slPhrases: TStrings);
774var i, iLength: Integer;
775    sPhrases, sPhrase: string;
776begin
777 //Parse Phrases
778 sPhrases := SearchInListEdit.Text;
779 iLength := Length(sPhrases);
780 sPhrase := '';
781 for i:=1 to iLength do
782  begin
783   if ((sPhrases[i] = ' ') or (sPhrases[i] = ',') or (i = iLength)) then
784    begin
785     if not ((sPhrases[i] = ' ') or (sPhrases[i] = ',')) then
786      sPhrase := sPhrase + sPhrases[i];
787     if (sPhrase > ' ') then
788      slPhrases.Add(UpperCase(sPhrase));//End of phrase, add to phrase list
789     sPhrase := '';//Reset sPhrase
790    end else
791    begin
792     if (sPhrases[i] > ' ') then
793      sPhrase := sPhrase + sPhrases[i];
794    end;//End if ((sPhrases[i] = ' ') or (sPhrases[i] = ','))
795  end;//End for-loop i
796end;
797
798procedure TSearchResultsView.ResultsNoteBookChanging(Sender: TObject;
799  var AllowChange: Boolean);
800var
801  CurrentTV: TLazSearchResultTV;
802begin
803  CurrentTV := GetTreeView(ResultsNoteBook.PageIndex);
804  FFocusTreeViewInOnChange := Assigned(CurrentTV) and CurrentTV.Focused;
805end;
806
807procedure TSearchResultsView.ClosePage(PageIndex: integer);
808var
809  CurrentTV: TLazSearchResultTV;
810begin
811  if (PageIndex>=0) and (PageIndex<ResultsNoteBook.PageCount) then
812  begin
813    CurrentTV:= GetTreeView(PageIndex);
814    if Assigned(CurrentTV) and CurrentTV.Updating then
815      exit;
816
817    ResultsNoteBook.Pages[PageIndex].Free;
818  end;
819  if ResultsNoteBook.PageCount = 0 then
820    Close
821  else
822    AsyncUpdateCloseButtons:=svcbEnable;
823end;
824
825{Sets the Items from the treeview on the currently selected page in the TNoteBook}
826procedure TSearchResultsView.SetItems(Index: Integer; Value: TStrings);
827var
828  CurrentTV: TLazSearchResultTV;
829begin
830  if Index > -1 then
831  begin
832    CurrentTV:= GetTreeView(Index);
833    if Assigned(CurrentTV) then
834    begin
835      if CurrentTV.Updating then
836        CurrentTV.UpdateItems.Assign(Value)
837      else
838        CurrentTV.Items.Assign(Value);
839      CurrentTV.Skipped:=0;
840    end;
841  end;
842end;
843
844function TSearchResultsView.GetItems(Index: integer): TStrings;
845var
846  CurrentTV: TLazSearchResultTV;
847begin
848  result:= nil;
849  CurrentTV:= GetTreeView(Index);
850  if Assigned(CurrentTV) then
851  begin
852    if CurrentTV.Updating then
853      result:= CurrentTV.UpdateItems
854    else
855      Result := CurrentTV.ItemsAsStrings;
856  end;
857end;
858
859procedure TSearchResultsView.SetMaxItems(const AValue: integer);
860begin
861  if FMaxItems=AValue then exit;
862  FMaxItems:=AValue;
863end;
864
865procedure TSearchResultsView.UpdateToolbar;
866var
867  CurrentTV: TLazSearchResultTV;
868  state: Boolean;
869begin
870  CurrentTV:= GetTreeView(ResultsNoteBook.PageIndex);
871  state := Assigned(CurrentTV) and not CurrentTV.Updating;
872  RefreshButton.Enabled := state;
873  SearchAgainButton.Enabled := state;
874  ClosePageButton.Enabled := state;
875  SearchInListEdit.Enabled := state;
876  if state then
877    AsyncUpdateCloseButtons:=svcbEnable;
878end;
879
880{ Returns a list of all pages (visible tabs) on the same line of Tabs as the ActivaPage }
881function TSearchResultsView.GetPagesOnActiveLine(aOnSide: TOnSide {=osOthers}): TFPlist;
882var
883  lActiveMidY, lActiveIndex, ix, hh: integer;
884  lActiveRect, lRect, lLastRect: TRect;
885begin
886  Result := nil;
887  with ResultsNoteBook do begin
888    if ActivePage = Nil then Exit;
889    Result := TFPList.Create;
890    lActiveIndex := ResultsNoteBook.ActivePageIndex;
891    lActiveRect := TabRect(lActiveIndex);
892    hh := (lActiveRect.Bottom - lActiveRect.Top) div 2;
893    { Some widgetsets returned a negative value from Bottom-Top calculation. }
894    if hh < 0 then begin       // Do a sanity check.
895      DebugLn(['TSearchResultsView.GetPagesOnActiveLine: TabRect Bottom-Top calculation'+
896               ' for ActivePage returned a negative value "', hh, '".']);
897      hh := -hh;
898    end;
899    lActiveMidY := lActiveRect.Top + hh;
900    { Search closable tabs left of current tab }
901    if aOnSide in [osLeft, osOthers] then begin
902      lLastRect := lActiveRect;
903      for ix := lActiveIndex-1 downto 0 do begin
904        lRect := TabRect(ix);
905        if (lRect.Top >= lActiveMidY) or (lRect.Bottom <= lActiveMidY)
906        or (lRect.Right > lLastRect.Left) then
907          break;
908        Result.Insert(0, Pages[ix]);
909        lLastRect := lRect;
910      end;
911    end;
912    { Current tab }
913    Result.Add(Pages[lActiveIndex]);
914    { Search closable tabs right of current tab }
915    if aOnSide in [osOthers, osRight] then begin
916      lLastRect := lActiveRect;
917      for ix := lActiveIndex+1 to PageCount-1  do begin
918        lRect := TabRect(ix);
919        if (lRect.Top >= lActiveMidY) or (lRect.Bottom <= lActiveMidY)
920        or (lRect.Left < lLastRect.Right) then
921          break;
922        Result.Add(Pages[ix]);
923        lLastRect := lRect;
924      end;
925    end;
926  end;
927end;
928
929procedure TSearchResultsView.DoAsyncUpdateCloseButtons(Data: PtrInt);
930var
931  lPageList: TFPlist = nil;
932  lActiveIx: integer = -1;
933  aEnable: Boolean;
934begin
935  if FClosingTabs then
936    exit;
937  if FAsyncUpdateCloseButtons=svcbNone then exit;
938  aEnable:=FAsyncUpdateCloseButtons=svcbEnable;
939  FAsyncUpdateCloseButtons:=svcbNone;
940
941  if aEnable and (ResultsNoteBook.PageCount>0) then begin
942    lPageList := GetPagesOnActiveLine;
943    if Assigned(lPageList) and (lPageList.Count>0) then
944      repeat
945        inc(lActiveIx);
946        if lPageList[lActiveIx]=Pointer(ResultsNoteBook.ActivePage) then
947          break;
948      until lActiveIx>=lPageList.Count -1;
949  end;
950  aEnable := aEnable and Assigned(lPageList);
951  actCloseLeft.Enabled  := aEnable and (lActiveIx>0);
952  if aEnable then begin
953    actCloseOthers.Enabled:= lPageList.Count>1;
954    actCloseRight.Enabled := lActiveIx<(lPageList.Count-1);
955  end
956  else begin
957    actCloseOthers.Enabled:= False;
958    actCloseRight.Enabled := False;
959  end;
960  actCloseAll.Enabled   := aEnable;
961  lPageList.Free;
962end;
963
964procedure TSearchResultsView.ResultsNoteBookClosetabclicked(Sender: TObject);
965begin
966  if (Sender is TTabSheet) then
967    ClosePage(TTabSheet(Sender).PageIndex)
968end;
969
970procedure TSearchResultsView.TreeViewKeyDown(Sender: TObject; var Key: Word;
971  Shift: TShiftState);
972begin
973  if (Key = VK_RETURN) and (Shift = []) then
974  begin
975    Key:=VK_UNKNOWN;
976    if Assigned(FOnSelectionChanged) then
977      FOnSelectionChanged(Self);
978  end;
979end;
980
981{ Add Result will create a tab in the Results view window with an new
982  treeview or focus an existing TreeView and update it's searchoptions.}
983function TSearchResultsView.AddSearch(const ResultsName: string;
984  const SearchText: string;
985  const ReplaceText: string;
986  const ADirectories: string;
987  const AMask: string;
988  const TheOptions: TLazFindInFileSearchOptions): TTabSheet;
989var
990  NewTreeView: TLazSearchResultTV;
991  NewPage: LongInt;
992  SearchObj: TLazSearch;
993begin
994  Result:= nil;
995  if Assigned(ResultsNoteBook) then
996  begin
997    with ResultsNoteBook do
998    begin
999      FFocusTreeViewInEndUpdate := not (Assigned(ActivePage)
1000                                  and SearchInListEdit.IsParentOf(ActivePage));
1001      FWorkedSearchText:=BeautifyPageName(ResultsName);
1002      NewPage:= TCustomTabControl(ResultsNoteBook).Pages.Add(FWorkedSearchText);
1003      PageIndex:= NewPage;
1004      Page[PageIndex].OnKeyDown := @TreeViewKeyDown;
1005      if NewPage > -1 then
1006      begin
1007        NewTreeView:= TLazSearchResultTV.Create(Page[NewPage]);
1008        with NewTreeView do
1009        begin
1010          Parent:= Page[NewPage];
1011          Align:= alClient;
1012          BorderSpacing.Around := 0;
1013          OnKeyDown := @TreeViewKeyDown;
1014          OnAdvancedCustomDrawItem:= @TreeViewAdvancedCustomDrawItem;
1015          OnShowHint:= @LazTVShowHint;
1016          OnMouseMove:= @LazTVMousemove;
1017          OnMouseWheel:= @LazTVMouseWheel;
1018          OnMouseDown:=@TreeViewMouseDown;
1019          OnKeyPress:=@TreeViewKeyPress;
1020          ShowHint:= true;
1021          RowSelect := True;                        // we are using custom draw
1022          Options := Options + [tvoAllowMultiselect] - [tvoThemedDraw];
1023          PopupMenu := popList;
1024          NewTreeView.Canvas.Brush.Color:= clWhite;
1025        end;//with
1026        SearchObj:=NewTreeView.SearchObject;
1027        if SearchObj<>nil then begin
1028          SearchObj.SearchString:= SearchText;
1029          SearchObj.ReplaceText := ReplaceText;
1030          SearchObj.SearchDirectories:= ADirectories;
1031          SearchObj.SearchMask:= AMask;
1032          SearchObj.SearchOptions:= TheOptions;
1033        end;
1034        NewTreeView.Skipped:=0;
1035      end
1036      else
1037        NewTreeView:=nil;
1038      Result:= Pages[PageIndex];
1039      SearchInListEdit.Text:='';
1040      SearchInListEdit.Filter:='';
1041      SearchInListEdit.FilteredTreeview := NewTreeView;
1042    end;//with
1043  end;
1044end;//AddResult
1045
1046procedure TSearchResultsView.LazTVShowHint(Sender: TObject; HintInfo: PHintInfo);
1047var
1048  MatchPos: TLazSearchMatchPos;
1049  HintStr: string;
1050begin
1051  if Sender is TLazSearchResultTV then
1052  begin
1053    With Sender as TLazSearchResultTV do
1054    begin
1055      if (fMouseOverIndex >= 0) and (fMouseOverIndex < Items.Count) then
1056      begin
1057        if Assigned(Items[fMouseOverIndex].Data) then
1058          MatchPos:= TLazSearchMatchPos(Items[fMouseOverIndex].Data)
1059        else
1060          MatchPos:= nil;
1061        if MatchPos<>nil then
1062          HintStr:=MatchPos.Filename
1063                   +' ('+IntToStr(MatchPos.FileStartPos.Y)
1064                   +','+IntToStr(MatchPos.FileStartPos.X)+')'
1065                   +' '+MatchPos.TheText
1066        else
1067          HintStr:=Items[fMouseOverIndex].Text;
1068        Hint:= HintStr;
1069      end;//if
1070    end;//with
1071  end;//if
1072end;//LazTVShowHint
1073
1074procedure TSearchResultsView.Loaded;
1075begin
1076  inherited Loaded;
1077
1078  ActiveControl := ResultsNoteBook;
1079end;
1080
1081procedure TSearchResultsView.ActivateControl(aWinControl: TWinControl);
1082var
1083  aForm: TCustomForm;
1084begin
1085  if not aWinControl.CanFocus then exit;
1086  if Parent=nil then
1087    ActiveControl:=aWinControl
1088  else begin
1089    aForm:=GetParentForm(Self);
1090    if aForm<>nil then aForm.ActiveControl:=aWinControl;
1091  end;
1092end;
1093
1094procedure TSearchResultsView.UpdateShowing;
1095begin
1096  inherited UpdateShowing;
1097  AsyncUpdateCloseButtons:=svcbDisable;
1098end;
1099
1100procedure TSearchResultsView.TreeViewAdvancedCustomDrawItem(
1101  Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState;
1102  Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean);
1103var
1104  CurPart: string;
1105  TheTop: integer;
1106  MatchObj: TObject;
1107  MatchPos,FirstMatchPos: TLazSearchMatchPos;
1108  TextEnd, DrawnTextLength: integer;
1109  ARect: TRect;
1110  TV: TLazSearchResultTV;
1111begin
1112  if Stage <> cdPostPaint then Exit;
1113
1114  TV:=Sender as TLazSearchResultTV;
1115  if [cdsSelected,cdsMarked] * State <> [] then
1116    TV.Canvas.Font.Color := clHighlightText;
1117
1118  ARect:=Node.DisplayRect(true);
1119  TV.Canvas.FillRect(ARect);
1120
1121  MatchObj := TLazSearchMatchPos(Node.Data);
1122  if MatchObj is TLazSearchMatchPos then
1123    MatchPos:= TLazSearchMatchPos(Node.Data)
1124  else
1125    MatchPos:= nil;
1126
1127  if Assigned(MatchPos) then
1128  begin
1129    FirstMatchPos:=MatchPos;
1130    TheTop:= ARect.Top;
1131    TextEnd:=ARect.Left;
1132    DrawnTextLength:=0;
1133
1134    CurPart:=MatchPos.ShownFilename+' ('+IntToStr(MatchPos.FileStartPos.Y)
1135        +':'+IntToStr(MatchPos.FileStartPos.X);
1136    MatchPos:=MatchPos.NextInThisLine;
1137    SetBkMode(TV.Canvas.Handle, TRANSPARENT);
1138    while assigned(MatchPos) do begin
1139      CurPart:=CurPart+','+IntToStr(MatchPos.FileStartPos.X);
1140      MatchPos:=MatchPos.NextInThisLine;
1141    end;
1142    CurPart:=CurPart+') ';
1143    TV.Canvas.TextOut(TextEnd, TheTop, CurPart);
1144    TextEnd:= TextEnd + TV.Canvas.TextWidth(CurPart);
1145
1146    MatchPos:=FirstMatchPos;
1147    while assigned(MatchPos) do begin
1148      //debugln(['TSearchResultsView.TreeViewAdvancedCustomDrawItem MatchPos.TheText="',MatchPos.TheText,'" MatchPos.MatchStart=',MatchPos.MatchStart,' MatchPos.MatchLen=',MatchPos.MatchLen]);
1149      // draw normal text
1150      CurPart:=copy(MatchPos.TheText, DrawnTextLength+1, MatchPos.MatchStart-1-DrawnTextLength);
1151      CurPart:=Utf8EscapeControlChars(CurPart, emHexPascal);
1152      DrawnTextLength:=MatchPos.MatchStart-1;
1153      TV.Canvas.TextOut(TextEnd, TheTop, CurPart);
1154      TextEnd:= TextEnd + TV.Canvas.TextWidth(CurPart);
1155      // draw found text (matched)
1156      CurPart:=ShortDotsLine(copy(MatchPos.TheText, DrawnTextLength+1, MatchPos.MatchLen));
1157      DrawnTextLength:=DrawnTextLength+MatchPos.MatchLen;
1158      TV.Canvas.Font.Style:= TV.Canvas.Font.Style + [fsBold];
1159      TV.Canvas.TextOut(TextEnd, TheTop, CurPart);
1160      TextEnd:= TextEnd + TV.Canvas.TextWidth(CurPart);
1161      TV.Canvas.Font.Style:= TV.Canvas.Font.Style - [fsBold];
1162
1163      if MatchPos.NextInThisLine=nil then begin
1164        CurPart:=copy(MatchPos.TheText, DrawnTextLength+1, Length(MatchPos.TheText));
1165        CurPart:=Utf8EscapeControlChars(CurPart, emHexPascal);
1166        TV.Canvas.TextOut(TextEnd, TheTop, CurPart);
1167      end;
1168      MatchPos:=MatchPos.NextInThisLine;
1169    end;
1170  end
1171  else begin
1172    // this is usually the filename only
1173    // draw it here too, so that the correct colors are used
1174    TV.Canvas.TextOut(ARect.Left, ARect.Top, Node.Text);
1175  end;//if
1176end;//TreeViewDrawItem
1177
1178{Returns the Position within the source file from a properly formated search result}
1179function TSearchResultsView.GetSourcePositon: TPoint;
1180var
1181  MatchPos: TLazSearchMatchPos;
1182begin
1183  Result.x:= -1;
1184  Result.y:= -1;
1185  MatchPos:=GetSelectedMatchPos;
1186  if MatchPos=nil then exit;
1187  Result:=MatchPos.FileStartPos;
1188end;//GetSourcePositon
1189
1190{Returns The file name portion of a properly formated search result}
1191function TSearchResultsView.GetSourceFileName: string;
1192var
1193  MatchPos: TLazSearchMatchPos;
1194begin
1195  MatchPos:=GetSelectedMatchPos;
1196  if MatchPos=nil then
1197    Result:=''
1198  else
1199    Result:=MatchPos.Filename;
1200end;//GetSourceFileName
1201
1202{Returns the selected text in the currently active TreeView.}
1203function TSearchResultsView.GetSelectedText: string;
1204var
1205  ThePage: TTabSheet;
1206  TheTreeView: TLazSearchResultTV;
1207  i: integer;
1208begin
1209  result:= '';
1210  i:= ResultsNoteBook.PageIndex;
1211  if i > -1 then
1212  begin
1213    ThePage:= ResultsNoteBook.Pages[i];
1214    if Assigned(ThePage) then
1215    begin
1216      TheTreeView:= GetTreeView(ThePage.PageIndex);
1217      if Assigned(TheTreeView.Selected) then
1218        Result:= TheTreeView.Selected.Text;
1219    end;//if
1220  end;//if
1221end;//GetSelectedText
1222
1223function TSearchResultsView.GetSelectedMatchPos: TLazSearchMatchPos;
1224var
1225  ThePage: TTabSheet;
1226  TheTreeView: TLazSearchResultTV;
1227  i: integer;
1228begin
1229  Result:= nil;
1230  i:= ResultsNoteBook.PageIndex;
1231  if i > -1 then
1232  begin
1233    ThePage:= ResultsNoteBook.Pages[i];
1234    if Assigned(ThePage) then
1235    begin
1236      TheTreeView:= GetTreeView(ThePage.PageIndex);
1237      if Assigned(TheTreeView.Selected) then
1238        Result := TLazSearchMatchPos(TheTreeView.Selected.Data);
1239    end;
1240  end;
1241end;
1242
1243function TSearchResultsView.GetPageIndex(const APageName: string): integer;
1244var
1245  Paren, i: integer;
1246  PN: String;
1247begin
1248  Result:= -1;
1249  for i:= 0 to ResultsNoteBook.PageCount - 1 do
1250  begin
1251    PN:= ResultsNoteBook.Page[i].Caption;
1252    Paren:= Pos(' (', PN);
1253    if (Paren>0) and (PosEx(')', PN, Paren+2)>0) then
1254      PN:= LeftStr(PN, Paren-1);
1255    if PN = APageName then
1256    begin
1257      Result:= i;
1258      break;
1259    end;
1260  end;
1261end;
1262
1263{Returns a the TreeView control from a Tab if both the page and the TreeView
1264 exist else returns nil}
1265function TSearchResultsView.GetTreeView(APageIndex: integer): TLazSearchResultTV;
1266var
1267  i: integer;
1268  ThePage: TTabSheet;
1269begin
1270  Result:= nil;
1271  if (APageIndex > -1) and (APageIndex < ResultsNoteBook.PageCount) then
1272  begin
1273    ThePage:= ResultsNoteBook.Pages[APageIndex];
1274    if Assigned(ThePage) then
1275    begin
1276      for i:= 0 to ThePage.ComponentCount - 1 do
1277      begin
1278        if ThePage.Components[i] is TLazSearchResultTV then
1279        begin
1280          Result:= TLazSearchResultTV(ThePage.Components[i]);
1281          break;
1282        end;
1283      end;
1284    end;
1285  end;
1286end;
1287
1288procedure TSearchResultsView.SetAsyncUpdateCloseButtons(const AValue: TSVCloseButtonsState);
1289var
1290  Old: TSVCloseButtonsState;
1291begin
1292  if FAsyncUpdateCloseButtons=AValue then Exit;
1293  Old:=FAsyncUpdateCloseButtons;
1294  FAsyncUpdateCloseButtons:=AValue;
1295  if Old=svcbNone then
1296    Application.QueueAsyncCall(@DoAsyncUpdateCloseButtons,0);
1297end;
1298
1299procedure TLazSearchResultTV.SetSkipped(const AValue: integer);
1300var
1301  SrcList: TStrings;
1302  s: String;
1303  HasSkippedLine: Boolean;
1304  SkippedLine: String;
1305begin
1306  if FSkipped=AValue then exit;
1307  FSkipped:=AValue;
1308  s:=rsFoundButNotListedHere;
1309  if fUpdating then
1310    SrcList:=fUpdateStrings
1311  else
1312    SrcList:=ItemsAsStrings;
1313  if (SrcList.Count>0) and (copy(SrcList[SrcList.Count-1],1,length(s))=s) then
1314    HasSkippedLine:=true
1315  else
1316    HasSkippedLine:=false;
1317  SkippedLine:=s+IntToStr(FSkipped);
1318  if FSkipped>0 then begin
1319    if HasSkippedLine then begin
1320      SrcList[SrcList.Count-1]:=SkippedLine;
1321    end else begin
1322      SrcList.add(SkippedLine);
1323    end;
1324  end else begin
1325    if HasSkippedLine then
1326      SrcList.Delete(SrcList.Count-1);
1327  end;
1328end;
1329
1330procedure TLazSearchResultTV.AddNode(Line: string; MatchPos: TLazSearchMatchPos);
1331var
1332  Node: TTreeNode;
1333  ChildNode: TTreeNode;
1334  AVLNode: TAvlTreeNode;
1335begin
1336  if MatchPos=nil then exit;
1337  AVLNode:=fFilenameToNode.FindKey(PChar(MatchPos.FileName),@CompareFilenameWithTVNode);
1338  if AVLNode<>nil then
1339    Node := TTreeNode(AVLNode.Data)
1340  else
1341    Node := nil;
1342
1343  //enter a new file entry
1344  if not Assigned(Node) then
1345    begin
1346    Node := Items.Add(Nil, MatchPos.FileName);
1347    fFilenameToNode.Add(Node);
1348    end;
1349
1350  ChildNode := Items.AddChild(Node, Line);
1351  Node.Expanded:=true;
1352  ChildNode.Data := MatchPos;
1353end;
1354
1355{******************************************************************************
1356  TLazSearchResultTV
1357******************************************************************************}
1358Constructor TLazSearchResultTV.Create(AOwner: TComponent);
1359begin
1360  inherited Create(AOwner);
1361  ReadOnly := True;
1362  fSearchObject:= TLazSearch.Create;
1363  fUpdateStrings:= TStringList.Create;
1364  fFilenameToNode:=TAvlTree.Create(@CompareTVNodeTextAsFilename);
1365  fUpdating:= false;
1366  fUpdateCount:= 0;
1367  FSearchInListPhrases := '';
1368  fFiltered := False;
1369end;//Create
1370
1371Destructor TLazSearchResultTV.Destroy;
1372begin
1373  if Assigned(fSearchObject) then
1374    FreeAndNil(fSearchObject);
1375  //if UpdateStrings is empty, the objects are stored in Items due to filtering
1376  //filtering clears UpdateStrings
1377  if (fUpdateStrings.Count = 0) then
1378    FreeObjectsTN(Items);
1379  fFilenameToNode.Free;
1380  Assert(Assigned(fUpdateStrings), 'fUpdateStrings = Nil');
1381  FreeObjects(fUpdateStrings);
1382  FreeAndNil(fUpdateStrings);
1383  inherited Destroy;
1384end;//Destroy
1385
1386procedure TLazSearchResultTV.BeginUpdate;
1387var
1388  s: TStrings;
1389begin
1390  inc(fUpdateCount);
1391  if (fUpdateCount = 1) then
1392  begin
1393    // save old treeview content
1394    if Assigned(Items) then
1395    begin
1396      s := ItemsAsStrings;
1397      fUpdateStrings.Assign(s);
1398      s.Free;
1399    end;
1400    fUpdating:= true;
1401  end;
1402end;
1403
1404procedure TLazSearchResultTV.EndUpdate;
1405var
1406  i: integer;
1407begin
1408  if (fUpdateCount = 0) then
1409    RaiseGDBException('TLazSearchResultTV.EndUpdate');
1410  Dec(fUpdateCount);
1411  if (fUpdateCount = 0) then
1412  begin
1413    ShortenPaths;
1414    fUpdating:= false;
1415    FreeObjectsTN(Items);
1416    Items.BeginUpdate;
1417    Items.Clear;
1418    fFilenameToNode.Clear;
1419    for i := 0 to fUpdateStrings.Count - 1 do
1420      AddNode(fUpdateStrings[i], TLazSearchMatchPos(fUpdateStrings.Objects[i]));
1421    Items.EndUpdate;
1422  end;//if
1423end;//EndUpdate
1424
1425procedure TLazSearchResultTV.ShortenPaths;
1426var
1427  i: Integer;
1428  AnObject: TObject;
1429  SharedPath: String;
1430  MatchPos: TLazSearchMatchPos;
1431  SrcList: TStrings;
1432  SharedLen: Integer;
1433  ShownText: String;
1434  FreeSrcList: Boolean;
1435begin
1436  if fUpdateCount>0 then exit;
1437
1438  if fUpdating then begin
1439    SrcList:=fUpdateStrings;
1440    FreeSrcList:=false;
1441  end else begin
1442    SrcList:=ItemsAsStrings;
1443    FreeSrcList:=true;
1444  end;
1445  try
1446    // find shared path (the path of all filenames, that is the same)
1447    SharedPath:='';
1448    for i:=0 to SrcList.Count-1 do begin
1449      AnObject:=SrcList.Objects[i];
1450      if AnObject is TLazSearchMatchPos then begin
1451        MatchPos:=TLazSearchMatchPos(AnObject);
1452        if i=0 then
1453          SharedPath:=ExtractFilePath(MatchPos.Filename)
1454        else if (SharedPath<>'') then begin
1455          SharedLen:=0;
1456          while (SharedLen<length(MatchPos.Filename))
1457          and (SharedLen<length(SharedPath))
1458          and (MatchPos.Filename[SharedLen+1]=SharedPath[SharedLen+1])
1459          do
1460            inc(SharedLen);
1461          while (SharedLen>0) and (SharedPath[SharedLen]<>PathDelim) do
1462            dec(SharedLen);
1463          if SharedLen<>length(SharedPath) then
1464            SharedPath:=copy(SharedPath,1,SharedLen);
1465        end;
1466      end;
1467    end;
1468
1469    // shorten shown paths
1470    SharedLen:=length(SharedPath);
1471    for i:=0 to SrcList.Count-1 do begin
1472      AnObject:=SrcList.Objects[i];
1473      if AnObject is TLazSearchMatchPos then begin
1474        MatchPos:=TLazSearchMatchPos(AnObject);
1475        MatchPos.ShownFilename:=copy(MatchPos.Filename,SharedLen+1,
1476                                     length(MatchPos.Filename));
1477        ShownText:=BeautifyLineAt(MatchPos);
1478        SrcList[i]:=ShownText;
1479        SrcList.Objects[i]:=MatchPos;
1480      end;
1481    end;
1482  finally
1483    if FreeSrcList then SrcList.Free;
1484  end;
1485end;
1486
1487procedure TLazSearchResultTV.FreeObjectsTN(tnItems: TTreeNodes);
1488var i: Integer;
1489begin
1490  fFilenameToNode.Clear;
1491  for i:=0 to tnItems.Count-1 do
1492    if Assigned(tnItems[i].Data) then
1493      TLazSearchMatchPos(tnItems[i].Data).Free;
1494end;
1495
1496procedure TLazSearchResultTV.FreeObjects(slItems: TStrings);
1497var i: Integer;
1498begin
1499  if (slItems.Count <= 0) then Exit;
1500  for i:=0 to slItems.Count-1 do
1501    if Assigned(slItems.Objects[i]) then
1502      slItems.Objects[i].Free;
1503end;
1504
1505function TLazSearchResultTV.BeautifyLineAt(SearchPos: TLazSearchMatchPos): string;
1506begin
1507  with SearchPos do
1508    Result:=BeautifyLineXY(ShownFilename, TheText, FileStartPos.X, FileStartPos.Y);
1509end;
1510
1511function TLazSearchResultTV.ItemsAsStrings: TStrings;
1512var
1513  i: integer;
1514begin
1515  Result := TStringList.Create;
1516  for i := 0 to Items.Count - 1 do
1517    Result.AddObject(Items[i].Text,TObject(Items[i].Data));
1518end;
1519
1520{ TLazSearchMatchPos }
1521
1522destructor TLazSearchMatchPos.Destroy;
1523begin
1524  FreeAndNil(FNextInThisLine);
1525  inherited Destroy;
1526end;
1527
1528end.
1529
1530