zoukankan      html  css  js  c++  java
  • TColorPickerButton组件


    http://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.
    
  • 相关阅读:
    客户端无法获取IP
    两种添加数据到WEB DropDownList 控件的方法
    DataReader的使用方法
    标准SQL的update语句三种用法
    标准SQL的update语句三种用法
    DataReader的使用方法
    DataReader的使用方法
    标准SQL的update语句三种用法
    DataReader的使用方法
    标准SQL的update语句三种用法
  • 原文地址:https://www.cnblogs.com/xe2011/p/3390697.html
Copyright © 2011-2022 走看看