TColorPickerButton组件


https://files.cnblogs.com/xe2011/VCL_TColorPB12.rar

在DELPHI7中可以正常使用 在DELPHI XE5中 下面会有些问题

安装方法

  1. 打开 DELPHI  
  2. 新建 - Package
  3. 选中Package.BPL,右键 ADD找到本并添加.PAS源文件
  4. 选中Package.BPL,右键 Compile
  5. 选中Package.BPL,右键 Instal


卸载这个组件的时候,找到这个安装包
选中Package.BPL,右键 UnInstal

 

说明

TColorPickerButton is a special speed button which can be used to let the user select
a specific color. The control does not use the standard Windows color dialog, but uses
a popup window very similar to the one in Office97, which has been improved a lot
to support the task of picking one color out of millions. Included is also the
ability to pick one of the predefined system colors (e.g. clBtnFace).

TColorPickerButton works only with D4 and BCB!
(BCB check by Josue Andrade Gomes gomesj@bsi.com.br)

(c) 1999, written by Dipl. Ing. Mike Lischke (public@lischke-online.de)
All rights reserved. This control is freeware and may be used in any software
product (free or commercial) under the condition that I'm given proper credit
(titel, name and eMail address in the documentation or the About box of the
product this control is used in).

Portions copyright by Borland. The implementation of the speed button has been
taken from Delphi sources.

The use of the new control is quite simple. Just install the ColorPickerButton.pas into your component palette. By default the target
component page is "Tools". That's all.

Here's a list of methods and properties which differ from TSpeedButton:

  public
    property DroppedDown: Boolean;
    Read to get the drop down state of the color popup or write to set it.
  published
    property CustomText: String;
    Determines the text of the second special button on the popup. If empty this button is neither shown nor is it then possible to select a color
    from the color comb. If set then the button is shown and the user can click on it to show the color comb (accelerator allowed).
    property DefaultText: String;
    Determines the text of the first special button on the popup. If empty this button is not shown, else it is used to select the default color (clDefault)
    (accelerator allowed).
    property DropDownArrowColor: TColor;
    Determines the color of the small triangle on the right of the button.
    property DropDownWidth: Integer;
    Determines the size of the area on the right which can be clicked to drop down the picker window.
    property IndicatorBorder: TIndicatorBorder;
    Set one of four border styles the color preview rectangle is drawn in (none, flat, sunken, raised)
    property PopupSpacing: Integer;
    Denotes the spacing within the color popup window (>= 0, this is the place left on the left and right side of the popup)
    property SelectionColor: TColor;
    Contains the currently selected color .
    property ShowSystemColors: Boolean;
    Determines whether predefined system colors like clBtnFace or clWindow should be shown.

    property OnChange: TNotifyEvent;
    Triggered when the selection color of the button changes.
    property OnDefaultSelect;
    Triggered when the user selected the default color (either with the mouse or by accelerator key).
    property OnDropChanged: TNotifyEvent;
    Triggered after the visibility state of the picker window has changed. DroppedDown is already set according to the state.
    property OnDropChanging: TDropChangingEvent;
    Triggered just before the visibility state of the picker window changes. DroppedDown is still in the old state and you can reject dropping down
    or hiding the window by setting Allowed to False.
    property OnHint: THintEvent;
    For each color in the picker window a hint window appears when the mouse is over the belonging button or comb. If the mouse is not over any button
    or hovers over the default text or the custom text, respectively, then a hint is requested from the application by this event. There's a Cell paramter to
    tell what cell is meant. It can be NoCell, CustomCell or DefaultCell. BTW: By setting ShowHint to False all hints are disabled, even
    those of the color and comb buttons. If you don't supply an OnHint event then the hint string of the color picker button is shown.

As you can see there's nothing special with the control. Just use it and show the world what really amazing applications can be produced with Delphi.

Have fun and

 

 

 

unit ColorPickerButton;

// This unit contains a special speed button which can be used to let the user select
// a specific color. The control does not use the standard Windows color dialog, but
// a popup window very similar to the one in Office97, which has been improved a lot
// to support the task of picking one color out of millions. Included is also the
// ability to pick one of the predefined system colors (e.g. clBtnFace).
// Note: The layout is somewhat optimized to look pretty with the predefined box size
// of 18 pixels (the size of one little button in the predefined color area) and
// the number of color comb levels. It is easily possible to change this, but
// if you want to do so then you have probably to make some additional
// changes to the overall layout.
//
// TColorPickerButton works only with D4 and BCB!
// (BCB check by Josue Andrade Gomes gomesj@bsi.com.br)
//
// (c) 1999, written by Dipl. Ing. Mike Lischke (public@lischke-online.de)
// All rights reserved. This unit is freeware and may be used in any software
// product (free or commercial) under the condition that I'm given proper credit
// (Titel, Name and eMail address in the documentation or the About box of the
// product this source code is used in).
// Portions copyright by Borland. The implementation of the speed button has been
// taken from Delphi sources.
//
// 22-JUN-99 ml: a few improvements for the overall layout (mainly indicator rectangle
// does now draw in four different styles and considers the layout
// property of the button (changed to version 1.2, BCB compliance is
// now proved by Josue Andrade Gomes)
// 18-JUN-99 ml: message redirection bug removed (caused an AV under some circumstances)
// and accelerator key handling bug removed (wrong flag for EndSelection)
// (changed to version 1.1)
// 16-JUN-99 ml: initial release

interface

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

const // constants used in OnHint and internally to indicate a specific cell
  DefaultCell = -3;
  CustomCell = -2;
  NoCell = -1;

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

  TIndicatorBorder = (ibNone, ibFlat, ibSunken, ibRaised);

  THintEvent = procedure(Sender: TObject; Cell: Integer; var Hint: String)
    of object;
  TDropChangingEvent = procedure(Sender: TObject; var Allowed: Boolean)
    of object;

  TColorPickerButton = class(TGraphicControl)
  private
    FGroupIndex: Integer;
    FGlyph: Pointer;
    FDown: Boolean;
    FDragging: Boolean;
    FAllowAllUp: Boolean;
    FLayout: TButtonLayout;
    FSpacing: Integer;
    FMargin: Integer;
    FFlat: Boolean;
    FMouseInControl: Boolean;
    FTransparent: Boolean;
    FIndicatorBorder: TIndicatorBorder;

    FDropDownArrowColor: TColor;
    FDropDownWidth: Integer;
    FDropDownZone: Boolean;
    FDroppedDown: Boolean;
    FSelectionColor: TColor;
    FState: TButtonState;
    FColorPopup: TWinControl;
    FPopupWnd: HWND;

    FOnChange, FOnDefaultSelect, FOnDropChanged: TNotifyEvent;
    FOnDropChanging: TDropChangingEvent;
    FOnHint: THintEvent;
    procedure GlyphChanged(Sender: TObject);
    procedure UpdateExclusive;
    function GetGlyph: TBitmap;
    procedure SetDropDownArrowColor(Value: TColor);
    procedure SetDropDownWidth(Value: Integer);
    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 SetMargin(Value: Integer);
    procedure UpdateTracking;
    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;
    procedure WMLButtonDblClk(var Message: TWMLButtonDown);
      message WM_LBUTTONDBLCLK;

    procedure DrawButtonSeperatorUp(Canvas: TCanvas);
    procedure DrawButtonSeperatorDown(Canvas: TCanvas);
    procedure DrawTriangle(Canvas: TCanvas; Top, Left, Width: Integer);
    procedure SetDroppedDown(const Value: Boolean);
    procedure SetSelectionColor(const Value: TColor);
    procedure PopupWndProc(var Msg: TMessage);
    function GetCustomText: String;
    procedure SetCustomText(const Value: String);
    function GetDefaultText: String;
    procedure SetDefaultText(const Value: String);
    procedure SetShowSystemColors(const Value: Boolean);
    function GetShowSystemColors: Boolean;
    procedure SetTransparent(const Value: Boolean);
    procedure SetIndicatorBorder(const Value: TIndicatorBorder);
    function GetPopupSpacing: Integer;
    procedure SetPopupSpacing(const Value: Integer);
  protected
    procedure DoDefaultEvent; virtual;
    procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); 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;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Click; override;

    property DroppedDown: Boolean read FDroppedDown write SetDroppedDown;
  published
    property Action;
    property AllowAllUp: Boolean read FAllowAllUp write SetAllowAllUp
      default False;
    property Anchors;
    property BiDiMode;
    property Caption;
    property Constraints;
    property CustomText: String read GetCustomText write SetCustomText;
    property DefaultText: String read GetDefaultText write SetDefaultText;
    property Down: Boolean read FDown write SetDown default False;
    property DropDownArrowColor: TColor read FDropDownArrowColor
      write SetDropDownArrowColor default clBlack;
    property DropDownWidth: Integer read FDropDownWidth write SetDropDownWidth
      default 15;
    property Enabled;
    property Flat: Boolean read FFlat write SetFlat default False;
    property Font;
    property Glyph: TBitmap read GetGlyph write SetGlyph;
    property GroupIndex: Integer read FGroupIndex write SetGroupIndex default 0;
    property IndicatorBorder: TIndicatorBorder read FIndicatorBorder
      write SetIndicatorBorder default ibFlat;
    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 ParentBiDiMode;
    property ParentFont;
    property ParentShowHint;
    property PopupSpacing: Integer read GetPopupSpacing write SetPopupSpacing;
    property SelectionColor: TColor read FSelectionColor write SetSelectionColor
      default clBlack;
    property ShowHint;
    property ShowSystemColors: Boolean read GetShowSystemColors
      write SetShowSystemColors;
    property Spacing: Integer read FSpacing write SetSpacing default 4;
    property Transparent: Boolean read FTransparent write SetTransparent
      default True;
    property Visible;

    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnClick;
    property OnDblClick;
    property OnDefaultSelect: TNotifyEvent read FOnDefaultSelect
      write FOnDefaultSelect;
    property OnDropChanged: TNotifyEvent read FOnDropChanged
      write FOnDropChanged;
    property OnDropChanging: TDropChangingEvent read FOnDropChanging
      write FOnDropChanging;
    property OnHint: THintEvent read FOnHint write FOnHint;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
  end;

procedure Register;

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

implementation

uses ActnList, ImgList;

const
  DRAW_BUTTON_UP = 8208;
  DRAW_BUTTON_DOWN = 8720;

type
  TColorEntry = record
    Name: PChar;
    case Boolean of
      True:
        (R, G, B, reserved: Byte);
      False:
        (Color: COLORREF);
  end;

const
  DefaultColorCount = 40;
  // these colors are the same as used in Office 97/2000
  DefaultColors: array [0 .. DefaultColorCount - 1] of TColorEntry =
    ((Name: 'Black'; Color: $000000), (Name: 'Brown'; Color: $003399),
    (Name: 'Olive Green'; Color: $003333), (Name: 'Dark Green'; Color: $003300),
    (Name: 'Dark Teal'; Color: $663300), (Name: 'Dark blue'; Color: $800000),
    (Name: 'Indigo'; Color: $993333), (Name: 'Gray-80%'; Color: $333333),

    (Name: 'Dark Red'; Color: $000080), (Name: 'Orange'; Color: $0066FF),
    (Name: 'Dark Yellow'; Color: $008080), (Name: 'Green'; Color: $008000),
    (Name: 'Teal'; Color: $808000), (Name: 'Blue'; Color: $FF0000),
    (Name: 'Blue-Gray'; Color: $996666), (Name: 'Gray-50%'; Color: $808080),

    (Name: 'Red'; Color: $0000FF), (Name: 'Light Orange'; Color: $0099FF),
    (Name: 'Lime'; Color: $00CC99), (Name: 'Sea Green'; Color: $669933),
    (Name: 'Aqua'; Color: $CCCC33), (Name: 'Light Blue'; Color: $FF6633),
    (Name: 'Violet'; Color: $800080), (Name: 'Grey-40%'; Color: $969696),

    (Name: 'Pink'; Color: $FF00FF), (Name: 'Gold'; Color: $00CCFF),
    (Name: 'Yellow'; Color: $00FFFF), (Name: 'Bright Green'; Color: $00FF00),
    (Name: 'Turquoise'; Color: $FFFF00), (Name: 'Sky Blue'; Color: $FFCC00),
    (Name: 'Plum'; Color: $663399), (Name: 'Gray-25%'; Color: $C0C0C0),

    (Name: 'Rose'; Color: $CC99FF), (Name: 'Tan'; Color: $99CCFF),
    (Name: 'Light Yellow'; Color: $99FFFF), (Name: 'Light Green';
    Color: $CCFFCC), (Name: 'Light Turquoise'; Color: $FFFFCC),
    (Name: 'Pale Blue'; Color: $FFCC99), (Name: 'Lavender'; Color: $FF99CC),
    (Name: 'White'; Color: $FFFFFF));

  SysColorCount = 25;
  SysColors: array [0 .. SysColorCount - 1] of TColorEntry =
    ((Name: 'system color: scroll bar'; Color: COLORREF(clScrollBar)),
    (Name: 'system color: background'; Color: COLORREF(clBackground)),
    (Name: 'system color: active caption'; Color: COLORREF(clActiveCaption)),
    (Name: 'system color: inactive caption'; Color: COLORREF(clInactiveCaption)
    ), (Name: 'system color: menu'; Color: COLORREF(clMenu)),
    (Name: 'system color: window'; Color: COLORREF(clWindow)),
    (Name: 'system color: window frame'; Color: COLORREF(clWindowFrame)),
    (Name: 'system color: menu text'; Color: COLORREF(clMenuText)),
    (Name: 'system color: window text'; Color: COLORREF(clWindowText)),
    (Name: 'system color: caption text'; Color: COLORREF(clCaptionText)),
    (Name: 'system color: active border'; Color: COLORREF(clActiveBorder)),
    (Name: 'system color: inactive border'; Color: COLORREF(clInactiveBorder)),
    (Name: 'system color: application workspace';
    Color: COLORREF(clAppWorkSpace)), (Name: 'system color: highlight';
    Color: COLORREF(clHighlight)), (Name: 'system color: highlight text';
    Color: COLORREF(clHighlightText)), (Name: 'system color: button face';
    Color: COLORREF(clBtnFace)), (Name: 'system color: button shadow';
    Color: COLORREF(clBtnShadow)), (Name: 'system color: gray text';
    Color: COLORREF(clGrayText)), (Name: 'system color: button text';
    Color: COLORREF(clBtnText)), (Name: 'system color: inactive caption text';
    Color: COLORREF(clInactiveCaptionText)),
    (Name: 'system color: button highlight'; Color: COLORREF(clBtnHighlight)),
    (Name: 'system color: 3D dark shadow'; Color: COLORREF(cl3DDkShadow)),
    (Name: 'system color: 3D light'; Color: COLORREF(cl3DLight)),
    (Name: 'system color: info text'; Color: COLORREF(clInfoText)),
    (Name: 'system color: info background'; Color: COLORREF(clInfoBk)));

type
  TGlyphList = class(TImageList)
  private
    FUsed: 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;

  TGlyphCache = class
  private
    FGlyphLists: TList;
  public
    constructor Create;
    destructor Destroy; override;

    function GetList(AWidth, AHeight: Integer): TGlyphList;
    procedure ReturnList(List: TGlyphList);
    function Empty: Boolean;
  end;

  TButtonGlyph = class
  private
    FOriginal: TBitmap;
    FGlyphList: TGlyphList;
    FIndexes: array [TButtonState] of Integer;
    FTransparentColor: TColor;
    FNumGlyphs: TNumGlyphs;
    FOnChange: TNotifyEvent;
    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;
      const DropDownWidth: Integer; BiDiFlags: Longint);
  public
    constructor Create;
    destructor Destroy; override;

    function Draw(Canvas: TCanvas; const Client: TRect; const Offset: TPoint;
      const Caption: string; Layout: TButtonLayout; Margin, Spacing: Integer;
      State: TButtonState; Transparent: Boolean; const DropDownWidth: Integer;
      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;

  TCombEntry = record
    Position: TPoint;
    Color: COLORREF;
  end;

  TCombArray = array of TCombEntry;

  TFloatPoint = record
    X, Y: Extended;
  end;

  TRGB = record
    Red, Green, Blue: Single;
  end;

  TSelectionMode = (smNone, smColor, smBW, smRamp);

  TColorPopup = class(TWinControl)
  private
    FDefaultText, FCustomText: String;
    FCurrentColor: TColor;
    FCanvas: TCanvas;
    FMargin, FSpacing, FColumnCount, FRowCount, FSysRowCount, FBoxSize: Integer;
    FSelectedIndex, FHoverIndex: Integer;
    FWindowRect, FCustomTextRect, FDefaultTextRect, FColorCombRect, FBWCombRect,
      FSliderRect, FCustomColorRect: TRect;
    FShowSysColors: Boolean;

    // custom color picking
    FCombSize, FLevels: Integer;
    FBWCombs, FColorCombs: TCombArray;
    FCombCorners: array [0 .. 5] of TFloatPoint;
    FCenterColor: TRGB;
    FCenterIntensity: Single; // scale factor for the center color
    FCustomIndex, // If FSelectedIndex contains CustomCell then this index shows
    // which index in the custom area has been selected.
    // Positive values indicate the color comb and negativ values
    // indicate the B&W combs (complement). This value is offset with
    // 1 to use index 0 to show no selection.
    FRadius: Integer;
    FSelectionMode: TSelectionMode; // indicates where the user has clicked
    // with the mouse to restrict draw selection
    procedure SelectColor(Color: TColor);
    procedure ChangeHoverSelection(Index: Integer);
    procedure DrawCell(Index: Integer);
    procedure InvalidateCell(Index: Integer);
    procedure EndSelection(Cancel: Boolean);
    function GetCellRect(Index: Integer; var Rect: TRect): Boolean;
    function GetColumn(Index: Integer): Integer;
    function GetIndex(Row, Col: Integer): Integer;
    function GetRow(Index: Integer): Integer;
    procedure Initialise;
    procedure AdjustWindow;
    procedure SetSpacing(Value: Integer);
    procedure SetSelectedColor(const Value: TColor);
    procedure CMHintShow(var Message: TMessage); message CM_HINTSHOW;
    procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;
    procedure CNSysKeyDown(var Message: TWMChar); message CN_SYSKEYDOWN;
    procedure WMActivateApp(var Message: TWMActivateApp);
      message WM_ACTIVATEAPP;
    procedure WMLButtonDown(var Message: TWMLButtonDown);
      message WM_LBUTTONDOWN;
    procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
    procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
    procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    function SelectionFromPoint(P: TPoint): Integer;
    procedure DrawCombControls;
    procedure DrawComb(Canvas: TCanvas; X, Y, Size: Integer);
    function HandleBWArea(const Message: TWMMouse): Boolean;
    function HandleColorComb(const Message: TWMMouse): Boolean;
    function HandleSlider(const Message: TWMMouse): Boolean;
    function PtInComb(Comb: TCombEntry; P: TPoint; Scale: Integer): Boolean;
    procedure HandleCustomColors(var Message: TWMMouse);
    function GetHint(Cell: Integer): String;
    function FindBWArea(X, Y: Integer): Integer;
    function FindColorArea(X, Y: Integer): Integer;
    procedure DrawSeparator(Left, Top, Right: Integer);
    procedure ChangeSelection(NewSelection: Integer);
  protected
    procedure CalculateCombLayout;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;
    procedure ShowPopupAligned;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    property SelectedColor: TColor read FCurrentColor write SetSelectedColor;
    property Spacing: Integer read FSpacing write SetSpacing;
  end;

const
  DefCenterColor: TRGB = (Red: 1; Green: 1; Blue: 1); // White
  DefColors: array [0 .. 5] of TRGB = ((Red: 1; Green: 0; Blue: 1), // Magenta
    (Red: 1; Green: 0; Blue: 0), // Red
    (Red: 1; Green: 1; Blue: 0), // Yellow
    (Red: 0; Green: 1; Blue: 0), // Green
    (Red: 0; Green: 1; Blue: 1), // Cyan
    (Red: 0; Green: 0; Blue: 1) // Blue
    );
  DefCenter: TFloatPoint = (X: 0; Y: 0);

var
  GlyphCache: TGlyphCache;
  ButtonCount: Integer;

  // ----------------- TGlyphList ------------------------------------------------

constructor TGlyphList.CreateSize(AWidth, AHeight: Integer);

begin
  inherited CreateSize(AWidth, AHeight);
  FUsed := TBits.Create;
end;

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

destructor TGlyphList.Destroy;

begin
  FUsed.Free;
  inherited Destroy;
end;

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

function TGlyphList.AllocateIndex: Integer;

begin
  Result := FUsed.OpenBit;
  if Result >= FUsed.Size then
  begin
    Result := inherited Add(nil, nil);
    FUsed.Size := Result + 1;
  end;
  FUsed[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 FUsed[Index] then
  begin
    Dec(FCount);
    FUsed[Index] := False;
  end;
end;

// ----------------- TGlyphCache -----------------------------------------------

constructor TGlyphCache.Create;

begin
  inherited Create;
  FGlyphLists := TList.Create;
end;

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

destructor TGlyphCache.Destroy;

begin
  FGlyphLists.Free;
  inherited Destroy;
end;

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

function TGlyphCache.GetList(AWidth, AHeight: Integer): TGlyphList;

var
  I: Integer;

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

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

procedure TGlyphCache.ReturnList(List: TGlyphList);

begin
  if List = nil then
    Exit;
  if List.Count = 0 then
  begin
    FGlyphLists.Remove(List);
    List.Free;
  end;
end;

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

function TGlyphCache.Empty: Boolean;

begin
  Result := FGlyphLists.Count = 0;
end;

// ----------------- TButtonGlyph ----------------------------------------------

constructor TButtonGlyph.Create;

var
  I: TButtonState;

begin
  inherited Create;
  FOriginal := TBitmap.Create;
  FOriginal.OnChange := GlyphChanged;
  FTransparentColor := clOlive;
  FNumGlyphs := 1;
  for I := Low(I) to High(I) do
    FIndexes[I] := -1;
  if GlyphCache = nil then
    GlyphCache := TGlyphCache.Create;
end;

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

destructor TButtonGlyph.Destroy;

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

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

procedure TButtonGlyph.Invalidate;

var
  I: TButtonState;

begin
  for I := Low(I) to High(I) do
  begin
    if FIndexes[I] <> -1 then
      FGlyphList.Delete(FIndexes[I]);
    FIndexes[I] := -1;
  end;
  GlyphCache.ReturnList(FGlyphList);
  FGlyphList := nil;
end;

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

procedure TButtonGlyph.GlyphChanged(Sender: TObject);

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

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

procedure TButtonGlyph.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 TButtonGlyph.SetNumGlyphs(Value: TNumGlyphs);

begin
  if (Value <> FNumGlyphs) and (Value > 0) then
  begin
    Invalidate;
    FNumGlyphs := Value;
    GlyphChanged(Glyph);
  end;
end;

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

function TButtonGlyph.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 := FIndexes[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
            FIndexes[State] := FGlyphList.AddMasked(TmpImage, FTransparentColor)
          else
            FIndexes[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;
          FIndexes[State] := FGlyphList.AddMasked(TmpImage, clDefault);
        end;
    end;
  finally
    TmpImage.Free;
  end;
  Result := FIndexes[State];
  FOriginal.Dormant;
end;

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

procedure TButtonGlyph.DrawButtonGlyph(Canvas: TCanvas; const GlyphPos: TPoint;
  State: TButtonState; Transparent: Boolean);

var
  Index: Integer;

begin
  if Assigned(FOriginal) then
  begin
    if (FOriginal.Width = 0) or (FOriginal.Height = 0) then
      Exit;

    Index := CreateButtonGlyph(State);

    with GlyphPos do
      if Transparent or (State = bsExclusive) then
        ImageList_DrawEx(FGlyphList.Handle, Index, Canvas.Handle, X, Y, 0, 0,
          clNone, clNone, ILD_Transparent)
      else
        ImageList_DrawEx(FGlyphList.Handle, Index, Canvas.Handle, X, Y, 0, 0,
          ColorToRGB(clBtnFace), clNone, ILD_Normal);
  end;
end;

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

procedure TButtonGlyph.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 TButtonGlyph.CalcButtonLayout(Canvas: TCanvas; const Client: TRect;
  const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
  Margin, Spacing: Integer; var GlyphPos: TPoint; var TextBounds: TRect;
  const DropDownWidth: Integer; 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 - DropDownWidth,
    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
  with GlyphPos do
  begin
  //  Inc(X, Client.Left + Offset.X);
 //   Inc(Y, Client.Top + Offset.Y);
  end;
  //OffsetRect(TextBounds, TextPos.X + Client.Left + Offset.X,
 //   TextPos.Y + Client.Top + Offset.X);
end;

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

function TButtonGlyph.Draw(Canvas: TCanvas; const Client: TRect;
  const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
  Margin, Spacing: Integer; State: TButtonState; Transparent: Boolean;
  const DropDownWidth: Integer; BiDiFlags: Longint): TRect;

var
  GlyphPos: TPoint;
  R: TRect;

begin
  CalcButtonLayout(Canvas, Client, Offset, Caption, Layout, Margin, Spacing,
    GlyphPos, R, DropDownWidth, BiDiFlags);
  DrawButtonGlyph(Canvas, GlyphPos, State, Transparent);
  DrawButtonText(Canvas, Caption, R, State, BiDiFlags);

  // return a rectangle wherein the color indicator can be drawn
  if Caption = '' then
  begin
    Result := Client;
    Dec(Result.Right, DropDownWidth + 2);
    InflateRect(Result, -2, -2);

    // consider glyph if no text is to be painted (else it is already taken into account)
    if Assigned(FOriginal) and (FOriginal.Width > 0) and (FOriginal.Height > 0)
    then
      case Layout of
        blGlyphLeft:
          begin
            Result.Left := GlyphPos.X + FOriginal.Width + 4;
            Result.Top := GlyphPos.Y;
            Result.Bottom := GlyphPos.Y + FOriginal.Height;
          end;
        blGlyphRight:
          begin
            Result.Right := GlyphPos.X - 4;
            Result.Top := GlyphPos.Y;
            Result.Bottom := GlyphPos.Y + FOriginal.Height;
          end;
        blGlyphTop:
          Result.Top := GlyphPos.Y + FOriginal.Height + 4;
        blGlyphBottom:
          Result.Bottom := GlyphPos.Y - 4;
      end;
  end
  else
  begin
    // consider caption
    Result := Rect(R.Left, R.Bottom, R.Right, R.Bottom + 6);
    if (Result.Bottom + 2) > Client.Bottom then
      Result.Bottom := Client.Bottom - 2;
  end;
end;

// ----------------- TColorPopup ------------------------------------------------

constructor TColorPopup.Create(AOwner: TComponent);

begin
  inherited;
  ControlStyle := ControlStyle - [csAcceptsControls] + [csOpaque];

  FCanvas := TCanvas.Create;
  Color := clBtnFace;
  ShowHint := True;

  Initialise;
end;

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

procedure TColorPopup.Initialise;

var
  I: Integer;

begin
  FBoxSize := 18;
  FMargin := GetSystemMetrics(SM_CXEDGE);
  FSpacing := 8;
  FHoverIndex := NoCell;
  FSelectedIndex := NoCell;

  // init comb caclulation
  for I := 0 to 5 do
  begin
    FCombCorners[I].X := 0.5 * cos(Pi * (90 - I * 60) / 180);
    FCombCorners[I].Y := 0.5 * sin(Pi * (90 - I * 60) / 180);
  end;
  FRadius := 66;
  FLevels := 7;
  FCombSize := Trunc(FRadius / (FLevels - 1));
  FCenterColor := DefCenterColor;
  FCenterIntensity := 1;
end;

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

destructor TColorPopup.Destroy;

begin
  FBWCombs := nil;
  FColorCombs := nil;
  FCanvas.Free;
  inherited;
end;

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

procedure TColorPopup.CNSysKeyDown(var Message: TWMKeyDown);

// handles accelerator keys

begin
  with Message do
  begin
    if (Length(FDefaultText) > 0) and IsAccel(CharCode, FDefaultText) then
    begin
      ChangeSelection(DefaultCell);
      EndSelection(False);
      Result := 1;
    end
    else if (FSelectedIndex <> CustomCell) and (Length(FCustomText) > 0) and
      IsAccel(CharCode, FCustomText) then
    begin
      ChangeSelection(CustomCell);
      Result := 1;
    end
    else
      inherited;
  end;
end;

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

procedure TColorPopup.CNKeyDown(var Message: TWMKeyDown);

// if an arrow key is pressed, then move the selection

var
  Row, MaxRow, Column: Integer;

begin
  inherited;

  if FHoverIndex <> NoCell then
  begin
    Row := GetRow(FHoverIndex);
    Column := GetColumn(FHoverIndex);
  end
  else
  begin
    Row := GetRow(FSelectedIndex);
    Column := GetColumn(FSelectedIndex);
  end;

  if FShowSysColors then
    MaxRow := DefaultColorCount + SysColorCount - 1
  else
    MaxRow := DefaultColorCount - 1;

  case Message.CharCode of
    VK_DOWN:
      begin
        if Row = DefaultCell then
        begin
          Row := 0;
          Column := 0;
        end
        else if Row = CustomCell then
        begin
          if Length(FDefaultText) > 0 then
          begin
            Row := DefaultCell;
            Column := Row;
          end
          else
          begin
            Row := 0;
            Column := 0;
          end;
        end
        else
        begin
          Inc(Row);
          if GetIndex(Row, Column) < 0 then
          begin
            if Length(FCustomText) > 0 then
            begin
              Row := CustomCell;
              Column := Row;
            end
            else
            begin
              if Length(FDefaultText) > 0 then
              begin
                Row := DefaultCell;
                Column := Row;
              end
              else
              begin
                Row := 0;
                Column := 0;
              end;
            end;
          end;
        end;
        ChangeHoverSelection(GetIndex(Row, Column));
        Message.Result := 1;
      end;

    VK_UP:
      begin
        if Row = DefaultCell then
        begin
          if Length(FCustomText) > 0 then
          begin
            Row := CustomCell;
            Column := Row;
          end
          else
          begin
            Row := GetRow(MaxRow);
            Column := GetColumn(MaxRow);
          end
        end
        else if Row = CustomCell then
        begin
          Row := GetRow(MaxRow);
          Column := GetColumn(MaxRow);
        end
        else if Row > 0 then
          Dec(Row)
        else
        begin
          if Length(FDefaultText) > 0 then
          begin
            Row := DefaultCell;
            Column := Row;
          end
          else if Length(FCustomText) > 0 then
          begin
            Row := CustomCell;
            Column := Row;
          end
          else
          begin
            Row := GetRow(MaxRow);
            Column := GetColumn(MaxRow);
          end;
        end;
        ChangeHoverSelection(GetIndex(Row, Column));
        Message.Result := 1;
      end;

    VK_RIGHT:
      begin
        if Row = DefaultCell then
        begin
          Row := 0;
          Column := 0;
        end
        else if Row = CustomCell then
        begin
          if Length(FDefaultText) > 0 then
          begin
            Row := DefaultCell;
            Column := Row;
          end
          else
          begin
            Row := 0;
            Column := 0;
          end;
        end
        else if Column < FColumnCount - 1 then
          Inc(Column)
        else
        begin
          Column := 0;
          Inc(Row);
        end;

        if GetIndex(Row, Column) = NoCell then
        begin
          if Length(FCustomText) > 0 then
          begin
            Row := CustomCell;
            Column := Row;
          end
          else if Length(FDefaultText) > 0 then
          begin
            Row := DefaultCell;
            Column := Row;
          end
          else
          begin
            Row := 0;
            Column := 0;
          end;
        end;
        ChangeHoverSelection(GetIndex(Row, Column));
        Message.Result := 1;
      end;

    VK_LEFT:
      begin
        if Row = DefaultCell then
        begin
          if Length(FCustomText) > 0 then
          begin
            Row := CustomCell;
            Column := Row;
          end
          else
          begin
            Row := GetRow(MaxRow);
            Column := GetColumn(MaxRow);
          end;
        end
        else if Row = CustomCell then
        begin
          Row := GetRow(MaxRow);
          Column := GetColumn(MaxRow);
        end
        else if Column > 0 then
          Dec(Column)
        else
        begin
          if Row > 0 then
          begin
            Dec(Row);
            Column := FColumnCount - 1;
          end
          else
          begin
            if Length(FDefaultText) > 0 then
            begin
              Row := DefaultCell;
              Column := Row;
            end
            else if Length(FCustomText) > 0 then
            begin
              Row := CustomCell;
              Column := Row;
            end
            else
            begin
              Row := GetRow(MaxRow);
              Column := GetColumn(MaxRow);
            end;
          end;
        end;
        ChangeHoverSelection(GetIndex(Row, Column));
        Message.Result := 1;
      end;

    VK_ESCAPE:
      begin
        EndSelection(True);
        Message.Result := 1;
      end;

    VK_RETURN, VK_SPACE:
      begin
        // this case can only occur if there was no click on the window
        // hence the hover index is the new color
        FSelectedIndex := FHoverIndex;
        EndSelection(False);
        Message.Result := 1;
      end;
  end;
end;

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

procedure TColorPopup.DrawSeparator(Left, Top, Right: Integer);

var
  R: TRect;

begin
  R := Rect(Left, Top, Right, Top);
  DrawEdge(FCanvas.Handle, R, EDGE_ETCHED, BF_TOP);
end;

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

procedure TColorPopup.DrawCell(Index: Integer);

var
  R, MarkRect: TRect;
  CellColor: TColor;

begin
  // for the custom text area
  if (Length(FCustomText) > 0) and (Index = CustomCell) then
  begin
    // the extent of the actual text button
    R := FCustomTextRect;

    // fill background
    FCanvas.Brush.Color := clBtnFace;
    FCanvas.FillRect(R);

    with FCustomTextRect do
      DrawSeparator(Left, Top - 2 * FMargin, Right);

    InflateRect(R, -1, 0);

    // fill background
    if (FSelectedIndex = Index) and (FHoverIndex <> Index) then
      FCanvas.Brush.Color := clBtnHighlight
    else
      FCanvas.Brush.Color := clBtnFace;

    FCanvas.FillRect(R);
    // draw button
    if (FSelectedIndex = Index) or
      ((FHoverIndex = Index) and (csLButtonDown in ControlState)) then
      DrawEdge(FCanvas.Handle, R, BDR_SUNKENOUTER, BF_RECT)
    else if FHoverIndex = Index then
      DrawEdge(FCanvas.Handle, R, BDR_RAISEDINNER, BF_RECT);

    // draw custom text
    DrawText(FCanvas.Handle, PChar(FCustomText), Length(FCustomText), R,
      DT_CENTER or DT_VCENTER or DT_SINGLELINE);

    // draw preview color rectangle
    if FCustomIndex = 0 then
    begin
      FCanvas.Brush.Color := clBtnShadow;
      FCanvas.FrameRect(FCustomColorRect);
    end
    else
    begin
      FCanvas.Pen.Color := clGray;
      if FCustomIndex > 0 then
        FCanvas.Brush.Color := FColorCombs[FCustomIndex - 1].Color
      else
        FCanvas.Brush.Color := FBWCombs[-(FCustomIndex + 1)].Color;
      with FCustomColorRect do
        FCanvas.Rectangle(Left, Top, Right, Bottom);
    end;
  end
  else
    // for the default text area
    if (Length(FDefaultText) > 0) and (Index = DefaultCell) then
    begin
      R := FDefaultTextRect;

      // Fill background
      FCanvas.Brush.Color := clBtnFace;
      FCanvas.FillRect(R);

      InflateRect(R, -1, -1);

      // fill background
      if (FSelectedIndex = Index) and (FHoverIndex <> Index) then
        FCanvas.Brush.Color := clBtnHighlight
      else
        FCanvas.Brush.Color := clBtnFace;

      FCanvas.FillRect(R);
      // draw button
      if (FSelectedIndex = Index) or
        ((FHoverIndex = Index) and (csLButtonDown in ControlState)) then
        DrawEdge(FCanvas.Handle, R, BDR_SUNKENOUTER, BF_RECT)
      else if FHoverIndex = Index then
        DrawEdge(FCanvas.Handle, R, BDR_RAISEDINNER, BF_RECT);

      // draw small rectangle
      with MarkRect do
      begin
        MarkRect := R;
        InflateRect(MarkRect, -FMargin - 1, -FMargin - 1);
        FCanvas.Brush.Color := clBtnShadow;
        FCanvas.FrameRect(MarkRect);
      end;

      // draw default text
      SetBkMode(FCanvas.Handle, Transparent);
      DrawText(FCanvas.Handle, PChar(FDefaultText), Length(FDefaultText), R,
        DT_CENTER or DT_VCENTER or DT_SINGLELINE);
    end
    else
    begin
      if GetCellRect(Index, R) then
      begin
        if Index < DefaultColorCount then
          CellColor := TColor(DefaultColors[Index].Color)
        else
          CellColor := TColor(SysColors[Index - DefaultColorCount].Color);
        FCanvas.Pen.Color := clGray;
        // fill background
        if (FSelectedIndex = Index) and (FHoverIndex <> Index) then
          FCanvas.Brush.Color := clBtnHighlight
        else
          FCanvas.Brush.Color := clBtnFace;
        FCanvas.FillRect(R);

        // draw button
        if (FSelectedIndex = Index) or
          ((FHoverIndex = Index) and (csLButtonDown in ControlState)) then
          DrawEdge(FCanvas.Handle, R, BDR_SUNKENOUTER, BF_RECT)
        else if FHoverIndex = Index then
          DrawEdge(FCanvas.Handle, R, BDR_RAISEDINNER, BF_RECT);

        FCanvas.Brush.Color := CellColor;

        // draw the cell colour
        InflateRect(R, -(FMargin + 1), -(FMargin + 1));
        FCanvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
      end;
    end;
end;

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

procedure TColorPopup.DrawComb(Canvas: TCanvas; X, Y: Integer; Size: Integer);

// draws one single comb at position X, Y and with size Size
// fill color must already be set on call

var
  I: Integer;
  P: array [0 .. 5] of TPoint;

begin
  for I := 0 to 5 do
  begin
    P[I].X := Round(FCombCorners[I].X * Size + X);
    P[I].Y := Round(FCombCorners[I].Y * Size + Y);
  end;
  Canvas.Polygon(P);
end;

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

procedure TColorPopup.DrawCombControls;

var
  I, Index: Integer;
  XOffs, YOffs, Count: Integer;
  dColor: Single;
  OffScreen: TBitmap;
{$IFDEF DEBUG}
  R: TRect;
{$ENDIF}
begin
  // to make the painting (and selecting) flicker free we use an offscreen
  // bitmap here
  OffScreen := TBitmap.Create;
  try
    OffScreen.Width := Width;
    OffScreen.Height := FColorCombRect.Bottom - FColorCombRect.Top +
      FBWCombRect.Bottom - FBWCombRect.Top + 2 * FMargin;

    with OffScreen.Canvas do
    begin
      Brush.Color := clBtnFace;
      FillRect(ClipRect);
      Pen.Style := psClear;
      // draw color comb from FColorCombs array
      XOffs := FRadius + FColorCombRect.Left;
      YOffs := FRadius;

      // draw the combs
      for I := 0 to High(FColorCombs) do
      begin
        Brush.Color := FColorCombs[I].Color;
        DrawComb(OffScreen.Canvas, FColorCombs[I].Position.X + XOffs,
          FColorCombs[I].Position.Y + YOffs, FCombSize);
      end;

      // mark selected comb
      if FCustomIndex > 0 then
      begin
        Index := FCustomIndex - 1;
        Pen.Style := psSolid;
        Pen.Mode := pmXOR;
        Pen.Color := clWhite;
        Pen.Width := 2;
        Brush.Style := bsClear;
        DrawComb(OffScreen.Canvas, FColorCombs[Index].Position.X + XOffs,
          FColorCombs[Index].Position.Y + YOffs, FCombSize);
        Pen.Style := psClear;
        Pen.Mode := pmCopy;
        Pen.Width := 1;
      end;

      // draw white-to-black combs
      XOffs := FColorCombRect.Left;
      YOffs := FColorCombRect.Bottom - FColorCombRect.Top - 4;
      // brush is automatically reset to bsSolid
      for I := 0 to High(FBWCombs) do
      begin
        Brush.Color := FBWCombs[I].Color;
        if I in [0, High(FBWCombs)] then
          DrawComb(OffScreen.Canvas, FBWCombs[I].Position.X + XOffs,
            FBWCombs[I].Position.Y + YOffs, 2 * FCombSize)
        else
          DrawComb(OffScreen.Canvas, FBWCombs[I].Position.X + XOffs,
            FBWCombs[I].Position.Y + YOffs, FCombSize);
      end;

      // mark selected comb
      if FCustomIndex < 0 then
      begin
        Index := -(FCustomIndex + 1);
        Pen.Style := psSolid;
        Pen.Mode := pmXOR;
        Pen.Color := clWhite;
        Pen.Width := 2;
        Brush.Style := bsClear;
        if Index in [0, High(FBWCombs)] then
          DrawComb(OffScreen.Canvas, FBWCombs[Index].Position.X + XOffs,
            FBWCombs[Index].Position.Y + YOffs, 2 * FCombSize)
        else
          DrawComb(OffScreen.Canvas, FBWCombs[Index].Position.X + XOffs,
            FBWCombs[Index].Position.Y + YOffs, FCombSize);
        Pen.Style := psClear;
        Pen.Mode := pmCopy;
        Pen.Width := 1;
      end;

      // center-color trackbar
      XOffs := FSliderRect.Left;
      YOffs := FSliderRect.Top - FColorCombRect.Top;
      Count := FSliderRect.Bottom - FSliderRect.Top - 1;
      dColor := 255 / Count;
      Pen.Style := psSolid;
      // b&w ramp
      for I := 0 to Count do
      begin
        Pen.Color := RGB(Round((Count - I) * dColor),
          Round((Count - I) * dColor), Round((Count - I) * dColor));
        MoveTo(XOffs, YOffs + I);
        LineTo(XOffs + 10, YOffs + I);
      end;

      // marker
      Inc(XOffs, 11);
      Inc(YOffs, Round(Count * (1 - FCenterIntensity)));
      Brush.Color := clBlack;
      Polygon([Point(XOffs, YOffs), Point(XOffs + 5, YOffs - 3),
        Point(XOffs + 5, YOffs + 3)]);

{$IFDEF DEBUG}
      Brush.Color := clRed;
      R := FColorCombRect;
      OffsetRect(R, 0, -FColorCombRect.Top);
      FrameRect(R);
      R := FBWCombRect;
      OffsetRect(R, 0, -FColorCombRect.Top);
      FrameRect(R);
      R := FSliderRect;
      OffsetRect(R, 0, -FColorCombRect.Top);
      FrameRect(R);
{$ENDIF}
      Pen.Style := psClear;
    end;
    // finally put the drawing on the screen
    FCanvas.Draw(0, FColorCombRect.Top, OffScreen);
  finally
    OffScreen.Free;
  end;
end;

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

procedure TColorPopup.WMPaint(var Message: TWMPaint);

var
  PS: TPaintStruct;
  I: Cardinal;
  R: TRect;
  SeparatorTop: Integer;

begin
  if Message.DC = 0 then
    FCanvas.Handle := BeginPaint(Handle, PS)
  else
    FCanvas.Handle := Message.DC;
  try
    // use system default font for popup text
    FCanvas.Font.Handle := GetStockObject(DEFAULT_GUI_FONT);
    if FColorCombs = nil then
      CalculateCombLayout;

    // default area text
    if Length(FDefaultText) > 0 then
      DrawCell(DefaultCell);

    // Draw colour cells
    for I := 0 to DefaultColorCount - 1 do
      DrawCell(I);

    if FShowSysColors then
    begin
      SeparatorTop := FRowCount * FBoxSize + FMargin;
      if Length(FDefaultText) > 0 then
        Inc(SeparatorTop, FDefaultTextRect.Bottom);
      with FCustomTextRect do
        DrawSeparator(FMargin + FSpacing, SeparatorTop,
          Width - FMargin - FSpacing);

      for I := 0 to SysColorCount - 1 do
        DrawCell(I + DefaultColorCount);
    end;

    // Draw custom text
    if Length(FCustomText) > 0 then
      DrawCell(CustomCell);

    if FSelectedIndex = CustomCell then
      DrawCombControls;

    // draw raised window edge (ex-window style WS_EX_WINDOWEDGE is supposed to do this,
    // but for some reason doesn't paint it)
    R := ClientRect;
    DrawEdge(FCanvas.Handle, R, EDGE_RAISED, BF_RECT);
  finally
    FCanvas.Font.Handle := 0; // a stock object never needs to be freed
    FCanvas.Handle := 0;
    if Message.DC = 0 then
      EndPaint(Handle, PS);
  end;
end;

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

function TColorPopup.SelectionFromPoint(P: TPoint): Integer;

// determines the button at the given position

begin
  Result := NoCell;

  // first check we aren't in text box
  if (Length(FCustomText) > 0) and PtInRect(FCustomTextRect, P) then
    Result := CustomCell
  else if (Length(FDefaultText) > 0) and PtInRect(FDefaultTextRect, P) then
    Result := DefaultCell
  else
  begin
    // take into account text box
    if Length(FDefaultText) > 0 then
      Dec(P.Y, FDefaultTextRect.Bottom - FDefaultTextRect.Top);

    // Get the row and column
    if P.X > FSpacing then
    begin
      Dec(P.X, FSpacing);
      // take the margin into account, 2 * FMargin is too small while 3 * FMargin
      // is correct, but looks a bit strange (the arrow corner is so small, it isn't
      // really recognized by the eye) hence I took 2.5 * FMargin
      Dec(P.Y, 5 * FMargin div 2);
      if (P.X >= 0) and (P.Y >= 0) then
      begin
        // consider system colors
        if FShowSysColors and ((P.Y div FBoxSize) >= FRowCount) then
        begin
          // here we know the point is out of the default color area, so
          // take the separator line between default and system colors into account
          Dec(P.Y, 3 * FMargin);
          // if we now are back in the default area then the point was originally
          // between both areas and we have therefore to reject a hit
          if (P.Y div FBoxSize) < FRowCount then
            Exit;
        end;
        Result := GetIndex(P.Y div FBoxSize, P.X div FBoxSize);
      end;
    end;
  end;
end;

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

function TColorPopup.HandleSlider(const Message: TWMMouse): Boolean;

// determines whether the mouse position is within the slider area (result is then True
// else False) and acts accordingly

var
  Shift: TShiftState;
  dY: Integer;
  R: TRect;

begin
  Result := PtInRect(FSliderRect, Point(Message.XPos, Message.YPos)) and
    (FSelectionMode = smNone) or ((Message.XPos >= FSliderRect.Left) and
    (Message.XPos <= FSliderRect.Right) and (FSelectionMode = smRamp));
  if Result then
  begin
    Shift := KeysToShiftState(Message.Keys);
    if ssLeft in Shift then
    begin
      FSelectionMode := smRamp;
      // left mouse button pressed -> change the intensity of the center color comb
      dY := FSliderRect.Bottom - FSliderRect.Top;
      FCenterIntensity := 1 - (Message.YPos - FSliderRect.Top) / dY;
      if FCenterIntensity < 0 then
        FCenterIntensity := 0;
      if FCenterIntensity > 1 then
        FCenterIntensity := 1;
      FCenterColor.Red := DefCenterColor.Red * FCenterIntensity;
      FCenterColor.Green := DefCenterColor.Green * FCenterIntensity;
      FCenterColor.Blue := DefCenterColor.Blue * FCenterIntensity;
      R := FSliderRect;
      Dec(R.Top, 3);
      Inc(R.Bottom, 3);
      Inc(R.Left, 10);
      InvalidateRect(Handle, @R, False);
      FColorCombs := nil;
      InvalidateRect(Handle, @FColorCombRect, False);
      InvalidateRect(Handle, @FCustomColorRect, False);
      UpdateWindow(Handle);
    end;
  end;
end;

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

function TColorPopup.PtInComb(Comb: TCombEntry; P: TPoint;
  Scale: Integer): Boolean;

// simplyfied "PointInPolygon" test, we know a comb is "nearly" a circle...

begin
  Result := (Sqr(Comb.Position.X - P.X) + Sqr(Comb.Position.Y - P.Y)) <=
    (Scale * Scale);
end;

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

function TColorPopup.FindBWArea(X, Y: Integer): Integer;

// Looks for a comb at position (X, Y) in the black&white area.
// Result is -1 if nothing could be found else the index of the particular comb
// into FBWCombs.

var
  I: Integer;
  Pt: TPoint;
  Scale: Integer;

begin
  Result := -1;
  Pt := Point(X - FBWCombRect.Left, Y - FBWCombRect.Top);

  for I := 0 to High(FBWCombs) do
  begin
    if I in [0, High(FBWCombs)] then
      Scale := FCombSize
    else
      Scale := FCombSize div 2;
    if PtInComb(FBWCombs[I], Pt, Scale) then
    begin
      Result := I;
      Break;
    end;
  end;
end;

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

function TColorPopup.HandleBWArea(const Message: TWMMouse): Boolean;

// determines whether the mouse position is within the B&W comb area (result is then True
// else False) and acts accordingly

var
  Index: Integer;
  Shift: TShiftState;

begin
  Result := PtInRect(FBWCombRect, Point(Message.XPos, Message.YPos)) and
    (FSelectionMode in [smNone, smBW]);
  if Result then
  begin
    Shift := KeysToShiftState(Message.Keys);
    if ssLeft in Shift then
    begin
      FSelectionMode := smBW;
      Index := FindBWArea(Message.XPos, Message.YPos);

      if Index > -1 then
      begin
        // remove selection comb if it was previously in color comb
        if FCustomIndex > 0 then
          InvalidateRect(Handle, @FColorCombRect, False);
        if FCustomIndex <> -(Index + 1) then
        begin
          FCustomIndex := -(Index + 1);
          InvalidateRect(Handle, @FBWCombRect, False);
          InvalidateRect(Handle, @FCustomColorRect, False);
          UpdateWindow(Handle);
        end;
      end
      else
        Result := False;
    end;
  end;
end;

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

function TColorPopup.FindColorArea(X, Y: Integer): Integer;

// Looks for a comb at position (X, Y) in the custom color area.
// Result is -1 if nothing could be found else the index of the particular comb
// into FColorCombs.

var
  I: Integer;
  Pt: TPoint;

begin
  Result := -1;
  Pt := Point(X - (FRadius + FColorCombRect.Left),
    Y - (FRadius + FColorCombRect.Top));

  for I := 0 to High(FColorCombs) do
  begin
    if PtInComb(FColorCombs[I], Pt, FCombSize div 2) then
    begin
      Result := I;
      Break;
    end;
  end;
end;

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

function TColorPopup.HandleColorComb(const Message: TWMMouse): Boolean;

// determines whether the mouse position is within the color comb area (result is then True
// else False) and acts accordingly

var
  Index: Integer;
  Shift: TShiftState;

begin
  Result := PtInRect(FColorCombRect, Point(Message.XPos, Message.YPos)) and
    (FSelectionMode in [smNone, smColor]);
  if Result then
  begin
    Shift := KeysToShiftState(Message.Keys);
    if ssLeft in Shift then
    begin
      FSelectionMode := smColor;
      Index := FindColorArea(Message.XPos, Message.YPos);
      if Index > -1 then
      begin
        // remove selection comb if it was previously in b&w comb
        if FCustomIndex < 0 then
          InvalidateRect(Handle, @FBWCombRect, False);
        if FCustomIndex <> (Index + 1) then
        begin
          FCustomIndex := Index + 1;
          InvalidateRect(Handle, @FColorCombRect, False);
          InvalidateRect(Handle, @FCustomColorRect, False);
          UpdateWindow(Handle);
        end;
      end
      else
        Result := False;
    end;
  end;
end;

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

procedure TColorPopup.HandleCustomColors(var Message: TWMMouse);

begin
  if not HandleSlider(Message) then
    if not HandleBWArea(Message) then
      if not HandleColorComb(Message) then
      begin
        // user has clicked somewhere else, so remove last custom selection
        if FCustomIndex > 0 then
          InvalidateRect(Handle, @FColorCombRect, False)
        else if FCustomIndex < 0 then
          InvalidateRect(Handle, @FBWCombRect, False);

        InvalidateRect(Handle, @FCustomColorRect, False);
        FCustomIndex := 0;
        UpdateWindow(Handle);
      end;
end;

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

procedure TColorPopup.WMMouseMove(var Message: TWMMouseMove);

var
  NewSelection: Integer;

begin
  inherited;
  // determine new hover index
  NewSelection := SelectionFromPoint(Point(Message.XPos, Message.YPos));

  if NewSelection <> FHoverIndex then
    ChangeHoverSelection(NewSelection);
  if (NewSelection = -1) and PtInRect(ClientRect,
    Point(Message.XPos, Message.YPos)) and (csLButtonDown in ControlState) then
    HandleCustomColors(Message);
end;

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

procedure TColorPopup.WMLButtonDown(var Message: TWMLButtonDown);

begin
  inherited;

  if PtInRect(ClientRect, Point(Message.XPos, Message.YPos)) then
  begin

    if FHoverIndex <> NoCell then
    begin
      InvalidateCell(FHoverIndex);
      UpdateWindow(Handle);
    end;

    if FHoverIndex = -1 then
      HandleCustomColors(Message);
  end
  else
    EndSelection(True); // hide popup window if the user has clicked elsewhere
end;

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

procedure TColorPopup.ShowPopupAligned;

var
  Pt: TPoint;
  Parent: TColorPickerButton;
  ParentTop: Integer;
  R: TRect;
  H: Integer;

begin
  HandleNeeded;
  if FSelectedIndex = CustomCell then
  begin
    // make room for the custem color picking area
    R := Rect(FWindowRect.Left, FWindowRect.Bottom - 3, FWindowRect.Right,
      FWindowRect.Bottom);
    H := FBWCombRect.Bottom + 2 * FMargin;
  end
  else
  begin
    // hide the custem color picking area
    R := Rect(FWindowRect.Left, FWindowRect.Bottom - 3, FWindowRect.Right,
      FWindowRect.Bottom);
    H := FWindowRect.Bottom;
  end;
  // to ensure the window frame is drawn correctly we invalidate the lower bound explicitely
  InvalidateRect(Handle, @R, True);

  // Make sure the window is still entirely visible and aligned.
  // There's no VCL parent window as this popup is a child of the desktop,
  // but we have the owner and get the parent from this.
  Parent := TColorPickerButton(Owner);
  Pt := Parent.Parent.ClientToScreen(Point(Parent.Left - 1,
    Parent.Top + Parent.Height));
  if (Pt.Y + H) > Screen.Height then
    Pt.Y := Screen.Height - H;
  ParentTop := Parent.Parent.ClientToScreen(Point(Parent.Left, Parent.Top)).Y;
  if Pt.Y < ParentTop then
    Pt.Y := ParentTop - H;
  if (Pt.X + Width) > Screen.Width then
    Pt.X := Screen.Width - Width;
  if Pt.X < 0 then
    Pt.X := 0;
  SetWindowPos(Handle, HWND_TOPMOST, Pt.X, Pt.Y, FWindowRect.Right, H,
    SWP_SHOWWINDOW);
end;

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

procedure TColorPopup.ChangeSelection(NewSelection: Integer);

begin
  if NewSelection <> NoCell then
  begin
    if FSelectedIndex <> NoCell then
      InvalidateCell(FSelectedIndex);
    FSelectedIndex := NewSelection;
    if FSelectedIndex <> NoCell then
      InvalidateCell(FSelectedIndex);

    if FSelectedIndex = CustomCell then
      ShowPopupAligned;
  end;
end;

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

procedure TColorPopup.WMLButtonUp(var Message: TWMLButtonUp);

var
  NewSelection: Integer;
  LastMode: TSelectionMode;

begin
  inherited;
  // determine new selection index
  NewSelection := SelectionFromPoint(Point(Message.XPos, Message.YPos));
  LastMode := FSelectionMode;
  FSelectionMode := smNone;
  if (NewSelection <> NoCell) or ((FSelectedIndex = CustomCell) and
    (FCustomIndex <> 0)) then
  begin
    ChangeSelection(NewSelection);
    if ((FSelectedIndex = CustomCell) and (LastMode in [smColor, smBW])) or
      (FSelectedIndex <> NoCell) and (FSelectedIndex <> CustomCell) then
      EndSelection(False)
    else
      SetCapture(TColorPickerButton(Owner).FPopupWnd);
  end
  else
    // we need to restore the mouse capturing, else the utility window will loose it
    // (safety feature of Windows?)
    SetCapture(TColorPickerButton(Owner).FPopupWnd);
end;

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

function TColorPopup.GetIndex(Row, Col: Integer): Integer;

begin
  Result := NoCell;
  if ((Row = CustomCell) or (Col = CustomCell)) and (Length(FCustomText) > 0)
  then
    Result := CustomCell
  else if ((Row = DefaultCell) or (Col = DefaultCell)) and
    (Length(FDefaultText) > 0) then
    Result := DefaultCell
  else if (Col in [0 .. FColumnCount - 1]) and (Row >= 0) then
  begin

    if Row < FRowCount then
    begin
      Result := Row * FColumnCount + Col;
      // consider not fully filled last row
      if Result >= DefaultColorCount then
        Result := NoCell;
    end
    else if FShowSysColors then
    begin
      Dec(Row, FRowCount);
      if Row < FSysRowCount then
      begin
        Result := Row * FColumnCount + Col;
        // consider not fully filled last row
        if Result >= SysColorCount then
          Result := NoCell
        else
          Inc(Result, DefaultColorCount);
      end;
    end;
  end;
end;

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

function TColorPopup.GetRow(Index: Integer): Integer;

begin
  if (Index = CustomCell) and (Length(FCustomText) > 0) then
    Result := CustomCell
  else if (Index = DefaultCell) and (Length(FDefaultText) > 0) then
    Result := DefaultCell
  else
    Result := Index div FColumnCount;
end;

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

function TColorPopup.GetColumn(Index: Integer): Integer;

begin
  if (Index = CustomCell) and (Length(FCustomText) > 0) then
    Result := CustomCell
  else if (Index = DefaultCell) and (Length(FDefaultText) > 0) then
    Result := DefaultCell
  else
    Result := Index mod FColumnCount;
end;

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

procedure TColorPopup.SelectColor(Color: TColor);

// looks up the given color in our lists and sets the proper indices

var
  I: Integer;
  C: COLORREF;
  found: Boolean;

begin
  found := False;

  // handle special colors first
  if Color = clNone then
    FSelectedIndex := NoCell
  else if Color = clDefault then
    FSelectedIndex := DefaultCell
  else
  begin
    // if the incoming color is one of the predefined colors (clBtnFace etc.) and
    // system colors are active then start looking in the system color list
    if FShowSysColors and (Color < 0) then
    begin
      for I := 0 to SysColorCount - 1 do
        if TColor(SysColors[I].Color) = Color then
        begin
          FSelectedIndex := I + DefaultColorCount;
          found := True;
          Break;
        end;
    end;

    if not found then
    begin
      C := ColorToRGB(Color);
      for I := 0 to DefaultColorCount - 1 do
        // only Borland knows why the result of ColorToRGB is Longint not COLORREF,
        // in order to make the compiler quiet I need a Longint cast here
        if ColorToRGB(DefaultColors[I].Color) = Longint(C) then
        begin
          FSelectedIndex := I;
          found := True;
          Break;
        end;

      // look in the system colors if not already done yet
      if not found and FShowSysColors and (Color >= 0) then
      begin
        for I := 0 to SysColorCount - 1 do
        begin
          if ColorToRGB(TColor(SysColors[I].Color)) = Longint(C) then
          begin
            FSelectedIndex := I + DefaultColorCount;
            found := True;
            Break;
          end;
        end;
      end;

      if not found then
      begin
        if FColorCombs = nil then
          CalculateCombLayout;
        FCustomIndex := 0;
        FSelectedIndex := NoCell;
        for I := 0 to High(FBWCombs) do
          if FBWCombs[I].Color = C then
          begin
            FSelectedIndex := CustomCell;
            FCustomIndex := -(I + 1);
            found := True;
            Break;
          end;

        if not found then
          for I := 0 to High(FColorCombs) do
            if FColorCombs[I].Color = C then
            begin
              FSelectedIndex := CustomCell;
              FCustomIndex := I + 1;
              Break;
            end;
      end;
    end;
  end;
end;

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

function TColorPopup.GetCellRect(Index: Integer; var Rect: TRect): Boolean;

// gets the dimensions of the colour cell given by Index

begin
  Result := False;
  if Index = CustomCell then
  begin
    Rect := FCustomTextRect;
    Result := True;
  end
  else if Index = DefaultCell then
  begin
    Rect := FDefaultTextRect;
    Result := True;
  end
  else if Index >= 0 then
  begin
    Rect.Left := GetColumn(Index) * FBoxSize + FMargin + FSpacing;
    Rect.Top := GetRow(Index) * FBoxSize + 2 * FMargin;

    // move everything down if we are displaying a default text area
    if Length(FDefaultText) > 0 then
      Inc(Rect.Top, FDefaultTextRect.Bottom - 2 * FMargin);

    // move everything further down if we consider syscolors
    if Index >= DefaultColorCount then
      Inc(Rect.Top, 3 * FMargin);

    Rect.Right := Rect.Left + FBoxSize;
    Rect.Bottom := Rect.Top + FBoxSize;

    Result := True;
  end;
end;

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

procedure TColorPopup.AdjustWindow;

// works out an appropriate size and position of this window

var
  TextSize, DefaultSize: TSize;
  DC: HDC;
  WHeight: Integer;

begin
  // If we are showing a custom or default text area, get the font and text size.
  if (Length(FCustomText) > 0) or (Length(FDefaultText) > 0) then
  begin
    DC := GetDC(Handle);
    FCanvas.Handle := DC;
    FCanvas.Font.Handle := GetStockObject(DEFAULT_GUI_FONT);
    try
      // Get the size of the custom text (if there IS custom text)
      TextSize.cx := 0;
      TextSize.cy := 0;
      if Length(FCustomText) > 0 then
        TextSize := FCanvas.TextExtent(FCustomText);

      // Get the size of the default text (if there IS default text)
      if Length(FDefaultText) > 0 then
      begin
        DefaultSize := FCanvas.TextExtent(FDefaultText);
        if DefaultSize.cx > TextSize.cx then
          TextSize.cx := DefaultSize.cx;
        if DefaultSize.cy > TextSize.cy then
          TextSize.cy := DefaultSize.cy;
      end;

      Inc(TextSize.cx, 2 * FMargin);
      Inc(TextSize.cy, 4 * FMargin + 2);

    finally
      FCanvas.Font.Handle := 0;
      FCanvas.Handle := 0;
      ReleaseDC(Handle, DC);
    end;
  end;

  // Get the number of columns and rows
  FColumnCount := 8;
  FRowCount := DefaultColorCount div FColumnCount;
  if (DefaultColorCount mod FColumnCount) <> 0 then
    Inc(FRowCount);

  FWindowRect := Rect(0, 0, FColumnCount * FBoxSize + 2 * FMargin + 2 *
    FSpacing, FRowCount * FBoxSize + 4 * FMargin);

  FRadius := Trunc(7 * (FColumnCount * FBoxSize) / 16);
  FCombSize := Round(0.5 + FRadius / (FLevels - 1));

  // if default text, then expand window if necessary, and set text width as
  // window width
  if Length(FDefaultText) > 0 then
  begin
    if TextSize.cx > (FWindowRect.Right - FWindowRect.Left) then
      FWindowRect.Right := FWindowRect.Left + TextSize.cx;
    TextSize.cx := FWindowRect.Right - FWindowRect.Left - 2 * FMargin;

    // work out the text area
    FDefaultTextRect := Rect(FMargin + FSpacing, 2 * FMargin,
      FMargin - FSpacing + TextSize.cx, 2 * FMargin + TextSize.cy);
    Inc(FWindowRect.Bottom, FDefaultTextRect.Bottom - FDefaultTextRect.Top + 2
      * FMargin);
  end;

  if FShowSysColors then
  begin
    FSysRowCount := SysColorCount div FColumnCount;
    if (SysColorCount mod FColumnCount) <> 0 then
      Inc(FSysRowCount);
    Inc(FWindowRect.Bottom, FSysRowCount * FBoxSize + 2 * FMargin);
  end;

  // if custom text, then expand window if necessary, and set text width as
  // window width
  if Length(FCustomText) > 0 then
  begin
    if TextSize.cx > (FWindowRect.Right - FWindowRect.Left) then
      FWindowRect.Right := FWindowRect.Left + TextSize.cx;
    TextSize.cx := FWindowRect.Right - FWindowRect.Left - 2 * FMargin;

    // work out the text area
    WHeight := FWindowRect.Bottom - FWindowRect.Top;
    FCustomTextRect := Rect(FMargin + FSpacing, WHeight,
      FMargin - FSpacing + TextSize.cx, WHeight + TextSize.cy);
    // precalculate also the small preview box for custom color selection for fast updates
    FCustomColorRect := Rect(0, 0, FBoxSize, FBoxSize);
    InflateRect(FCustomColorRect, -(FMargin + 1), -(FMargin + 1));
    OffsetRect(FCustomColorRect, FCustomTextRect.Right - FBoxSize - FMargin,
      FCustomTextRect.Top + (FCustomTextRect.Bottom - FCustomTextRect.Top -
      FCustomColorRect.Bottom - FMargin - 1) div 2);

    Inc(FWindowRect.Bottom, FCustomTextRect.Bottom - FCustomTextRect.Top + 2
      * FMargin);
  end;

  // work out custom color choice area (color combs) (FWindowRect covers only the always visible part)
  FColorCombRect := Rect(FMargin + FSpacing, FWindowRect.Bottom,
    FMargin + FSpacing + 2 * FRadius, FWindowRect.Bottom + 2 * FRadius);
  // work out custom color choice area (b&w combs)
  FBWCombRect := Rect(FColorCombRect.Left, FColorCombRect.Bottom - 4,
    Round(17 * FCombSize * cos(Pi / 6) / 2) + 6 * FCombSize,
    FColorCombRect.Bottom + 2 * FCombSize);
  // work out slider area
  FSliderRect := Rect(FColorCombRect.Right, FColorCombRect.Top + FCombSize,
    FColorCombRect.Right + 20, FColorCombRect.Bottom - FCombSize);

  // set the window size
  with FWindowRect do
    SetBounds(Left, Top, Right - Left, Bottom - Top);
end;

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

procedure TColorPopup.ChangeHoverSelection(Index: Integer);

begin
  if not FShowSysColors and (Index >= DefaultColorCount) or
    (Index >= (DefaultColorCount + SysColorCount)) then
    Index := NoCell;

  // remove old hover selection
  InvalidateCell(FHoverIndex);

  FHoverIndex := Index;
  InvalidateCell(FHoverIndex);
  UpdateWindow(Handle);
end;

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

procedure TColorPopup.EndSelection(Cancel: Boolean);

begin
  with Owner as TColorPickerButton do
  begin
    if not Cancel then
    begin
      if FSelectedIndex > -1 then
        if FSelectedIndex < DefaultColorCount then
          SelectionColor := TColor(DefaultColors[FSelectedIndex].Color)
        else
          SelectionColor :=
            TColor(SysColors[FSelectedIndex - DefaultColorCount].Color)
      else if FSelectedIndex = CustomCell then
      begin
        if FCustomIndex < 0 then
          SelectionColor := FBWCombs[-(FCustomIndex + 1)].Color
        else if FCustomIndex > 0 then
          SelectionColor := FColorCombs[FCustomIndex - 1].Color;
      end
      else
        DoDefaultEvent;
    end;
    DroppedDown := False;
  end;
end;

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

procedure TColorPopup.WMKillFocus(var Message: TWMKillFocus);

begin
  inherited;
  (Owner as TColorPickerButton).DroppedDown := False;
end;

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

procedure TColorPopup.CalculateCombLayout;

// fills arrays with centers and colors for the custom color and black & white combs,
// these arrays are used to quickly draw the combx and do hit tests

// --------------- local functions -----------------------

  function RGBFromFloat(Color: TRGB): COLORREF;

  begin
    Result := RGB(Round(255 * Color.Red), Round(255 * Color.Green),
      Round(255 * Color.Blue));
  end;

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

  function GrayFromIntensity(Intensity: Byte): COLORREF;

  begin
    Result := RGB(Intensity, Intensity, Intensity);
  end;

// --------------- end local functions -------------------

var
  CurrentIndex: Cardinal;
  CurrentColor: TRGB;
  CurrentPos: TFloatPoint;
  CombCount: Cardinal;
  I, J, Level: Cardinal;
  Scale: Extended;

  // triangle vars
  Pos1, Pos2: TFloatPoint;
  dPos1, dPos2: TFloatPoint;
  Color1, Color2: TRGB;
  dColor1, dColor2: TRGB;
  dPos: TFloatPoint;
  dColor: TRGB;

begin
  // this ensures the radius and comb size is set correctly
  HandleNeeded;
  if FLevels < 1 then
    FLevels := 1;
  // To draw perfectly aligned combs we split the final comb into six triangles (sextants)
  // and calculate each separately. The center comb is stored as first entry in the array
  // and will not considered twice (as with the other shared combs too).
  //
  // The way used here for calculation of the layout seems a bit complicated, but works
  // correctly for all cases (even if the comb corners are rotated).

  // initialization
  CurrentIndex := 0;
  CurrentColor := FCenterColor;

  // number of combs can be calculated by:
  // 1 level: 1 comb (the center)
  // 2 levels: 1 comb + 6 combs
  // 3 levels: 1 comb + 1 * 6 combs + 2 * 6 combs
  // n levels: 1 combs + 1 * 6 combs + 2 * 6 combs + .. + (n-1) * 6 combs
  // this equals to 1 + 6 * (1 + 2 + 3 + .. + (n-1)), by using Gauss' famous formula we get:
  // Count = 1 + 6 * (((n-1) * n) / 2)
  // Because there's always an even number involved (either n or n-1) we can use an integer div
  // instead of a float div here...
  CombCount := 1 + 6 * (((FLevels - 1) * FLevels) div 2);
  SetLength(FColorCombs, CombCount);

  // store center values
  FColorCombs[CurrentIndex].Position := Point(0, 0);
  FColorCombs[CurrentIndex].Color := RGBFromFloat(CurrentColor);
  Inc(CurrentIndex);

  // go out off here if there are not further levels to draw
  if FLevels < 2 then
    Exit;

  // now go for each sextant, the generic corners have been calculated already at creation
  // time for a comb with diameter 1
  // ------
  // /\  1 /\
  // /  \  /  \
  // / 2  \/  0 \
  // -----------
  // \ 3  /\  5 /
  // \  /  \  /
  // \/  4 \/
  // ------

  for I := 0 to 5 do
  begin
    // initialize triangle corner values
    //
    // center (always at 0,0)
    // /\
    // dPos1      /  \    dPos2
    // dColor1   /    \   dColor2
    // / dPos \
    // /--------\ (span)
    // /  dColor  \
    // /____________\
    // comb corner 1     comb corner 2
    //
    // Pos1, Pos2, Color1, Color2 are running terms for both sides of the triangle
    // incremented by dPos1/2 and dColor1/2.
    // dPos and dColor are used to interpolate a span between the values just mentioned.
    //
    // The small combs are actually oriented with corner 0 at top (i.e. mirrored at y = x,
    // compared with the values in FCombCorners), we can achieve that by simply exchanging
    // X and Y values.

    Scale := 2 * FRadius * cos(Pi / 6);
    Pos1.X := FCombCorners[I].Y * Scale;
    Pos1.Y := FCombCorners[I].X * Scale;
    Color1 := DefColors[I];
    if I = 5 then
    begin
      Pos2.X := FCombCorners[0].Y * Scale;
      Pos2.Y := FCombCorners[0].X * Scale;
      Color2 := DefColors[0];
    end
    else
    begin
      Pos2.X := FCombCorners[I + 1].Y * Scale;
      Pos2.Y := FCombCorners[I + 1].X * Scale;
      Color2 := DefColors[I + 1];
    end;
    dPos1.X := Pos1.X / (FLevels - 1);
    dPos1.Y := Pos1.Y / (FLevels - 1);
    dPos2.X := Pos2.X / (FLevels - 1);
    dPos2.Y := Pos2.Y / (FLevels - 1);

    dColor1.Red := (Color1.Red - FCenterColor.Red) / (FLevels - 1);
    dColor1.Green := (Color1.Green - FCenterColor.Green) / (FLevels - 1);
    dColor1.Blue := (Color1.Blue - FCenterColor.Blue) / (FLevels - 1);

    dColor2.Red := (Color2.Red - FCenterColor.Red) / (FLevels - 1);
    dColor2.Green := (Color2.Green - FCenterColor.Green) / (FLevels - 1);
    dColor2.Blue := (Color2.Blue - FCenterColor.Blue) / (FLevels - 1);

    Pos1 := DefCenter;
    Pos2 := DefCenter;
    Color1 := FCenterColor;
    Color2 := FCenterColor;

    // Now that we have finished the initialization for this step we'll go
    // through a loop for each level to calculate the spans.
    // We can ignore level 0 (as this is the center we already have determined) as well
    // as the last step of each span (as this is the start value in the next triangle and will
    // be calculated there). We have, though, take them into the calculation of the running terms.
    for Level := 0 to FLevels - 1 do
    begin
      if Level > 0 then
      begin
        // initialize span values
        dPos.X := (Pos2.X - Pos1.X) / Level;
        dPos.Y := (Pos2.Y - Pos1.Y) / Level;
        dColor.Red := (Color2.Red - Color1.Red) / Level;
        dColor.Green := (Color2.Green - Color1.Green) / Level;
        dColor.Blue := (Color2.Blue - Color1.Blue) / Level;
        CurrentPos := Pos1;
        CurrentColor := Color1;

        for J := 0 to Level - 1 do
        begin
          // store current values in the array
          FColorCombs[CurrentIndex].Position.X := Round(CurrentPos.X);
          FColorCombs[CurrentIndex].Position.Y := Round(CurrentPos.Y);
          FColorCombs[CurrentIndex].Color := RGBFromFloat(CurrentColor);
          Inc(CurrentIndex);

          // advance in span
          CurrentPos.X := CurrentPos.X + dPos.X;
          CurrentPos.Y := CurrentPos.Y + dPos.Y;

          CurrentColor.Red := CurrentColor.Red + dColor.Red;
          CurrentColor.Green := CurrentColor.Green + dColor.Green;
          CurrentColor.Blue := CurrentColor.Blue + dColor.Blue;
        end;
      end;
      // advance running terms
      Pos1.X := Pos1.X + dPos1.X;
      Pos1.Y := Pos1.Y + dPos1.Y;
      Pos2.X := Pos2.X + dPos2.X;
      Pos2.Y := Pos2.Y + dPos2.Y;

      Color1.Red := Color1.Red + dColor1.Red;
      Color1.Green := Color1.Green + dColor1.Green;
      Color1.Blue := Color1.Blue + dColor1.Blue;

      Color2.Red := Color2.Red + dColor2.Red;
      Color2.Green := Color2.Green + dColor2.Green;
      Color2.Blue := Color2.Blue + dColor2.Blue;
    end;
  end;

  // second step is to build a list for the black & white area
  // 17 entries from pure white to pure black
  // the first and last are implicitely of double comb size
  SetLength(FBWCombs, 17);
  CurrentIndex := 0;
  FBWCombs[CurrentIndex].Color := GrayFromIntensity(255);
  FBWCombs[CurrentIndex].Position := Point(FCombSize, FCombSize);
  Inc(CurrentIndex);

  CurrentPos.X := 3 * FCombSize;
  CurrentPos.Y := 3 * (FCombSize div 4);
  dPos.X := Round(FCombSize * cos(Pi / 6) / 2);
  dPos.Y := Round(FCombSize * (1 + sin(Pi / 6)) / 2);
  for I := 0 to 14 do
  begin
    FBWCombs[CurrentIndex].Color := GrayFromIntensity((16 - CurrentIndex) * 15);
    if Odd(I) then
      FBWCombs[CurrentIndex].Position := Point(Round(CurrentPos.X + I * dPos.X),
        Round(CurrentPos.Y + dPos.Y))
    else
      FBWCombs[CurrentIndex].Position := Point(Round(CurrentPos.X + I * dPos.X),
        Round(CurrentPos.Y));
    Inc(CurrentIndex);
  end;
  FBWCombs[CurrentIndex].Color := 0;
  FBWCombs[CurrentIndex].Position :=
    Point(Round(CurrentPos.X + 16 * dPos.X + FCombSize), FCombSize);
end;

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

procedure TColorPopup.CreateParams(var Params: TCreateParams);

begin
  inherited CreateParams(Params);
  with Params do
  begin
    WndParent := GetDesktopWindow;
    Style := WS_CLIPSIBLINGS or WS_CHILD;
    ExStyle := WS_EX_TOPMOST or WS_EX_TOOLWINDOW;
    WindowClass.Style := CS_DBLCLKS or CS_SAVEBITS;
  end;
end;

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

procedure TColorPopup.CreateWnd;

begin
  inherited;
  AdjustWindow;
end;

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

procedure TColorPopup.SetSpacing(Value: Integer);

begin
  if Value < 0 then
    Value := 0;
  if FSpacing <> Value then
  begin
    FSpacing := Value;
    Invalidate;
  end;
end;

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

procedure TColorPopup.InvalidateCell(Index: Integer);

var
  R: TRect;

begin
  if GetCellRect(Index, R) then
    InvalidateRect(Handle, @R, False);
end;

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

function TColorPopup.GetHint(Cell: Integer): String;

begin
  Result := '';
  if Assigned(TColorPickerButton(Owner).FOnHint) then
    TColorPickerButton(Owner).FOnHint(Owner, Cell, Result);
end;

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

procedure TColorPopup.CMHintShow(var Message: TMessage);

// determine hint message (tooltip) and out-of-hint rect

var
  Index: Integer;
  R, G, B: Byte;
  Colors: TCombArray;

begin
  Colors := nil;
  with TCMHintShow(Message) do
  begin
    if not TColorPickerButton(Owner).ShowHint then
      Message.Result := 1
    else
    begin
      with HintInfo^ do
      begin
        // show that we want a hint
        Result := 0;
        // predefined colors always get their names as tooltip
        if FHoverIndex >= 0 then
        begin
          GetCellRect(FHoverIndex, CursorRect);
          if FHoverIndex < DefaultColorCount then
            HintStr := DefaultColors[FHoverIndex].Name
          else
            HintStr := SysColors[FHoverIndex - DefaultColorCount].Name;
        end
        else
          // both special cells get their hint either from the application by
          // means of the OnHint event or the hint string of the owner control
          if (FHoverIndex = DefaultCell) or (FHoverIndex = CustomCell) then
          begin
            HintStr := GetHint(FHoverIndex);
            if HintStr = '' then
              HintStr := TColorPickerButton(Owner).Hint
            else
            begin
              // if the application supplied a hint by event then deflate the cursor rect
              // to the belonging button
              if FHoverIndex = DefaultCell then
                CursorRect := FDefaultTextRect
              else
                CursorRect := FCustomTextRect;
            end;
          end
          else
          begin
            // well, mouse is not hovering over one of the buttons, now check for
            // the ramp and the custom color areas
            if PtInRect(FSliderRect, Point(CursorPos.X, CursorPos.Y)) then
            begin
              // in case of the intensity slider we show the current intensity
              HintStr := Format('Intensity: %d%%',
                [Round(100 * FCenterIntensity)]);
              CursorRect := Rect(FSliderRect.Left, CursorPos.Y - 2,
                FSliderRect.Right, CursorPos.Y + 2);
              HintPos := ClientToScreen(Point(FSliderRect.Right,
                CursorPos.Y - 8));
              HideTimeout := 5000;
              CursorRect := Rect(FSliderRect.Left, CursorPos.Y,
                FSliderRect.Right, CursorPos.Y);
            end
            else
            begin
              Index := -1;
              if PtInRect(FBWCombRect, Point(CursorPos.X, CursorPos.Y)) then
              begin
                // considering black&white area...
                if csLButtonDown in ControlState then
                  Index := -(FCustomIndex + 1)
                else
                  Index := FindBWArea(CursorPos.X, CursorPos.Y);
                Colors := FBWCombs;
              end
              else if PtInRect(FColorCombRect, Point(CursorPos.X, CursorPos.Y))
              then
              begin
                // considering color comb area...
                if csLButtonDown in ControlState then
                  Index := FCustomIndex - 1
                else
                  Index := FindColorArea(CursorPos.X, CursorPos.Y);
                Colors := FColorCombs;
              end;

              if (Index > -1) and (Colors <> nil) then
              begin
                with Colors[Index] do
                begin
                  R := GetRValue(Color);
                  G := GetGValue(Color);
                  B := GetBValue(Color);
                end;
                HintStr := Format('red: %d, green: %d, blue: %d', [R, G, B]);
                HideTimeout := 5000;
              end
              else
                HintStr := GetHint(NoCell);

              // make the hint follow the mouse
              CursorRect := Rect(CursorPos.X, CursorPos.Y, CursorPos.X,
                CursorPos.Y);
            end;
          end;
      end;
    end;
  end;
end;

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

procedure TColorPopup.SetSelectedColor(const Value: TColor);

begin
  FCurrentColor := Value;
  SelectColor(Value);
end;

// ----------------- TColorPickerButton ------------------------------------------

constructor TColorPickerButton.Create(AOwner: TComponent);

begin
  inherited Create(AOwner);
  FSelectionColor := clBlack;
  FColorPopup := TColorPopup.Create(Self);
  // park the window somewhere it can't be seen
  FColorPopup.Left := -1000;
  FPopupWnd := AllocateHWnd(PopupWndProc);

  FGlyph := TButtonGlyph.Create;
  TButtonGlyph(FGlyph).OnChange := GlyphChanged;
  SetBounds(0, 0, 45, 22);
  FDropDownWidth := 15;
  ControlStyle := [csCaptureMouse, csDoubleClicks];
  ParentFont := True;
  Color := clBtnFace;
  FSpacing := 4;
  FMargin := -1;
  FLayout := blGlyphLeft;
  FTransparent := True;
  FIndicatorBorder := ibFlat;

  Inc(ButtonCount);
end;

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

destructor TColorPickerButton.Destroy;

begin
  DeallocateHWnd(FPopupWnd);
  Dec(ButtonCount);
  // the color popup window will automatically be freed since the button is the owner
  // of the popup
  TButtonGlyph(FGlyph).Free;
  inherited Destroy;
end;

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

procedure TColorPickerButton.PopupWndProc(var Msg: TMessage);

var
  P: TPoint;

begin
  case Msg.Msg of
    WM_MOUSEFIRST .. WM_MOUSELAST:
      begin
        with TWMMouse(Msg) do
        begin
          P := SmallPointToPoint(Pos);
          MapWindowPoints(FPopupWnd, FColorPopup.Handle, P, 1);
          Pos := PointToSmallPoint(P);
        end;
        FColorPopup.WindowProc(Msg);
      end;
    CN_KEYDOWN, CN_SYSKEYDOWN:
      FColorPopup.WindowProc(Msg);
  else
    with Msg do
      Result := DefWindowProc(FPopupWnd, Msg, wParam, lParam);
  end;
end;

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

procedure TColorPickerButton.SetDropDownArrowColor(Value: TColor);

begin
  if not(FDropDownArrowColor = Value) then;
  begin
    FDropDownArrowColor := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetDropDownWidth(Value: Integer);

begin
  if not(FDropDownWidth = Value) then;
  begin
    FDropDownWidth := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.Paint;

const
  MAX_WIDTH = 5;
  DownStyles: array [Boolean] of Integer = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
  FillStyles: array [Boolean] of Integer = (BF_MIDDLE, 0);

var
  PaintRect: TRect;
  ExtraRect: TRect;
  DrawFlags: Integer;
  Offset: TPoint;
  LeftPos: Integer;

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

  Canvas.Font := Self.Font;

  // Creates a rectangle that represent the button and the drop down area,
  // determines also the position to draw the arrow...
  PaintRect := Rect(0, 0, Width, Height);
  ExtraRect := Rect(Width - FDropDownWidth, 0, Width, Height);
  LeftPos := (Width - FDropDownWidth) + ((FDropDownWidth + MAX_WIDTH) div 2) -
    MAX_WIDTH - 1;

  // Determines if the button is a flat or normal button... each uses
  // different painting methods
  if not FFlat then
  begin
    DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;

    if FState in [bsDown, bsExclusive] then
      DrawFlags := DrawFlags or DFCS_PUSHED;

    // Check if the mouse is in the drop down zone. If it is we then check
    // the state of the button to determine the drawing sequence
    if FDropDownZone then
    begin
      if FDroppedDown then
      begin
        // paint pressed Drop Down Button
        DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DRAW_BUTTON_UP);
        DrawFrameControl(Canvas.Handle, ExtraRect, DFC_BUTTON,
          DRAW_BUTTON_DOWN);
      end
      else
      begin
        // paint depressed Drop Down Button
        DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DRAW_BUTTON_UP);
        DrawFrameControl(Canvas.Handle, ExtraRect, DFC_BUTTON, DRAW_BUTTON_UP);
        DrawButtonSeperatorUp(Canvas);
      end;
    end
    else
    begin
      DrawFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);

      // Determine the type of drop down seperator...
      if (FState in [bsDown, bsExclusive]) then
        DrawButtonSeperatorDown(Canvas)
      else
        DrawButtonSeperatorUp(Canvas);
    end;
  end
  else
  begin
    if (FState in [bsDown, bsExclusive]) or
      (FMouseInControl and (FState <> bsDisabled)) or
      (csDesigning in ComponentState) then
    begin
      // Check if the mouse is in the drop down zone. If it is we then check
      // the state of the button to determine the drawing sequence
      if FDropDownZone then
      begin
        if FDroppedDown then
        begin
          // Paint pressed Drop Down Button
          DrawEdge(Canvas.Handle, PaintRect, DownStyles[False],
            FillStyles[FTransparent] or BF_RECT);
          DrawEdge(Canvas.Handle, ExtraRect, DownStyles[True],
            FillStyles[FTransparent] or BF_RECT);
        end
        else
        begin
          // Paint depressed Drop Down Button
          DrawEdge(Canvas.Handle, PaintRect, DownStyles[False],
            FillStyles[FTransparent] or BF_RECT);
          DrawEdge(Canvas.Handle, ExtraRect, DownStyles[False],
            FillStyles[FTransparent] or BF_RECT);
          DrawButtonSeperatorUp(Canvas);
        end;
      end
      else
      begin
        DrawEdge(Canvas.Handle, PaintRect,
          DownStyles[FState in [bsDown, bsExclusive]],
          FillStyles[FTransparent] or BF_RECT);

        if (FState in [bsDown, bsExclusive]) then
          DrawButtonSeperatorDown(Canvas)
        else
          DrawButtonSeperatorUp(Canvas);
      end;
    end
    else if not FTransparent then
    begin
      Canvas.Brush.Style := bsSolid;
      Canvas.Brush.Color := Color;
      Canvas.FillRect(PaintRect);
    end;
    InflateRect(PaintRect, -1, -1);
  end;

  if (FState in [bsDown, bsExclusive]) and not(FDropDownZone) 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;

  PaintRect := TButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption,
    FLayout, FMargin, FSpacing, FState, FTransparent, FDropDownWidth,
    DrawTextBiDiModeFlags(0));

  // draw color indicator
  Canvas.Brush.Color := FSelectionColor;
  Canvas.Pen.Color := clBtnShadow;

  case FIndicatorBorder of
    ibNone:
      Canvas.FillRect(PaintRect);
    ibFlat:
      with PaintRect do
        Canvas.Rectangle(Left, Top, Right, Bottom);
  else
    if FIndicatorBorder = ibSunken then
      DrawEdge(Canvas.Handle, PaintRect, BDR_SUNKENOUTER, BF_RECT)
    else
      DrawEdge(Canvas.Handle, PaintRect, BDR_RAISEDINNER, BF_RECT);
    InflateRect(PaintRect, -1, -1);
    Canvas.FillRect(PaintRect);
  end;

  // Draws the arrow for the correct state
  if FState = bsDisabled then
  begin
    Canvas.Pen.Style := psClear;
    Canvas.Brush.Color := clBtnShadow;
  end
  else
  begin
    Canvas.Pen.Color := FDropDownArrowColor;
    Canvas.Brush.Color := FDropDownArrowColor;
  end;

  if FDropDownZone and FDroppedDown or (FState = bsDown) and not(FDropDownZone)
  then
    DrawTriangle(Canvas, (Height div 2) + 1, LeftPos + 1, MAX_WIDTH)
  else
    DrawTriangle(Canvas, (Height div 2), LeftPos, MAX_WIDTH);
end;


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

procedure TColorPickerButton.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 TColorPickerButton.Loaded;

var
  State: TButtonState;

begin
  inherited Loaded;
  if Enabled then
    State := bsUp
  else
    State := bsDisabled;
  TButtonGlyph(FGlyph).CreateButtonGlyph(State);
end;

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

procedure TColorPickerButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);

begin
  inherited MouseDown(Button, Shift, X, Y);

  if (Button = mbLeft) and Enabled then
  begin
    // Determine if mouse is currently in the drop down section...
    FDropDownZone := (X > Width - FDropDownWidth);

    // If so display the button in the proper state and display the menu
    if FDropDownZone then
    begin
      if not FDroppedDown then
      begin
        Update;
        DroppedDown := True;
      end;

      // Setting this flag to false is very important, we want the dsUp state to
      // be used to display the button properly the next time the mouse moves in
      FDragging := False;
    end
    else
    begin
      if not FDown then
      begin
        FState := bsDown;
        Invalidate;
      end;

      FDragging := True;
    end;
  end;
end;

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

procedure TColorPickerButton.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;
end;

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

procedure TColorPickerButton.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 TColorPickerButton.Click;

begin
  inherited Click;
end;

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

procedure TColorPickerButton.DoDefaultEvent;

begin
  if Assigned(FOnDefaultSelect) then
    FOnDefaultSelect(Self);
end;

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

function TColorPickerButton.GetPalette: HPALETTE;

begin
  Result := Glyph.Palette;
end;

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

function TColorPickerButton.GetGlyph: TBitmap;

begin
  Result := TButtonGlyph(FGlyph).Glyph;
end;

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

procedure TColorPickerButton.SetGlyph(Value: TBitmap);

begin
  TButtonGlyph(FGlyph).Glyph := Value;
  Invalidate;
end;

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

function TColorPickerButton.GetNumGlyphs: TNumGlyphs;

begin
  Result := TButtonGlyph(FGlyph).NumGlyphs;
end;

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

procedure TColorPickerButton.DrawButtonSeperatorUp(Canvas: TCanvas);

begin
  with Canvas do
  begin
    Pen.Style := psSolid;
    Brush.Style := bsClear;
    Pen.Color := clBtnHighlight;
    Rectangle(Width - DropDownWidth, 1, Width - DropDownWidth + 1, Height - 1);
    Pen.Color := clBtnShadow;
    Rectangle(Width - DropDownWidth - 1, 1, Width - DropDownWidth, Height - 1);
  end;
end;

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

procedure TColorPickerButton.DrawButtonSeperatorDown(Canvas: TCanvas);

begin
  with Canvas do
  begin
    Pen.Style := psSolid;
    Brush.Style := bsClear;
    Pen.Color := clBtnHighlight;
    Rectangle(Width - DropDownWidth + 1, 2, Width - DropDownWidth + 2,
      Height - 2);
    Pen.Color := clBtnShadow;
    Rectangle(Width - DropDownWidth, 2, Width - DropDownWidth + 1, Height - 2);
  end;
end;

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

procedure TColorPickerButton.DrawTriangle(Canvas: TCanvas;
  Top, Left, Width: Integer);

begin
  if Odd(Width) then
    Inc(Width);
  Canvas.Polygon([Point(Left, Top), Point(Left + Width, Top),
    Point(Left + Width div 2, Top + Width div 2)]);
end;

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

procedure TColorPickerButton.SetNumGlyphs(Value: TNumGlyphs);

begin
  if Value < 0 then
    Value := 1
  else if Value > 4 then
    Value := 4;

  if Value <> TButtonGlyph(FGlyph).NumGlyphs then
  begin
    TButtonGlyph(FGlyph).NumGlyphs := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.GlyphChanged(Sender: TObject);

begin
  Invalidate;
end;

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

procedure TColorPickerButton.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 TColorPickerButton.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 TColorPickerButton.SetFlat(Value: Boolean);

begin
  if Value <> FFlat then
  begin
    FFlat := Value;
    if Value then
      ControlStyle := ControlStyle - [csOpaque]
    else
      ControlStyle := ControlStyle + [csOpaque];
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetGroupIndex(Value: Integer);

begin
  if FGroupIndex <> Value then
  begin
    FGroupIndex := Value;
    UpdateExclusive;
  end;
end;

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

procedure TColorPickerButton.SetLayout(Value: TButtonLayout);

begin
  if FLayout <> Value then
  begin
    FLayout := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetMargin(Value: Integer);

begin
  if (Value <> FMargin) and (Value >= -1) then
  begin
    FMargin := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetSpacing(Value: Integer);

begin
  if Value <> FSpacing then
  begin
    FSpacing := Value;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetAllowAllUp(Value: Boolean);

begin
  if FAllowAllUp <> Value then
  begin
    FAllowAllUp := Value;
    UpdateExclusive;
  end;
end;

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

procedure TColorPopup.WMActivateApp(var Message: TWMActivateApp);

begin
  inherited;
  if not Message.Active then
    EndSelection(True);
end;

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

procedure TColorPickerButton.WMLButtonDblClk(var Message: TWMLButtonDown);

begin
  inherited;
  if FDown then
    DblClick;
end;

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

procedure TColorPickerButton.CMEnabledChanged(var Message: TMessage);

const
  NewState: array [Boolean] of TButtonState = (bsDisabled, bsUp);

begin
  TButtonGlyph(FGlyph).CreateButtonGlyph(NewState[Enabled]);
  UpdateTracking;
  Repaint;
end;

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

procedure TColorPickerButton.CMButtonPressed(var Message: TMessage);

var
  Sender: TColorPickerButton;

begin
  if Message.wParam = FGroupIndex then
  begin
    Sender := TColorPickerButton(Message.lParam);
    if Sender <> Self then
    begin
      if Sender.Down and FDown then
      begin
        FDown := False;
        FState := bsUp;
        Invalidate;
      end;
      FAllowAllUp := Sender.AllowAllUp;
    end;
  end;
end;

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

procedure TColorPickerButton.CMDialogChar(var Message: TCMDialogChar);

begin
  with Message do
    if IsAccel(CharCode, Caption) and Enabled and Visible and Assigned(Parent)
      and Parent.Showing then
    begin
      Click;
      Result := 1;
    end
    else
      inherited;
end;

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

procedure TColorPickerButton.CMFontChanged(var Message: TMessage);

begin
  Invalidate;
end;

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

procedure TColorPickerButton.CMTextChanged(var Message: TMessage);

begin
  Invalidate;
end;

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

procedure TColorPickerButton.CMSysColorChange(var Message: TMessage);

begin
  with TButtonGlyph(FGlyph) do
  begin
    Invalidate;
    CreateButtonGlyph(FState);
  end;
end;

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

procedure TColorPickerButton.CMMouseEnter(var Message: TMessage);

begin
  inherited;
  if FFlat and not FMouseInControl and Enabled then
  begin
    FMouseInControl := True;
    Repaint;
  end;
end;

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

procedure TColorPickerButton.CMMouseLeave(var Message: TMessage);

begin
  inherited;
  if FFlat and FMouseInControl and Enabled and not FDragging then
  begin
    FMouseInControl := False;
    Invalidate;
  end;
end;

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

procedure TColorPickerButton.SetDroppedDown(const Value: Boolean);

var
  Allowed: Boolean;

begin
  if FDroppedDown <> Value then
  begin
    Allowed := True;
    if Assigned(FOnDropChanging) then
      FOnDropChanging(Self, Allowed);
    if Allowed then
    begin
      FDroppedDown := Value;
      if FDroppedDown then
      begin
        FState := bsDown;
        TColorPopup(FColorPopup).SelectedColor := FSelectionColor;
        TColorPopup(FColorPopup).ShowPopupAligned;
        SetCapture(FPopupWnd);
      end
      else
      begin
        FState := bsUp;
        ReleaseCapture;
        ShowWindow(FColorPopup.Handle, SW_HIDE);
      end;
      if Assigned(FOnDropChanged) then
        FOnDropChanged(Self);
      Invalidate;
    end;
  end;
end;

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

procedure TColorPickerButton.SetSelectionColor(const Value: TColor);

begin
  if FSelectionColor <> Value then
  begin
    FSelectionColor := Value;
    Invalidate;
    if FDroppedDown then
      TColorPopup(FColorPopup).SelectColor(Value);
    if Assigned(FOnChange) then
      FOnChange(Self);
  end;
end;

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

function TColorPickerButton.GetCustomText: String;

begin
  Result := TColorPopup(FColorPopup).FCustomText;
end;

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

procedure TColorPickerButton.SetCustomText(const Value: String);

begin
  with TColorPopup(FColorPopup) do
  begin
    if FCustomText <> Value then
    begin
      FCustomText := Value;
      if (FCustomText = '') and (FSelectedIndex = CustomCell) then
        FSelectedIndex := NoCell;
      AdjustWindow;
      if FDroppedDown then
      begin
        Invalidate;
        ShowPopupAligned;
      end;
    end;
  end;
end;

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

function TColorPickerButton.GetDefaultText: String;

begin
  Result := TColorPopup(FColorPopup).FDefaultText;
end;

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

procedure TColorPickerButton.SetDefaultText(const Value: String);

begin
  if TColorPopup(FColorPopup).FDefaultText <> Value then
  begin
    with TColorPopup(FColorPopup) do
    begin
      FDefaultText := Value;
      AdjustWindow;
      if FDroppedDown then
      begin
        Invalidate;
        ShowPopupAligned;
      end;
    end;
  end;
end;

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

procedure TColorPickerButton.SetShowSystemColors(const Value: Boolean);

begin
  with TColorPopup(FColorPopup) do
  begin
    if FShowSysColors <> Value then
    begin
      FShowSysColors := Value;
      AdjustWindow;
      if FDroppedDown then
      begin
        Invalidate;
        ShowPopupAligned;
      end;
    end;
  end;
end;

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

function TColorPickerButton.GetShowSystemColors: Boolean;

begin
  Result := TColorPopup(FColorPopup).FShowSysColors;
end;

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

procedure TColorPickerButton.SetTransparent(const Value: Boolean);

begin
  if Value <> FTransparent then
  begin
    FTransparent := Value;
    if Value then
      ControlStyle := ControlStyle - [csOpaque]
    else
      ControlStyle := ControlStyle + [csOpaque];
    Invalidate;
  end;
end;

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

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

// --------------- local functions -----------------------

  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;

// --------------- end local functions -------------------

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

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

procedure Register;

begin
  RegisterComponents('Tools', [TColorPickerButton]);
end;

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

procedure TColorPickerButton.SetIndicatorBorder(const Value: TIndicatorBorder);

begin
  if FIndicatorBorder <> Value then
  begin
    FIndicatorBorder := Value;
    Invalidate;
  end;
end;

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

function TColorPickerButton.GetPopupSpacing: Integer;

begin
  Result := TColorPopup(FColorPopup).Spacing;
end;

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

procedure TColorPickerButton.SetPopupSpacing(const Value: Integer);

begin
  TColorPopup(FColorPopup).Spacing := Value;
end;

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

end.

 

posted @ 2013-10-27 11:20  XE2011  阅读(493)  评论(0编辑  收藏  举报