(*
  History++ plugin for Miranda IM: the free IM client for Microsoft* Windows*

  Copyright (C) 2006-2009 theMIROn, 2003-2006 Art Fedorov.
  History+ parts (C) 2001 Christian Kastner

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)

{ -----------------------------------------------------------------------------
  GlobalSearch (historypp project)

  Version:   1.0
  Created:   05.08.2004
  Author:    Oxygen

  [ Description ]

  Here we have the form and UI for global searching. Curious
  can go to hpp_searchthread for internals of searching.

  [ History ]

  1.5 (05.08.2004)
  First version

  [ Modifications ]
  none

  [ Known Issues ]

  * When doing HotSearch, and then backspacing to empty search AnsiString
  grid doesn't return to the first item HotSearch started from
  unlike in HistoryForm. Probably shouldn't be done, because too much checking
  to reset LastHotIdx should be done, considering how much filtering &
  sorting is performed.

  Contributors: theMIROn, Art Fedorov
  ----------------------------------------------------------------------------- }

unit GlobalSearch;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, Menus,
  HistoryGrid,
  m_api,
  hpp_global, hpp_searchthread,
  RichEdit,
  ImgList, HistoryControls, Buttons, Math, CommCtrl,
  Contnrs, ToolWin, ShellAPI;

const
  HM_SRCH_CONTACTICONCHANGED = HM_SRCH_BASE + 3;

type
  THPPContactInfo = class(TObject)
  public
    Proto: AnsiString;
    Codepage: Cardinal;
    RTLMode: TRTLMode;
    Name: String;
    ProfileName: String;
    Handle: THandle;
  end;

  TSearchItem = record
    hDBEvent: THandle;
    Contact: THPPContactInfo;
  end;

  TfmGlobalSearch = class(TForm)
    paClient: TPanel;
    paSearch: TPanel;
    laSearch: TLabel;
    edSearch: THppEdit;
    bnSearch: TButton;
    sb: TStatusBar;
    paProgress: TPanel;
    pb: TProgressBar;
    laProgress: TLabel;
    pmGrid: TPopupMenu;
    Open1: TMenuItem;
    Copy1: TMenuItem;
    CopyText1: TMenuItem;
    N1: TMenuItem;
    N2: TMenuItem;
    spContacts: TSplitter;
    ilContacts: TImageList;
    paContacts: TPanel;
    lvContacts: TListView;
    SendMessage1: TMenuItem;
    ReplyQuoted1: TMenuItem;
    SaveSelected1: TMenuItem;
    SaveDialog: TSaveDialog;
    tiFilter: TTimer;
    paHistory: TPanel;
    hg: THistoryGrid;
    paFilter: TPanel;
    sbClearFilter: TSpeedButton;
    edFilter: THppEdit;
    pbFilter: TPaintBox;
    Delete1: TMenuItem;
    N3: TMenuItem;
    Bookmark1: TMenuItem;
    ToolBar: THppToolBar;
    paAdvanced: TPanel;
    paRange: TPanel;
    rbAny: TRadioButton;
    rbAll: TRadioButton;
    rbExact: TRadioButton;
    laAdvancedHead: TLabel;
    sbAdvancedClose: TSpeedButton;
    sbRangeClose: TSpeedButton;
    dtRange1: TDateTimePicker;
    laRange1: TLabel;
    laRange2: TLabel;
    dtRange2: TDateTimePicker;
    laRangeHead: TLabel;
    tbEventsFilter: THppSpeedButton;
    tbAdvanced: THppToolButton;
    tbRange: THppToolButton;
    ToolButton2: THppToolButton;
    ilToolbar: TImageList;
    beRange: TBevel;
    beAdvanced: TBevel;
    ToolButton3: THppToolButton;
    tbSearch: THppToolButton;
    tbFilter: THppToolButton;
    pmEventsFilter: TPopupMenu;
    N4: TMenuItem;
    Customize1: TMenuItem;
    pmInline: TPopupMenu;
    InlineCopy: TMenuItem;
    InlineCopyAll: TMenuItem;
    InlineSelectAll: TMenuItem;
    MenuItem10: TMenuItem;
    InlineTextFormatting: TMenuItem;
    MenuItem6: TMenuItem;
    InlineSendMessage: TMenuItem;
    InlineReplyQuoted: TMenuItem;
    pmLink: TPopupMenu;
    OpenLink: TMenuItem;
    OpenLinkNW: TMenuItem;
    MenuItem2: TMenuItem;
    CopyLink: TMenuItem;
    mmAcc: TMainMenu;
    mmToolbar: TMenuItem;
    mmService: TMenuItem;
    mmHideMenu: TMenuItem;
    mmShortcuts: TMenuItem;
    mmBookmark: TMenuItem;
    tbBookmarks: THppToolButton;
    ToolButton1: THppToolButton;
    TopPanel: TPanel;
    N5: TMenuItem;
    SelectAll1: TMenuItem;
    pmFile: TPopupMenu;
    FileActions: TMenuItem;
    BrowseReceivedFiles: TMenuItem;
    OpenFileFolder: TMenuItem;
    N6: TMenuItem;
    CopyFilename: TMenuItem;
    N7: TMenuItem;
    paEvents: TPanel;
    laEvents: TLabel;
    beEvents: TBevel;
    laEventsHead: TLabel;
    sbEventsClose: TSpeedButton;
    tbEvents: THppToolButton;
    cbEvents: TComboBox;
    ToolButton4: THppToolButton;
    tbCopy: THppToolButton;
    tbDelete: THppToolButton;
    tbSave: THppToolButton;
    procedure pbFilterPaint(Sender: TObject);
    procedure edFilterKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure tiFilterTimer(Sender: TObject);
    procedure sbClearFilterClick(Sender: TObject);
    procedure edPassKeyPress(Sender: TObject; var Key: Char);
    procedure edSearchKeyPress(Sender: TObject; var Key: Char);
    procedure hgItemDelete(Sender: TObject; Index: Integer);
    procedure OnCNChar(var Message: TWMChar); message WM_CHAR;
    procedure tbSaveClick(Sender: TObject);
    procedure hgPopup(Sender: TObject);
    procedure ReplyQuoted1Click(Sender: TObject);
    procedure SendMessage1Click(Sender: TObject);
    procedure edFilterKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure hgItemFilter(Sender: TObject; Index: Integer; var Show: Boolean);
    procedure edFilterChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure edSearchChange(Sender: TObject);
    procedure hgKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure hgState(Sender: TObject; State: TGridState);
    procedure hgSearchFinished(Sender: TObject; const Text: String; Found: Boolean);
    procedure hgSearchItem(Sender: TObject; Item, ID: Integer; var Found: Boolean);
    // procedure FormHide(Sender: TObject);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer;
      MousePos: TPoint; var Handled: Boolean);
    procedure lvContactsSelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
    procedure hgNameData(Sender: TObject; Index: Integer; var Name: String);
    procedure hgTranslateTime(Sender: TObject; Time: Cardinal; var Text: String);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bnSearchClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure hgItemData(Sender: TObject; Index: Integer; var Item: THistoryItem);
    procedure hgDblClick(Sender: TObject);
    procedure edSearchEnter(Sender: TObject);
    procedure edSearchKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure hgProcessRichText(Sender: TObject; Handle: Cardinal; Item: Integer);
    procedure FormShow(Sender: TObject);
    procedure hgKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure hgUrlClick(Sender: TObject; Item: Integer; const URLText: String; Button: TMouseButton);
    procedure edPassKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure hgSelect(Sender: TObject; Item, OldItem: Integer);
    procedure tbCopyClick(Sender: TObject);
    procedure CopyText1Click(Sender: TObject);
    procedure tbDeleteClick(Sender: TObject);
    procedure hgRTLEnabled(Sender: TObject; BiDiMode: TBiDiMode);
    procedure Bookmark1Click(Sender: TObject);
    procedure hgBookmarkClick(Sender: TObject; Item: Integer);
    procedure lvContactsContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
    procedure tbAdvancedClick(Sender: TObject);
    procedure tbRangeClick(Sender: TObject);
    procedure sbAdvancedCloseClick(Sender: TObject);
    procedure sbRangeCloseClick(Sender: TObject);
    procedure tbEventsFilterClick(Sender: TObject);
    procedure EventsFilterItemClick(Sender: TObject);
    procedure Customize1Click(Sender: TObject);
    procedure InlineCopyClick(Sender: TObject);
    procedure hgInlinePopup(Sender: TObject);
    procedure hgInlineKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure InlineCopyAllClick(Sender: TObject);
    procedure InlineSelectAllClick(Sender: TObject);
    procedure InlineTextFormattingClick(Sender: TObject);
    procedure InlineReplyQuotedClick(Sender: TObject);
    procedure CopyLinkClick(Sender: TObject);
    procedure OpenLinkClick(Sender: TObject);
    procedure OpenLinkNWClick(Sender: TObject);
    procedure mmHideMenuClick(Sender: TObject);
    procedure mmToolbarClick(Sender: TObject);
    procedure pmEventsFilterPopup(Sender: TObject);
    procedure tbBookmarksClick(Sender: TObject);
    procedure SelectAll1Click(Sender: TObject);
    procedure OpenFileFolderClick(Sender: TObject);
    procedure BrowseReceivedFilesClick(Sender: TObject);
    procedure tbEventsClick(Sender: TObject);
    procedure sbEventsCloseClick(Sender: TObject);
    procedure lvContactsDblClick(Sender: TObject);
    procedure hgChar(Sender: TObject; var achar: WideChar; Shift: TShiftState);
    procedure edFilterKeyPress(Sender: TObject; var Key: Char);
  private
    UserMenu: hMenu;
    UserMenuContact: THandle;
    WasReturnPressed: Boolean;
    LastUpdateTime: Cardinal;
    HotString: String;
    hHookContactIconChanged: THandle;
    FContactFilter: THandle;
    FFiltered: Boolean;
    IsSearching: Boolean;
    IsBookmarksMode: Boolean;
    History: array of TSearchItem;
    FilterHistory: array of Integer;
    CurContact: THandle;
    SearchThread: TSearchThread;
    stime: DWord;
    ContactsFound: Integer;
    AllItems: Integer;
    AllContacts: Integer;
    HotFilterString: String;
    FormState: TGridState;
    SavedLinkUrl: String;
    SavedFileDir: String;

    procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
    procedure WMSysColorChange(var Message: TMessage); message WM_SYSCOLORCHANGE;

    procedure SMPrepare(var M: TMessage); message HM_STRD_PREPARE;
    procedure SMProgress(var M: TMessage); message HM_STRD_PROGRESS;
    procedure SMItemsFound(var M: TMessage); message HM_STRD_ITEMSFOUND;
    procedure SMNextContact(var M: TMessage); message HM_STRD_NEXTCONTACT;
    procedure SMFinished(var M: TMessage); message HM_STRD_FINISHED;

    function FindHistoryItemByHandle(hDBEvent: THandle): Integer;
    procedure DeleteEventFromLists(Item: Integer);

    procedure HMEventDeleted(var M: TMessage); message HM_MIEV_EVENTDELETED;
    procedure HMPreShutdown(var M: TMessage); message HM_MIEV_PRESHUTDOWN;
    procedure HMContactDeleted(var M: TMessage); message HM_MIEV_CONTACTDELETED;
    // procedure HMMetaDefaultChanged(var M: TMessage); message HM_MIEV_METADEFCHANGED;

    procedure HMContactIconChanged(var M: TMessage); message HM_SRCH_CONTACTICONCHANGED;

    procedure HMIcons2Changed(var M: TMessage); message HM_NOTF_ICONS2CHANGED;
    procedure HMBookmarksChanged(var M: TMessage); message HM_NOTF_BOOKMARKCHANGED;
    procedure HMFiltersChanged(var M: TMessage); message HM_NOTF_FILTERSCHANGED;
    procedure HMAccChanged(var M: TMessage); message HM_NOTF_ACCCHANGED;
    procedure HMNickChanged(var M: TMessage); message HM_NOTF_NICKCHANGED;
    procedure TranslateForm;

    procedure HookEvents;
    procedure UnhookEvents;

    procedure ShowContacts(Show: Boolean);

    procedure SearchNext(Rev: Boolean; Warp: Boolean = True);
    procedure ReplyQuoted(Item: Integer);
    procedure StartHotFilterTimer;
    procedure EndHotFilterTimer;

    procedure StopSearching;
  private
    LastAddedContact: THPPContactInfo;
    ContactList: TObjectList;
    // function FindContact(hContact: Integer): THPPContactInfo;
    function AddContact(hContact: THandle): THPPContactInfo;
  protected
    procedure LoadPosition;
    procedure SavePosition;
    procedure WndProc(var Message: TMessage); override;

    function IsFileEvent(Index: Integer): Boolean;

    procedure ToggleAdvancedPanel(Show: Boolean);
    procedure ToggleRangePanel(Show: Boolean);
    procedure ToggleEventsPanel(Show: Boolean);
    procedure OrganizePanels;
    procedure ToggleMainMenu(Enabled: Boolean);

    procedure SetEventFilter(FilterIndex: Integer = -1);
    procedure CreateEventsFilterMenu;
  public
    CustomizeFiltersForm: TForm;
    procedure SetRecentEventsPosition(OnTop: Boolean);
  published
    // fix for splitter baug:
    procedure AlignControls(Control: TControl; var ARect: TRect); override;

    function GetSearchItem(GridIndex: Integer): TSearchItem;
    function GetContactInfo(hContact: THandle): THPPContactInfo;

    procedure DisableFilter;
    procedure FilterOnContact(hContact: THandle);

    procedure LoadButtonIcons;
    procedure LoadContactsIcons;
    procedure LoadToolbarIcons;

    procedure LoadAccMenu;
    procedure LoadEventFilterButton;
  public
    { Public declarations }
  end;

var
  fmGlobalSearch: TfmGlobalSearch;

const
  DEFAULT_SEARCH_TEXT = 'http: ftp: www. ftp.';

var
  GlobalSearchAllResultsIcon: Integer = -1;

implementation

uses
  hpp_options, hpp_itemprocess, hpp_messages, CustomizeFiltersForm,
  hpp_database, hpp_eventfilters, hpp_contacts, hpp_events, hpp_richedit,
  hpp_forms, hpp_services, hpp_bookmarks;

{$R *.DFM}

function TfmGlobalSearch.AddContact(hContact: THandle): THPPContactInfo;
var
  ci: THPPContactInfo;
  SubContact: TMCONTACT;
  SubProtocol: AnsiString;
begin
  ci := THPPContactInfo.Create;
  ci.Handle      := hContact;
  ci.Proto       := GetContactProto(CurContact, SubContact, SubProtocol);
  ci.Codepage    := GetContactCodePage(hContact, ci.Proto);
  ci.Name        := GetContactDisplayName(ci.Handle, ci.Proto, True);
  ci.ProfileName := GetContactDisplayName(0, SubProtocol);
  ci.RTLMode     := GetContactRTLModeTRTL(ci.Handle, ci.Proto);
  ContactList.Add(ci);
  Result := ci;
end;

// fix for infamous splitter bug!
// thanks to Greg Chapman
// http://groups.google.com/group/borland.public.delphi.objectpascal/browse_thread/thread/218a7511123851c3/5ada76e08038a75b%235ada76e08038a75b?sa=X&oi=groupsr&start=2&num=3
procedure TfmGlobalSearch.AlignControls(Control: TControl; var ARect: TRect);
begin
  inherited;
  if paContacts.Width = 0 then
    paContacts.Left := spContacts.Left;
end;

procedure TfmGlobalSearch.FormCreate(Sender: TObject);
// var
// NonClientMetrics: TNonClientMetrics;
begin
  // Setting different system font different way. For me works the same
  // but some said it produces better results than DesktopFont
  // Leave it here for possible future use.
  //
  // NonClientMetrics.cbSize := SizeOf(NonClientMetrics);
  // SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0);
  // Font.Handle := CreateFontIndirect(NonClientMetrics.lfMessageFont);
  // if Scaled then begin
  // Font.Height := NonClientMetrics.lfMessageFont.lfHeight;
  // end;
  Icon.ReleaseHandle;
  Icon.Handle := CopyIcon(hppIcons[HPP_ICON_GLOBALSEARCH].Handle);

  DesktopFont := True;
  MakeFontsParent(Self);

  DoubleBuffered := True;
  MakeDoubleBufferedParent(Self);
  TopPanel.DoubleBuffered := False;
  hg.DoubleBuffered := False;

  FormState := gsIdle;

  ContactList := TObjectList.Create;

  ilContacts.Handle := Clist_GetImageList();
  // delphi 2006 doesn't save toolbar's flat property in dfm if it is True
  // delphi 2006 doesn't save toolbar's edgeborder property in dfm
  ToolBar.Flat := True;
  ToolBar.EdgeBorders := [];

  LoadToolbarIcons;
  LoadButtonIcons;
  LoadContactsIcons;

  TranslateForm;

  // File actions from context menu support
  AddMenuArray(pmGrid, [FileActions], -1);

  LoadAccMenu; // load accessability menu before LoadToolbar
  // put here because we want to translate everything
  // before copying to menu
  ToggleMainMenu(GetDBBool(hppDBName, 'Accessability', False));
end;

procedure TfmGlobalSearch.SMFinished(var M: TMessage);
// var
// sbt: WideString;
begin
  stime := GetTickCount - SearchThread.SearchStart;
  AllContacts := SearchThread.AllContacts;
  AllItems := SearchThread.AllEvents;
  IsSearching := False;
  bnSearch.Caption := TranslateW('Search');
  paProgress.Hide;
  // paFilter.Show;
  // if change, change also in hg.State:
  // sbt := WideFormat(TranslateW('%.0n items in %d contacts found. Searched for %.1f sec in %.0n items.'),[Length(History)/1, ContactsFound, stime/1000, AllItems/1]);
  // sb.SimpleText := sbt;
  hgState(Self, hg.State);
  if Length(History) = 0 then
    ShowContacts(False);
end;

procedure TfmGlobalSearch.SMItemsFound(var M: TMessage);
var
  li: TListItem;
  ci: THPPContactInfo;
  Buffer: PDBArray;
  FiltOldSize, OldSize, i, BufCount: Integer;
begin
  // wParam - array of hDBEvent, lParam - array size
  Buffer := PDBArray(M.wParam);
  BufCount := Integer(M.LParam);
  OldSize := Length(History);
  SetLength(History, OldSize + BufCount);

  if (LastAddedContact = nil) or (LastAddedContact.Handle <> CurContact) then
  begin
    ci := AddContact(CurContact);
    LastAddedContact := ci;
  end;

  for i := 0 to BufCount - 1 do
  begin
    History[OldSize + i].hDBEvent := Buffer^[i];
    History[OldSize + i].Contact := LastAddedContact;
    // History[OldSize + i].hContact := CurContact;
    // History[OldSize + i].ContactName := CurContactName;
    // History[OldSize + i].ProfileName := CurProfileName;
    // History[OldSize + i].Proto := CurProto;
  end;

  FreeMem(Buffer, SizeOf(Buffer^));

  if (lvContacts.Items.Count = 0) or
    (THandle(lvContacts.Items.Item[lvContacts.Items.Count - 1].Data) <> CurContact) then
  begin
    if lvContacts.Items.Count = 0 then
    begin
      li := lvContacts.Items.Add;
      li.Caption := TranslateW('All Results');
      li.ImageIndex := GlobalSearchAllResultsIcon;
      li.Selected := True;
    end;
    li := lvContacts.Items.Add;
    if CurContact = 0 then
      li.Caption := TranslateW('System History')
    else
    begin
      li.Caption := LastAddedContact.Name;
      Inc(ContactsFound);
    end;
    li.ImageIndex := cli^.pfnGetContactIcon(CurContact);
    li.Data := Pointer(CurContact);
  end;

  if FFiltered then
  begin
    if CurContact = FContactFilter then
    begin
      FiltOldSize := Length(FilterHistory);
      for i := 0 to BufCount - 1 do
        FilterHistory[FiltOldSize + i] := OldSize + i;
      hg.Allocate(Length(FilterHistory));
    end;
  end
  else
    hg.Allocate(Length(History));

  if (hg.Count > 0) and (hg.Selected = -1) then
    hg.Selected := 0;

  paFilter.Visible := True;
  if not paContacts.Visible then
  begin
    ShowContacts(True);
    hg.Selected := 0;
    hg.SetFocus;
  end;

  tbEventsFilter.Enabled := True;
  // dirty hack: readjust scrollbars
  hg.Perform(WM_SIZE, SIZE_RESTORED, MakeLParam(hg.ClientWidth, hg.ClientHeight));
  // hg.Repaint;
  // Application.ProcessMessages;
end;

procedure TfmGlobalSearch.SMNextContact(var M: TMessage);
var
  CurProto: AnsiString;
begin
  // wParam - hContact, lParam - 0
  CurContact := M.wParam;
  if CurContact = 0 then
    CurProto := 'ICQ'
  else
    CurProto := GetContactProto(CurContact);
  laProgress.Caption := Format(TranslateW('Searching "%s"...'),
    [GetContactDisplayName(CurContact, CurProto, True)]);
end;

procedure TfmGlobalSearch.SMPrepare(var M: TMessage);
begin
  LastUpdateTime := 0;
  ContactsFound := 0;
  AllItems := 0;
  AllContacts := 0;
  FFiltered := False;

  // hg.Filter := GenerateEvents(FM_EXCLUDE,[]);
  hg.Selected := -1;
  hg.Allocate(0);

  SetLength(FilterHistory, 0);
  SetLength(History, 0);

  IsSearching := True;
  bnSearch.Caption := TranslateW('Stop');

  tbEventsFilter.Enabled := False;
  sb.SimpleText := TranslateW('Searching... Please wait.');
  laProgress.Caption := TranslateW('Preparing search...');
  pb.Position := 0;
  paProgress.Show;
  paFilter.Visible := False;
  // ShowContacts(False);
  lvContacts.Items.Clear;
  ContactList.Clear;
  LastAddedContact := nil;
end;

procedure TfmGlobalSearch.SMProgress(var M: TMessage);
begin
  // wParam - progress; lParam - max

  if (GetTickCount - LastUpdateTime) < 100 then
    exit;
  LastUpdateTime := GetTickCount;

  pb.Max := M.LParam;
  pb.Position := M.wParam;
  // Application.ProcessMessages;

  // if change, change also in hg.OnState
  sb.SimpleText := Format(TranslateW('Searching... %.0n items in %d contacts found'),
    [Length(History) / 1, ContactsFound]);
end;

procedure TfmGlobalSearch.StartHotFilterTimer;
begin
  if tiFilter.Interval = 0 then
    EndHotFilterTimer
  else
  begin
    tiFilter.Enabled := False;
    tiFilter.Enabled := True;
    if pbFilter.Tag <> 1 then
    begin // use Tag to not repaint every keystroke
      pbFilter.Tag := 1;
      pbFilter.Repaint;
    end;
  end;
end;

procedure TfmGlobalSearch.tbAdvancedClick(Sender: TObject);
begin
  // when called from menu item handler
  if Sender <> tbAdvanced then
    tbAdvanced.Down := not tbAdvanced.Down;
  ToggleAdvancedPanel(tbAdvanced.Down);
end;

procedure TfmGlobalSearch.tbEventsFilterClick(Sender: TObject);
var
  p: TPoint;
begin
  p := tbEventsFilter.ClientOrigin;
  tbEventsFilter.ClientToScreen(p);
  pmEventsFilter.Popup(p.X, p.Y + tbEventsFilter.Height);
end;

procedure TfmGlobalSearch.tbRangeClick(Sender: TObject);
begin
  if Sender <> tbRange then
    tbRange.Down := not tbRange.Down;
  ToggleRangePanel(tbRange.Down);
end;

procedure TfmGlobalSearch.tiFilterTimer(Sender: TObject);
begin
  EndHotFilterTimer;
end;

procedure TfmGlobalSearch.edFilterChange(Sender: TObject);
begin
  StartHotFilterTimer;
end;

procedure TfmGlobalSearch.edFilterKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key in [VK_UP, VK_DOWN, VK_NEXT, VK_PRIOR] then
  begin
    SendMessage(hg.Handle, WM_KEYDOWN, Key, 0);
    Key := 0;
  end;
end;

procedure TfmGlobalSearch.edFilterKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
  begin
    hg.SetFocus;
    Key := 0;
  end;
end;

procedure TfmGlobalSearch.FormDestroy(Sender: TObject);
begin
  fmGlobalSearch := nil;
  if Assigned(CustomizeFiltersForm) then
    CustomizeFiltersForm.Release;
  ContactList.Free;
end;

procedure TfmGlobalSearch.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
begin
  inherited;
  with Message.MinMaxInfo^ do
  begin
    ptMinTrackSize.X := 320;
    ptMinTrackSize.Y := 240;
  end
end;

procedure TfmGlobalSearch.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
  Ctrl: TControl;
begin
  Handled := True;
  Ctrl := paClient.ControlAtPos(paClient.ScreenToClient(MousePos), False, True);
{$RANGECHECKS OFF}
  if Assigned(Ctrl) then
  begin
    if Ctrl.Name = 'paContacts' then
    begin
      Handled := not TListView(Ctrl).Focused;
      if Handled then
      begin
        // ??? what to do here?
        // how to tell listview to scroll?
      end;
    end
    else
    begin
      hg.Perform(WM_MOUSEWHEEL, MakeLong(MK_CONTROL, WheelDelta), 0);
    end;
  end;
{$RANGECHECKS ON}
end;

procedure TfmGlobalSearch.ToggleAdvancedPanel(Show: Boolean);
var
  Lock: Boolean;
begin
  Lock := Visible;
  if Lock then
    Lock := LockWindowUpdate(Handle);
  try
    tbAdvanced.Down := Show;
    paAdvanced.Visible := Show and tbAdvanced.Enabled;
    OrganizePanels;
  finally
    if Lock then
      LockWindowUpdate(0);
  end;
end;

procedure TfmGlobalSearch.ToggleRangePanel(Show: Boolean);
var
  Lock: Boolean;
begin
  Lock := Visible;
  if Lock then
    Lock := LockWindowUpdate(Handle);
  try
    tbRange.Down := Show;
    paRange.Visible := Show and tbRange.Enabled;
    edSearchChange(Self);
    OrganizePanels;
  finally
    if Lock then
      LockWindowUpdate(0);
  end;
end;

procedure TfmGlobalSearch.ToggleEventsPanel(Show: Boolean);
var
  Lock: Boolean;
begin
  Lock := Visible;
  if Lock then
    Lock := LockWindowUpdate(Handle);
  try
    tbEvents.Down := Show;
    paEvents.Visible := Show and tbEvents.Enabled;
    edSearchChange(Self);
    OrganizePanels;
  finally
    if Lock then
      LockWindowUpdate(0);
  end;
end;

procedure TfmGlobalSearch.mmToolbarClick(Sender: TObject);
var
  i, n: Integer;
  pm: TPopupMenu;
  mi: TMenuItem;
  flag: Boolean;
begin
  for i := 0 to mmToolbar.Count - 1 do
  begin
    if mmToolbar.Items[i].Owner is THppToolButton then
    begin
      flag := TToolButton(mmToolbar.Items[i].Owner).Enabled
    end
    else if mmToolbar.Items[i].Owner is TSpeedButton then
    begin
      TMenuItem(mmToolbar.Items[i]).Caption := TSpeedButton(mmToolbar.Items[i].Owner).Hint;
      flag := TSpeedButton(mmToolbar.Items[i].Owner).Enabled
    end
    else
      flag := True;
    mmToolbar.Items[i].Enabled := flag;
    if mmToolbar.Items[i].Tag = 0 then
      continue;
    pm := TPopupMenu(Pointer(mmToolbar.Items[i].Tag));
    for n := pm.Items.Count - 1 downto 0 do
    begin
      mi := TMenuItem(pm.Items[n]);
      pm.Items.Remove(mi);
      mmToolbar.Items[i].Insert(0, mi);
    end;
  end;
end;

procedure TfmGlobalSearch.sbAdvancedCloseClick(Sender: TObject);
begin
  ToggleAdvancedPanel(False);
end;

procedure TfmGlobalSearch.sbClearFilterClick(Sender: TObject);
begin
  edFilter.Text := '';
  EndHotFilterTimer;
  hg.SetFocus;
end;

procedure TfmGlobalSearch.sbRangeCloseClick(Sender: TObject);
begin
  ToggleRangePanel(False);
end;

procedure TfmGlobalSearch.TranslateForm;
begin
  Caption := TranslateUnicodeString(Caption);

  laSearch.Caption := TranslateUnicodeString(laSearch.Caption);
  bnSearch.Caption := TranslateUnicodeString(bnSearch.Caption);
  edSearch.Left := laSearch.Left + laSearch.Width + 5;
  edSearch.Width := bnSearch.Left - edSearch.Left - 5;

  laAdvancedHead.Caption := TranslateUnicodeString(laAdvancedHead.Caption);
  rbAny.Caption := TranslateUnicodeString(rbAny.Caption);
  rbAll.Caption := TranslateUnicodeString(rbAll.Caption);
  rbExact.Caption := TranslateUnicodeString(rbExact.Caption);

  laRangeHead.Caption := TranslateUnicodeString(laRangeHead.Caption);
  laRange1.Caption := TranslateUnicodeString(laRange1.Caption);
  laRange2.Caption := TranslateUnicodeString(laRange2.Caption);

  laEventsHead.Caption := TranslateUnicodeString(laEventsHead.Caption);
  laEvents.Caption := TranslateUnicodeString(laEvents.Caption);
  cbEvents.Left := laEvents.Left + laEvents.Width + 10;

  sbClearFilter.Hint := TranslateUnicodeString(sbClearFilter.Hint);

  SaveDialog.Title := TranslateUnicodeString(PWideChar(SaveDialog.Title));

  TranslateToolbar(ToolBar);

  TranslateMenu(pmGrid.Items);
  TranslateMenu(pmInline.Items);
  TranslateMenu(pmLink.Items);
  TranslateMenu(pmFile.Items);
  TranslateMenu(pmEventsFilter.Items);

  hg.TxtFullLog    := TranslateUnicodeString(hg.TxtFullLog);
  hg.TxtGenHist1   := TranslateUnicodeString(hg.TxtGenHist1);
  hg.TxtGenHist2   := TranslateUnicodeString(hg.TxtGenHist2);
  hg.TxtHistExport := TranslateUnicodeString(hg.TxtHistExport);
  hg.TxtNoItems    := TranslateUnicodeString(hg.TxtNoItems);
  hg.TxtNoSuch     := TranslateUnicodeString(hg.TxtNoSuch);
  hg.TxtPartLog    := TranslateUnicodeString(hg.TxtPartLog);
  hg.TxtStartUp    := TranslateUnicodeString(hg.TxtStartUp);
  hg.TxtSessions   := TranslateUnicodeString(hg.TxtSessions);
end;

procedure TfmGlobalSearch.FilterOnContact(hContact: THandle);
var
  i: Integer;
begin
  if FFiltered and (FContactFilter = hContact) then
    exit;
  FFiltered := True;
  FContactFilter := hContact;
  SetLength(FilterHistory, 0);
  for i := 0 to Length(History) - 1 do
  begin
    if History[i].Contact.Handle = hContact then
    begin
      SetLength(FilterHistory, Length(FilterHistory) + 1);
      FilterHistory[High(FilterHistory)] := i;
    end;
  end;
  hg.Allocate(0);
  if Length(FilterHistory) > 0 then
  begin
    hg.Allocate(Length(FilterHistory));
    hg.Selected := 0;
  end
  else
    hg.Selected := -1;
  // dirty hack: readjust scrollbars
  hg.Perform(WM_SIZE, SIZE_RESTORED, MakeLParam(hg.ClientWidth, hg.ClientHeight));
end;

{ function TfmGlobalSearch.FindContact(hContact: Integer): THPPContactInfo;
  begin
  Result := nil;
  end; }

function TfmGlobalSearch.FindHistoryItemByHandle(hDBEvent: THandle): Integer;
var
  i: Integer;
begin
  Result := -1;
  for i := 0 to Length(History) - 1 do
  begin
    if History[i].hDBEvent = hDBEvent then
    begin
      Result := i;
      break;
    end;
  end;
end;

procedure TfmGlobalSearch.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  try
    Action := caFree;
    SavePosition;
    UnhookEvents;
  except
  end;
end;

procedure TfmGlobalSearch.StopSearching;
begin
  bnSearch.Enabled := False;
  try
    SearchThread.Terminate(tpHigher);
    while IsSearching do
      Application.ProcessMessages;
  finally
    bnSearch.Enabled := True;
    if edSearch.CanFocus then
      edSearch.SetFocus;
  end;
end;

procedure TfmGlobalSearch.bnSearchClick(Sender: TObject);
var
  SearchProtected: Boolean;
begin
  if IsSearching then
  begin
    StopSearching;
    exit;
  end;
  // if edSearch.Text = '' then
  // raise Exception.Create('Enter text to search');

  SearchProtected := False;

  if Assigned(SearchThread) then
    FreeAndNil(SearchThread);
  SearchThread := TSearchThread.Create(True);

  if IsBookmarksMode then
    SearchThread.SearchMethod := [smBookmarks]
  else if edSearch.Text = '' then
    SearchThread.SearchMethod := []
  else if rbAny.Checked then
    SearchThread.SearchMethod := [smAnyWord]
  else if rbAll.Checked then
    SearchThread.SearchMethod := [smAllWords]
  else
    SearchThread.SearchMethod := [smExact];

  if paRange.Visible then
  begin
    SearchThread.SearchMethod := SearchThread.SearchMethod + [smRange];
    SearchThread.SearchRangeFrom := dtRange1.Date;
    SearchThread.SearchRangeTo := dtRange2.Date;
  end;

  if paEvents.Visible and (cbEvents.ItemIndex <> -1) then
  begin
    SearchThread.SearchMethod := SearchThread.SearchMethod + [smEvents];
    SearchThread.SearchEvents := hppEventFilters[cbEvents.ItemIndex].Events;
  end;

  SearchThread.Priority := tpLower;
  SearchThread.ParentHandle := Handle;
  SearchThread.SearchText := edSearch.Text;
  SearchThread.SearchProtectedContacts := SearchProtected;
  SearchThread.Resume;
end;

// takes index from *History* array as parameter
procedure TfmGlobalSearch.DeleteEventFromLists(Item: Integer);
var
  i: Integer;
  EventDeleted: Boolean;
begin
  if Item = -1 then
    exit;

  i := High(History);
  if Item <> i then
    Move(History[Item + 1], History[Item], (i - Item) * SizeOf(History[0]));
  SetLength(History, i);

  if not FFiltered then
    exit;

  EventDeleted := False;
  for i := 0 to Length(FilterHistory) - 1 do
  begin
    if EventDeleted then
    begin
      if i < Length(FilterHistory) - 1 then
        FilterHistory[i] := FilterHistory[i + 1];
      Dec(FilterHistory[i]);
    end
    else if FilterHistory[i] = Item then
      EventDeleted := True;
  end;
  if EventDeleted then
    SetLength(FilterHistory, Length(FilterHistory) - 1);
end;

procedure TfmGlobalSearch.DisableFilter;
begin
  if not FFiltered then
    exit;
  FFiltered := False;
  SetLength(FilterHistory, 0);
  hg.Allocate(0);
  if Length(History) > 0 then
  begin
    hg.Allocate(Length(History));
    hg.Selected := 0;
  end
  else
    hg.Selected := -1;
  // dirty hack: readjust scrollbars
  hg.Perform(WM_SIZE, SIZE_RESTORED, MakeLParam(hg.ClientWidth, hg.ClientHeight));
end;

procedure TfmGlobalSearch.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  flag: UINT;
  AppSysMenu: THandle;
begin
  CanClose := (hg.State in [gsIdle, gsInline]);
  if CanClose and IsSearching then
  begin
    // disable close button
    AppSysMenu := GetSystemMenu(Handle, False);
    flag := MF_GRAYED;
    EnableMenuItem(AppSysMenu, SC_CLOSE, MF_BYCOMMAND or flag);
    // laProgress.Caption := TranslateW('Please wait while closing the window...');
    // laProgress.Font.Style := [fsBold];
    // pb.Visible := False;
    if paProgress.Visible then
      paProgress.Hide;
    sb.SimpleText := TranslateW('Please wait while closing the window...');
    // terminate thread
    SearchThread.Terminate(tpHigher);
    repeat
      Application.ProcessMessages
    until not IsSearching;
  end;
  if CanClose and Assigned(SearchThread) then
    FreeAndNil(SearchThread);
end;

procedure TfmGlobalSearch.hgItemData(Sender: TObject; Index: Integer; var Item: THistoryItem);
begin
  Item := ReadEvent(GetSearchItem(Index).hDBEvent, GetSearchItem(Index).Contact.Codepage);
  Item.Proto := GetSearchItem(Index).Contact.Proto;
  Item.RTLMode := GetSearchItem(Index).Contact.RTLMode;
  Item.Bookmarked := BookmarkServer[GetSearchItem(Index).Contact.Handle].Bookmarked
    [GetSearchItem(Index).hDBEvent];
end;

procedure TfmGlobalSearch.hgItemDelete(Sender: TObject; Index: Integer);
var
  si: TSearchItem;
begin
  si := GetSearchItem(Index);
  if (FormState = gsDelete) and (si.hDBEvent <> 0) then
    db_event_delete(si.Contact.Handle, si.hDBEvent);
  if FFiltered then
    Index := FilterHistory[Index];
  DeleteEventFromLists(Index);
  hgState(hg, hg.State);
  Application.ProcessMessages;
end;

procedure TfmGlobalSearch.hgItemFilter(Sender: TObject; Index: Integer; var Show: Boolean);
begin
  if HotFilterString = '' then
    exit;
  if Pos(WideUpperCase(HotFilterString), WideUpperCase(hg.Items[Index].Text)) = 0 then
    Show := False;
end;

procedure TfmGlobalSearch.hgBookmarkClick(Sender: TObject; Item: Integer);
var
  val: Boolean;
  hContact, hDBEvent: THandle;
begin
  hContact := GetSearchItem(Item).Contact.Handle;
  hDBEvent := GetSearchItem(Item).hDBEvent;
  val := not BookmarkServer[hContact].Bookmarked[hDBEvent];
  BookmarkServer[hContact].Bookmarked[hDBEvent] := val;
end;

procedure TfmGlobalSearch.hgDblClick(Sender: TObject);
var
  oep: TOpenEventParams;
begin
  if hg.Selected = -1 then
    exit;
  oep.cbSize := SizeOf(oep);
  oep.hContact := GetSearchItem(hg.Selected).Contact.Handle;
  oep.hDBEvent := GetSearchItem(hg.Selected).hDBEvent;
  CallService(MS_HPP_OPENHISTORYEVENT, wParam(@oep), 0);
end;

procedure TfmGlobalSearch.edSearchChange(Sender: TObject);
begin
  bnSearch.Enabled := (edSearch.Text <> '') or paRange.Visible or paEvents.Visible;
end;

procedure TfmGlobalSearch.edSearchEnter(Sender: TObject);
begin
  // edSearch.SelectAll;
end;

procedure TfmGlobalSearch.LoadAccMenu;
var
  i: Integer;
  wstr: String;
  menuitem: TMenuItem;
  pm: TPopupMenu;
begin
  mmToolbar.Clear;
  for i := ToolBar.ButtonCount - 1 downto 0 do
  begin
    if ToolBar.Buttons[i].Style = tbsSeparator then
    begin
      menuitem := TMenuItem.Create(mmToolbar);
      menuitem.Caption := '-';
    end
    else
    begin
      menuitem := TMenuItem.Create(ToolBar.Buttons[i]);
      wstr := ToolBar.Buttons[i].Caption;
      if wstr = '' then
        wstr := ToolBar.Buttons[i].Hint;
      if wstr <> '' then
      begin
        pm := TPopupMenu(ToolBar.Buttons[i].PopupMenu);
        if pm = nil then
          menuitem.OnClick := ToolBar.Buttons[i].OnClick
        else
        begin
          menuitem.Tag := THandle(Pointer(pm));
        end;
        menuitem.Caption := wstr;
        menuitem.ShortCut := TextToShortCut(ToolBar.Buttons[i].HelpKeyword);
        menuitem.Enabled := ToolBar.Buttons[i].Enabled;
        menuitem.Visible := ToolBar.Buttons[i].Visible;
      end;
    end;
    mmToolbar.Insert(0, menuitem);
  end;
  mmToolbar.RethinkHotkeys;
end;

procedure LoadHPPIcons(var sb:TSpeedButton;idx:integer);
begin
  with sb.Glyph do
  begin
    Width := 16;
    Height := 16;
    Canvas.Brush.Color := clBtnFace;
    Canvas.FillRect(Canvas.ClipRect);
    DrawiconEx(Canvas.Handle, 0, 0, hppIcons[idx].Handle, 16, 16, 0,
      Canvas.Brush.Handle, DI_NORMAL);
  end;
end;

procedure TfmGlobalSearch.LoadButtonIcons;
begin
  LoadHPPIcons(sbClearFilter,HPP_ICON_HOTFILTERCLEAR);
  LoadHPPIcons(sbAdvancedClose,HPP_ICON_SESS_HIDE);
  LoadHPPIcons(sbRangeClose,HPP_ICON_SESS_HIDE);
  LoadHPPIcons(sbEventsClose,HPP_ICON_SESS_HIDE);
end;

procedure TfmGlobalSearch.LoadContactsIcons;
begin
  lvContacts.Items.BeginUpdate;

  if GlobalSearchAllResultsIcon = -1 then
    GlobalSearchAllResultsIcon := ImageList_AddIcon(ilContacts.Handle,
      hppIcons[HPP_ICON_SEARCH_ALLRESULTS].Handle)
  else
    ImageList_ReplaceIcon(ilContacts.Handle, GlobalSearchAllResultsIcon,
      hppIcons[HPP_ICON_SEARCH_ALLRESULTS].Handle);

  lvContacts.Items.EndUpdate;
end;

procedure TfmGlobalSearch.LoadEventFilterButton;
var
  pad: DWord;
  PadH, { PadV, } GlyphHeight: Integer;
  sz: TSize;
  FirstName, Name: String;
  PaintRect: TRect;
  DrawTextFlags: Cardinal;
  GlyphWidth: Integer;
begin
  FirstName := hppEventFilters[0].Name;
  Name := hppEventFilters[tbEventsFilter.Tag].Name;
  tbEventsFilter.Hint := Name; // show hint because the whole name may not fit in button

  pad := SendMessage(ToolBar.Handle, TB_GETPADDING, 0, 0);
  // PadV := HiWord(pad);
  PadH := LoWord(pad);

  tbEventsFilter.Glyph.Canvas.Font := tbEventsFilter.Font;
  sz := tbEventsFilter.Glyph.Canvas.TextExtent(FirstName);
  GlyphHeight := Max(sz.cy, 16);
  GlyphWidth := 16 + sz.cx + tbEventsFilter.Spacing;

  tbEventsFilter.Glyph.Height := GlyphHeight;
  tbEventsFilter.Glyph.Width := GlyphWidth * 2;
  tbEventsFilter.Glyph.Canvas.Brush.Color := clBtnFace;
  tbEventsFilter.Glyph.Canvas.FillRect(tbEventsFilter.Glyph.Canvas.ClipRect);
  DrawiconEx(tbEventsFilter.Glyph.Canvas.Handle, sz.cx + tbEventsFilter.Spacing,
    ((GlyphHeight - 16) div 2), hppIcons[HPP_ICON_DROPDOWNARROW].Handle, 16, 16, 0,
    tbEventsFilter.Glyph.Canvas.Brush.Handle, DI_NORMAL);
  DrawState(tbEventsFilter.Glyph.Canvas.Handle, 0, nil,
    hppIcons[HPP_ICON_DROPDOWNARROW].Handle, 0, sz.cx + tbEventsFilter.Spacing +
    GlyphWidth, ((GlyphHeight - 16) div 2), 0, 0, DST_ICON or DSS_DISABLED);

  PaintRect := Rect(0, ((GlyphHeight - sz.cy) div 2), GlyphWidth - 16 - tbEventsFilter.Spacing,
    tbEventsFilter.Glyph.Height);
  DrawTextFlags := DT_END_ELLIPSIS or DT_NOPREFIX or DT_CENTER;
  tbEventsFilter.Glyph.Canvas.Font.Color := clWindowText;
  DrawTextW(tbEventsFilter.Glyph.Canvas.Handle, @Name[1], Length(Name), PaintRect,
    DrawTextFlags);
  OffsetRect(PaintRect, GlyphWidth, 0);
  tbEventsFilter.Glyph.Canvas.Font.Color := clGrayText;
  DrawTextW(tbEventsFilter.Glyph.Canvas.Handle, @Name[1], Length(Name), PaintRect,
    DrawTextFlags);
  tbEventsFilter.Width := GlyphWidth + 2 * PadH;
  tbEventsFilter.NumGlyphs := 2;
end;

procedure TfmGlobalSearch.LoadPosition;
var
  n: Integer;
begin
  // if Utils_RestoreWindowPosition(Self.Handle,0,0,hppDBName,'GlobalSearchWindow.') <> 0 then begin
  // Self.Left := (Screen.Width-Self.Width) div 2;
  // Self.Top := (Screen.Height - Self.Height) div 2;
  // end;
  Utils_RestoreFormPosition(Self, 0, hppDBName, 'GlobalSearchWindow.');

  n := GetDBInt(hppDBName, 'GlobalSearchWindow.ContactListWidth', -1);
  if n <> -1 then
  begin
    paContacts.Width := n;
  end;
  spContacts.Left := paContacts.Left + paContacts.Width + 1;
  edFilter.Width := paFilter.Width - edFilter.Left - 2;

  SetRecentEventsPosition(GetDBInt(hppDBName, 'SortOrder', 0) <> 0);

  ToggleAdvancedPanel(GetDBBool(hppDBName, 'GlobalSearchWindow.ShowAdvanced', False));
  case GetDBInt(hppDBName, 'GlobalSearchWindow.AdvancedOptions', 0) of
    0:
      rbAny.Checked := True;
    1:
      rbAll.Checked := True;
    2:
      rbExact.Checked := True
  else
    rbAny.Checked := True;
  end;
  ToggleRangePanel(GetDBBool(hppDBName, 'GlobalSearchWindow.ShowRange', False));
  ToggleEventsPanel(GetDBBool(hppDBName, 'GlobalSearchWindow.ShowEvents', False));
  dtRange1.Date := Trunc(GetDBDateTime(hppDBName, 'GlobalSearchWindow.RangeFrom', Now));
  dtRange2.Date := Trunc(GetDBDateTime(hppDBName, 'GlobalSearchWindow.RangeTo', Now));
  edSearch.Text := GetDBWideStr(hppDBName, 'GlobalSearchWindow.LastSearch',
    DEFAULT_SEARCH_TEXT);
end;

procedure TfmGlobalSearch.LoadToolbarIcons;
var
  il: HIMAGELIST;
  ii: Integer;
begin
  ImageList_Remove(ilToolbar.Handle, -1); // clears image list
  il := ImageList_Create(16, 16, ILC_COLOR32 or ILC_MASK, 10, 2);
  if il <> 0 then
    ilToolbar.Handle := il
  else
    il := ilToolbar.Handle;
  ToolBar.Images := ilToolbar;

  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_SEARCHADVANCED].Handle);
  tbAdvanced.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_SEARCHRANGE].Handle);
  tbRange.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_HOTFILTER].Handle);
  tbEvents.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_BOOKMARK].Handle);
  tbBookmarks.ImageIndex := ii;

  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_HOTFILTER].Handle);
  tbFilter.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_HOTSEARCH].Handle);
  tbSearch.ImageIndex := ii;

  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_TOOL_COPY].Handle);
  tbCopy.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_TOOL_DELETE].Handle);
  tbDelete.ImageIndex := ii;
  ii := ImageList_AddIcon(il, hppIcons[HPP_ICON_TOOL_SAVE].Handle);
  tbSave.ImageIndex := ii;

  LoadEventFilterButton;
end;

procedure TfmGlobalSearch.lvContactsContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: Boolean);
var
  Item: TListItem;
  hContact: THandle;
begin
  Handled := True;
  Item := TListItem(lvContacts.GetItemAt(MousePos.X, MousePos.Y));
  if Item = nil then
    exit;
  hContact := THandle(Item.Data);
  if hContact = 0 then
    exit;
  UserMenu := Menu_BuildContactMenu(hContact);
  if UserMenu <> 0 then
  begin
    UserMenuContact := hContact;
    MousePos := lvContacts.ClientToScreen(MousePos);
    Application.CancelHint;
    TrackPopupMenu(UserMenu, TPM_TOPALIGN or TPM_LEFTALIGN or TPM_LEFTBUTTON, MousePos.X,
      MousePos.Y, 0, Handle, nil);
    DestroyMenu(UserMenu);
    UserMenu := 0;
    // UserMenuContact := 0;
  end;
end;

procedure TfmGlobalSearch.lvContactsSelectItem(Sender: TObject; Item: TListItem;
  Selected: Boolean);
var
  hCont: THandle;
  // i,Index: Integer;
begin
  if not Selected then
    exit;

  { Index := -1;
    hCont := THANDLE(Item.Data);
    for i := 0 to Length(History) - 1 do
    if History[i].hContact = hCont then begin
    Index := i;
    break;
    end;
    if Index = -1 then exit;
    hg.Selected := Index; }

  // OXY: try to make selected item the topmost
  // while hg.GetFirstVisible <> Index do begin
  // if hg.VertScrollBar.Position = hg.VertScrollBar.Range then break;
  // hg.VertScrollBar.Position := hg.VertScrollBar.Position + 1;
  // end;

  if Item.Index = 0 then
    DisableFilter
  else
  begin
    hCont := THANDLE(Item.Data);
    FilterOnContact(hCont);
  end;
end;

procedure TfmGlobalSearch.OnCNChar(var Message: TWMChar);
// make tabs work!
begin
  if not(csDesigning in ComponentState) then
    with Message do
    begin
      Result := 1;
      if (Perform(WM_GETDLGCODE, 0, 0) and DLGC_WANTCHARS = 0) and
        (GetParentForm(Self).Perform(CM_DIALOGCHAR, CharCode, KeyData) <> 0) then
        exit;
      Result := 0;
    end;
end;

procedure TfmGlobalSearch.OrganizePanels;
var
  PrevPanel: TPanel;
begin
  PrevPanel := paSearch;
  if paAdvanced.Visible then
  begin
    paAdvanced.Top := PrevPanel.Top + PrevPanel.Width;
    PrevPanel := paAdvanced;
  end;
  if paRange.Visible then
  begin
    paRange.Top := PrevPanel.Top + PrevPanel.Width;
    PrevPanel := paRange;
  end;
  if paEvents.Visible then
  begin
    paEvents.Top := PrevPanel.Top + PrevPanel.Width;
  end;
end;

procedure TfmGlobalSearch.pbFilterPaint(Sender: TObject);
var
  ic: hIcon;
begin
  if tiFilter.Enabled then
    ic := hppIcons[HPP_ICON_HOTFILTERWAIT].Handle
  else
    ic := hppIcons[HPP_ICON_HOTFILTER].Handle;

  DrawiconEx(pbFilter.Canvas.Handle, 0, 0, ic, 16, 16, 0, pbFilter.Canvas.Brush.Handle,
    DI_NORMAL);
end;

procedure TfmGlobalSearch.pmEventsFilterPopup(Sender: TObject);
var
  i: Integer;
  pmi, mi: TMenuItem;
begin
  if Customize1.Parent <> pmEventsFilter.Items then
  begin
    pmi := TMenuItem(Customize1.Parent);
    for i := pmi.Count - 1 downto 0 do
    begin
      mi := TMenuItem(pmi.Items[i]);
      pmi.Remove(mi);
      pmEventsFilter.Items.Insert(0, mi);
    end;
  end;
  Application.CancelHint;
end;

procedure TfmGlobalSearch.ReplyQuoted(Item: Integer);
begin
  if (GetSearchItem(Item).Contact.Handle = 0) or (hg.SelCount = 0) then
    exit;
  SendMessageTo(GetSearchItem(Item).Contact.Handle,
    hg.FormatSelected(GridOptions.ReplyQuotedFormat));
end;

procedure TfmGlobalSearch.ReplyQuoted1Click(Sender: TObject);
begin
  if hg.Selected <> -1 then
  begin
    if GetSearchItem(hg.Selected).Contact.Handle = 0 then
      exit;
    ReplyQuoted(hg.Selected);
  end;
end;

procedure TfmGlobalSearch.tbSaveClick(Sender: TObject);
var
  t, t1: String;
  SaveFormat: TSaveFormat;
  RecentFormat: TSaveFormat;
begin
  if hg.Selected = -1 then
    exit;
  RecentFormat := TSaveFormat(GetDBInt(hppDBName, 'ExportFormat', 0));
  SaveFormat := RecentFormat;
  PrepareSaveDialog(SaveDialog, SaveFormat, True);
  t1 := TranslateW('Partial History [%s] - [%s]');
  t1 := Format(t1, [hg.ProfileName, hg.ContactName]);
  t := MakeFileName(t1);
  SaveDialog.FileName := t;
  if not SaveDialog.Execute then
    exit;
  for SaveFormat := High(SaveFormats) downto Low(SaveFormats) do
    if SaveDialog.FilterIndex = SaveFormats[SaveFormat].Index then
      break;
  if SaveFormat <> sfAll then
    RecentFormat := SaveFormat;
  hg.SaveSelected(SaveDialog.Files[0], RecentFormat);
  WriteDBInt(hppDBName, 'ExportFormat', Integer(RecentFormat));
end;

procedure TfmGlobalSearch.SavePosition;
begin
  Utils_SaveFormPosition(Self, 0, hppDBName, 'GlobalSearchWindow.');

  WriteDBInt(hppDBName, 'GlobalSearchWindow.ContactListWidth', paContacts.Width);

  WriteDBBool(hppDBName, 'GlobalSearchWindow.ShowAdvanced', paAdvanced.Visible);
  if rbAny.Checked then
    WriteDBInt(hppDBName, 'GlobalSearchWindow.AdvancedOptions', 0)
  else if rbAll.Checked then
    WriteDBInt(hppDBName, 'GlobalSearchWindow.AdvancedOptions', 1)
  else
    WriteDBInt(hppDBName, 'GlobalSearchWindow.AdvancedOptions', 2);

  WriteDBBool(hppDBName, 'GlobalSearchWindow.ShowRange', paRange.Visible);
  WriteDBBool(hppDBName, 'GlobalSearchWindow.ShowEvents', paEvents.Visible);

  if Trunc(dtRange1.Date) = Trunc(Now) then
    DBDelete(hppDBName, 'GlobalSearchWindow.RangeFrom')
  else
    WriteDBDateTime(hppDBName, 'GlobalSearchWindow.RangeFrom', Trunc(dtRange1.Date));
  if Trunc(dtRange2.Date) = Trunc(Now) then
    DBDelete(hppDBName, 'GlobalSearchWindow.RangeTo')
  else
    WriteDBDateTime(hppDBName, 'GlobalSearchWindow.RangeTo', Trunc(dtRange2.Date));

  WriteDBWideStr(hppDBName, 'GlobalSearchWindow.LastSearch', edSearch.Text);
end;

procedure TfmGlobalSearch.edSearchKeyPress(Sender: TObject; var Key: Char);
begin
  // to prevent ** BLING ** when press Enter
  // to prevent ** BLING ** when press Tab
  // to prevent ** BLING ** when press Esc
  if Ord(Key) in [VK_RETURN, VK_TAB, VK_ESCAPE] then
    Key := #0;
end;

procedure TfmGlobalSearch.edSearchKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_RETURN) and bnSearch.Enabled then
    bnSearch.Click;
end;

procedure TfmGlobalSearch.EndHotFilterTimer;
begin
  tiFilter.Enabled := False;
  HotFilterString := edFilter.Text;
  hg.GridUpdate([guFilter]);
  if pbFilter.Tag <> 0 then
  begin
    pbFilter.Tag := 0;
    pbFilter.Repaint;
  end;
end;

procedure TfmGlobalSearch.EventsFilterItemClick(Sender: TObject);
begin
  SetEventFilter(TMenuItem(Sender).Tag);
end;

function TfmGlobalSearch.IsFileEvent(Index: Integer): Boolean;
begin
  Result := (Index <> -1) and (mtFile in hg.Items[Index].MessageType);
  if Result then
  begin
    SavedLinkUrl := ExtractFileName(String(hg.Items[Index].Extended));
    SavedFileDir := ExtractFileDir(String(hg.Items[Index].Extended));
  end;
end;

procedure TfmGlobalSearch.hgPopup(Sender: TObject);
begin
  Delete1.Visible := False;
  SaveSelected1.Visible := False;
  if hg.Selected <> -1 then
  begin
    Delete1.Visible := True;
    SaveSelected1.Visible := (hg.SelCount > 1);
    if GetSearchItem(hg.Selected).Contact.Handle = 0 then
    begin
      SendMessage1.Visible := False;
      ReplyQuoted1.Visible := False;
    end;
    if hg.Items[hg.Selected].Bookmarked then
      Bookmark1.Caption := TranslateW('Remove &Bookmark')
    else
      Bookmark1.Caption := TranslateW('Set &Bookmark');
    FileActions.Visible := IsFileEvent(hg.Selected);
    if FileActions.Visible then
      OpenFileFolder.Visible := (SavedFileDir <> '');
    pmGrid.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
  end;
end;

procedure TfmGlobalSearch.hgProcessRichText(Sender: TObject; Handle: Cardinal; Item: Integer);
var
  ItemRenderDetails: TItemRenderDetails;
begin
  ZeroMemory(@ItemRenderDetails, SizeOf(ItemRenderDetails));
  ItemRenderDetails.cbSize := SizeOf(ItemRenderDetails);
  ItemRenderDetails.hContact := GetSearchItem(Item).Contact.Handle;
  ItemRenderDetails.hDBEvent := GetSearchItem(Item).hDBEvent;
  ItemRenderDetails.pProto := PAnsiChar(hg.Items[Item].Proto);
  ItemRenderDetails.pModule := PAnsiChar(hg.Items[Item].Module);
  ItemRenderDetails.pText := nil;
  ItemRenderDetails.pExtended := PAnsiChar(hg.Items[Item].Extended);
  ItemRenderDetails.dwEventTime := hg.Items[Item].Time;
  ItemRenderDetails.wEventType := hg.Items[Item].EventType;
  ItemRenderDetails.IsEventSent := (mtOutgoing in hg.Items[Item].MessageType);

  if Handle = hg.InlineRichEdit.Handle then
    ItemRenderDetails.dwFlags := ItemRenderDetails.dwFlags or IRDF_INLINE;
  if hg.IsSelected(Item) then
    ItemRenderDetails.dwFlags := ItemRenderDetails.dwFlags or IRDF_SELECTED;

  ItemRenderDetails.bHistoryWindow := IRDHW_GLOBALSEARCH;

  NotifyEventHooks(hHppRichEditItemProcess, Handle, LPARAM(@ItemRenderDetails));
end;

procedure TfmGlobalSearch.hgTranslateTime(Sender: TObject; Time: Cardinal; var Text: String);
begin
  Text := TimestampToString(Time);
end;

procedure TfmGlobalSearch.HookEvents;
begin
  hHookContactIconChanged := HookEventMessage(ME_CLIST_CONTACTICONCHANGED,
    Self.Handle, HM_SRCH_CONTACTICONCHANGED);
end;

procedure TfmGlobalSearch.UnhookEvents;
begin
  UnhookEvent(hHookContactIconChanged);
end;

procedure TfmGlobalSearch.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_COMMAND:
      begin
        if mmAcc.DispatchCommand(Message.wParam) then
          exit;
        inherited;
        if Message.Result <> 0 then
          exit;
        Message.Result := Clist_MenuProcessCommand(Message.WParamLo, MPCF_CONTACTMENU, UserMenuContact);
        exit;
      end;
    WM_MEASUREITEM:
      if Self.UserMenu <> 0 then
      begin
        Message.Result := Menu_MeasureItem(Message.LParam);
        if Message.Result <> 0 then
          exit;
      end;
    WM_DRAWITEM:
      if Self.UserMenu <> 0 then
      begin
        Message.Result := Menu_DrawItem(Message.LParam);
        if Message.Result <> 0 then
          exit;
      end;
  end;
  inherited;
end;

procedure TfmGlobalSearch.FormShow(Sender: TObject);
begin
  paFilter.Visible := False;
  ToggleAdvancedPanel(False);
  ShowContacts(False);

  IsSearching := False;
  SearchThread := nil;

  hg.Codepage := hppCodepage;
  hg.RTLMode := hppRTLDefault;
  hg.TxtStartUp := TranslateW('Ready to search') + #10#13#10#13 +
    TranslateW('Click Search button to start');
  hg.Allocate(0);

  LoadPosition;

  HookEvents;

  edSearch.SetFocus;
  edSearch.SelectAll;
  edSearchChange(Self);

  CreateEventsFilterMenu;
  // SetEventFilter(0);
  SetEventFilter(GetShowAllEventsIndex);
end;

function TfmGlobalSearch.GetSearchItem(GridIndex: Integer): TSearchItem;
begin
  if not FFiltered then
    Result := History[GridIndex]
  else
    Result := History[FilterHistory[GridIndex]];
end;

function TfmGlobalSearch.GetContactInfo(hContact: THandle): THPPContactInfo;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to ContactList.Count - 1 do
    if hContact = THPPContactInfo(ContactList.Items[i]).Handle then
    begin
      Result := THPPContactInfo(ContactList.Items[i]);
      break;
    end;
end;

procedure TfmGlobalSearch.HMContactDeleted(var M: TMessage);
// var
// ci: THPPContactInfo;
// i: Integer;
begin
  { wParam - hContact; lParam - zero }
  // do here something because the contact is deleted
  if IsSearching then
    exit;
  // need to remove contact
  // ci := GetContactInfo(M.WParam);
  // if ci = nil then exit;
  // for i := 1 to lvContacts.Items.Count - 1 do
  // if ci.Handle = THandle(lvContacts.Items[i].Data) then begin
  // lvContacts.Items.Delete(i);
  // break;
  // end;
  // ContactList.Remove(ci);
end;

procedure TfmGlobalSearch.HMNickChanged(var M: TMessage);
var
  ci: THPPContactInfo;
  i: Integer;
  SubContact: TMCONTACT;
  SubProtocol: AnsiString;
begin
  { wParam - hContact; lParam - zero }
  if IsSearching then
    exit;
  ci := GetContactInfo(M.wParam);
  if ci = nil then
    exit;
  GetContactProto(CurContact, SubContact, SubProtocol);
  ci.ProfileName := GetContactDisplayName(0, SubProtocol);
  ci.Name := GetContactDisplayName(ci.Handle, ci.Proto, True);
  for i := 1 to lvContacts.Items.Count - 1 do
    if M.wParam = THandle(lvContacts.Items[i].Data) then
    begin
      lvContacts.Items[i].Caption := ci.Name;
      break;
    end;
  hg.Invalidate;
end;

procedure TfmGlobalSearch.HMContactIconChanged(var M: TMessage);
var
  i: Integer;
begin
  { wParam - hContact; lParam - IconID }
  // contact icon has changed
  // meTest.Lines.Add(GetContactDisplayName(M.wParam)+' changed icon to '+IntToStr(m.LParam));
  if not paContacts.Visible then
    exit;
  for i := 0 to lvContacts.Items.Count - 1 do
  begin
    if THandle(M.wParam) = THandle(lvContacts.Items[i].Data) then
    begin
      lvContacts.Items[i].ImageIndex := Integer(M.LParam);
      break;
    end;
  end;
end;

procedure TfmGlobalSearch.HMEventDeleted(var M: TMessage);
var
  i: Integer;
begin
  { wParam - hContact; lParam - hDBEvent }
  if hg.State = gsDelete then
    exit;
  // if WPARAM(message.wParam) <> hContact then exit;
  for i := 0 to hg.Count - 1 do
    if GetSearchItem(i).hDBEvent = THandle(M.LParam) then
    begin
      hg.Delete(i);
      hgState(hg, hg.State);
      exit;
    end;
  // if event is not in filter, we must search the overall array
  if FFiltered then
  begin
    i := FindHistoryItemByHandle(M.LParam);
    if i <> -1 then
      DeleteEventFromLists(i);
  end;
end;

procedure TfmGlobalSearch.HMFiltersChanged(var M: TMessage);
begin
  CreateEventsFilterMenu;
  SetEventFilter(0);
end;

procedure TfmGlobalSearch.HMIcons2Changed(var M: TMessage);
begin
  Icon.Handle := CopyIcon(hppIcons[HPP_ICON_GLOBALSEARCH].Handle);
  LoadToolbarIcons;
  LoadButtonIcons;
  LoadContactsIcons;
  pbFilter.Repaint;
  // hg.Repaint;
end;

procedure TfmGlobalSearch.mmHideMenuClick(Sender: TObject);
begin
  WriteDBBool(hppDBName, 'Accessability', False);
  NotifyAllForms(HM_NOTF_ACCCHANGED, WPARAM(False), 0);
end;

procedure TfmGlobalSearch.HMAccChanged(var M: TMessage);
begin
  ToggleMainMenu(Boolean(M.wParam));
end;

procedure TfmGlobalSearch.HMBookmarksChanged(var M: TMessage);
var
  i: Integer;
  Found: Boolean;
begin
  Found := False;
  for i := 0 to hg.Count - 1 do
    if GetSearchItem(i).hDBEvent = THandle(M.LParam) then
    begin
      hg.ResetItem(i);
      Found := True;
      break;
    end;
  if Found then
    hg.Repaint;
end;

procedure TfmGlobalSearch.HMPreShutdown(var M: TMessage);
begin
  Close;
end;

procedure TfmGlobalSearch.hgKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  pm: TPopupMenu;
begin
  if hg.State = gsInline then
    pm := pmInline
  else
    pm := pmGrid;

  if IsFormShortCut([pm], Key, Shift) then
  begin
    Key := 0;
    exit;
  end;

  WasReturnPressed := (Key = VK_RETURN);
end;

procedure TfmGlobalSearch.hgKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if not WasReturnPressed then
    exit;

  if (Key = VK_RETURN) and (Shift = []) then
  begin
    if hg.Selected <> -1 then
      hg.EditInline(hg.Selected);
  end;
  if (Key = VK_RETURN) and (Shift = [ssCtrl]) then
  begin
    hgDblClick(hg);
  end;
end;

procedure TfmGlobalSearch.hgNameData(Sender: TObject; Index: Integer; var Name: String);
var
  si: TSearchItem;
begin
  si := GetSearchItem(Index);
  if FFiltered then
  begin
    if mtIncoming in hg.Items[Index].MessageType then
      Name := si.Contact.Name
    else if not GridOptions.ForceProfileName then
      Name := si.Contact.ProfileName;
  end
  else
  begin
    if mtIncoming in hg.Items[Index].MessageType then
      Name := WideFormat(TranslateW('From %s'), [si.Contact.Name])
    else
      Name := WideFormat(TranslateW('To %s'), [si.Contact.Name]);
  end;
  // there should be anoter way to use bookmarks names
  // if IsBookmarksMode then
  // Name := Name + ' [' + BookmarkServer[si.Contact.Handle].BookmarkName[si.hDBEvent] + ']';
end;

procedure TfmGlobalSearch.hgUrlClick(Sender: TObject; Item: Integer; const URLText: String;
  Button: TMouseButton);
begin
  if URLText = '' then
    exit;
  if (Button = mbLeft) or (Button = mbMiddle) then
    OpenUrl(URLText, True)
  else
  begin
    SavedLinkUrl := URLText;
    pmLink.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
  end;
end;

procedure TfmGlobalSearch.edPassKeyPress(Sender: TObject; var Key: Char);
begin
  // to prevent ** BLING ** when press Enter
  // to prevent ** BLING ** when press Tab
  // to prevent ** BLING ** when press Esc
  if Ord(Key) in [VK_RETURN, VK_TAB, VK_ESCAPE] then
    Key := #0;
end;

procedure TfmGlobalSearch.edPassKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
  begin
    bnSearch.Click;
    Key := 0;
  end;
end;

procedure TfmGlobalSearch.ShowContacts(Show: Boolean);
begin
  paContacts.Visible := Show;
  spContacts.Visible := Show;
  if (Show) and (paContacts.Width > 0) then
    spContacts.Left := paContacts.Width + paContacts.Left + 1;
end;

procedure TfmGlobalSearch.SearchNext(Rev: Boolean; Warp: Boolean = True);
var
  stext: String;
  res: Integer;
  mcase, Down: Boolean;
  WndHandle: HWND;
begin
  stext := HotString;
  mcase := False;
  if stext = '' then
    exit;
  Down := not hg.Reversed;
  if Rev then
    Down := not Down;
  res := hg.Search(stext, mcase, not Warp, False, Warp, Down);
  if res <> -1 then
  begin
    // found
    hg.Selected := res;
    sb.SimpleText := Format(TranslateW('HotSearch: %s (F3 to find next)'), [stext]);
  end
  else
  begin
    WndHandle := Handle;
    // not found
    if Warp and (Down = not hg.Reversed) then
    begin
      // do warp?
      if HppMessageBox(WndHandle, TranslateW('You have reached the end of the history.') +
        #10#13 + TranslateW('Do you want to continue searching at the beginning?'),
        TranslateW('History++ Search'), MB_YESNOCANCEL or MB_DEFBUTTON1 or MB_ICONQUESTION) = ID_YES
      then
        SearchNext(Rev, False);
    end
    else
    begin
      // not warped
      hgState(Self, gsIdle);
      HppMessageBox(WndHandle, WideFormat('"%s" not found', [stext]),
        TranslateW('History++ Search'), MB_OK or MB_DEFBUTTON1 or 0);
    end;
  end;
end;

procedure TfmGlobalSearch.SendMessage1Click(Sender: TObject);
begin
  if hg.Selected <> -1 then
  begin
    if GetSearchItem(hg.Selected).Contact.Handle = 0 then
      exit;
    SendMessageTo(GetSearchItem(hg.Selected).Contact.Handle);
  end;
end;

procedure TfmGlobalSearch.SetEventFilter(FilterIndex: Integer);
var
  i, fi: Integer;
  mi: TMenuItem;
begin
  if FilterIndex = -1 then
  begin
    fi := tbEventsFilter.Tag + 1;
    if fi > High(hppEventFilters) then
      fi := 0;
  end
  else
    fi := FilterIndex;

  tbEventsFilter.Tag := fi;
  LoadEventFilterButton;
  // tbEventsFilter.Repaint;
  mi := TMenuItem(Customize1.Parent);
  for i := 0 to mi.Count - 1 do
    if mi[i].RadioItem then
      mi[i].Checked := (mi[i].Tag = fi);

  hg.Filter := hppEventFilters[fi].Events;
end;

procedure TfmGlobalSearch.SetRecentEventsPosition(OnTop: Boolean);
begin
  hg.Reversed := not OnTop;
end;

procedure TfmGlobalSearch.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  Mask: Integer;
begin
  if (Key = VK_ESCAPE) or ((Key = VK_F4) and (ssAlt in Shift)) then
  begin
    if (Key = VK_ESCAPE) and IsSearching then
      StopSearching
    else
      Close;
    Key := 0;
    exit;
  end;

  if (Key = VK_F10) and (Shift = []) then
  begin
    WriteDBBool(hppDBName, 'Accessability', True);
    NotifyAllForms(HM_NOTF_ACCCHANGED, WPARAM(True), 0);
    Key := 0;
    exit;
  end;

  if (Key = VK_F3) and ((Shift = []) or (Shift = [ssShift])) and (Length(History) > 0) then
  begin
    SearchNext(ssShift in Shift, True);
    Key := 0;
  end;

  if hg.State = gsInline then
    exit;

  if IsFormShortCut([mmAcc], Key, Shift) then
  begin
    Key := 0;
    exit;
  end;

  with Sender as TWinControl do
  begin
    if Perform(CM_CHILDKEY, Key, LParam(Sender)) <> 0 then
      exit;
    Mask := 0;
    case Key of
      VK_TAB:
        Mask := DLGC_WANTTAB;
      VK_RETURN, VK_EXECUTE, VK_ESCAPE, VK_CANCEL:
        Mask := DLGC_WANTALLKEYS;
    end;
    if (Mask <> 0) and (Perform(CM_WANTSPECIALKEY, Key, 0) = 0) and
      (Perform(WM_GETDLGCODE, 0, 0) and Mask = 0) and (Self.Perform(CM_DIALOGKEY, Key, 0) <> 0)
    then
      exit;
  end;
end;

procedure TfmGlobalSearch.hgSearchFinished(Sender: TObject; const Text: String; Found: Boolean);
var
  t: String;
begin
  if Text = '' then
  begin
    HotString := Text;
    hgState(Self, gsIdle);
    exit;
  end;
  HotString := Text;

  if not Found then
    t := HotString
  else
    t := Text;
  sb.SimpleText := Format(TranslateW('HotSearch: %s (F3 to find next)'), [t]);
end;

procedure TfmGlobalSearch.hgSearchItem(Sender: TObject; Item, ID: Integer; var Found: Boolean);
begin
  Found := (THandle(ID) = GetSearchItem(Item).hDBEvent);
end;

procedure TfmGlobalSearch.hgSelect(Sender: TObject; Item, OldItem: Integer);
{ var
  i,hCont,Index: Integer; }
begin
  tbCopy.Enabled := (Item <> -1);
  tbDelete.Enabled := (Item <> -1);
  tbSave.Enabled := (hg.SelCount > 1);

  if hg.HotString = '' then
  begin
    // redraw status bar
    hgState(hg, gsIdle);
  end;

  { if Item = -1 then exit;
    index := -1;
    hCont := History[Item].hContact;
    for i := 0 to lvContacts.Items.Count-1 do
    if THandle(lvContacts.Items.Item[i].Data) = hCont then begin
    Index := i;
    break;
    end;
    if Index = -1 then exit;
    lvContacts.OnSelectItem := nil;
    lvContacts.Items.Item[index].MakeVisible(false);
    lvContacts.Items.Item[index].Selected := true;
    lvContacts.OnSelectItem := self.lvContactsSelectItem; }
end;

procedure TfmGlobalSearch.hgState(Sender: TObject; State: TGridState);
var
  t: String;
begin
  if csDestroying in ComponentState then
    exit;
  if IsSearching then
    t := Format(TranslateW('Searching... %.0n items in %d contacts found'),
      [Length(History) / 1, ContactsFound])
  else
    case State of
      gsIdle:
        t := Format
          (TranslateW('%.0n items in %d contacts found. Searched for %.1f sec in %.0n items.'),
          [Length(History) / 1, ContactsFound, stime / 1000, AllItems / 1]);
      gsLoad:
        t := TranslateW('Loading...');
      gsSave:
        t := TranslateW('Saving...');
      gsSearch:
        t := TranslateW('Searching...');
      gsDelete:
        t := TranslateW('Deleting...');
    end;
  sb.SimpleText := t;
end;

procedure TfmGlobalSearch.tbCopyClick(Sender: TObject);
begin
  if hg.Selected = -1 then
    exit;
  CopyToClip(hg.FormatSelected(GridOptions.ClipCopyFormat), Handle,
    GetSearchItem(hg.Selected).Contact.Codepage);
end;

procedure TfmGlobalSearch.CopyText1Click(Sender: TObject);
begin
  if hg.Selected = -1 then
    exit;
  CopyToClip(hg.FormatSelected(GridOptions.ClipCopyTextFormat), Handle,
    GetSearchItem(hg.Selected).Contact.Codepage);
end;

procedure TfmGlobalSearch.CreateEventsFilterMenu;
var
  i: Integer;
  mi: TMenuItem;
  ShowAllEventsIndex: Integer;
begin
  for i := pmEventsFilter.Items.Count - 1 downto 0 do
    if pmEventsFilter.Items[i].RadioItem then
      pmEventsFilter.Items.Delete(i);
  cbEvents.Items.Clear;

  ShowAllEventsIndex := GetShowAllEventsIndex;
  for i := 0 to High(hppEventFilters) do
  begin
    mi := TMenuItem.Create(pmEventsFilter);
    mi.Caption := StringReplace(hppEventFilters[i].Name, '&', '&&', [rfReplaceAll]);
    mi.GroupIndex := 1;
    mi.RadioItem := True;
    mi.Tag := i;
    mi.OnClick := EventsFilterItemClick;
    if i = ShowAllEventsIndex then
      mi.Default := True;
    pmEventsFilter.Items.Insert(i, mi);
    cbEvents.Items.Insert(i, mi.Caption);
  end;

  cbEvents.DropDownCount := Length(hppEventFilters);
  cbEvents.ItemIndex := ShowAllEventsIndex;
end;

procedure TfmGlobalSearch.Customize1Click(Sender: TObject);
begin
  if not Assigned(fmCustomizeFilters) then
  begin
    CustomizeFiltersForm := TfmCustomizeFilters.Create(Self);
    CustomizeFiltersForm.Show;
  end
  else
  begin
    BringFormToFront(fmCustomizeFilters);
  end;
end;

procedure TfmGlobalSearch.tbDeleteClick(Sender: TObject);
begin
  if hg.SelCount = 0 then
    exit;
  if hg.SelCount > 1 then
  begin
    if HppMessageBox(Handle,
      WideFormat(TranslateW('Do you really want to delete selected items (%.0f)?'),
      [hg.SelCount / 1]), TranslateW('Delete Selected'), MB_YESNOCANCEL or MB_DEFBUTTON1 or
      MB_ICONQUESTION) <> IDYES then
      exit;
  end
  else
  begin
    if HppMessageBox(Handle, TranslateW('Do you really want to delete selected item?'),
      TranslateW('Delete'), MB_YESNOCANCEL or MB_DEFBUTTON1 or MB_ICONQUESTION) <> IDYES then
      exit;
  end;
  SetSafetyMode(False);
  try
    FormState := gsDelete;
    hg.DeleteSelected;
    FormState := gsIdle;
  finally
    SetSafetyMode(True);
  end;
end;

procedure TfmGlobalSearch.hgRTLEnabled(Sender: TObject; BiDiMode: TBiDiMode);
begin
  edSearch.BiDiMode := BiDiMode;
  edFilter.BiDiMode := BiDiMode;
  dtRange1.BiDiMode := BiDiMode;
  dtRange2.BiDiMode := BiDiMode;
  // lvContacts.BiDiMode := BiDiMode;
end;

procedure TfmGlobalSearch.Bookmark1Click(Sender: TObject);
var
  val: Boolean;
  hDBEvent: THandle;
begin
  hDBEvent := GetSearchItem(hg.Selected).hDBEvent;
  val := not BookmarkServer[GetSearchItem(hg.Selected).Contact.Handle].Bookmarked[hDBEvent];
  BookmarkServer[GetSearchItem(hg.Selected).Contact.Handle].Bookmarked[hDBEvent] := val;
end;

procedure TfmGlobalSearch.hgInlinePopup(Sender: TObject);
begin
  InlineCopy.Enabled := hg.InlineRichEdit.SelLength > 0;
  InlineReplyQuoted.Enabled := InlineCopy.Enabled;
  InlineTextFormatting.Checked := GridOptions.TextFormatting;
  if hg.Selected <> -1 then
  begin
    InlineSendMessage.Visible := (GetSearchItem(hg.Selected).Contact.Handle <> 0);
    InlineReplyQuoted.Visible := (GetSearchItem(hg.Selected).Contact.Handle <> 0);
  end;
  pmInline.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
end;

procedure TfmGlobalSearch.InlineCopyClick(Sender: TObject);
begin
  if hg.InlineRichEdit.SelLength = 0 then
    exit;
  hg.InlineRichEdit.CopyToClipboard;
end;

procedure TfmGlobalSearch.InlineCopyAllClick(Sender: TObject);
var
  cr: TCharRange;
begin
  hg.InlineRichEdit.Lines.BeginUpdate;
  hg.InlineRichEdit.Perform(EM_EXGETSEL, 0, LParam(@cr));
  hg.InlineRichEdit.SelectAll;
  hg.InlineRichEdit.CopyToClipboard;
  hg.InlineRichEdit.Perform(EM_EXSETSEL, 0, LParam(@cr));
  hg.InlineRichEdit.Lines.EndUpdate;
end;

procedure TfmGlobalSearch.InlineSelectAllClick(Sender: TObject);
begin
  hg.InlineRichEdit.SelectAll;
end;

procedure TfmGlobalSearch.InlineTextFormattingClick(Sender: TObject);
begin
  GridOptions.TextFormatting := not GridOptions.TextFormatting;
end;

procedure TfmGlobalSearch.InlineReplyQuotedClick(Sender: TObject);
begin
  if hg.Selected <> -1 then
  begin
    if GetSearchItem(hg.Selected).Contact.Handle = 0 then
      exit;
    if hg.InlineRichEdit.SelLength = 0 then
      exit;
    SendMessageTo(GetSearchItem(hg.Selected).Contact.Handle,
      hg.FormatSelected(GridOptions.ReplyQuotedTextFormat));
  end;
end;

procedure TfmGlobalSearch.hgInlineKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if IsFormShortCut([mmAcc, pmInline], Key, Shift) then
  begin
    Key := 0;
    exit;
  end;
end;

procedure TfmGlobalSearch.OpenLinkClick(Sender: TObject);
begin
  if SavedLinkUrl = '' then
    exit;
  OpenUrl(SavedLinkUrl, False);
  SavedLinkUrl := '';
end;

procedure TfmGlobalSearch.OpenLinkNWClick(Sender: TObject);
begin
  if SavedLinkUrl = '' then
    exit;
  OpenUrl(SavedLinkUrl, True);
  SavedLinkUrl := '';
end;

procedure TfmGlobalSearch.CopyLinkClick(Sender: TObject);
begin
  if SavedLinkUrl = '' then
    exit;
  CopyToClip(SavedLinkUrl, Handle, CP_ACP);
  SavedLinkUrl := '';
end;

procedure TfmGlobalSearch.ToggleMainMenu(Enabled: Boolean);
begin
  if Enabled then
  begin
    ToolBar.EdgeBorders := [ebTop];
    Menu := mmAcc
  end
  else
  begin
    ToolBar.EdgeBorders := [];
    Menu := nil;
  end;
end;

procedure TfmGlobalSearch.WMSysColorChange(var Message: TMessage);
begin
  inherited;
  LoadToolbarIcons;
  LoadButtonIcons;
  Repaint;
end;

procedure TfmGlobalSearch.tbBookmarksClick(Sender: TObject);
begin
  if Sender <> tbBookmarks then
    tbBookmarks.Down := not tbBookmarks.Down;
  IsBookmarksMode := tbBookmarks.Down;

  if IsSearching then
    StopSearching;

  paSearch.Visible := not IsBookmarksMode;
  tbAdvanced.Enabled := not IsBookmarksMode;
  ToggleAdvancedPanel(tbAdvanced.Down);
  tbRange.Enabled := not IsBookmarksMode;
  ToggleRangePanel(tbRange.Down);
  tbEvents.Enabled := not IsBookmarksMode;
  ToggleEventsPanel(tbEvents.Down);

  if IsBookmarksMode then
    bnSearch.Click
  else if edSearch.CanFocus then
    edSearch.SetFocus;
end;

procedure TfmGlobalSearch.SelectAll1Click(Sender: TObject);
begin
  hg.SelectAll;
end;

procedure TfmGlobalSearch.OpenFileFolderClick(Sender: TObject);
begin
  if SavedFileDir = '' then
    exit;
  ShellExecuteW(0, 'open', PWideChar(SavedFileDir), nil, nil, SW_SHOW);
  SavedFileDir := '';
end;

procedure TfmGlobalSearch.BrowseReceivedFilesClick(Sender: TObject);
var
  Path: Array [0 .. MAX_PATH] of AnsiChar;
  hContact: THandle;
begin
  hContact := GetSearchItem(hg.Selected).Contact.Handle;
  CallService(MS_FILE_GETRECEIVEDFILESFOLDER, hContact, LParam(@Path));
  ShellExecuteA(0, 'open', Path, nil, nil, SW_SHOW);
end;

procedure TfmGlobalSearch.tbEventsClick(Sender: TObject);
begin
  if Sender <> tbEvents then
    tbEvents.Down := not tbEvents.Down;
  ToggleEventsPanel(tbEvents.Down);
end;

procedure TfmGlobalSearch.sbEventsCloseClick(Sender: TObject);
begin
  ToggleEventsPanel(False);
end;

procedure TfmGlobalSearch.lvContactsDblClick(Sender: TObject);
var
  hContact: THandle;
begin
  if lvContacts.Selected = nil then
    exit;
  hContact := THandle(lvContacts.Selected.Data);
  if hContact = 0 then
    exit;
  SendMessageTo(hContact);
end;

procedure TfmGlobalSearch.hgChar(Sender: TObject; var achar: WideChar; Shift: TShiftState);
var
  Mes: TWMChar;
begin
  edFilter.SetFocus;
  edFilter.SelStart := Length(edFilter.Text);
  edFilter.SelLength := 0;
  // edFilter.Text := AnsiChar;
  ZeroMemory(@Mes, SizeOf(Mes));
  Mes.Msg := WM_CHAR;
  Mes.CharCode := Word(achar);
  Mes.KeyData := ShiftStateToKeyData(Shift);
  edFilter.Perform(WM_CHAR, TMessage(Mes).wParam, TMessage(Mes).LParam);
  achar := #0;
end;

procedure TfmGlobalSearch.edFilterKeyPress(Sender: TObject; var Key: Char);
begin
  // to prevent ** BLING ** when press Enter
  // to prevent ** BLING ** when press Tab
  // to prevent ** BLING ** when press Esc
  if Ord(Key) in [VK_RETURN, VK_TAB, VK_ESCAPE] then
    Key := #0;
end;

initialization

  fmGlobalSearch := nil;

end.