1{ 2 /*************************************************************************** 3 findreplacedialog.pp 4 -------------------- 5 6 ***************************************************************************/ 7 8 Author: Mattias Gaertner 9 10 ***************************************************************************** 11 See the file COPYING.modifiedLGPL.txt, included in this distribution, 12 for details about the license. 13 ***************************************************************************** 14 15 Abstract: 16 Find and replace dialog form. 17 Usage: 18 Add to program 19 "Application.CreateForm(TLazFindReplaceDialog, FindReplaceDlg);" 20 Set the FindReplaceDlg.Options property 21 then do MResult:=FindReplaceDlg.ShowModal 22 ShowModal can have three possible results: 23 - mrOk for Find/Replace. 24 - mrAll for ReplaceAll 25 - mrCancel for Cancel 26 27} 28unit FindReplaceDialog; 29 30{$mode objfpc}{$H+} 31 32interface 33 34uses 35 Classes, SysUtils, RegExpr, LCLProc, LCLType, Controls, StdCtrls, Forms, 36 Buttons, ExtCtrls, Dialogs, Graphics, ButtonPanel, 37 SynEditTypes, SynEdit, 38 IDEHelpIntf, IDEImagesIntf, IDEWindowIntf, IDEDialogs, 39 LazarusIdeStrConsts, InputHistory, EnvironmentOpts; 40 41type 42 TFindDlgComponent = (fdcText, fdcReplace); 43 TOnFindDlgKey = procedure(Sender: TObject; var Key: Word; Shift:TShiftState; 44 FindDlgComponent: TFindDlgComponent) of Object; 45 46 TLazFindReplaceState = record 47 FindText: string; 48 ReplaceText: string; 49 Options: TSynSearchOptions; 50 end; 51 52 { TLazFindReplaceDialog } 53 54 TLazFindReplaceDialog = class(TForm) 55 BackwardRadioButton: TRadioButton; 56 BtnPanel: TButtonPanel; 57 CaseSensitiveCheckBox: TCheckBox; 58 DirectionGroupBox: TGroupBox; 59 EntireScopeRadioButton: TRadioButton; 60 ForwardRadioButton: TRadioButton; 61 FromCursorRadioButton: TRadioButton; 62 GlobalRadioButton: TRadioButton; 63 MultiLineCheckBox: TCheckBox; 64 OptionsGroupBox: TGroupBox; 65 OriginGroupBox: TGroupBox; 66 PromptOnReplaceCheckBox: TCheckBox; 67 RegularExpressionsCheckBox: TCheckBox; 68 ReplaceTextComboBox: TComboBox; 69 ReplaceWithCheckbox: TCheckBox; 70 ScopeGroupBox: TGroupBox; 71 SelectedRadioButton: TRadioButton; 72 EnableAutoCompleteSpeedButton: TSpeedButton; 73 TextToFindComboBox: TComboBox; 74 TextToFindLabel: TLabel; 75 WholeWordsOnlyCheckBox: TCheckBox; 76 procedure EnableAutoCompleteSpeedButtonClick(Sender: TObject); 77 procedure FormChangeBounds(Sender: TObject); 78 procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction); 79 procedure FormShow(Sender: TObject); 80 procedure HelpButtonClick(Sender: TObject); 81 procedure OptionsGroupBoxResize(Sender: TObject); 82 procedure ReplaceWithCheckboxChange(Sender: TObject); 83 procedure TextToFindComboboxKeyDown(Sender: TObject; var Key: Word; 84 Shift: TShiftState); 85 procedure OkButtonClick(Sender: TObject); 86 procedure ReplaceAllButtonClick(Sender: TObject); 87 procedure CancelButtonClick(Sender: TObject); 88 private 89 FOnKey: TOnFindDlgKey; 90 fReplaceAllClickedLast: boolean; 91 RegExpr: TRegExpr; 92 DlgHistoryIndex: array[TFindDlgComponent] of integer; 93 DlgUserText: array[TFindDlgComponent] of string; 94 function CheckInput: boolean; 95 function GetComponentText(c: TFindDlgComponent): string; 96 function GetEnableAutoComplete: boolean; 97 procedure SetComponentText(c: TFindDlgComponent; const AValue: string); 98 procedure SetEnableAutoComplete(const AValue: boolean); 99 procedure SetOnKey(const AValue: TOnFindDlgKey); 100 procedure SetOptions(NewOptions: TSynSearchOptions); 101 function GetOptions: TSynSearchOptions; 102 function GetFindText: AnsiString; 103 procedure SetFindText(const NewFindText: AnsiString); 104 function GetReplaceText: AnsiString; 105 procedure SetReplaceText(const NewReplaceText: AnsiString); 106 procedure SetComboBoxText(AComboBox: TComboBox; const AText: AnsiString); 107 public 108 constructor Create(TheOwner: TComponent); override; 109 destructor Destroy; override; 110 procedure UpdateHints; 111 procedure ResetUserHistory; 112 procedure RestoreState(const AState: TLazFindReplaceState); 113 procedure SaveState(out AState: TLazFindReplaceState); 114 public 115 property Options: TSynSearchOptions read GetOptions write SetOptions; 116 property EnableAutoComplete: boolean read GetEnableAutoComplete 117 write SetEnableAutoComplete; 118 property FindText:AnsiString read GetFindText write SetFindText; 119 property ReplaceText:AnsiString read GetReplaceText write SetReplaceText; 120 property OnKey: TOnFindDlgKey read FOnKey write SetOnKey; 121 property ComponentText[c: TFindDlgComponent]: string 122 read GetComponentText write SetComponentText; 123 end; 124 125var 126 LazFindReplaceDialog: TLazFindReplaceDialog = nil; 127 128 129implementation 130 131{$R *.lfm} 132 133{ TLazFindReplaceDialog } 134 135constructor TLazFindReplaceDialog.Create(TheOwner:TComponent); 136begin 137 inherited Create(TheOwner); 138 Caption:=''; 139 TextToFindComboBox.Text:=''; 140 TextToFindLabel.Caption:=dlgTextToFind; 141 ReplaceTextComboBox.Text:=''; 142 ReplaceWithCheckbox.Caption:=dlgReplaceWith; 143 IDEImages.AssignImage(EnableAutoCompleteSpeedButton, 'autocomplete'); 144 OptionsGroupBox.Caption:=lisOptions; 145 146 with CaseSensitiveCheckBox do begin 147 Caption:=dlgCaseSensitive; 148 Hint:=lisDistinguishBigAndSmallLettersEGAAndA; 149 end; 150 151 with WholeWordsOnlyCheckBox do begin 152 Caption:=dlgWholeWordsOnly; 153 Hint:=lisOnlySearchForWholeWords; 154 end; 155 156 with RegularExpressionsCheckBox do begin 157 Caption:=dlgRegularExpressions; 158 Hint:=lisActivateRegularExpressionSyntaxForTextAndReplaceme; 159 end; 160 161 with MultiLineCheckBox do begin 162 Caption:=lisFindFileMultiLinePattern; 163 Hint:=lisAllowSearchingForMultipleLines; 164 end; 165 166 with PromptOnReplaceCheckBox do begin 167 Caption:=dlgPromptOnReplace; 168 Hint:=lisAskBeforeReplacingEachFoundText; 169 end; 170 171 OriginGroupBox.Caption := dlgSROrigin; 172 FromCursorRadioButton.Caption := dlgFromCursor; 173 EntireScopeRadioButton.Caption := dlgFromBeginning; 174 175 ScopeGroupBox.Caption := dlgSearchScope; 176 GlobalRadioButton.Caption := dlgGlobal; 177 SelectedRadioButton.Caption := dlgSelectedText; 178 179 DirectionGroupBox.Caption := dlgDirection; 180 ForwardRadioButton.Caption := lisFRForwardSearch; 181 BackwardRadioButton.Caption := lisFRBackwardSearch; 182 183 // CloseButton works now as ReplaceAllButton 184 BtnPanel.CloseButton.Caption := dlgReplaceAll; 185 IDEImages.AssignImage(BtnPanel.CloseButton, 'btn_all'); 186 187 fReplaceAllClickedLast:=false; 188 UpdateHints; 189 190 AutoSize:=IDEDialogLayoutList.Find(Self,false)=nil; 191 IDEDialogLayoutList.ApplyLayout(Self); 192end; 193 194destructor TLazFindReplaceDialog.Destroy; 195begin 196 RegExpr.Free; 197 inherited Destroy; 198 if LazFindReplaceDialog=Self then 199 LazFindReplaceDialog:=nil; 200end; 201 202procedure TLazFindReplaceDialog.FormClose(Sender: TObject; 203 var CloseAction: TCloseAction); 204begin 205 IDEDialogLayoutList.SaveLayout(Self); 206end; 207 208procedure TLazFindReplaceDialog.FormShow(Sender: TObject); 209begin 210 TextToFindComboBox.DropDownCount:=EnvironmentOptions.DropDownCount; 211 ReplaceTextComboBox.DropDownCount:=EnvironmentOptions.DropDownCount; 212end; 213 214procedure TLazFindReplaceDialog.UpdateHints; 215begin 216 if EnableAutoComplete then 217 EnableAutoCompleteSpeedButton.Hint:=lisAutoCompletionOn 218 else 219 EnableAutoCompleteSpeedButton.Hint:=lisAutoCompletionOff; 220end; 221 222procedure TLazFindReplaceDialog.ResetUserHistory; 223var 224 c: TFindDlgComponent; 225begin 226 for c := Low(TFindDlgComponent) to High(TFindDlgComponent) do 227 DlgHistoryIndex[c] := -1; 228end; 229 230procedure TLazFindReplaceDialog.RestoreState(const AState: TLazFindReplaceState); 231begin 232 Options:=AState.Options; 233 FindText:=AState.FindText; 234 ReplaceText:=AState.ReplaceText; 235end; 236 237procedure TLazFindReplaceDialog.SaveState(out AState: TLazFindReplaceState); 238begin 239 FillChar(AState{%H-}, SizeOf(TLazFindReplaceState), 0); 240 AState.Options:=Options; 241 AState.FindText:=FindText; 242 AState.ReplaceText:=ReplaceText; 243end; 244 245procedure TLazFindReplaceDialog.TextToFindComboboxKeyDown(Sender: TObject; 246 var Key: Word; Shift: TShiftState); 247var 248 Component: TFindDlgComponent; 249 HistoryList: TStringList; 250 CurText: string; 251 CurIndex: integer; 252 253 procedure SetHistoryText; 254 var s: string; 255 begin 256 if DlgHistoryIndex[Component]>=0 then 257 s := HistoryList[DlgHistoryIndex[Component]] 258 else 259 s := DlgUserText[Component]; 260 //debugln(' SetHistoryText "',s,'"'); 261 ComponentText[Component]:=s 262 end; 263 264 procedure FetchFocus; 265 begin 266 if Sender is TWinControl then 267 TWinControl(Sender).SetFocus; 268 end; 269 270begin 271 //debugln('TLazFindReplaceDialog.TextToFindComboBoxKeyDown Key=',Key,' RETURN=',VK_RETURN,' TAB=',VK_TAB,' DOWN=',VK_DOWN,' UP=',VK_UP); 272 if Sender=TextToFindComboBox then 273 Component:=fdcText 274 else 275 Component:=fdcReplace; 276 277 if Assigned(OnKey) then begin 278 OnKey(Sender, Key, Shift, Component); 279 end 280 else 281 begin 282 if Component=fdcText then 283 HistoryList:= InputHistories.FindHistory 284 else 285 HistoryList:= InputHistories.ReplaceHistory; 286 CurIndex := DlgHistoryIndex[Component]; 287 CurText := ComponentText[Component]; 288 if Key=VK_UP then begin 289 // go forward in history 290 if CurIndex >= 0 then begin 291 if (HistoryList[CurIndex] <> CurText) then 292 DlgUserText[Component] := CurText; // save user text 293 dec(DlgHistoryIndex[Component]); 294 SetHistoryText; 295 end; 296 FetchFocus; 297 Key:=VK_UNKNOWN; 298 end 299 else if Key=VK_DOWN then begin 300 // go back in history 301 if (CurIndex<0) 302 or (HistoryList[CurIndex] <> CurText) then 303 DlgUserText[Component] := CurText; // save user text 304 if CurIndex < HistoryList.Count-1 then 305 begin 306 inc(DlgHistoryIndex[Component]); 307 SetHistoryText; 308 end; 309 FetchFocus; 310 Key:=VK_UNKNOWN; 311 end; 312 end; 313end; 314 315procedure TLazFindReplaceDialog.HelpButtonClick(Sender: TObject); 316begin 317 LazarusHelp.ShowHelpForIDEControl(Self); 318end; 319 320procedure TLazFindReplaceDialog.OptionsGroupBoxResize(Sender: TObject); 321var 322 h: integer; 323begin 324 DisableAlign; 325 h := (OptionsGroupBox.Height-12) div 3; 326 OriginGroupBox.Height := h; 327 ScopeGroupBox.Height := h; 328 DirectionGroupBox.Height := OptionsGroupBox.Height-2*h-12; 329 EnableAlign; 330end; 331 332procedure TLazFindReplaceDialog.ReplaceWithCheckboxChange(Sender: TObject); 333begin 334 if ReplaceWithCheckbox.Checked then 335 BtnPanel.ShowButtons := BtnPanel.ShowButtons + [pbClose] 336 else 337 BtnPanel.ShowButtons := BtnPanel.ShowButtons - [pbClose]; 338 ReplaceTextComboBox.Enabled:=ReplaceWithCheckbox.Checked; 339 PromptOnReplaceCheckBox.Enabled:=ReplaceWithCheckbox.Checked; 340 if ReplaceWithCheckbox.Checked then 341 begin 342 Caption := lisReplace; 343 BtnPanel.OKButton.Caption := lisBtnReplace; 344 end else 345 begin 346 Caption := lisMenuFind; 347 BtnPanel.OKButton.Caption := lisBtnFind; 348 end; 349end; 350 351procedure TLazFindReplaceDialog.FormChangeBounds(Sender: TObject); 352var 353 w: integer; 354begin 355 DisableAlign; 356 w := (ClientWidth - 18) div 2; 357 OptionsGroupBox.Width := w; 358 OriginGroupBox.Width := w; 359 ScopeGroupBox.Width := w; 360 DirectionGroupBox.Width := w; 361 EnableAlign; 362end; 363 364procedure TLazFindReplaceDialog.EnableAutoCompleteSpeedButtonClick( 365 Sender: TObject); 366begin 367 TextToFindComboBox.AutoComplete:=EnableAutoCompleteSpeedButton.Down; 368 ReplaceTextComboBox.AutoComplete:=EnableAutoCompleteSpeedButton.Down; 369 UpdateHints; 370end; 371 372procedure TLazFindReplaceDialog.OkButtonClick(Sender:TObject); 373begin 374 if not CheckInput then exit; 375 fReplaceAllClickedLast:=false; 376 ActiveControl:=TextToFindComboBox; 377 ModalResult:=mrOk; 378end; 379 380procedure TLazFindReplaceDialog.ReplaceAllButtonClick(Sender:TObject); 381begin 382 if not CheckInput then exit; 383 fReplaceAllClickedLast:=true; 384 ActiveControl:=TextToFindComboBox; 385 ModalResult:=mrAll; 386end; 387 388procedure TLazFindReplaceDialog.CancelButtonClick(Sender:TObject); 389begin 390 ActiveControl:=TextToFindComboBox; 391end; 392 393function TLazFindReplaceDialog.CheckInput: boolean; 394begin 395 Result:=false; 396 if RegularExpressionsCheckBox.Checked then begin 397 if RegExpr=nil then RegExpr:=TRegExpr.Create; 398 try 399 RegExpr.Expression:=FindText; 400 RegExpr.Exec('test'); 401 except 402 on E: ERegExpr do begin 403 IDEMessageDialog(lisUEErrorInRegularExpression, 404 E.Message,mtError,[mbCancel]); 405 exit; 406 end; 407 end; 408 if ReplaceTextComboBox.Enabled then begin 409 try 410 RegExpr.Substitute(ReplaceText); 411 except 412 on E: ERegExpr do begin 413 IDEMessageDialog(lisUEErrorInRegularExpression, 414 E.Message,mtError,[mbCancel]); 415 exit; 416 end; 417 end; 418 end; 419 end; 420 Result:=true; 421end; 422 423function TLazFindReplaceDialog.GetComponentText(c: TFindDlgComponent): string; 424begin 425 case c of 426 fdcText: Result:=FindText; 427 else 428 Result:=Replacetext; 429 end; 430end; 431 432function TLazFindReplaceDialog.GetEnableAutoComplete: boolean; 433begin 434 Result:=EnableAutoCompleteSpeedButton.Down; 435end; 436 437procedure TLazFindReplaceDialog.SetComponentText(c: TFindDlgComponent; 438 const AValue: string); 439begin 440 case c of 441 fdcText: FindText:=AValue; 442 else 443 Replacetext:=AValue; 444 end; 445end; 446 447procedure TLazFindReplaceDialog.SetEnableAutoComplete(const AValue: boolean); 448begin 449 EnableAutoCompleteSpeedButton.Down:=AValue; 450 TextToFindComboBox.AutoComplete:=AValue; 451 ReplaceTextComboBox.AutoComplete:=AValue; 452 UpdateHints; 453end; 454 455procedure TLazFindReplaceDialog.SetOnKey(const AValue: TOnFindDlgKey); 456begin 457 FOnKey:=AValue; 458end; 459 460procedure TLazFindReplaceDialog.SetOptions(NewOptions:TSynSearchOptions); 461begin 462 CaseSensitiveCheckBox.Checked:=ssoMatchCase in NewOptions; 463 WholeWordsOnlyCheckBox.Checked:=ssoWholeWord in NewOptions; 464 RegularExpressionsCheckBox.Checked:=ssoRegExpr in NewOptions; 465 MultiLineCheckBox.Checked:=ssoRegExprMultiLine in NewOptions; 466 PromptOnReplaceCheckBox.Checked:=ssoPrompt in NewOptions; 467 468 if ssoEntireScope in NewOptions 469 then EntireScopeRadioButton.Checked:=True 470 else FromCursorRadioButton.Checked:=True; 471 if ssoSelectedOnly in NewOptions 472 then SelectedRadioButton.Checked:=True 473 else GlobalRadioButton.Checked:=True; 474 if ssoBackwards in NewOptions 475 then BackwardRadioButton.Checked:=True 476 else ForwardRadioButton.Checked:=True; 477 478 if ssoReplace in NewOptions then 479 BtnPanel.ShowButtons := BtnPanel.ShowButtons + [pbClose] 480 else 481 BtnPanel.ShowButtons := BtnPanel.ShowButtons - [pbClose]; 482 ReplaceWithCheckbox.Checked := ssoReplace in NewOptions; 483 ReplaceTextComboBox.Enabled := ssoReplace in NewOptions; 484 PromptOnReplaceCheckBox.Enabled := ssoReplace in NewOptions; 485 fReplaceAllClickedLast := ssoReplaceAll in NewOptions; 486 487 if ssoReplace in NewOptions then 488 begin 489 Caption := lisReplace; 490 BtnPanel.OKButton.Caption := lisBtnReplace; 491 end else 492 begin 493 Caption := lisMenuFind; 494 BtnPanel.OKButton.Caption := lisBtnFind; 495 end; 496 //DebugLn(['TLazFindReplaceDialog.SetOptions END ssoSelectedOnly=',ssoSelectedOnly in NewOptions,' SelectedRadioButton.Checked=',SelectedRadioButton.Checked]); 497end; 498 499function TLazFindReplaceDialog.GetOptions:TSynSearchOptions; 500begin 501 Result:=[]; 502 if CaseSensitiveCheckBox.Checked then Include(Result,ssoMatchCase); 503 if WholeWordsOnlyCheckBox.Checked then Include(Result,ssoWholeWord); 504 if RegularExpressionsCheckBox.Checked then Include(Result,ssoRegExpr); 505 if MultiLineCheckBox.Checked then Include(Result,ssoRegExprMultiLine); 506 if PromptOnReplaceCheckBox.Checked then Include(Result,ssoPrompt); 507 508 if EntireScopeRadioButton.Checked then Include(Result,ssoEntireScope); 509 if SelectedRadioButton.Checked then include(Result,ssoSelectedOnly); 510 if BackwardRadioButton.Checked then include(Result,ssoBackwards); 511 if pbClose in BtnPanel.ShowButtons then include(Result,ssoReplace); 512 if fReplaceAllClickedLast then include(Result,ssoReplaceAll); 513end; 514 515function TLazFindReplaceDialog.GetFindText:AnsiString; 516begin 517 Result:=TextToFindComboBox.Text; 518end; 519 520procedure TLazFindReplaceDialog.SetFindText(const NewFindText: AnsiString); 521begin 522// SetComboBoxText(TextToFindComboBox,NewFindText); 523 TextToFindComboBox.Text:=NewFindText; 524 TextToFindComboBox.SelectAll; 525 //debugln('TLazFindReplaceDialog.SetFindText A TextToFindComboBox.SelStart=',dbgs(TextToFindComboBox.SelStart),' TextToFindComboBox.SelLength=',dbgs(TextToFindComboBox.SelLength),' TextToFindComboBox.Text="',TextToFindComboBox.Text,'" NewFindText="',DbgStr(NewFindText),'"'); 526end; 527 528function TLazFindReplaceDialog.GetReplaceText:AnsiString; 529begin 530 Result:=ReplaceTextComboBox.Text; 531end; 532 533procedure TLazFindReplaceDialog.SetReplaceText(const NewReplaceText:AnsiString); 534begin 535 SetComboBoxText(ReplaceTextComboBox,NewReplaceText); 536end; 537 538procedure TLazFindReplaceDialog.SetComboBoxText(AComboBox:TComboBox; 539 const AText:AnsiString); 540var a:integer; 541begin 542 a:=AComboBox.Items.IndexOf(AText); 543 //debugln('TLazFindReplaceDialog.SetComboBoxText ',AText,' ',a); 544 if a>=0 then 545 AComboBox.ItemIndex:=a 546 else begin 547 AComboBox.Items.Add(AText); 548 AComboBox.ItemIndex:=AComboBox.Items.IndexOf(AText); 549 end; 550end; 551 552end. 553