unit MFSearch;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  SclRView, RVEdit, CRVData, RVTable, SRVControl, SRVButton, SRVEdit, SRVPanel,
  SRVSkinManager, StdCtrls, ExtCtrls, RVLinear;

type
  TfrmSearch = class(TForm)
    srve: TSRichViewEdit;
    SRVSkinManager1: TSRVSkinManager;
    pnlDialogSearch: TSRVPanel;
    btnPrior: TSRVButton;
    btnNext: TSRVButton;
    btnClose: TSRVButton;
    pnlSearch: TSRVPanel;
    txtSearchText: TSRVEdit;
    SRVPanel1: TSRVPanel;
    Label1: TLabel;
    lblSearch: TLabel;
    procedure srveKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure btnCloseClick(Sender: TObject);
    procedure txtSearchTextChange(Sender: TObject);
    procedure btnNextClick(Sender: TObject);
    procedure btnPriorClick(Sender: TObject);
    procedure txtSearchTextEditHints(Sender: TSRVEditControl; Text: String;
      EditHints: TStringList; var ItemIndex: Integer);
    procedure txtSearchTextKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure FindText(Text : string; Option : TRVESearchOptions);
    procedure ShowSearchPanel();
  end;

var
  frmSearch: TfrmSearch;

implementation

{$R *.DFM}

{------------------------------------------------------------------------------}
// Showing the search panel on Ctrl+F
procedure TfrmSearch.srveKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  If (ssCtrl in Shift) and
     ((Key = Ord('F')) or (Key = Ord('f'))) then begin
    pnlDialogSearch.Left := srve.Left + srve.Width - pnlDialogSearch.Width - 30;
    pnlDialogSearch.Top := srve.Top;
    ShowSearchPanel();
  end;
end;
{------------------------------------------------------------------------------}
// Showing the search panel
procedure TfrmSearch.ShowSearchPanel();
var s: String;
    p: Integer;
begin
  if srve.RichViewEdit.SelectionExists then begin
    s := srve.RichViewEdit.GetSelText;
    p := Pos(#13,s);
    if p<>0 then s := Copy(s,1,p-1);
    txtSearchText.Text := s;
  end;
  pnlDialogSearch.Show();
  txtSearchText.SetFocus;
end;
{------------------------------------------------------------------------------}
// Hinding the search panel
procedure TfrmSearch.btnCloseClick(Sender: TObject);
begin
  pnlDialogSearch.Hide();
  srve.SetFocus;  
end;
{------------------------------------------------------------------------------}
// Returns the count of SubText in Text
function GetCountInText(SubText, Text : string) : Integer;
var PText, PSubText: PChar;
begin
  PText := PChar(Text);
  PSubText := PChar(SubText);
  Result := 0;
  repeat
    PText := StrPos(PText, PSubText);
    if PText<>nil then begin
      inc(Result);
      inc(PText, Length(SubText));
    end;
  until PText=nil;
end;
{------------------------------------------------------------------------------}
// Searches for Text in srve
procedure TfrmSearch.FindText(Text : string; Option : TRVESearchOptions);
var
     s : string;
     TotalCount, Count, SelStart, SelLength : Integer;
begin
  If (Text <> '') then begin
    // Calculating the count of occurences
    s := RVGetTextRange(srve.RichViewEdit, 0, RVGetTextLength(srve.RichViewEdit));
    TotalCount := GetCountInText(Text, s);
    srve.RichViewEdit.SearchText(Text, Option);
    // Calculating the count of occurences after the selected text
    RVGetSelection(srve.RichViewEdit, SelStart, SelLength);
    if SelLength>0 then
      inc(SelStart, SelLength);
    if srve.RichViewEdit.GetSelText=Text then begin
      s := RVGetTextRange(srve.RichViewEdit, SelStart, RVGetTextLength(srve.RichViewEdit)-SelStart);
      Count := GetCountInText(Text, s);
      end
    else if TotalCount=0 then
      Count := 0
    else
      Count := -1;
    // Displaying
    if (Count>=0) then
      lblSearch.Caption := IntToStr(TotalCount - Count) + ' of ' + IntToStr(TotalCount) + '  '
    else
      lblSearch.Caption := '? of ' + IntToStr(TotalCount) + '  ';
    if TotalCount > 0 then
      lblSearch.Color := clWhite
    else
      lblSearch.Color := $6666FF;
    srve.ScrollToCaret;
  end;
end;
{------------------------------------------------------------------------------}
// Search on typing in txtSearch
procedure TfrmSearch.txtSearchTextChange(Sender: TObject);
var StartItemNo, StartOffs, EndItemNo, EndOffs: Integer;
begin
  if srve.RichViewEdit.SelectionExists then begin
    srve.RichViewEdit.GetSelectionBounds(StartItemNo, StartOffs, EndItemNo, EndOffs, True);
    srve.RichViewEdit.SetSelectionBounds(StartItemNo, StartOffs, StartItemNo, StartOffs);
  end;
  FindText(txtSearchText.Text, [rvseoDown, rvseoMultiItem, rvseoMatchCase]);
end;
{------------------------------------------------------------------------------}
// Searching to the end
procedure TfrmSearch.btnNextClick(Sender: TObject);
begin
  FindText(txtSearchText.Text, [rvseoDown, rvseoMultiItem, rvseoMatchCase]);
end;
{------------------------------------------------------------------------------}
// Searching to the beginning
procedure TfrmSearch.btnPriorClick(Sender: TObject);
begin
  FindText(txtSearchText.Text, [rvseoMultiItem, rvseoMatchCase]);
end;
{------------------------------------------------------------------------------}
// Displaying search hints
procedure TfrmSearch.txtSearchTextEditHints(Sender: TSRVEditControl;
  Text: String; EditHints: TStringList; var ItemIndex: Integer);
var
     s, txt : String;
     i, MaxCount, Len : Integer;
     PTextStart, PText, PSubText: PChar;
begin
  // we do not generate suggestions for empty text
  if Text  = '' then
    exit;
  // we do not generate suggestions for non-words (text with delimiters)
  for i := 1 to Length(Text) do
   if Pos(Text[i], srve.RichViewEdit.Delimiters)<>0 then
     exit;
  // preparing
  MaxCount := 100;
  s := RVGetTextRange(srve.RichViewEdit, 0, RVGetTextLength(srve.RichViewEdit));
  PText := PChar(s);
  PTextStart := PText;
  PSubText := PChar(Text);
  // enumerating all words in PText containing PSubText (case sensitive)
  repeat
    PText := StrPos(PText, PSubText);
    if PText<>nil then begin
      // searhing for word start
      Len := -1;
      while not (PText[Len] in [#0, #1, #10, #13, #9]) and
        (Pos(PText[Len], srve.RichViewEdit.Delimiters)=0) and
        (PText>PTextStart) do
        dec(PText);
      // searhing for word end
      Len := 0;
      while not (PText[Len] in [#0, #1, #10, #13, #9]) and
        (Pos(PText[Len], srve.RichViewEdit.Delimiters)=0) do
        inc(Len);
      // adding suggestion in EditHints
      txt := Copy(PText, 1, Len);
      if EditHints.IndexOf(txt)<0 then begin
        EditHints.Append(txt);
        dec(MaxCount);
      end;
      inc(PText, Len);
    end;
  until (PText=nil) or (MaxCount=0);
end;
{------------------------------------------------------------------------------}
// Closing the search panel on Escape
procedure TfrmSearch.txtSearchTextKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_ESCAPE then begin
    pnlDialogSearch.Hide();
    srve.SetFocus;
  end;
end;

initialization
   RVNonTextCharacter := #1; // charater that RVGetTextRange inserts in place
                             // of non-text objects

end.
