unit vcl.wwcombobutton;

interface

uses Windows, Messages, Classes, Controls, Forms, Graphics, StdCtrls,
  ExtCtrls, CommCtrl, buttons;

{$I wwIfDef.pas}
{$C PRELOAD}

type
  // TButtonLayout = (blGlyphLeft, blGlyphRight, blGlyphTop, blGlyphBottom);
  // TButtonState = (bsUp, bsDisabled, bsDown, bsExclusive);
  // TButtonStyle = (bsAutoDetect, bsWin31, bsNew);
  // TNumGlyphs = 1..4;

  TwwComboButton = class;

  TwwComboButtonActionLink = class(TControlActionLink)
  protected
    FClient: TSpeedButton;
    procedure AssignClient(AClient: TObject); override;
    function IsCheckedLinked: Boolean; override;
{$IFDEF wwDelphi6Up}
    function IsGroupIndexLinked: Boolean; override;
    procedure SetGroupIndex(Value: Integer); override;
{$ENDIF}
    procedure SetChecked(Value: Boolean); override;
  end;

  TwwComboButton = class(TGraphicControl)
  private
    FGroupIndex: Integer;
    FDown: Boolean;
    FDragging: Boolean;
    FAllowAllUp: Boolean;
    FLayout: TButtonLayout;
    FSpacing: Integer;
    FTransparent: Boolean;
    FMargin: Integer;
    FFlat: Boolean;
    FMouseInControl: Boolean;
    FEllipsis: Boolean;
    procedure GlyphChanged(Sender: TObject);
    procedure UpdateExclusive;
    function GetGlyph: TBitmap;
    procedure SetGlyph(Value: TBitmap);
    function GetNumGlyphs: TNumGlyphs;
    procedure SetNumGlyphs(Value: TNumGlyphs);
    procedure SetDown(Value: Boolean);
    procedure SetFlat(Value: Boolean);
    procedure SetAllowAllUp(Value: Boolean);
    procedure SetGroupIndex(Value: Integer);
    procedure SetLayout(Value: TButtonLayout);
    procedure SetSpacing(Value: Integer);
    procedure SetTransparent(Value: Boolean);
    procedure SetMargin(Value: Integer);
    procedure UpdateTracking;
    procedure WMLButtonDblClk(var Message: TWMLButtonDown);
      message WM_LBUTTONDBLCLK;
    procedure CMEnabledChanged(var Message: TMessage);
      message CM_ENABLEDCHANGED;
    procedure CMButtonPressed(var Message: TMessage); message CM_BUTTONPRESSED;
    procedure CMDialogChar(var Message: TCMDialogChar); message CM_DIALOGCHAR;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
    procedure CMSysColorChange(var Message: TMessage);
      message CM_SYSCOLORCHANGE;
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
  protected
    FGlyph: Pointer;
    FState: TButtonState;
    procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); override;
    function GetActionLinkClass: TControlActionLinkClass; override;
    function GetPalette: HPALETTE; override;
    procedure Loaded; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure Paint; override;
    function IsVistaComboNonEditable: Boolean; virtual;
    function IsVistaTransparentButton: Boolean; virtual;
    function ParentMouseInControl: Boolean; virtual;
    function ParentDroppedDown: Boolean; virtual;
    property MouseInControl: Boolean read FMouseInControl;
    property Ellipsis: Boolean read FEllipsis write FEllipsis;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Click; override;
  published
    property Action;
    property AllowAllUp: Boolean read FAllowAllUp write SetAllowAllUp
      default False;
    property Anchors;
    property BiDiMode;
    property Constraints;
    property GroupIndex: Integer read FGroupIndex write SetGroupIndex default 0;
    property Down: Boolean read FDown write SetDown default False;
    property Caption;
    property Enabled;
    property Flat: Boolean read FFlat write SetFlat default False;
    property Font;
    property Glyph: TBitmap read GetGlyph write SetGlyph;
    property Layout: TButtonLayout read FLayout write SetLayout
      default blGlyphLeft;
    property Margin: Integer read FMargin write SetMargin default -1;
    property NumGlyphs: TNumGlyphs read GetNumGlyphs write SetNumGlyphs
      default 1;
    property ParentFont;
    property ParentShowHint;
    property ParentBiDiMode;
    property PopupMenu;
    property ShowHint;
    property Spacing: Integer read FSpacing write SetSpacing default 4;
    property Transparent: Boolean read FTransparent write SetTransparent
      default True;
    property Visible;
    property OnClick;
    property OnDblClick;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
  end;

  TwwSpinButtonDirection = (wwsdUp, wwsdDown);

  TwwSpinControlButton = class(TwwComboButton)
  private
    FScrollDirection: TwwSpinButtonDirection;
  protected
    procedure Paint; override;
    property ScrollDirection: TwwSpinButtonDirection read FScrollDirection
      write FScrollDirection;
  end;

  TGlyphList = class(TImageList)
  private
    Used: TBits;
    FCount: Integer;
    function AllocateIndex: Integer;
  public
    constructor CreateSize(AWidth, AHeight: Integer);
    destructor Destroy; override;
    function AddMasked(Image: TBitmap; MaskColor: TColor): Integer;
    procedure Delete(Index: Integer);
    property Count: Integer read FCount;
  end;

  TwwComboButtonGlyph = class
  private
    FOriginal: TBitmap;
    FGlyphList: TGlyphList;
    FIndexs: array [TButtonState] of Integer;
    FTransparentColor: TColor;
    FNumGlyphs: TNumGlyphs;
    FOnChange: TNotifyEvent;
    FComboButton: TControl;
    procedure GlyphChanged(Sender: TObject);
    procedure SetGlyph(Value: TBitmap);
    procedure SetNumGlyphs(Value: TNumGlyphs);
    procedure Invalidate;
    function CreateButtonGlyph(State: TButtonState): Integer;
    procedure DrawButtonGlyph(Canvas: TCanvas; const GlyphPos: TPoint;
      State: TButtonState; Transparent: Boolean);
    procedure DrawButtonText(Canvas: TCanvas; const Caption: string;
      TextBounds: TRect; State: TButtonState; BiDiFlags: Longint);
    procedure CalcButtonLayout(Canvas: TCanvas; const Client: TRect;
      const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
      Margin, Spacing: Integer; var GlyphPos: TPoint; var TextBounds: TRect;
      BiDiFlags: Longint);
  public
    property ComboButton: TControl read FComboButton;

    constructor Create(AComboButton: TControl);
    destructor Destroy; override;
    { return the text rectangle }
    function Draw(Canvas: TCanvas; const Client: TRect; const Offset: TPoint;
      const Caption: string; Layout: TButtonLayout; Margin, Spacing: Integer;
      State: TButtonState; Transparent: Boolean; BiDiFlags: Longint): TRect;
    property Glyph: TBitmap read FOriginal write SetGlyph;
    property NumGlyphs: TNumGlyphs read FNumGlyphs write SetNumGlyphs;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  end;

implementation

uses Consts, SysUtils, ActnList, ImgList, wwframe, system.Types, system.UITypes,
{$IFDEF wwDelphi7Up}
  Themes,
{$ENDIF}
{$IFDEF ThemeManager}
  thememgr, themesrv, uxtheme,
{$ELSE}
  uxtheme,
{$ENDIF}
  wwcommon;

// {$R Buttons.res}

{ function GetBitBtnGlyph(Kind: TBitBtnKind): TBitmap;
  begin
  if BitBtnGlyphs[Kind] = nil then
  begin
  BitBtnGlyphs[Kind] := TBitmap.Create;
  BitBtnGlyphs[Kind].LoadFromResourceName(HInstance, BitBtnResNames[Kind]);
  end;
  Result := BitBtnGlyphs[Kind];
  end;
}
type
  TGlyphCache = class
  private
    GlyphLists: TList;
  public
    constructor Create;
    destructor Destroy; override;
    function GetList(AWidth, AHeight: Integer): TGlyphList;
    procedure ReturnList(List: TGlyphList);
    function Empty: Boolean;
  end;

  { TGlyphList }

constructor TGlyphList.CreateSize(AWidth, AHeight: Integer);
begin
  inherited CreateSize(AWidth, AHeight);
  Used := TBits.Create;
end;

destructor TGlyphList.Destroy;
begin
  Used.Free;
  inherited Destroy;
end;

function TGlyphList.AllocateIndex: Integer;
begin
  Result := Used.OpenBit;
  if Result >= Used.Size then
  begin
    Result := inherited Add(nil, nil);
    Used.Size := Result + 1;
  end;
  Used[Result] := True;
end;

function TGlyphList.AddMasked(Image: TBitmap; MaskColor: TColor): Integer;
begin
  Result := AllocateIndex;
  ReplaceMasked(Result, Image, MaskColor);
  Inc(FCount);
end;

procedure TGlyphList.Delete(Index: Integer);
begin
  if Used[Index] then
  begin
    Dec(FCount);
    Used[Index] := False;
  end;
end;

{ TGlyphCache }

constructor TGlyphCache.Create;
begin
  inherited Create;
  GlyphLists := TList.Create;
end;

destructor TGlyphCache.Destroy;
begin
  GlyphLists.Free;
  inherited Destroy;
end;

function TGlyphCache.GetList(AWidth, AHeight: Integer): TGlyphList;
var
  I: Integer;
begin
  for I := GlyphLists.Count - 1 downto 0 do
  begin
    Result := GlyphLists[I];
    with Result do
      if (AWidth = Width) and (AHeight = Height) then
        Exit;
  end;
  Result := TGlyphList.CreateSize(AWidth, AHeight);
  GlyphLists.Add(Result);
end;

procedure TGlyphCache.ReturnList(List: TGlyphList);
begin
  if List = nil then
    Exit;
  if List.Count = 0 then
  begin
    GlyphLists.Remove(List);
    List.Free;
  end;
end;

function TGlyphCache.Empty: Boolean;
begin
  Result := GlyphLists.Count = 0;
end;

var
  GlyphCache: TGlyphCache = nil;
  ButtonCount: Integer = 0;

  { TwwComboButtonGlyph }

constructor TwwComboButtonGlyph.Create(AComboButton: TControl);
var
  I: TButtonState;
begin
  inherited Create;
  FOriginal := TBitmap.Create;
  FOriginal.OnChange := GlyphChanged;
  FTransparentColor := clOlive;
  FNumGlyphs := 1;
  for I := Low(I) to High(I) do
    FIndexs[I] := -1;
  if GlyphCache = nil then
    GlyphCache := TGlyphCache.Create;
  FComboButton := AComboButton;
end;

destructor TwwComboButtonGlyph.Destroy;
begin
  FOriginal.Free;
  Invalidate;
  if Assigned(GlyphCache) and GlyphCache.Empty then
  begin
    GlyphCache.Free;
    GlyphCache := nil;
  end;
  inherited Destroy;
end;

procedure TwwComboButtonGlyph.Invalidate;
var
  I: TButtonState;
begin
  for I := Low(I) to High(I) do
  begin
    if FIndexs[I] <> -1 then
      FGlyphList.Delete(FIndexs[I]);
    FIndexs[I] := -1;
  end;
  GlyphCache.ReturnList(FGlyphList);
  FGlyphList := nil;
end;

procedure TwwComboButtonGlyph.GlyphChanged(Sender: TObject);
begin
  if Sender = FOriginal then
  begin
    FTransparentColor := FOriginal.TransparentColor;
    Invalidate;
    if Assigned(FOnChange) then
      FOnChange(Self);
  end;
end;

procedure TwwComboButtonGlyph.SetGlyph(Value: TBitmap);
var
  Glyphs: Integer;
begin
  Invalidate;
  FOriginal.Assign(Value);
  if (Value <> nil) and (Value.Height > 0) then
  begin
    FTransparentColor := Value.TransparentColor;
    if Value.Width mod Value.Height = 0 then
    begin
      Glyphs := Value.Width div Value.Height;
      if Glyphs > 4 then
        Glyphs := 1;
      SetNumGlyphs(Glyphs);
    end;
  end;
end;

procedure TwwComboButtonGlyph.SetNumGlyphs(Value: TNumGlyphs);
begin
  if (Value <> FNumGlyphs) and (Value > 0) then
  begin
    Invalidate;
    FNumGlyphs := Value;
    GlyphChanged(Glyph);
  end;
end;

function TwwComboButtonGlyph.CreateButtonGlyph(State: TButtonState): Integer;
const
  ROP_DSPDxax = $00E20746;
var
  TmpImage, DDB, MonoBmp: TBitmap;
  IWidth, IHeight: Integer;
  IRect, ORect: TRect;
  I: TButtonState;
  DestDC: HDC;
begin
  if (State = bsDown) and (NumGlyphs < 3) then
    State := bsUp;
  Result := FIndexs[State];
  if Result <> -1 then
    Exit;
  if (FOriginal.Width or FOriginal.Height) = 0 then
    Exit;
  IWidth := FOriginal.Width div FNumGlyphs;
  IHeight := FOriginal.Height;
  if FGlyphList = nil then
  begin
    if GlyphCache = nil then
      GlyphCache := TGlyphCache.Create;
    FGlyphList := GlyphCache.GetList(IWidth, IHeight);
  end;
  TmpImage := TBitmap.Create;
  try
    TmpImage.Width := IWidth;
    TmpImage.Height := IHeight;
    IRect := Rect(0, 0, IWidth, IHeight);
    TmpImage.Canvas.Brush.Color := clBtnFace;
    TmpImage.Palette := CopyPalette(FOriginal.Palette);
    I := State;
    if Ord(I) >= NumGlyphs then
      I := bsUp;
    ORect := Rect(Ord(I) * IWidth, 0, (Ord(I) + 1) * IWidth, IHeight);
    case State of
      bsUp, bsDown, bsExclusive:
        begin
          TmpImage.Canvas.CopyRect(IRect, FOriginal.Canvas, ORect);
          if FOriginal.TransparentMode = tmFixed then
            FIndexs[State] := FGlyphList.AddMasked(TmpImage, FTransparentColor)
          else
            FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault);
        end;
      bsDisabled:
        begin
          MonoBmp := nil;
          DDB := nil;
          try
            MonoBmp := TBitmap.Create;
            DDB := TBitmap.Create;
            DDB.Assign(FOriginal);
            DDB.HandleType := bmDDB;
            if NumGlyphs > 1 then
              with TmpImage.Canvas do
              begin { Change white & gray to clBtnHighlight and clBtnShadow }
                CopyRect(IRect, DDB.Canvas, ORect);
                MonoBmp.Monochrome := True;
                MonoBmp.Width := IWidth;
                MonoBmp.Height := IHeight;

                { Convert white to clBtnHighlight }
                DDB.Canvas.Brush.Color := clWhite;
                MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect);
                Brush.Color := clBtnHighlight;
                DestDC := Handle;
                SetTextColor(DestDC, clBlack);
                SetBkColor(DestDC, clWhite);
                BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0,
                  0, ROP_DSPDxax);

                { Convert gray to clBtnShadow }
                DDB.Canvas.Brush.Color := clGray;
                MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect);
                Brush.Color := clBtnShadow;
                DestDC := Handle;
                SetTextColor(DestDC, clBlack);
                SetBkColor(DestDC, clWhite);
                BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0,
                  0, ROP_DSPDxax);

                { Convert transparent color to clBtnFace }
                DDB.Canvas.Brush.Color := ColorToRGB(FTransparentColor);
                MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect);
                Brush.Color := clBtnFace;
                DestDC := Handle;
                SetTextColor(DestDC, clBlack);
                SetBkColor(DestDC, clWhite);
                BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0,
                  0, ROP_DSPDxax);
              end
            else
            begin
              { Create a disabled version }
              with MonoBmp do
              begin
                Assign(FOriginal);
                HandleType := bmDDB;
                Canvas.Brush.Color := clBlack;
                Width := IWidth;
                if Monochrome then
                begin
                  Canvas.Font.Color := clWhite;
                  Monochrome := False;
                  Canvas.Brush.Color := clWhite;
                end;
                Monochrome := True;
              end;
              with TmpImage.Canvas do
              begin
                Brush.Color := clBtnFace;
                FillRect(IRect);
                Brush.Color := clBtnHighlight;
                SetTextColor(Handle, clBlack);
                SetBkColor(Handle, clWhite);
                BitBlt(Handle, 1, 1, IWidth, IHeight, MonoBmp.Canvas.Handle, 0,
                  0, ROP_DSPDxax);
                Brush.Color := clBtnShadow;
                SetTextColor(Handle, clBlack);
                SetBkColor(Handle, clWhite);
                BitBlt(Handle, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0,
                  0, ROP_DSPDxax);
              end;
            end;
          finally
            DDB.Free;
            MonoBmp.Free;
          end;
          FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault);
        end;
    end;
  finally
    TmpImage.Free;
  end;
  Result := FIndexs[State];
  FOriginal.Dormant;
end;

procedure TwwComboButtonGlyph.DrawButtonGlyph(Canvas: TCanvas;
  const GlyphPos: TPoint; State: TButtonState; Transparent: Boolean);
var
  Index: Integer;
{$IFDEF wwUseThemeManager}
  Details: TThemedElementDetails;
  R: TRect;
  Button: TThemedButton;
{$ENDIF}
begin
  if FOriginal = nil then
    Exit;
  if (FOriginal.Width = 0) or (FOriginal.Height = 0) then
    Exit;
  Index := CreateButtonGlyph(State);
  with GlyphPos do
  begin
    if wwUseThemes(ComboButton) then
    begin
{$IFDEF wwUseThemeManager}
      R.TopLeft := GlyphPos;
      R.Right := R.Left + FOriginal.Width div FNumGlyphs;
      R.Bottom := R.Top + FOriginal.Height;
      case State of
        bsDisabled:
          Button := tbPushButtonDisabled;
        bsDown, bsExclusive:
          Button := tbPushButtonPressed;
      else
        // bsUp
        Button := tbPushButtonNormal;
      end;
      Details := StyleServices.GetElementDetails(Button);
      StyleServices.DrawIcon(Canvas.Handle, Details, R,
        FGlyphList.Handle, Index);
{$ENDIF}
    end
    else if Transparent or (State = bsExclusive) then
    begin
      ImageList_DrawEx(FGlyphList.Handle, Index, Canvas.Handle, X, Y, 0, 0,
        clNone, clNone, ILD_Transparent)
    end
    else
      ImageList_DrawEx(FGlyphList.Handle, Index, Canvas.Handle, X, Y, 0, 0,
        ColorToRGB(clBtnFace), clNone, ILD_Normal);
  end;
end;

procedure TwwComboButtonGlyph.DrawButtonText(Canvas: TCanvas;
  const Caption: string; TextBounds: TRect; State: TButtonState;
  BiDiFlags: Longint);
begin
  with Canvas do
  begin
    Brush.Style := bsClear;
    if State = bsDisabled then
    begin
      OffsetRect(TextBounds, 1, 1);
      Font.Color := clBtnHighlight;
      DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
        DT_CENTER or DT_VCENTER or BiDiFlags);
      OffsetRect(TextBounds, -1, -1);
      Font.Color := clBtnShadow;
      DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
        DT_CENTER or DT_VCENTER or BiDiFlags);
    end
    else
      DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
        DT_CENTER or DT_VCENTER or BiDiFlags);
  end;
end;

procedure TwwComboButtonGlyph.CalcButtonLayout(Canvas: TCanvas;
  const Client: TRect; const Offset: TPoint; const Caption: string;
  Layout: TButtonLayout; Margin, Spacing: Integer; var GlyphPos: TPoint;
  var TextBounds: TRect; BiDiFlags: Longint);
var
  TextPos: TPoint;
  ClientSize, GlyphSize, TextSize: TPoint;
  TotalSize: TPoint;
begin
  if (BiDiFlags and DT_RIGHT) = DT_RIGHT then
    if Layout = blGlyphLeft then
      Layout := blGlyphRight
    else if Layout = blGlyphRight then
      Layout := blGlyphLeft;
  { calculate the item sizes }
  ClientSize := Point(Client.Right - Client.Left, Client.Bottom - Client.Top);

  if FOriginal <> nil then
    GlyphSize := Point(FOriginal.Width div FNumGlyphs, FOriginal.Height)
  else
    GlyphSize := Point(0, 0);

  if Length(Caption) > 0 then
  begin
    TextBounds := Rect(0, 0, Client.Right - Client.Left, 0);
    DrawText(Canvas.Handle, PChar(Caption), Length(Caption), TextBounds,
      DT_CALCRECT or BiDiFlags);
    TextSize := Point(TextBounds.Right - TextBounds.Left,
      TextBounds.Bottom - TextBounds.Top);
  end
  else
  begin
    TextBounds := Rect(0, 0, 0, 0);
    TextSize := Point(0, 0);
  end;

  { If the layout has the glyph on the right or the left, then both the
    text and the glyph are centered vertically.  If the glyph is on the top
    or the bottom, then both the text and the glyph are centered horizontally. }
  if Layout in [blGlyphLeft, blGlyphRight] then
  begin
    GlyphPos.Y := (ClientSize.Y - GlyphSize.Y + 1) div 2;
    TextPos.Y := (ClientSize.Y - TextSize.Y + 1) div 2;
  end
  else
  begin
    GlyphPos.X := (ClientSize.X - GlyphSize.X + 1) div 2;
    TextPos.X := (ClientSize.X - TextSize.X + 1) div 2;
  end;

  { if there is no text or no bitmap, then Spacing is irrelevant }
  if (TextSize.X = 0) or (GlyphSize.X = 0) then
    Spacing := 0;

  { adjust Margin and Spacing }
  if Margin = -1 then
  begin
    if Spacing = -1 then
    begin
      TotalSize := Point(GlyphSize.X + TextSize.X, GlyphSize.Y + TextSize.Y);
      if Layout in [blGlyphLeft, blGlyphRight] then
        Margin := (ClientSize.X - TotalSize.X) div 3
      else
        Margin := (ClientSize.Y - TotalSize.Y) div 3;
      Spacing := Margin;
    end
    else
    begin
      TotalSize := Point(GlyphSize.X + Spacing + TextSize.X,
        GlyphSize.Y + Spacing + TextSize.Y);
      if Layout in [blGlyphLeft, blGlyphRight] then
        Margin := (ClientSize.X - TotalSize.X + 1) div 2
      else
        Margin := (ClientSize.Y - TotalSize.Y + 1) div 2;
    end;
  end
  else
  begin
    if Spacing = -1 then
    begin
      TotalSize := Point(ClientSize.X - (Margin + GlyphSize.X),
        ClientSize.Y - (Margin + GlyphSize.Y));
      if Layout in [blGlyphLeft, blGlyphRight] then
        Spacing := (TotalSize.X - TextSize.X) div 2
      else
        Spacing := (TotalSize.Y - TextSize.Y) div 2;
    end;
  end;

  case Layout of
    blGlyphLeft:
      begin
        GlyphPos.X := Margin;
        TextPos.X := GlyphPos.X + GlyphSize.X + Spacing;
      end;
    blGlyphRight:
      begin
        GlyphPos.X := ClientSize.X - Margin - GlyphSize.X;
        TextPos.X := GlyphPos.X - Spacing - TextSize.X;
      end;
    blGlyphTop:
      begin
        GlyphPos.Y := Margin;
        TextPos.Y := GlyphPos.Y + GlyphSize.Y + Spacing;
      end;
    blGlyphBottom:
      begin
        GlyphPos.Y := ClientSize.Y - Margin - GlyphSize.Y;
        TextPos.Y := GlyphPos.Y - Spacing - TextSize.Y;
      end;
  end;

  { fixup the result variables }
  Inc(GlyphPos.X, Client.Left + Offset.X);
  Inc(GlyphPos.Y, Client.Top + Offset.Y);

  { Themed text is not shifted, but gets a different color. }
  if wwUseThemes(ComboButton) then
    OffsetRect(TextBounds, TextPos.X + Client.Left, TextPos.Y + Client.Top)
  else
    OffsetRect(TextBounds, TextPos.X + Client.Left + Offset.X,
      TextPos.Y + Client.Top + Offset.Y);

end;

function TwwComboButtonGlyph.Draw(Canvas: TCanvas; const Client: TRect;
  const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
  Margin, Spacing: Integer; State: TButtonState; Transparent: Boolean;
  BiDiFlags: Longint): TRect;
var
  GlyphPos: TPoint;
begin
  CalcButtonLayout(Canvas, Client, Offset, Caption, Layout, Margin, Spacing,
    GlyphPos, Result, BiDiFlags);
  DrawButtonGlyph(Canvas, GlyphPos, State, Transparent);
  DrawButtonText(Canvas, Caption, Result, State, BiDiFlags);
end;

procedure TwwComboButtonActionLink.AssignClient(AClient: TObject);
begin
  inherited AssignClient(AClient);
  FClient := AClient as TSpeedButton;
end;

function TwwComboButtonActionLink.IsCheckedLinked: Boolean;
begin
  Result := inherited IsCheckedLinked and (FClient.GroupIndex <> 0) and
    FClient.AllowAllUp and (FClient.Down = (Action as TCustomAction).Checked);
end;

{$IFDEF wwDelphi6Up}

function TwwComboButtonActionLink.IsGroupIndexLinked: Boolean;
begin
  Result := (FClient is TSpeedButton) and
    (TSpeedButton(FClient).GroupIndex = (Action as TCustomAction).GroupIndex);
end;

procedure TwwComboButtonActionLink.SetGroupIndex(Value: Integer);
begin
  if IsGroupIndexLinked then
    TSpeedButton(FClient).GroupIndex := Value;
end;

{$ENDIF}

procedure TwwComboButtonActionLink.SetChecked(Value: Boolean);
begin
  if IsCheckedLinked then
    TSpeedButton(FClient).Down := Value;
end;

{ TwwComboButton }

constructor TwwComboButton.Create(AOwner: TComponent);
begin
  FGlyph := TwwComboButtonGlyph.Create(Self);
  TwwComboButtonGlyph(FGlyph).OnChange := GlyphChanged;
  inherited Create(AOwner);
  SetBounds(0, 0, 23, 22);
  ControlStyle := [csCaptureMouse, csDoubleClicks];
  ParentFont := True;
  Color := clBtnFace;
  FSpacing := 4;
  FMargin := -1;
  FLayout := blGlyphLeft;
  FTransparent := True;
  Inc(ButtonCount);
end;

destructor TwwComboButton.Destroy;
begin
  Dec(ButtonCount);
  inherited Destroy;
  TwwComboButtonGlyph(FGlyph).Free;
end;

{$IFDEF ThemeManager}

procedure PerformEraseBackground(Control: TControl; DC: HDC);
var
  LastOrigin: TPoint;
begin
  GetWindowOrgEx(DC, LastOrigin);
  SetWindowOrgEx(DC, LastOrigin.X + Control.Left,
    LastOrigin.Y + Control.Top, nil);
  Control.Parent.Perform(WM_ERASEBKGND, Integer(DC), Integer(DC));
  SetWindowOrgEx(DC, LastOrigin.X, LastOrigin.Y, nil);
end;
{$ENDIF}

function TwwComboButton.IsVistaComboNonEditable: Boolean;
begin
  Result := False;
end;

function TwwComboButton.IsVistaTransparentButton: Boolean;
begin
  Result := False;
end;

function TwwComboButton.ParentMouseInControl: Boolean;
begin
  Result := False;
end;

function TwwComboButton.ParentDroppedDown: Boolean;
begin
  Result := False;
end;

{
  procedure InvalidateTransparentArea(control : TControl; Canvas: TCanvas);
  var r: TRect;
  pc, oldpc: TControl;
  pt: TPoint;
  clearBackground: boolean;
  begin
  with Control do  r:= Rect(Left, Top, Left+Width, Top+Height);
  r:= Control.ClientRect;
  pc:= control;
  if pc.parent=nil then exit;

  // If parent is not transparent then just return
  //   if not fcIsTransparentParent(control) then exit;
  pc:= Control;
  While (pc.parent<>nil) do begin
  oldpc:= pc;
  pc:= pc.Parent;
  pt:= Point(0,0);
  // Don't invalidate area outside of control
  if Control.Left<0 then pt.x:= pt.x - Control.Left;
  if Control.Top<0 then pt.y:= pt.y - Control.Top;

  pt:= Control.ClientToScreen(pt);
  ScreenToClient(TWinControl(pc).handle, pt);
  r:= Rect(pt.X, pt.y, pt.x+Control.Width, pt.y+Control.Height);

  // Don't invalidate area outside of control
  if Control.Left<0 then r.Right:= r.Right - Control.Left;
  if Control.Top<0 then r.Top:= r.Top - Control.Top;


  StyleServices.DrawParentBackground(TWinControl(pc).handle, Canvas.handle, nil, false);
  if (not wwIsTransparentParent(oldpc)) and not wwIsClass(pc.ClassType, 'TTabSheet') then exit;
  end;
  end;

}
// Should likely support button style as well as combobutton
// 10/20/04 - If Assigned Glyph, then treat as ellipsis style background
procedure TwwComboButton.Paint;
const
  DownStyles: array [Boolean] of Integer = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
  FillStyles: array [Boolean] of Integer = (BF_MIDDLE, 0);
{$IFNDEF wwDelphi2012Up}
  CP_DROPDOWNBUTTONRIGHT = 6;
{$ENDIF}
  // tcDropDownButtonRightXXX
{$IFNDEF WWDELPHI2007}
  CBXSR_NORMAL = 1;
  CBXSR_HOT = 2;
  CBXSR_PRESSED = 3;
  CBXSR_DISABLED = 4;
{$ENDIF}
var
  PaintRect, PaintClipRect: TRect;
  DrawFlags: Integer;
  Offset: TPoint;
{$IFDEF wwUseThemeManager}
  ComboBox: TThemedCombobox;
  Details: TThemedElementDetails;
  W, X, Y: Integer;
  R: TRect;
  Pressed: Boolean;
{$ENDIF}
  haveClipRect: Boolean;
begin
  haveClipRect := False;

  if not Enabled then
  begin
    FState := bsDisabled;
    FDragging := False;
  end
  else if FState = bsDisabled then
    if FDown and (GroupIndex <> 0) then
      FState := bsExclusive
    else
      FState := bsUp;
  Canvas.Font := Self.Font;

  if wwUseThemes(Self) then
  begin
{$IFDEF wwUseThemeManager}
    if IsVistaComboNonEditable then
      PerformEraseBackground(Self, Canvas.Handle);

    if Ellipsis or (not Glyph.Empty) then
    begin
      Pressed := FState in [bsDown, bsExclusive];
      if Pressed then
        Details := StyleServices.GetElementDetails(tbPushButtonPressed)
      else if MouseInControl or
        (ParentMouseInControl and IsVistaComboNonEditable) then
        Details := StyleServices.GetElementDetails(tbPushButtonHot)
      else
        Details := StyleServices.GetElementDetails(tbPushButtonNormal);
      PaintRect := ClientRect;

      StyleServices.DrawElement(Canvas.Handle, Details, PaintRect);

      R := PaintRect;
      X := R.Left + ((R.Right - R.Left) shr 1) - 1;
      Y := R.Top + ((R.Bottom - R.Top) shr 1) - 1;
      W := ClientWidth shr 3;
      if W = 0 then
        W := 1;
      if Glyph.Empty then
      begin
        PatBlt(Canvas.Handle, X, Y, W, W, BLACKNESS);
        PatBlt(Canvas.Handle, X - (W * 2), Y, W, W, BLACKNESS);
        PatBlt(Canvas.Handle, X + (W * 2), Y, W, W, BLACKNESS);
      end
    end;

    ComboBox := tcDropDownButtonNormal; // Make compiler happy
    if (not Ellipsis) or (not Glyph.Empty) then
    begin
      if not Enabled then // Not ellpisis
        ComboBox := tcDropDownButtonDisabled
      else if (FState in [bsDown, bsExclusive]) or
        (ParentDroppedDown and IsVistaComboNonEditable) then
        ComboBox := tcDropDownButtonPressed
      else if MouseInControl or
        (ParentMouseInControl and IsVistaComboNonEditable) then
        ComboBox := tcDropDownButtonHot
      else
        ComboBox := tcDropDownButtonNormal;
      PaintRect := ClientRect;
      PaintClipRect := Rect(0, 0, 0, 0);

      if (Parent.Parent <> nil) and (Parent.Parent.Parent <> nil) and
        not wwIsClass(Parent.Parent.Parent.classtype, 'TCustomGrid') then
      begin
        PaintRect.Top := PaintRect.Top - 1;
        PaintRect.Bottom := PaintRect.Bottom + 1;
        PaintRect.Right := PaintRect.Right + 1;
        PaintRect.Left := PaintRect.Left + 1;
        if IsVista then
        // Themes in vista for combo buttons are offset differntly
          PaintRect.Left := PaintRect.Left - 2; // vista support
      end
      else
      begin // parent of combo is grid
        PaintRect.Bottom := PaintRect.Bottom + 1;
        if IsVista then
        // Themes in vista for combo buttons are offset differntly
          PaintRect.Left := PaintRect.Left - 1; // vista support
      end;

      if Glyph.Empty then
      begin
        if wwUseThemes(Self) and IsVista then
        begin
          if wwIsCustomStyle(Self) then
          begin
            PaintRect := ClientRect;
            PaintClipRect := PaintRect;
          end
          else
          begin
            InflateRect(PaintRect, 1, 1);
            PaintRect.Left := PaintRect.Left + 0;
            PaintRect.Right := PaintRect.Right + 4;
            PaintClipRect := PaintRect;

            if not IsVistaComboNonEditable then
            begin
              if not UseRightToLeftAlignment then
              // Support bidimode for button - 8/26/08
                PaintClipRect.Left := PaintClipRect.Left + 5
              else
              begin
                PaintRect.Left := ClientRect.Left - 4;
                PaintRect.Right := PaintRect.Right - 5;
                PaintClipRect.Right := PaintRect.Right - 2;
              end;
              haveClipRect := True;
            end
            else
            begin
              if UseRightToLeftAlignment then
              // Support bidimode for button - 8/26/08
              begin
                PaintRect.Left := ClientRect.Left - 4;
                PaintRect.Right := PaintRect.Right - 4;
                PaintClipRect.Right := PaintRect.Right - 2;
              end
            end
          end;
        end;

        Details := StyleServices.GetElementDetails(ComboBox);

        // 5/31/07 - Paint combo button using Vista dropdownbuttonright theme
        if (StyleServices.IsSystemStyle) and IsVistaTransparentButton then
        // Delphi 2012 - XE2 - This code messes up the painting when using xe2 styles (beta problem)
        begin
{$IFDEF wwDelphi2012Up}
          ComboBox := tcDropDownButtonRightNormal;
          Details.Part := CP_DROPDOWNBUTTONRIGHT;
          // InflateRect(PaintRect, 1, 1);
          if (not Enabled) then
            Details.State := Ord(tcDropDownButtonRightDisabled)
          else if (FState in [bsDown, bsExclusive]) then
          begin
            Details.State := Ord(tcDropDownButtonRightPressed)
          end
          else if MouseInControl then
          begin
            Details.State := Ord(tcDropDownButtonRightHot)
          end
          else
          begin
            Details.State := Ord(tcDropDownButtonRightNormal)
          end;
{$ELSE}
          Details.Part := CP_DROPDOWNBUTTONRIGHT;
          // InflateRect(PaintRect, 1, 1);
          if (not Enabled) then
            Details.State := CBXSR_DISABLED
          else if (FState in [bsDown, bsExclusive]) then
          begin
            Details.State := CBXSR_PRESSED
          end
          else if MouseInControl then
          begin
            Details.State := CBXSR_HOT
          end
          else
          begin
            Details.State := CBXSR_NORMAL;
          end;
{$ENDIF}
        end;
        // StyleServices.DrawParentBackground(Parent.Parent.Parent.Parent.Handle, Canvas.handle, nil, false);
        // StyleServices.DrawParentBackground(Parent.Handle, Canvas.handle, nil, false);

        // InvalidateTransparentArea(Parent, Canvas);
        if haveClipRect then
          StyleServices.DrawElement(Canvas.Handle, Details, PaintRect,
            @PaintClipRect)
        else
          StyleServices.DrawElement(Canvas.Handle, Details, PaintRect)
      end;
    end;

    if not Glyph.Empty then
    begin
      StyleServices.GetElementContentRect(Canvas.Handle, Details, PaintRect, PaintRect);

      if ComboBox = tcDropDownButtonPressed then
      begin
        Offset := Point(0, 0);
      end
      else
        Offset := Point(0, 0);

      TwwComboButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption,
        FLayout, FMargin, FSpacing, FState, Transparent,
        DrawTextBiDiModeFlags(0));
    end
{$ENDIF}
  end
  else
  begin
    PaintRect := Rect(0, 0, Width, Height);
    if not FFlat then
    begin
      DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
      if FState in [bsDown, bsExclusive] then
        DrawFlags := DrawFlags or DFCS_PUSHED;
      DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);
    end
    else
    begin
      if (FState in [bsDown, bsExclusive]) or
        (FMouseInControl and (FState <> bsDisabled)) or
        (csDesigning in ComponentState) then
        DrawEdge(Canvas.Handle, PaintRect,
          DownStyles[FState in [bsDown, bsExclusive]],
          FillStyles[Transparent] or BF_RECT)
      else if not Transparent then
      begin
        Canvas.Brush.Color := Color;
        Canvas.FillRect(PaintRect);
      end;
      InflateRect(PaintRect, -1, -1);
    end;
    if FState in [bsDown, bsExclusive] then
    begin
      if (FState = bsExclusive) and (not FFlat or not FMouseInControl) then
      begin
        Canvas.Brush.Bitmap := AllocPatternBitmap(clBtnFace, clBtnHighlight);
        Canvas.FillRect(PaintRect);
      end;
      Offset.X := 1;
      Offset.Y := 1;
    end
    else
    begin
      Offset.X := 0;
      Offset.Y := 0;
    end;
    TwwComboButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption,
      FLayout, FMargin, FSpacing, FState, Transparent,
      DrawTextBiDiModeFlags(0));
  end;
end;

procedure TwwSpinControlButton.Paint;
const
  DownStyles: array [Boolean] of Integer = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
  FillStyles: array [Boolean] of Integer = (BF_MIDDLE, 0);
var
  PaintRect: TRect;
  DrawFlags: Integer;
  Offset: TPoint;
{$IFDEF wwUseThemeManager}
  ScrollBox: TThemedSpin;
  Details: TThemedElementDetails;
{$ENDIF}
begin
  if not Enabled then
  begin
    FState := bsDisabled;
    FDragging := False;
  end
  else if FState = bsDisabled then
    if FDown and (GroupIndex <> 0) then
      FState := bsExclusive
    else
      FState := bsUp;
  Canvas.Font := Self.Font;

  if wwUseThemes(Self) then
  begin
{$IFDEF wwUseThemeManager}
    PerformEraseBackground(Self, Canvas.Handle);

    if ScrollDirection = wwsdUp then
    begin
      if not Enabled then
        ScrollBox := tsUpDisabled
      else if FState in [bsDown, bsExclusive] then
        ScrollBox := tsUpPressed
      else if MouseInControl then
        ScrollBox := tsUpHot
      else
        // Scrollbox:= tsArrowBtnUpNormal;
        ScrollBox := tsUpNormal;
    end
    else if ScrollDirection = wwsdDown then
    begin
      if not Enabled then
        ScrollBox := tsDownDisabled
      else if FState in [bsDown, bsExclusive] then
        ScrollBox := tsDownPressed
      else if MouseInControl then
        ScrollBox := tsDownHot
      else
        ScrollBox := tsDownNormal;
    end
    else
      ScrollBox := tsUpNormal;

    PaintRect := ClientRect;

    if (Parent.Parent <> nil) and (Parent.Parent.Parent <> nil) and
      not wwIsClass(Parent.Parent.Parent.classtype, 'TCustomGrid') then
    begin
      if wwIsCustomStyle(nil) then
      begin
        PaintRect.Top:= PaintRect.Top -2;
        PaintRect.Bottom:= PaintRect.Bottom + 2;
        PaintRect.Left := PaintRect.Left - 1;
        PaintRect.Right := PaintRect.Right + 2;
      end
      else begin
        if ScrollDirection = wwsdUp then
        begin
          PaintRect.Top := PaintRect.Top - 1;
          PaintRect.Bottom := PaintRect.Bottom - 1;
        end;
        PaintRect.Left := PaintRect.Left - 1;
        PaintRect.Right := PaintRect.Right + 2;
      end;
    end
    else
    begin // parent of combo is grid
      if ScrollDirection = wwsdUp then
      begin
        PaintRect.Top := PaintRect.Top - 1;
        PaintRect.Bottom := PaintRect.Bottom - 1;
      end;
      if ScrollDirection = wwsdDown then
        PaintRect.Bottom := PaintRect.Bottom + 1;
      PaintRect.Left := PaintRect.Left - 1;
      PaintRect.Right := PaintRect.Right + 1;
      // PaintRect.Bottom:= PaintRect.Bottom+1;
    end;

    Details := StyleServices.GetElementDetails(ScrollBox);
    StyleServices.DrawElement(Canvas.Handle, Details, PaintRect);
{$ENDIF}
  end
  else
  begin
    PaintRect := Rect(0, 0, Width, Height);
    if not FFlat then
    begin
      DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
      if FState in [bsDown, bsExclusive] then
        DrawFlags := DrawFlags or DFCS_PUSHED;
      DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);
    end
    else
    begin
      if (FState in [bsDown, bsExclusive]) or
        (FMouseInControl and (FState <> bsDisabled)) or
        (csDesigning in ComponentState) then
        DrawEdge(Canvas.Handle, PaintRect,
          DownStyles[FState in [bsDown, bsExclusive]],
          FillStyles[Transparent] or BF_RECT)
      else if not Transparent then
      begin
        Canvas.Brush.Color := Color;
        Canvas.FillRect(PaintRect);
      end;
      InflateRect(PaintRect, -1, -1);
    end;
    if FState in [bsDown, bsExclusive] then
    begin
      if (FState = bsExclusive) and (not FFlat or not FMouseInControl) then
      begin
        Canvas.Brush.Bitmap := AllocPatternBitmap(clBtnFace, clBtnHighlight);
        Canvas.FillRect(PaintRect);
      end;
      Offset.X := 1;
      Offset.Y := 1;
    end
    else
    begin
      Offset.X := 0;
      Offset.Y := 0;
    end;
    TwwComboButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption,
      FLayout, FMargin, FSpacing, FState, Transparent,
      DrawTextBiDiModeFlags(0));
  end;
end;

procedure TwwComboButton.UpdateTracking;
var
  P: TPoint;
begin
  if FFlat then
  begin
    if Enabled then
    begin
      GetCursorPos(P);
      FMouseInControl := not(FindDragTarget(P, True) = Self);
      if FMouseInControl then
        Perform(CM_MOUSELEAVE, 0, 0)
      else
        Perform(CM_MOUSEENTER, 0, 0);
    end;
  end;
end;

procedure TwwComboButton.Loaded;
var
  State: TButtonState;
begin
  inherited Loaded;
  if Enabled then
    State := bsUp
  else
    State := bsDisabled;
  TwwComboButtonGlyph(FGlyph).CreateButtonGlyph(State);
end;

procedure TwwComboButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited MouseDown(Button, Shift, X, Y);
  if (Button = mbLeft) and Enabled then
  begin
    if not FDown then
    begin
      FState := bsDown;
      Invalidate;
    end;
    FDragging := True;
  end;
end;

procedure TwwComboButton.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  NewState: TButtonState;
begin
  inherited MouseMove(Shift, X, Y);
  if FDragging then
  begin
    if not FDown then
      NewState := bsUp
    else
      NewState := bsExclusive;
    if (X >= 0) and (X < ClientWidth) and (Y >= 0) and (Y <= ClientHeight) then
      if FDown then
        NewState := bsExclusive
      else
        NewState := bsDown;
    if NewState <> FState then
    begin
      FState := NewState;
      Invalidate;
    end;
  end
  else if not FMouseInControl then
    UpdateTracking;
end;

procedure TwwComboButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  DoClick: Boolean;
begin
  inherited MouseUp(Button, Shift, X, Y);
  if FDragging then
  begin
    FDragging := False;
    DoClick := (X >= 0) and (X < ClientWidth) and (Y >= 0) and
      (Y <= ClientHeight);
    if FGroupIndex = 0 then
    begin
      { Redraw face in-case mouse is captured }
      FState := bsUp;
      FMouseInControl := False;
      if DoClick and not(FState in [bsExclusive, bsDown]) then
        Invalidate;
    end
    else if DoClick then
    begin
      SetDown(not FDown);
      if FDown then
        Repaint;
    end
    else
    begin
      if FDown then
        FState := bsExclusive;
      Repaint;
    end;
    if DoClick then
      Click;
    UpdateTracking;
  end;
end;

procedure TwwComboButton.Click;
begin
  inherited Click;
end;

function TwwComboButton.GetPalette: HPALETTE;
begin
  Result := Glyph.Palette;
end;

function TwwComboButton.GetActionLinkClass: TControlActionLinkClass;
begin
  Result := TwwComboButtonActionLink;
end;

function TwwComboButton.GetGlyph: TBitmap;
begin
  Result := TwwComboButtonGlyph(FGlyph).Glyph;
end;

procedure TwwComboButton.SetGlyph(Value: TBitmap);
begin
  TwwComboButtonGlyph(FGlyph).Glyph := Value;
  Invalidate;
end;

function TwwComboButton.GetNumGlyphs: TNumGlyphs;
begin
  Result := TwwComboButtonGlyph(FGlyph).NumGlyphs;
end;

procedure TwwComboButton.SetNumGlyphs(Value: TNumGlyphs);
begin
  if Value < 0 then
    Value := 1
  else if Value > 4 then
    Value := 4;
  if Value <> TwwComboButtonGlyph(FGlyph).NumGlyphs then
  begin
    TwwComboButtonGlyph(FGlyph).NumGlyphs := Value;
    Invalidate;
  end;
end;

procedure TwwComboButton.GlyphChanged(Sender: TObject);
begin
  Invalidate;
end;

procedure TwwComboButton.UpdateExclusive;
var
  Msg: TMessage;
begin
  if (FGroupIndex <> 0) and (Parent <> nil) then
  begin
    Msg.Msg := CM_BUTTONPRESSED;
    Msg.WParam := FGroupIndex;
    Msg.LParam := Longint(Self);
    Msg.Result := 0;
    Parent.Broadcast(Msg);
  end;
end;

procedure TwwComboButton.SetDown(Value: Boolean);
begin
  if FGroupIndex = 0 then
    Value := False;
  if Value <> FDown then
  begin
    if FDown and (not FAllowAllUp) then
      Exit;
    FDown := Value;
    if Value then
    begin
      if FState = bsUp then
        Invalidate;
      FState := bsExclusive
    end
    else
    begin
      FState := bsUp;
      Repaint;
    end;
    if Value then
      UpdateExclusive;
  end;
end;

procedure TwwComboButton.SetFlat(Value: Boolean);
begin
  if Value <> FFlat then
  begin
    FFlat := Value;
    Invalidate;
  end;
end;

procedure TwwComboButton.SetGroupIndex(Value: Integer);
begin
  if FGroupIndex <> Value then
  begin
    FGroupIndex := Value;
    UpdateExclusive;
  end;
end;

procedure TwwComboButton.SetLayout(Value: TButtonLayout);
begin
  if FLayout <> Value then
  begin
    FLayout := Value;
    Invalidate;
  end;
end;

procedure TwwComboButton.SetMargin(Value: Integer);
begin
  if (Value <> FMargin) and (Value >= -1) then
  begin
    FMargin := Value;
    Invalidate;
  end;
end;

procedure TwwComboButton.SetSpacing(Value: Integer);
begin
  if Value <> FSpacing then
  begin
    FSpacing := Value;
    Invalidate;
  end;
end;

procedure TwwComboButton.SetTransparent(Value: Boolean);
begin
  if Value <> FTransparent then
  begin
    FTransparent := Value;
    if Value then
      ControlStyle := ControlStyle - [csOpaque]
    else
      ControlStyle := ControlStyle + [csOpaque];
    Invalidate;
  end;
end;

procedure TwwComboButton.SetAllowAllUp(Value: Boolean);
begin
  if FAllowAllUp <> Value then
  begin
    FAllowAllUp := Value;
    UpdateExclusive;
  end;
end;

procedure TwwComboButton.WMLButtonDblClk(var Message: TWMLButtonDown);
begin
  inherited;
  if FDown then
    DblClick;
end;

procedure TwwComboButton.CMEnabledChanged(var Message: TMessage);
const
  NewState: array [Boolean] of TButtonState = (bsDisabled, bsUp);
begin
  TwwComboButtonGlyph(FGlyph).CreateButtonGlyph(NewState[Enabled]);
  UpdateTracking;
  Repaint;
end;

procedure TwwComboButton.CMButtonPressed(var Message: TMessage);
var
  Sender: TwwComboButton;
begin
  if Integer(Message.WParam) = FGroupIndex then
  begin
    Sender := TwwComboButton(Message.LParam);
    if Sender <> Self then
    begin
      if Sender.Down and FDown then
      begin
        FDown := False;
        FState := bsUp;
        if (Action is TCustomAction) then
          TCustomAction(Action).Checked := False;
        Invalidate;
      end;
      FAllowAllUp := Sender.AllowAllUp;
    end;
  end;
end;

procedure TwwComboButton.CMDialogChar(var Message: TCMDialogChar);
begin
  with Message do
    if IsAccel(CharCode, Caption) and Enabled and Visible and (Parent <> nil)
      and Parent.Showing then
    begin
      Click;
      Result := 1;
    end
    else
      inherited;
end;

procedure TwwComboButton.CMFontChanged(var Message: TMessage);
begin
  Invalidate;
end;

procedure TwwComboButton.CMTextChanged(var Message: TMessage);
begin
  Invalidate;
end;

procedure TwwComboButton.CMSysColorChange(var Message: TMessage);
begin
  with TwwComboButtonGlyph(FGlyph) do
  begin
    Invalidate;
    CreateButtonGlyph(FState);
  end;
end;

procedure TwwComboButton.CMMouseEnter(var Message: TMessage);
var
  NeedRepaint: Boolean;
begin
  inherited;
  { Don't draw a border if DragMode <> dmAutomatic since this button is meant to
    be used as a dock client. }
  NeedRepaint := FFlat and not FMouseInControl and Enabled and
    (DragMode <> dmAutomatic) and (GetCapture = 0);

  { Windows XP introduced hot states also for non-flat buttons. }
  if (NeedRepaint or wwUseThemes(Self)) and not(csDesigning in ComponentState)
  then
  begin
    FMouseInControl := True;
    if Enabled then
      Repaint;
  end;
end;

procedure TwwComboButton.CMMouseLeave(var Message: TMessage);
var
  NeedRepaint: Boolean;
begin
  inherited;
  NeedRepaint := FFlat and FMouseInControl and Enabled and not FDragging;
  { Windows XP introduced hot states also for non-flat buttons. }
  if NeedRepaint or wwUseThemes(Self) then
  begin
    FMouseInControl := False;
    if Enabled then
      Repaint;
  end;
end;

procedure TwwComboButton.ActionChange(Sender: TObject; CheckDefaults: Boolean);

  procedure CopyImage(ImageList: TCustomImageList; Index: Integer);
  begin
    with Glyph do
    begin
      Width := ImageList.Width;
      Height := ImageList.Height;
      Canvas.Brush.Color := clFuchsia; // ! for lack of a better color
      Canvas.FillRect(Rect(0, 0, Width, Height));
      ImageList.Draw(Canvas, 0, 0, Index);
    end;
  end;

begin
  inherited ActionChange(Sender, CheckDefaults);
  if Sender is TCustomAction then
    with TCustomAction(Sender) do
    begin
      if CheckDefaults or (Self.GroupIndex = 0) then
        Self.GroupIndex := GroupIndex;
      { Copy image from action's imagelist }
      if (Glyph.Empty) and (ActionList <> nil) and (ActionList.Images <> nil)
        and (ImageIndex >= 0) and (ImageIndex < ActionList.Images.Count) then
        CopyImage(ActionList.Images, ImageIndex);
    end;
end;

initialization

// FillChar(BitBtnGlyphs, SizeOf(BitBtnGlyphs), 0);
finalization

// DestroyLocals;
end.
