{ /*************************************************************************** findreplacedialog.pp -------------------- ***************************************************************************/ Author: Mattias Gaertner ***************************************************************************** See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** Abstract: Find and replace dialog form. Usage: Add to program "Application.CreateForm(TLazFindReplaceDialog, FindReplaceDlg);" Set the FindReplaceDlg.Options property then do MResult:=FindReplaceDlg.ShowModal ShowModal can have three possible results: - mrOk for Find/Replace. - mrAll for ReplaceAll - mrCancel for Cancel } unit FindReplaceDialog; {$mode objfpc}{$H+} interface uses Classes, SysUtils, RegExpr, LCLType, Controls, StdCtrls, Forms, Buttons, ExtCtrls, Dialogs, Graphics, ButtonPanel, SynEditTypes, SynEdit, IDEHelpIntf, IDEImagesIntf, IDEWindowIntf, IDEDialogs, InputHistory, EnvironmentOpts, LazarusIdeStrConsts; type TFindDlgComponent = (fdcText, fdcReplace); TOnFindDlgKey = procedure(Sender: TObject; var Key: Word; Shift:TShiftState; FindDlgComponent: TFindDlgComponent) of Object; TLazFindReplaceState = record FindText: string; ReplaceText: string; Options: TSynSearchOptions; end; { TLazFindReplaceDialog } TLazFindReplaceDialog = class(TForm) BackwardRadioButton: TRadioButton; BtnPanel: TButtonPanel; CaseSensitiveCheckBox: TCheckBox; DirectionGroupBox: TGroupBox; EntireScopeRadioButton: TRadioButton; ForwardRadioButton: TRadioButton; FromCursorRadioButton: TRadioButton; GlobalRadioButton: TRadioButton; MultiLineCheckBox: TCheckBox; OptionsGroupBox: TGroupBox; OriginGroupBox: TGroupBox; PromptOnReplaceCheckBox: TCheckBox; RegularExpressionsCheckBox: TCheckBox; ReplaceTextComboBox: TComboBox; ReplaceWithCheckbox: TCheckBox; ScopeGroupBox: TGroupBox; SelectedRadioButton: TRadioButton; EnableAutoCompleteSpeedButton: TSpeedButton; TextToFindComboBox: TComboBox; TextToFindLabel: TLabel; WholeWordsOnlyCheckBox: TCheckBox; procedure EnableAutoCompleteSpeedButtonClick(Sender: TObject); procedure FormChangeBounds(Sender: TObject); procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction); procedure FormShow(Sender: TObject); procedure HelpButtonClick(Sender: TObject); procedure OptionsGroupBoxResize(Sender: TObject); procedure ReplaceWithCheckboxChange(Sender: TObject); procedure TextToFindComboboxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure OkButtonClick(Sender: TObject); procedure ReplaceAllButtonClick(Sender: TObject); procedure CancelButtonClick(Sender: TObject); private FOnKey: TOnFindDlgKey; fReplaceAllClickedLast: boolean; RegExpr: TRegExpr; DlgHistoryIndex: array[TFindDlgComponent] of integer; DlgUserText: array[TFindDlgComponent] of string; function CheckInput: boolean; function GetComponentText(c: TFindDlgComponent): string; function GetEnableAutoComplete: boolean; procedure SetComponentText(c: TFindDlgComponent; const AValue: string); procedure SetEnableAutoComplete(const AValue: boolean); procedure SetOnKey(const AValue: TOnFindDlgKey); procedure SetOptions(NewOptions: TSynSearchOptions); function GetOptions: TSynSearchOptions; function GetFindText: AnsiString; procedure SetFindText(const NewFindText: AnsiString); function GetReplaceText: AnsiString; procedure SetReplaceText(const NewReplaceText: AnsiString); procedure SetComboBoxText(AComboBox: TComboBox; const AText: AnsiString); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure UpdateHints; procedure ResetUserHistory; procedure RestoreState(const AState: TLazFindReplaceState); procedure SaveState(out AState: TLazFindReplaceState); public property Options: TSynSearchOptions read GetOptions write SetOptions; property EnableAutoComplete: boolean read GetEnableAutoComplete write SetEnableAutoComplete; property FindText:AnsiString read GetFindText write SetFindText; property ReplaceText:AnsiString read GetReplaceText write SetReplaceText; property OnKey: TOnFindDlgKey read FOnKey write SetOnKey; property ComponentText[c: TFindDlgComponent]: string read GetComponentText write SetComponentText; end; var LazFindReplaceDialog: TLazFindReplaceDialog = nil; implementation {$R *.lfm} { TLazFindReplaceDialog } constructor TLazFindReplaceDialog.Create(TheOwner:TComponent); begin inherited Create(TheOwner); Caption:=''; TextToFindComboBox.Text:=''; TextToFindLabel.Caption:=dlgTextToFind; ReplaceTextComboBox.Text:=''; ReplaceWithCheckbox.Caption:=dlgReplaceWith; IDEImages.AssignImage(EnableAutoCompleteSpeedButton, 'autocomplete'); OptionsGroupBox.Caption:=lisOptions; with CaseSensitiveCheckBox do begin Caption:=dlgCaseSensitive; Hint:=lisDistinguishBigAndSmallLettersEGAAndA; end; with WholeWordsOnlyCheckBox do begin Caption:=dlgWholeWordsOnly; Hint:=lisOnlySearchForWholeWords; end; with RegularExpressionsCheckBox do begin Caption:=dlgRegularExpressions; Hint:=lisActivateRegularExpressionSyntaxForTextAndReplaceme; end; with MultiLineCheckBox do begin Caption:=lisFindFileMultiLinePattern; Hint:=lisAllowSearchingForMultipleLines; end; with PromptOnReplaceCheckBox do begin Caption:=dlgPromptOnReplace; Hint:=lisAskBeforeReplacingEachFoundText; end; OriginGroupBox.Caption := dlgSROrigin; FromCursorRadioButton.Caption := dlgFromCursor; EntireScopeRadioButton.Caption := dlgFromBeginning; ScopeGroupBox.Caption := dlgSearchScope; GlobalRadioButton.Caption := dlgGlobal; SelectedRadioButton.Caption := dlgSelectedText; DirectionGroupBox.Caption := dlgDirection; ForwardRadioButton.Caption := lisFRForwardSearch; BackwardRadioButton.Caption := lisFRBackwardSearch; // CloseButton works now as ReplaceAllButton BtnPanel.CloseButton.Caption := dlgReplaceAll; IDEImages.AssignImage(BtnPanel.CloseButton, 'btn_all'); fReplaceAllClickedLast:=false; UpdateHints; AutoSize:=IDEDialogLayoutList.Find(Self,false)=nil; IDEDialogLayoutList.ApplyLayout(Self); end; destructor TLazFindReplaceDialog.Destroy; begin RegExpr.Free; inherited Destroy; if LazFindReplaceDialog=Self then LazFindReplaceDialog:=nil; end; procedure TLazFindReplaceDialog.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin IDEDialogLayoutList.SaveLayout(Self); end; procedure TLazFindReplaceDialog.FormShow(Sender: TObject); begin TextToFindComboBox.DropDownCount:=EnvironmentOptions.DropDownCount; ReplaceTextComboBox.DropDownCount:=EnvironmentOptions.DropDownCount; end; procedure TLazFindReplaceDialog.UpdateHints; begin if EnableAutoComplete then EnableAutoCompleteSpeedButton.Hint:=lisAutoCompletionOn else EnableAutoCompleteSpeedButton.Hint:=lisAutoCompletionOff; end; procedure TLazFindReplaceDialog.ResetUserHistory; var c: TFindDlgComponent; begin for c := Low(TFindDlgComponent) to High(TFindDlgComponent) do DlgHistoryIndex[c] := -1; end; procedure TLazFindReplaceDialog.RestoreState(const AState: TLazFindReplaceState); begin Options:=AState.Options; FindText:=AState.FindText; ReplaceText:=AState.ReplaceText; end; procedure TLazFindReplaceDialog.SaveState(out AState: TLazFindReplaceState); begin FillChar(AState{%H-}, SizeOf(TLazFindReplaceState), 0); AState.Options:=Options; AState.FindText:=FindText; AState.ReplaceText:=ReplaceText; end; procedure TLazFindReplaceDialog.TextToFindComboboxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var Component: TFindDlgComponent; HistoryList: TStringList; CurText: string; CurIndex: integer; procedure SetHistoryText; var s: string; begin if DlgHistoryIndex[Component]>=0 then s := HistoryList[DlgHistoryIndex[Component]] else s := DlgUserText[Component]; //debugln(' SetHistoryText "',s,'"'); ComponentText[Component]:=s end; procedure FetchFocus; begin if Sender is TWinControl then TWinControl(Sender).SetFocus; end; begin //debugln('TLazFindReplaceDialog.TextToFindComboBoxKeyDown Key=',Key,' RETURN=',VK_RETURN,' TAB=',VK_TAB,' DOWN=',VK_DOWN,' UP=',VK_UP); if Sender=TextToFindComboBox then Component:=fdcText else Component:=fdcReplace; if Assigned(OnKey) then begin OnKey(Sender, Key, Shift, Component); end else begin if Component=fdcText then HistoryList:= InputHistories.FindHistory else HistoryList:= InputHistories.ReplaceHistory; CurIndex := DlgHistoryIndex[Component]; CurText := ComponentText[Component]; if Key=VK_UP then begin // go forward in history if CurIndex >= 0 then begin if (HistoryList[CurIndex] <> CurText) then DlgUserText[Component] := CurText; // save user text dec(DlgHistoryIndex[Component]); SetHistoryText; end; FetchFocus; Key:=VK_UNKNOWN; end else if Key=VK_DOWN then begin // go back in history if (CurIndex<0) or (HistoryList[CurIndex] <> CurText) then DlgUserText[Component] := CurText; // save user text if CurIndex < HistoryList.Count-1 then begin inc(DlgHistoryIndex[Component]); SetHistoryText; end; FetchFocus; Key:=VK_UNKNOWN; end; end; end; procedure TLazFindReplaceDialog.HelpButtonClick(Sender: TObject); begin LazarusHelp.ShowHelpForIDEControl(Self); end; procedure TLazFindReplaceDialog.OptionsGroupBoxResize(Sender: TObject); var h: integer; begin DisableAlign; h := (OptionsGroupBox.Height-12) div 3; OriginGroupBox.Height := h; ScopeGroupBox.Height := h; DirectionGroupBox.Height := OptionsGroupBox.Height-2*h-12; EnableAlign; end; procedure TLazFindReplaceDialog.ReplaceWithCheckboxChange(Sender: TObject); begin if ReplaceWithCheckbox.Checked then BtnPanel.ShowButtons := BtnPanel.ShowButtons + [pbClose] else BtnPanel.ShowButtons := BtnPanel.ShowButtons - [pbClose]; ReplaceTextComboBox.Enabled:=ReplaceWithCheckbox.Checked; PromptOnReplaceCheckBox.Enabled:=ReplaceWithCheckbox.Checked; if ReplaceWithCheckbox.Checked then begin Caption := lisReplace; BtnPanel.OKButton.Caption := lisBtnReplace; end else begin Caption := lisMenuFind; BtnPanel.OKButton.Caption := lisBtnFind; end; end; procedure TLazFindReplaceDialog.FormChangeBounds(Sender: TObject); var w: integer; begin DisableAlign; w := (ClientWidth - 18) div 2; OptionsGroupBox.Width := w; OriginGroupBox.Width := w; ScopeGroupBox.Width := w; DirectionGroupBox.Width := w; EnableAlign; end; procedure TLazFindReplaceDialog.EnableAutoCompleteSpeedButtonClick( Sender: TObject); begin TextToFindComboBox.AutoComplete:=EnableAutoCompleteSpeedButton.Down; ReplaceTextComboBox.AutoComplete:=EnableAutoCompleteSpeedButton.Down; UpdateHints; end; procedure TLazFindReplaceDialog.OkButtonClick(Sender:TObject); begin if not CheckInput then exit; fReplaceAllClickedLast:=false; ActiveControl:=TextToFindComboBox; ModalResult:=mrOk; end; procedure TLazFindReplaceDialog.ReplaceAllButtonClick(Sender:TObject); begin if not CheckInput then exit; fReplaceAllClickedLast:=true; ActiveControl:=TextToFindComboBox; ModalResult:=mrAll; end; procedure TLazFindReplaceDialog.CancelButtonClick(Sender:TObject); begin ActiveControl:=TextToFindComboBox; end; function TLazFindReplaceDialog.CheckInput: boolean; begin Result:=false; if RegularExpressionsCheckBox.Checked then begin if RegExpr=nil then RegExpr:=TRegExpr.Create; try RegExpr.Expression:=FindText; RegExpr.Exec('test'); except on E: ERegExpr do begin IDEMessageDialog(lisUEErrorInRegularExpression, E.Message,mtError,[mbCancel]); exit; end; end; if ReplaceTextComboBox.Enabled then begin try RegExpr.Substitute(ReplaceText); except on E: ERegExpr do begin IDEMessageDialog(lisUEErrorInRegularExpression, E.Message,mtError,[mbCancel]); exit; end; end; end; end; Result:=true; end; function TLazFindReplaceDialog.GetComponentText(c: TFindDlgComponent): string; begin case c of fdcText: Result:=FindText; else Result:=Replacetext; end; end; function TLazFindReplaceDialog.GetEnableAutoComplete: boolean; begin Result:=EnableAutoCompleteSpeedButton.Down; end; procedure TLazFindReplaceDialog.SetComponentText(c: TFindDlgComponent; const AValue: string); begin case c of fdcText: FindText:=AValue; else Replacetext:=AValue; end; end; procedure TLazFindReplaceDialog.SetEnableAutoComplete(const AValue: boolean); begin EnableAutoCompleteSpeedButton.Down:=AValue; TextToFindComboBox.AutoComplete:=AValue; ReplaceTextComboBox.AutoComplete:=AValue; UpdateHints; end; procedure TLazFindReplaceDialog.SetOnKey(const AValue: TOnFindDlgKey); begin FOnKey:=AValue; end; procedure TLazFindReplaceDialog.SetOptions(NewOptions:TSynSearchOptions); begin CaseSensitiveCheckBox.Checked:=ssoMatchCase in NewOptions; WholeWordsOnlyCheckBox.Checked:=ssoWholeWord in NewOptions; RegularExpressionsCheckBox.Checked:=ssoRegExpr in NewOptions; MultiLineCheckBox.Checked:=ssoRegExprMultiLine in NewOptions; PromptOnReplaceCheckBox.Checked:=ssoPrompt in NewOptions; if ssoEntireScope in NewOptions then EntireScopeRadioButton.Checked:=True else FromCursorRadioButton.Checked:=True; if ssoSelectedOnly in NewOptions then SelectedRadioButton.Checked:=True else GlobalRadioButton.Checked:=True; if ssoBackwards in NewOptions then BackwardRadioButton.Checked:=True else ForwardRadioButton.Checked:=True; if ssoReplace in NewOptions then BtnPanel.ShowButtons := BtnPanel.ShowButtons + [pbClose] else BtnPanel.ShowButtons := BtnPanel.ShowButtons - [pbClose]; ReplaceWithCheckbox.Checked := ssoReplace in NewOptions; ReplaceTextComboBox.Enabled := ssoReplace in NewOptions; PromptOnReplaceCheckBox.Enabled := ssoReplace in NewOptions; fReplaceAllClickedLast := ssoReplaceAll in NewOptions; if ssoReplace in NewOptions then begin Caption := lisReplace; BtnPanel.OKButton.Caption := lisBtnReplace; end else begin Caption := lisMenuFind; BtnPanel.OKButton.Caption := lisBtnFind; end; //DebugLn(['TLazFindReplaceDialog.SetOptions END ssoSelectedOnly=',ssoSelectedOnly in NewOptions,' SelectedRadioButton.Checked=',SelectedRadioButton.Checked]); end; function TLazFindReplaceDialog.GetOptions:TSynSearchOptions; begin Result:=[]; if CaseSensitiveCheckBox.Checked then Include(Result,ssoMatchCase); if WholeWordsOnlyCheckBox.Checked then Include(Result,ssoWholeWord); if RegularExpressionsCheckBox.Checked then Include(Result,ssoRegExpr); if MultiLineCheckBox.Checked then Include(Result,ssoRegExprMultiLine); if PromptOnReplaceCheckBox.Checked then Include(Result,ssoPrompt); if EntireScopeRadioButton.Checked then Include(Result,ssoEntireScope); if SelectedRadioButton.Checked then include(Result,ssoSelectedOnly); if BackwardRadioButton.Checked then include(Result,ssoBackwards); if pbClose in BtnPanel.ShowButtons then include(Result,ssoReplace); if fReplaceAllClickedLast then include(Result,ssoReplaceAll); end; function TLazFindReplaceDialog.GetFindText:AnsiString; begin Result:=TextToFindComboBox.Text; end; procedure TLazFindReplaceDialog.SetFindText(const NewFindText: AnsiString); begin // SetComboBoxText(TextToFindComboBox,NewFindText); TextToFindComboBox.Text:=NewFindText; TextToFindComboBox.SelectAll; //debugln('TLazFindReplaceDialog.SetFindText A TextToFindComboBox.SelStart=',dbgs(TextToFindComboBox.SelStart),' TextToFindComboBox.SelLength=',dbgs(TextToFindComboBox.SelLength),' TextToFindComboBox.Text="',TextToFindComboBox.Text,'" NewFindText="',DbgStr(NewFindText),'"'); end; function TLazFindReplaceDialog.GetReplaceText:AnsiString; begin Result:=ReplaceTextComboBox.Text; end; procedure TLazFindReplaceDialog.SetReplaceText(const NewReplaceText:AnsiString); begin SetComboBoxText(ReplaceTextComboBox,NewReplaceText); end; procedure TLazFindReplaceDialog.SetComboBoxText(AComboBox:TComboBox; const AText:AnsiString); var a:integer; begin a:=AComboBox.Items.IndexOf(AText); //debugln('TLazFindReplaceDialog.SetComboBoxText ',AText,' ',a); if a>=0 then AComboBox.ItemIndex:=a else begin AComboBox.Items.Add(AText); AComboBox.ItemIndex:=AComboBox.Items.IndexOf(AText); end; end; end.