unit u_search;

{$I u_defines.inc}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  Menus, StdCtrls, actnList, Buttons, SynEdit, SynEditSearch, SynEditTypes,
  RegExpr, SynEditTextBuffer, strutils,
  u_common, u_mru, u_widget, u_synmemo, u_interfaces, u_observer,
  u_writableComponent, u_dialogs, u_sharedres, u_dsgncontrols;

type

  // TSearchWidget persistents settings
  TSearchOptions = class(TWritableLfmTextComponent)
  private
    fPrompt: boolean;
    fFromCur: boolean;
    fRegex: boolean;
    fCaseSens:boolean;
    fBackWard: boolean;
    fWholeWord: boolean;
    fMrSearches: TStringList;
    fMrReplacements: TStringList;
    procedure cleanIvnalidHistoryItems;
    procedure setMrSearches(value: TStringList);
    procedure setMrReplacements(value: TStringList);
  protected
    procedure afterLoad; override;
    procedure beforeSave; override;
  published
    property prompt: boolean read fPrompt write fPrompt;
    property fromCursor: boolean read fFromCur write fFromCur;
    property regex: boolean read fRegex write fRegex;
    property caseSensistive: boolean read fCaseSens write fCaseSens;
    property backward: boolean read fBackWard write fBackWard;
    property wholeWord: boolean read fWholeWord write fWholeWord;
    property recentSearches: TStringList read fMrSearches write setMrSearches;
    property recentReplacements: TStringList read fMrReplacements write setMrReplacements;
  public
    constructor create(aOwner: TComponent); override;
    destructor destroy; override;
    procedure assign(source: TPersistent); override;
    procedure assignTo(target: TPersistent); override;
  end;

  TSearchScope = (scDoc, scSel, scProj, scOpened);

  TSearchWidget = class(TDexedWidget, IDocumentObserver, IProjectObserver)
    btnAllScope: TBitBtn;
    btnFind: TBitBtn;
    btnFindAll: TBitBtn;
    btnReplace: TBitBtn;
    btnReplaceAll: TBitBtn;
    cbToFind: TComboBox;
    cbReplaceWth: TComboBox;
    chkEnableRep: TCheckBox;
    chkPrompt: TCheckBox;
    chkRegex: TCheckBox;
    chkWWord: TCheckBox;
    chkBack: TCheckBox;
    chkFromCur: TCheckBox;
    chkCaseSens: TCheckBox;
    FlowPanel1: TFlowPanel;
    grpOpts: TGroupBox;
    imgList: TImageList;
    Panel1: TPanel;
    Panel2: TPanel;
    procedure btnAllScopeClick(Sender: TObject);
    procedure cbReplaceWthChange(Sender: TObject);
    procedure cbReplaceWthKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure cbToFindChange(Sender: TObject);
    procedure cbToFindKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState
      );
    procedure chkEnableRepChange(Sender: TObject);
  private
    fDoc: TDexedMemo;
    fDocSelStart: TPoint;
    fDocSelStop: TPoint;
    fToFind: string;
    fReplaceWth: string;
    fActReplaceNext: TAction;
    fActFindNext: TAction;
    fActReplaceAll: TAction;
    fActFindAll: TAction;
    fSearchMru, fReplaceMru: TMruList;
    fCancelAll: boolean;
    fHasSearched: boolean;
    fHasRestarted: boolean;
    fProj: ICommonProject;
    fFindScope: TSearchScope;
    function getOptions: TSynSearchOptions;
    procedure actReplaceAllExecute(sender: TObject);
    procedure replaceEvent(Sender: TObject; const ASearch, AReplace:
      string; Line, Column: integer; var ReplaceAction: TSynReplaceAction);
    procedure replaceInSelEvent(Sender: TObject; const ASearch, AReplace:
      string; Line, Column: integer; var ReplaceAction: TSynReplaceAction);
    procedure projNew(project: ICommonProject);
    procedure projChanged(project: ICommonProject);
    procedure projClosing(project: ICommonProject);
    procedure projFocused(project: ICommonProject);
    procedure projCompiling(project: ICommonProject);
    procedure projCompiled(project: ICommonProject; success: boolean);

    procedure docNew(document: TDexedMemo);
    procedure docClosing(document: TDexedMemo);
    procedure docFocused(document: TDexedMemo);
    procedure docChanged(document: TDexedMemo);

    function findAll(const filename: string; lines: TStrings;
        showNoResult: boolean = true): integer;
  protected
    procedure updateImperative; override;
  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;

    procedure actFindNextExecute(sender: TObject);
    procedure actReplaceNextExecute(sender: TObject);
    procedure actFindAllExecute(sender: TObject);
  end;

implementation
{$R *.lfm}

const
  OptsFname = 'search.txt';
  FindScopeStr: array[TSearchScope] of string = ('Document', 'Selection', 'Project', 'Opened docs');

{$REGION TSearchOptions ------------------------------------------------------}
constructor TSearchOptions.create(aOwner: TComponent);
begin
  inherited;
  fMrReplacements := TStringList.Create;
  fMrSearches := TStringList.Create;
end;

destructor TSearchOptions.destroy;
begin
  fMrSearches.Free;
  fMrReplacements.Free;
  inherited;
end;

procedure TSearchOptions.assign(source: TPersistent);
var
  widg: TSearchWidget;
begin
  if source is TSearchWidget then
  begin
    widg := TSearchWidget(source);
    fMrSearches.Assign(widg.fSearchMru);
    fMrReplacements.Assign(widg.fReplaceMru);
    fPrompt     := widg.chkPrompt.Checked;
    fBackWard   := widg.chkBack.Checked;
    fCaseSens   := widg.chkCaseSens.Checked;
    fRegex      := widg.chkRegex.Checked;
    fFromCur    := widg.chkFromCur.Checked;
    fWholeWord  := widg.chkWWord.Checked;
  end
  else inherited;
end;

procedure TSearchOptions.assignTo(target: TPersistent);
var
  widg: TSearchWidget;
begin
  if target is TSearchWidget then
  begin
    widg := TSearchWidget(target);
    widg.cbToFind.Items.Assign(fMrSearches);
    widg.fSearchMru.Assign(fMrSearches);
    widg.cbReplaceWth.Items.Assign(fMrReplacements);
    widg.fReplaceMru.Assign(fMrReplacements);
    widg.chkPrompt.Checked  := fPrompt;
    widg.chkBack.Checked    := fBackWard;
    widg.chkCaseSens.Checked:= fCaseSens;
    widg.chkRegex.Checked   := fRegex;
    widg.chkFromCur.Checked := fFromCur;
    widg.chkWWord.Checked   := fWholeWord;
  end
  else inherited;
end;

procedure TSearchOptions.setMrSearches(value: TStringList);
begin
  fMrSearches.Assign(value);
end;

procedure TSearchOptions.cleanIvnalidHistoryItems;
var
  i: integer;
begin
  for i := fMrReplacements.Count-1 downto 0 do
    if fMrReplacements[i].length > 128 then
      fMrReplacements.Delete(i);
  for i := fMrSearches.Count-1 downto 0 do
      if fMrSearches[i].length > 128 then
        fMrSearches.Delete(i);
end;

procedure TSearchOptions.setMrReplacements(value: TStringList);
begin
  fMrReplacements.Assign(value);
end;

procedure TSearchOptions.afterLoad;
begin
  cleanIvnalidHistoryItems;
end;

procedure TSearchOptions.beforeSave;
begin
  cleanIvnalidHistoryItems;
end;
{$ENDREGION}

{$REGION Standard Comp/Obj------------------------------------------------------}
constructor TSearchWidget.Create(aOwner: TComponent);
var
  fname: string;
begin
  inherited;
  toolbarVisible:=false;
  fActFindNext := TAction.Create(self);
  fActFindNext.Caption := 'Find';
  fActFindNext.OnExecute := @actFindNextExecute;
  fActFindAll := TAction.Create(self);
  fActFindAll.Caption := 'Find all';
  fActFindAll.OnExecute := @actFindAllExecute;
  fActReplaceNext := TAction.Create(self);
  fActReplaceNext.Caption := 'Replace';
  fActReplaceNext.OnExecute := @actReplaceNextExecute;
  fActReplaceAll := TAction.Create(self);
  fActReplaceAll.Caption := 'Replace all';
  fActReplaceAll.OnExecute := @actReplaceAllExecute;

  fSearchMru := TMruList.Create;
  fReplaceMru:= TMruList.Create;

  fname := getDocPath + OptsFname;
  if fname.fileExists then
    with TSearchOptions.create(nil) do
  try
    loadFromFile(fname);
    assignTo(self);
  finally
    free;
  end;

  btnFind.Action := fActFindNext;
  btnReplace.Action := fActReplaceNext;
  btnReplaceAll.Action := fActReplaceAll;
  btnFindAll.Action := fActFindAll;
  case GetIconScaledSize of
    iss16:
    begin
      AssignPng(btnAllScope, 'DOCUMENT');
      AssignPng(btnFind, 'FIND');
      AssignPng(btnFindAll, 'FIND');
      AssignPng(btnReplace, 'TEXT_REPLACE');
      AssignPng(btnReplaceAll, 'TEXT_REPLACE');
    end;
    iss24:
    begin
      AssignPng(btnAllScope, 'DOCUMENT24');
      AssignPng(btnFind, 'FIND24');
      AssignPng(btnFindAll, 'FIND24');
      AssignPng(btnReplace, 'TEXT_REPLACE24');
      AssignPng(btnReplaceAll, 'TEXT_REPLACE24');
    end;
    iss32:
    begin
      AssignPng(btnAllScope, 'DOCUMENT32');
      AssignPng(btnFind, 'FIND32');
      AssignPng(btnFindAll, 'FIND32');
      AssignPng(btnReplace, 'TEXT_REPLACE32');
      AssignPng(btnReplaceAll, 'TEXT_REPLACE32');
    end;
  end;
  btnAllScope.Caption:= FindScopeStr[fFindScope];
  updateImperative;

  EntitiesConnector.addObserver(self);
end;

destructor TSearchWidget.Destroy;
begin
  with TSearchOptions.create(nil) do
  try
    assign(self);
    saveToFile(getDocPath + OptsFname);
  finally
    free;
  end;

  EntitiesConnector.removeObserver(self);
  fSearchMru.Free;
  fReplaceMru.Free;
  inherited;
end;
{$ENDREGION}

{$REGION IContextualActions---------------------------------------------------}
function TSearchWidget.getOptions: TSynSearchOptions;
begin
  result := [];
  if chkRegex.Checked     then
    result += [ssoRegExpr, ssoRegExprMultiLine];
  if chkWWord.Checked     then
    result += [ssoWholeWord];
  if chkBack.Checked      then
    result += [ssoBackwards];
  if chkCaseSens.Checked  then
    result += [ssoMatchCase];
  if chkPrompt.Checked    then
    result += [ssoPrompt];
end;

function dlgReplaceAll: TModalResult;
const
  Btns = [mbYes, mbNo, mbYesToAll, mbNoToAll];
begin
  exit( MessageDlg('dexed', 'Replace this match ?', mtConfirmation, Btns, ''));
end;

procedure TSearchWidget.replaceInSelEvent(Sender: TObject; const ASearch, AReplace:
  string; Line, Column: integer; var ReplaceAction: TSynReplaceAction);
var
  p: TPoint;
begin
  if fFindScope = scSel then
  begin
    p := Point(Column, Line);
    ReplaceAction := raReplace;
    if p < fDocSelStart then
      ReplaceAction := raSkip
    else if p > fDocSelStop then
    begin
      ReplaceAction := raCancel;
      fCancelAll := true;
    end;
  end;
end;

procedure TSearchWidget.replaceEvent(Sender: TObject; const ASearch, AReplace:
  string; Line, Column: integer; var ReplaceAction: TSynReplaceAction);
var
  p: TPoint;
begin

  if fFindScope = scSel then
  begin
    p := Point(Column, Line);
    if p < fDocSelStart then
    begin
      ReplaceAction := raSkip;
      exit;
    end
    else if p > fDocSelStop then
    begin
      ReplaceAction := raCancel;
      fCancelAll := true;
      exit;
    end;
  end;

  case dlgReplaceAll of
    mrYes: ReplaceAction := raReplace;
    mrNo: ReplaceAction := raSkip;
    mrYesToAll: ReplaceAction := raReplaceAll;
    mrCancel, mrClose, mrNoToAll:
      begin
        ReplaceAction := raCancel;
        fCancelAll := true;
      end;
  end;
end;

procedure TSearchWidget.actFindAllExecute(sender: TObject);
var
  i: integer;
  c: TSynEditStringList;
  f: string;
  s: integer = 0;
  m: IMessagesDisplay;
  h: IMultiDocHandler;
begin
  if (fDoc.isNotAssigned and (fFindScope <> scProj)) or
     (fProj.isNotAssigned and (fFindScope = scProj)) then
        exit;

  fSearchMru.Insert(0,fToFind);
  cbToFind.Items.Assign(fSearchMru);

  case fFindScope of
    scDoc, scSel:
    begin
      fDocSelStart := fDoc.BlockBegin;
      fDocSelStop := fDoc.BlockEnd;
      findAll(fDoc.fileName, fDoc.Lines, true);
    end;

    scProj:
    begin
      c := TSynEditStringList.Create;
      try
        for i := 0 to fProj.sourcesCount-1 do
        begin
          f := fProj.sourceAbsolute(i);
          c.LoadFromFile(f);
          s += findAll(f, c, false);
        end;
        if s.equals(0) then
        begin
          m := getMessageDisplay;
          m.message(format('0 result for the pattern <%s>', [fToFind]),
            nil, amcSearch, amkInf);
        end;
      finally
        c.Free;
      end;
    end;

    scOpened:
    begin
      c := TSynEditStringList.Create;
      h := getMultiDocHandler;
      try
        for i := 0 to h.documentCount-1 do
        begin
          f := h.getDocument(i).fileName;
          s += findAll(f, h.getDocument(i).Lines, false);
        end;
        if s.equals(0) then
        begin
          m := getMessageDisplay;
          m.message(format('0 result for the pattern <%s>', [fToFind]),
            nil, amcSearch, amkInf);
        end;
      finally
        c.Free;
      end;
    end;
  end;
end;

function TSearchWidget.findAll(const filename: string; lines: TStrings;
  showNoResult: boolean = true): integer;
var
  search: TSynEditSearch;
  options: TSynSearchOptions;
  start, stop: TPoint;
  startf, stopf: TPoint;
  msgs: IMessagesDisplay;
  msg: string;
  fmt: string;
  i: integer;
  j: integer = 0;
  k: integer;
  o: integer = -1;
  res: array of TPoint = nil;
  r: TRegExpr;
  s: string;
  rStart: integer;
begin
  result := 0;
  search := TSynEditSearch.Create;
  try
    options := getOptions;
    search.Sensitive := ssoMatchCase in options;
    search.Whole := ssoWholeWord in options;
    search.RegularExpressions:= ssoRegExpr in options;
    search.RegExprMultiLine:=ssoRegExpr in options;
    search.Pattern:=fToFind;
    if (fFindScope = scSel) and fDoc.SelAvail then
    begin
      start := fDoc.BlockBegin;
      stop := fDoc.BlockEnd;
    end
    else
    begin
      start := Point(1,1);
      stop := Point(high(integer), lines.Count);
    end;
    while search.FindNextOne(lines, start, stop, startf, stopf) do
    begin
      setLength(res, length(res) + 1);
      res[high(res)].X := startf.X;
      res[high(res)].Y := startf.Y;
      start := stopf;
    end;
    result := length(res);
    msgs := getMessageDisplay;
    if (not showNoResult and (result > 0)) or showNoResult then
    begin
      msg := format('%d result(s) for the pattern `%s` in %s',
        [length(res), fToFind, filename]);
      msgs.message(msg, nil, amcSearch, amkInf);
    end;
    fmt := fileName + '(%d,%d): "%s"';
    // highlighting
    if ssoRegExpr in options then
    begin
      r := TRegExpr.Create(fToFind);
      try
        r.ModifierI := not (ssoMatchCase in options);
        for i := 0 to high(res) do
        begin
          msg := Trim(lines[res[i].Y-1]);
          // current result is on same line as previous
          if res[i].Y = o then
            j += 1
          else
            j := 0;
          rStart := 1;
          s := '';
          k := 0;
          if r.Exec(msg) then
          repeat
            s += msg[rStart .. r.MatchPos[0]-1];
            // count of time we got the same line as result is the nth match to highlight
            if k = j then
            begin
              s += '`' + r.Match[0] + '`';
              rStart := r.MatchPos[0] + r.MatchLen[0];
              // dont bother with slicing trailing results once highlighting done
              break;
            end
            else
              s += r.Match[0];
            rStart := r.MatchPos[0] + r.MatchLen[0];
            k += 1;
          until
            not r.ExecNext();
          s += msg[rStart .. msg.length];
          msgs.message(format(fmt, [res[i].Y, res[i].X, s]), nil, amcSearch, amkInf);
          o := res[i].Y;
        end;
      finally
        r.Free;
      end;
    end
    else for i := 0 to high(res) do
    begin
      msg := Trim(lines[res[i].Y-1]);
      msg := strutils.ReplaceStr(msg, fToFind, '`' + fToFind + '`');
      msgs.message(format(fmt, [res[i].Y, res[i].X, msg]), nil, amcSearch, amkInf);
    end;
  finally
    search.free;
  end;
end;

procedure TSearchWidget.actFindNextExecute(sender: TObject);
const
  r: array[boolean] of string = ('beginning', 'end');
var
  s: string;
begin
  if fDoc.isNotAssigned then
    exit;

  fSearchMru.Insert(0, fToFind);
  cbToFind.Items.Assign(fSearchMru);

  if not chkFromCur.Checked then
  begin
    if chkBack.Checked then
      fDoc.CaretXY := Point(high(Integer), high(Integer))
    else
    begin
      if not fHasRestarted then
        fDoc.CaretXY := Point(1,1);
      fHasRestarted := true;
    end;
  end
  else if fHasSearched then
  begin
    if chkBack.Checked then
      fDoc.CaretX := fDoc.CaretX - 1
    else
      fDoc.CaretX := fDoc.CaretX + 1;
  end;
  if fDoc.SearchReplace(fToFind, '', getOptions).equals(0) then
  begin
    s := format('the expression cannot be found, restart from the %s ?', [r[chkBack.Checked]]);
    if dlgOkCancel(s) = mrOk then
    begin
      chkFromCur.Checked:=false;
      actFindNextExecute(nil);
    end;
  end
  else
  begin
    fHasSearched := true;
    fHasRestarted := false;
    chkFromCur.Checked := true;
    fDoc.setFocus;
  end;
  updateImperative;
end;

procedure TSearchWidget.actReplaceNextExecute(sender: TObject);
begin
  if fDoc.isNotAssigned then
    exit;

  fSearchMru.Insert(0, fToFind);
  fReplaceMru.Insert(0, fReplaceWth);
  cbToFind.Items.Assign(fSearchMru);
  cbReplaceWth.Items.Assign(fReplaceMru);

  if chkPrompt.Checked then
    fDoc.OnReplaceText := @replaceEvent;
  if not chkFromCur.Checked then
  begin
    if chkBack.Checked then
      fDoc.CaretXY := Point(high(Integer), high(Integer))
    else
      fDoc.CaretXY := Point(0,0);
  end
  else if fHasSearched then
  begin
    if chkBack.Checked then
      fDoc.CaretX := fDoc.CaretX - 1
    else
      fDoc.CaretX := fDoc.CaretX + fToFind.length;
  end;
  if fDoc.SearchReplace(fToFind, fReplaceWth, getOptions + [ssoReplace]) <> 0 then
    fHasSearched := true;
  fDoc.OnReplaceText := nil;
  updateImperative;
end;

procedure TSearchWidget.actReplaceAllExecute(sender: TObject);
var
  opts: TSynSearchOptions;
begin
  if fDoc.isNotAssigned then
    exit;

  fDocSelStart := fDoc.BlockBegin;
  fDocSelStop := fDoc.BlockEnd;

  cbReplaceWth.Items.Assign(fReplaceMru);
  opts := getOptions + [ssoReplace];
  opts -= [ssoBackwards];

  fSearchMru.Insert(0, fToFind);
  fReplaceMru.Insert(0, fReplaceWth);
  if chkPrompt.Checked then
    fDoc.OnReplaceText := @replaceEvent
  else if fFindScope = scSel then
  begin
    fDoc.CaretXY := fDocSelStart;
    fDoc.OnReplaceText := @replaceInSelEvent;
    // the event only called if ssoPrompt is included
    opts += [ssoPrompt];
  end;
  if fFindScope <> scSel then
  begin
    fDoc.CaretXY := Point(1,1);
    fDocSelStop := Point(high(integer), high(integer));
  end;
  while(true) do
  begin
    if fDoc.SearchReplace(fToFind, fReplaceWth, opts).equals(0) then
      break;
    if fCancelAll then
    begin
      fCancelAll := false;
      break;
    end;
  end;
  fDoc.OnReplaceText := nil;

  if fFindScope = scSel then
  begin
    fDoc.BlockBegin := fDocSelStart;
    fDoc.BlockEnd := fDocSelStop;
  end;


  updateImperative;
end;
{$ENDREGION}

{$REGION IProjectObserver ----------------------------------------------------}
procedure TSearchWidget.projNew(project: ICommonProject);
begin
  fProj := project;
  updateImperative;
end;

procedure TSearchWidget.projChanged(project: ICommonProject);
begin
end;

procedure TSearchWidget.projClosing(project: ICommonProject);
begin
  if fProj = project then
    fProj := nil;
  updateImperative;
end;

procedure TSearchWidget.projFocused(project: ICommonProject);
begin
  fProj := project;
  updateImperative;
end;

procedure TSearchWidget.projCompiling(project: ICommonProject);
begin
end;

procedure TSearchWidget.projCompiled(project: ICommonProject; success: boolean);
begin
end;
{$ENDREGION}

{$REGION IDocumentObserver ---------------------------------------------------}
procedure TSearchWidget.docNew(document: TDexedMemo);
begin
  fDoc := document;
  updateImperative;
end;

procedure TSearchWidget.docClosing(document: TDexedMemo);
begin
  if fDoc = document then
    fDoc := nil;
  updateImperative;
end;

procedure TSearchWidget.docFocused(document: TDexedMemo);
begin
  if fDoc = document then
    exit;
  fDoc := document;
  updateImperative;
end;

procedure TSearchWidget.docChanged(document: TDexedMemo);
begin
end;
{$ENDREGION}

{$REGION Misc. -----------------------------------------------------------------}
procedure TSearchWidget.cbToFindChange(Sender: TObject);
begin
  if Updating then
    exit;
  fToFind := cbToFind.Text;
  fHasSearched := false;
  updateImperative;
end;

procedure TSearchWidget.cbToFindKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key <> 13 then
    exit;
  actFindNextExecute(nil);
end;

procedure TSearchWidget.chkEnableRepChange(Sender: TObject);
begin
  if Updating then
    exit;
  updateImperative;
end;

procedure TSearchWidget.cbReplaceWthChange(Sender: TObject);
begin
  if Updating then
    exit;
  fReplaceWth := cbReplaceWth.Text;
  fHasSearched := false;
  updateImperative;
end;

procedure TSearchWidget.cbReplaceWthKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key <> 13 then
    exit;
  actReplaceNextExecute(nil);
end;

procedure TSearchWidget.btnAllScopeClick(Sender: TObject);
begin
  case fFindScope of
    scDoc: fFindScope := scSel;
    scSel: fFindScope := scProj;
    scProj: fFindScope := scOpened;
    scOpened: fFindScope := scDoc;
  end;
  btnAllScope.Caption:= FindScopeStr[fFindScope];
  if fFindScope <> scDoc then
  begin
    case GetIconScaledSize of
      iss16: AssignPng(btnAllScope, 'DOCUMENT_ALL');
      iss24: AssignPng(btnAllScope, 'DOCUMENT_ALL24');
      iss32: AssignPng(btnAllScope, 'DOCUMENT_ALL32');
    end;
    if fFindScope = scProj then
      btnAllScope.Hint := 'find in all the project sources'
    else
      btnAllScope.Hint := 'find in all the documents currently opened';
  end
  else
  begin
    case GetIconScaledSize of
      iss16: AssignPng(btnAllScope, 'DOCUMENT');
      iss24: AssignPng(btnAllScope, 'DOCUMENT24');
      iss32: AssignPng(btnAllScope, 'DOCUMENT32');
    end;
    btnAllScope.Hint := 'find in the selected source';
  end;
  updateImperative;
end;

procedure TSearchWidget.updateImperative;
var
  canAll: boolean;
  hasTxt: boolean;
begin
  canAll := ((fDoc.isAssigned and (fFindScope <> scProj)) or
            ((fFindScope = scProj) and fProj.isAssigned));
  hasTxt := fToFind.isNotEmpty and not fToFind.isBlank;
  btnFind.Enabled := fDoc.isAssigned and hasTxt;
  btnFindAll.Enabled := canAll and hasTxt;
  btnReplace.Enabled := fDoc.isAssigned and chkEnableRep.Checked and fToFind.isNotEmpty;
  btnReplaceAll.Enabled := btnReplace.Enabled;
  cbReplaceWth.Enabled := fDoc.isAssigned and chkEnableRep.Checked;
  cbToFind.Enabled := canAll;
end;
{$ENDREGION}

end.
