(*
    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
*)

unit HistoryControls;

{$define THEME_7_UP}

interface

uses
  Windows, Messages, Classes, Forms, UITypes,
  Controls, StdCtrls, ComCtrls, ExtCtrls, Buttons, Graphics;

type

  THppEdit = class(TEdit)
  private
    procedure WMChar(var Message: TWMKey); message WM_CHAR;
  end;

  THppToolBar = class(TToolBar)
  private
    procedure AddToolButtonStyle(const Control: TControl; var Style: Byte);
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

  THppToolButton = class(TToolButton)
  private
    FWholeDropDown: Boolean; // ignored unless Style = tbsDropDown is set
    procedure SetWholeDropDown(const Value: Boolean);
  published
    property WholeDropDown: Boolean read FWholeDropDown write SetWholeDropDown default False;
  end;

  THppSpeedButton = class(TSpeedButton)
  protected
    procedure Paint{Button}; override;
  end;

  THppGroupBox = class(TGroupBox)
  protected
    procedure Paint; override;
  end;

  THppForm = class(TForm)
  private
    FIconBig: TIcon;
    function IsIconBigStored: Boolean;
    procedure IconChanged(Sender: TObject);
    procedure SetIcons(hIcon: HICON; hIconBig: HICON);
    procedure SetIconBig(Value: TIcon);
    procedure CMIconChanged(var Message: TMessage); message CM_ICONCHANGED;
  protected
    procedure CreateWnd; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property IconBig: TIcon read FIconBig write SetIconBig stored IsIconBigStored;
  end;

  { //Saved for probably future use
  THppSaveDialog = class(TSaveDialog)
  private
    FShowModal: Boolean;
  public
    constructor Create(AOwner: TComponent); override;
  protected
    function TaskModalDialog(DialogFunc: Pointer; var DialogData): Bool; override;
  published
    property ShowModal: Boolean read FShowModal write FShowModal;
  end;
  }

implementation

uses CommCtrl, Themes, UxTheme, SysUtils, hpp_global;

{ THppEdit }

function IsWordSeparator(WC: WideChar): Boolean;
begin
  Result := (WC = WideChar(#0)) or IsWideCharSpace(WC) or IsWideCharPunct(WC);
end;

procedure THppEdit.WMChar(var Message: TWMKey);
var
  ss,sl: integer;
  txt: String;
  lastWS: Boolean;
  currentWS: Boolean;
begin
  // Ctrl+Backspace workaround
  if (Message.CharCode = 127) and (KeyDataToShiftState(Message.KeyData) = [ssCtrl]) then
  begin
    Message.Result := 0;
    Perform(EM_GETSEL,wParam(@ss),lParam(@sl));
    if (ss = 0) or (ss <> sl) then exit;
    sl := 0;
    txt := Text;
    lastWS := IsWordSeparator(txt[ss]);
    while ss > 0 do
    begin
      currentWS := IsWordSeparator(txt[ss]);
      if not lastWS and currentWS then break;
      lastWS := currentWS;
      Dec(ss);
      Inc(sl);
    end;
    Delete(txt,ss+1,sl);
    Text := txt;
    Perform(EM_SETSEL,wParam(@ss),lParam(@ss));
  end
  else
    inherited;
end;

{ THppToolBar }

procedure THppToolBar.AddToolButtonStyle(const Control: TControl; var Style: Byte);
const
  BTNS_WHOLEDROPDOWN = $0080;
  WholeDropDownStyles: array[Boolean] of DWORD = (0, BTNS_WHOLEDROPDOWN);
begin
  if Control.InheritsFrom(THppToolButton) and
    (GetComCtlVersion >= ComCtlVersionIE5) then
      Style := Style or WholeDropDownStyles[THppToolButton(Control).WholeDropDown];
end;

procedure THppToolBar.WndProc(var Message: TMessage);
var
  BT: PTBButton;
  BI: PTBButtonInfoW;
begin
  case Message.Msg of
    TB_INSERTBUTTON: begin
      BT := PTBButton(Message.LParam);
      AddToolButtonStyle(TControl(BT.dwData), BT.fsStyle);
    end;
    TB_SETBUTTONINFO: begin
      BI := PTBButtonInfoW(Message.LParam);
      AddToolButtonStyle(TControl(BI.lParam), BI.fsStyle);
    end;
  end;
  inherited;
end;

{ THppToolButton }

// Note: ignored unless Style = tbsDropDown is set
procedure THppToolButton.SetWholeDropDown(const Value: Boolean);
begin
  if FWholeDropDown = Value then exit;
  FWholeDropDown := Value;
  RefreshControl;
  // Trick: resize tool buttons.
  // TODO: refresh only when theme is loaded.
  if Assigned(FToolBar) then FToolBar.Invalidate;
  Width := 1;
end;

{ THppSpeedButton }

type
  EAbortPaint = class(EAbort);

// hack to prepaint non transparent sppedbuttons with themed
// parent control, such as doublebuffered toolbar.
// VCL bug.
procedure THppSpeedButton.Paint{Button};
begin
  {$IFDEF THEME_7_UP}
  with StyleServices do
    if not Transparent and ThemesEnabled and Assigned(Parent) then
      DrawParentBackground(Parent.Handle, Canvas.Handle, nil, True);
  {$ENDIF}
  inherited;
end;


{ THppGroupBox }

procedure THppGroupBox.Paint;
var
  spCaption: String;

  {$IFDEF THEME_7_UP}
  procedure PaintThemedGroupBox;
  var
    CaptionRect: TRect;
    OuterRect: TRect;
    Box: TThemedButton;
    Details: TThemedElementDetails;
  begin
    if Enabled then
      Box := tbGroupBoxNormal
    else
      Box := tbGroupBoxDisabled;
    Details := StyleServices.GetElementDetails(Box);
    with Canvas do
    begin
      if spCaption <> '' then
      begin
        with Details do
          UxTheme.GetThemeTextExtent(StyleServices.Theme[Element],Handle,
            Part,State,PChar(spCaption),Length(spCaption),DT_LEFT, nil,CaptionRect);
        if not UseRightToLeftAlignment then
          OffsetRect(CaptionRect, 8, 0)
        else
          OffsetRect(CaptionRect, Width - 8 - CaptionRect.Right, 0);
      end
      else
        CaptionRect := Rect(0, 0, 0, 0);

      OuterRect := ClientRect;
      OuterRect.Top := (CaptionRect.Bottom - CaptionRect.Top) div 2;
      with CaptionRect do
        ExcludeClipRect(Handle, Left, Top, Right, Bottom);
      StyleServices.DrawElement(Handle, Details, OuterRect);

      SelectClipRgn(Handle, 0);
      if Caption <> '' then
        StyleServices.DrawText(Handle, Details, spCaption, CaptionRect, DT_LEFT, 0);
    end;
  end;
  {$ENDIF}

  procedure PaintGroupBox;
  var
    H: Integer;
    R: TRect;
    Flags: Longint;
  begin
    with Canvas do
    begin
      H := Canvas.TextExtent('0').cY;
      R := Rect(0, H div 2 - 1, Width, Height);
      if Ctl3D then
      begin
        Inc(R.Left);
        Inc(R.Top);
        Brush.Color := clBtnHighlight;
        FrameRect(R);
        OffsetRect(R, -1, -1);
        Brush.Color := clBtnShadow;
      end
      else
        Brush.Color := clWindowFrame;
      FrameRect(R);
      if spCaption <> '' then
      begin
        if not UseRightToLeftAlignment then
          R := Rect(8, 0, 0, H)
        else
          R := Rect(R.Right - Canvas.TextExtent(spCaption).cX - 8, 0, 0, H);
        Flags := DrawTextBiDiModeFlags(DT_SINGLELINE);
        DrawTextW(Handle, PChar(spCaption), Length(spCaption), R, Flags or DT_CALCRECT);
        Brush.Color := Color;
        DrawTextW(Handle, PChar(spCaption), Length(spCaption), R, Flags);
      end;
    end;
  end;

begin
  spCaption := Caption;
  if spCaption <> '' then
    spCaption := ' '+spCaption+' ';
  Canvas.Font := Self.Font;
  {$IFDEF THEME_7_UP}
  if StyleServices.ThemesEnabled then
    PaintThemedGroupBox
  else
  {$ENDIF}
  PaintGroupBox;
end;

{ THppForm }

function THppForm.IsIconBigStored: Boolean;
begin
  Result := (not IsControl) and (FIconBig.Handle <> 0);
end;

procedure THppForm.SetIcons(hIcon: HICON; hIconBig: HICON);
begin
  if NewStyleControls then
  begin
    if HandleAllocated and (BorderStyle <> bsDialog) then
    begin
      SendMessage(Handle, WM_SETICON, ICON_SMALL, hIcon);
      SendMessage(Handle, WM_SETICON, ICON_BIG, hIconBig);
    end;
  end
  else
    if IsIconic(Handle) then Invalidate;
end;

procedure THppForm.IconChanged(Sender: TObject);
begin
  if FIconBig.Handle = 0 then
    SetIcons(0, Icon.Handle)
  else
    SetIcons(Icon.Handle, FIconBig.Handle);
end;

procedure THppForm.SetIconBig(Value: TIcon);
begin
  FIconBig.Assign(Value);
end;

procedure THppForm.CMIconChanged(var Message: TMessage);
begin
  if (Icon.Handle = 0) or (FIconBig.Handle = 0) then
    IconChanged(nil);
end;

procedure THppForm.CreateWnd;
begin
  inherited CreateWnd;
  if NewStyleControls then
    if BorderStyle <> bsDialog then
      IconChanged(nil)
    else
      SetIcons(0, 0);
end;

constructor THppForm.Create(AOwner: TComponent);
begin
  FIconBig := TIcon.Create;
  FIconBig.Width := GetSystemMetrics(SM_CXICON);
  FIconBig.Height := GetSystemMetrics(SM_CYICON);
  FIconBig.OnChange := IconChanged;
  inherited Create(AOwner);
  Icon.OnChange := IconChanged;
end;

destructor THppForm.Destroy;
begin
  inherited Destroy;
  FIconBig.Free;
end;

{ THppSaveDialog }
{ //Saved for probably future use

type
  THackCommonDialog = class(TComponent)
  protected
    FCtl3D: Boolean;
    FDefWndProc: Pointer;
    FHelpContext: THelpContext;
    FHandle: HWnd;
    FObjectInstance: Pointer;
    FTemplate: PAnsiChar;
  end;
var
  sCreationControl: TCommonDialog = nil;

procedure CenterWindow(Wnd: HWnd);
var
  Rect: TRect;
  Monitor: TMonitor;
begin
  GetWindowRect(Wnd, Rect);
  if Application.MainForm <> nil then
  begin
    if Assigned(Screen.ActiveForm) then
      Monitor := Screen.ActiveForm.Monitor
      else
        Monitor := Application.MainForm.Monitor;
  end
  else
    Monitor := Screen.Monitors[0];
  SetWindowPos(Wnd, 0,
    Monitor.Left + ((Monitor.Width - Rect.Right + Rect.Left) div 2),
    Monitor.Top + ((Monitor.Height - Rect.Bottom + Rect.Top) div 3),
    0, 0, SWP_NOACTIVATE or SWP_NOSIZE or SWP_NOZORDER);
end;

function DialogHook(Wnd: HWnd; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall;
begin
  Result := 0;
  if Msg = WM_INITDIALOG then
  begin
    CenterWindow(Wnd);
    THackCommonDialog(sCreationControl).FHandle := Wnd;
    THackCommonDialog(sCreationControl).FDefWndProc := Pointer(SetWindowLongPtr(Wnd, GWL_WNDPROC,
      Longint(THackCommonDialog(sCreationControl).FObjectInstance)));
    CallWindowProc(THackCommonDialog(sCreationControl).FObjectInstance, Wnd, Msg, WParam, LParam);
    sCreationControl := nil;
  end;
end;

function ExplorerHook(Wnd: HWnd; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall;
begin
  Result := 0;
  if Msg = WM_INITDIALOG then
  begin
    THackCommonDialog(sCreationControl).FHandle := Wnd;
    THackCommonDialog(sCreationControl).FDefWndProc := Pointer(SetWindowLongPtr(Wnd, GWL_WNDPROC,
      Longint(THackCommonDialog(sCreationControl).FObjectInstance)));
    CallWindowProc(THackCommonDialog(sCreationControl).FObjectInstance, Wnd, Msg, WParam, LParam);
    sCreationControl := nil;
  end
  else if (Msg = WM_NOTIFY) and (POFNotify(LParam)^.hdr.code = CDN_INITDONE) then
    CenterWindow(GetWindowLongPtr(Wnd, GWLP_HWNDPARENT));
end;

constructor THppSaveDialog.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FShowModal := False;
end;

function THppSaveDialog.TaskModalDialog(DialogFunc: Pointer; var DialogData): Bool;
type
  TDialogFunc = function(var DialogData): Bool stdcall;
var
  ActiveWindow: HWnd;
  FPUControlWord: Word;
  FocusState: TFocusState;
  WasEnabled: Boolean;
begin
  if FShowModal then
    Result := inherited TaskModalDialog(DialogFunc,DialogData)
  else begin
    if (ofOldStyleDialog in Options) or not NewStyleControls then
      TOpenFilename(DialogData).lpfnHook := DialogHook else
      TOpenFilename(DialogData).lpfnHook := ExplorerHook;
    ActiveWindow := GetActiveWindow;
    WasEnabled := IsWindowEnabled(ActiveWindow);
    if WasEnabled then EnableWindow(ActiveWindow, False);
    FocusState := SaveFocusState;
    try
      Application.HookMainWindow(MessageHook);
      asm
        // Avoid FPU control word change in NETRAP.dll, NETAPI32.dll, etc
        FNSTCW  FPUControlWord
      end;
      try
        sCreationControl := Self;
        Result := TDialogFunc(DialogFunc)(DialogData);
      finally
        asm
          FNCLEX
          FLDCW FPUControlWord
        end;
        Application.UnhookMainWindow(MessageHook);
      end;
    finally
      if WasEnabled then EnableWindow(ActiveWindow, True);
      SetActiveWindow(ActiveWindow);
      RestoreFocusState(FocusState);
    end;
  end;
end;}

end.