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