unit ThemeMgrDB;

//----------------------------------------------------------------------------------------------------------------------
// This unit is belongs to the Soft Gems Theme Manager package.
// Windows XP Theme Manager is freeware. You may freely use it in any software, including commercial software, provided
// you accept the following conditions:
//
// 1) The software may not be included into component collections and similar compilations which are sold. If you want
//    to distribute this software for money then contact me first and ask for my permission.
// 2) My copyright notices in the source code may not be removed or modified.
// 3) If you modify and/or distribute the code to any third party then you must not veil the original author. It must
//    always be clearly identifiable that I, Mike Lischke, am the original author.
// Although it is not required it would be a nice move to recognize my work by adding a citation to the application's
// about box or a similar place.
//
// The original code is ThemeMgrDB.pas, released 03. February 2002.
//
// The initial developer of the original code is:
//   Mike Lischke (public@soft-gems.net, www.soft-gems.net).
//
// Portions created by Mike Lischke are
// (C) 2001-2005 Mike Lischke. All Rights Reserved.
//----------------------------------------------------------------------------------------------------------------------
//
// This unit contains the implementation of TThemeManagerDB, which is an enhancement of TThemeManager to fix
// DB controls related XP painting problems. Since this requires to take in many DB related units (which might not be
// desirable in most applications) it is kept as a separate unit.
//
// Thanks to Bert Moorthaemer, who greatly helped me to get started here.
//----------------------------------------------------------------------------------------------------------------------
// For version information and history see help file.
//----------------------------------------------------------------------------------------------------------------------

interface

uses
  Windows, Sysutils, Messages, Classes, Controls, Graphics, ThemeMgr;

type
  TThemeManagerDB = class(TThemeManager)
  private
    FDBLookupControlList: TWindowProcList;
    procedure DBLookupControlWindowProc(Control: TControl; var Message : TMessage);

    procedure PreDBLookupControlWindowProc(var Message : TMessage);
  protected
    procedure HandleControlChange(Control: TControl; Inserting: Boolean); override;
    function NeedsBorderPaint(Control: TControl): Boolean; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

//----------------------------------------------------------------------------------------------------------------------

implementation

uses
  ThemeSrv;

type
  PInteger = ^Integer;
  PBoolean = ^Boolean;
  PAlignment = ^TAlignment;

//----------------- TThemeManagerDB --------------------------------------------------------------------------------------

constructor TThemeManagerDB.Create(AOwner: TComponent);

begin
  inherited;

  FDBLookupControlList := TWindowProcList.Create(Self, PreDBLookupControlWindowProc, TDBLookupControl);

  // If the current main manager is not a DB manager then force this instance to become the main manager.
  if not (CurrentThemeManager is TThemeManagerDB) then
    ForceAsMainManager;
end;

//----------------------------------------------------------------------------------------------------------------------

destructor TThemeManagerDB.Destroy;

begin
  FDBLookupControlList.Free;

  inherited;
end;

//----------------------------------------------------------------------------------------------------------------------

type
  // In order to have access to some private variables, which are otherwise not reachable, we (partially) redeclare
  // the combo box declaration. This maps directly to the actual declaration.
  // Fortunately, private variables declaration in TDBLookupComboBoxCast hasn't changed since Delphi 4. However
  // this must be checked with future Delphi versions (if subclassing for themes is then still needed).
  {$Hints off}
  TDBLookupComboBoxCast = class(TDBLookupControl)
  private
    FDataList: TPopupDataList;
    FButtonWidth: Integer;
    FText: string;
    FDropDownRows: Integer;
    FDropDownWidth: Integer;
    FDropDownAlign: TDropDownAlign;
    FListVisible: Boolean;
    FPressed: Boolean;
    FTracking: Boolean;
    FAlignment: TAlignment;
    FLookupMode: Boolean;
  end;
  {$Hints on}

procedure TThemeManagerDB.DBLookupControlWindowProc(Control: TControl; var Message : TMessage);

  //--------------- local function --------------------------------------------

  procedure PaintComboBox(DC: HDC);

  var
    W, X : Integer;
    S: string;
    AAlignment: TAlignment;
    Selected: Boolean;
    R: TRect;
    State : TThemedComboBox;
    P: TPoint;
    Hot: Boolean;
    Details: TThemedElementDetails;

  begin
    with TDBLookupComboBoxCast(Control) do
    begin
      Canvas.Font := Font;
      Canvas.Brush.Color := Color;
      Selected := HasFocus and not FListVisible and not (csPaintCopy in ControlState);
      GetCursorPos(P);
      Hot := (WindowFromPoint(P) = Handle) and not FListVisible;
      if Enabled then
        Canvas.Font.Color := Font.Color
      else
        Canvas.Font.Color := clGrayText;
      if Selected then
      begin
        Canvas.Font.Color := clHighlightText;
        Canvas.Brush.Color := clHighlight;
      end;
      if (csPaintCopy in ControlState) and (Field <> nil) and (Field.Lookup) then
      begin
        S := Field.DisplayText;
        AAlignment := Field.Alignment;
      end
      else
      begin
        if (csDesigning in ComponentState) and (Field = nil) then
          S := Name
        else
          S := FText;
        AAlignment := FAlignment;
      end;
      if UseRightToLeftAlignment then
        ChangeBiDiModeAlignment(AAlignment);
      W := ClientWidth - FButtonWidth;
      X := 2;
      case AAlignment of
        taRightJustify:
          X := W - Canvas.TextWidth(S) - 3;
        taCenter:
          X := (W - Canvas.TextWidth(S)) div 2;
      end;
      SetRect(R, 1, 1, W - 1, ClientHeight - 1);
      if (SysLocale.MiddleEast) and (BiDiMode = bdRightToLeft) then
      begin
        Inc(X, FButtonWidth);
        Inc(R.Left, FButtonWidth);
        R.Right := ClientWidth;
      end;
      if SysLocale.MiddleEast then
        TControlCanvas(Canvas).UpdateTextFlags;
      Canvas.TextRect(R, X, 2, S);
      if Selected then
        Canvas.DrawFocusRect(R);
      SetRect(R, W, 0, ClientWidth, ClientHeight);
      if (SysLocale.MiddleEast) and (BiDiMode = bdRightToLeft) then
      begin
        R.Left := 0;
        R.Right:= FButtonWidth;
      end;
      if not ListActive then
        State := tcDropDownButtonDisabled
      else
        if FPressed then
          State := tcDropDownButtonPressed
        else
          if Hot then
            State := tcDropDownButtonHot
          else
            State := tcDropDownButtonNormal;
      Details := ThemeServices.GetElementDetails(State);
      ThemeServices.DrawElement(DC, Details, R);
    end;
  end;

  //--------------- end local function ----------------------------------------

var
  PS : PaintStruct;

begin
  if ThemeServices.ThemesEnabled and (toAllowControls in Options) then
  begin
    case Message.Msg of
      WM_NCPAINT:
        begin
          FDBLookupControlList.DispatchMessage(Control, Message);
          ThemeServices.PaintBorder(Control as TWinControl, False);
        end;
      WM_PAINT :
        if TWinControl(Control) is TDBLookupComboBox then
        begin
          BeginPaint(TWinControl(Control).Handle, PS);
          PaintComboBox(PS.hdc);
          EndPaint(TWinControl(Control).Handle, PS);
          Message.Result := 0;
        end
        else
          FDBLookupControlList.DispatchMessage(Control, Message);
      CM_MOUSEENTER,
      CM_MOUSELEAVE:
        begin
          Control.Invalidate;
          FDBLookupControlList.DispatchMessage(Control, Message);
        end;
    else
      FDBLookupControlList.DispatchMessage(Control, Message);
    end;
  end
  else
    FDBLookupControlList.DispatchMessage(Control, Message);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TThemeManagerDB.PreDBLookupControlWindowProc(var Message: TMessage);

// Read more about this code in PreAnimateWindowProc.

begin
  TThemeManagerDB(CurrentThemeManager).DBLookupControlWindowProc(TControl(Self), Message);
end;

//----------------------------------------------------------------------------------------------------------------------

procedure TThemeManagerDB.HandleControlChange(Control: TControl; Inserting: Boolean);

var
  List: TWindowProcList;

begin
  List := nil;
  // Do subclassing work only on Windows XP or higher.
  if IsWindowsXP then
  begin
    if ThemeServices.ThemesEnabled then
    begin
      if Control is TDBLookupComboBox then
      begin
        if (toSubclassDBLookup in Options) or not Inserting then
          List := FDBLookupControlList
      end;

      if Assigned(List) then
      begin
        if Inserting then
          List.Add(Control)
        else
          List.Remove(Control);
      end
      else
        inherited;
    end;
  end;
end;

//----------------------------------------------------------------------------------------------------------------------

function TThemeManagerDB.NeedsBorderPaint(Control: TControl): Boolean;

begin
  Result := inherited NeedsBorderPaint(Control) or (Control is TDBLookupControl);
end;

//----------------------------------------------------------------------------------------------------------------------

end.
