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