Componentes.Terceros.jvcl/official/3.32/run/JvArrowButton.pas

1349 lines
36 KiB
ObjectPascal

{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvArrowBtn.PAS, released on 2002-05-26.
The Initial Developer of the Original Code is Peter Thörnqvist [peter3 at sourceforge dot net]
Portions created by Peter Thörnqvist are Copyright (C) 2002 Peter Thörnqvist.
All Rights Reserved.
Contributor(s):
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net
Description:
The TJvArrowButton component implements an arrow button like
the ones used in Office 97: one button and one arrow with
separate events.
Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvArrowButton.pas 11043 2006-11-26 07:21:48Z marquardt $
unit JvArrowButton;
{$I jvcl.inc}
interface
uses
{$IFDEF UNITVERSIONING}
JclUnitVersioning,
{$ENDIF UNITVERSIONING}
Classes, Windows, Messages, Controls, Graphics, Buttons, Menus,
{$IFDEF HAS_UNIT_TYPES}
Types,
{$ENDIF HAS_UNIT_TYPES}
{$IFDEF VCL}
CommCtrl,
{$ENDIF VCL}
{$IFDEF VisualCLX}
QImgList,
{$ENDIF VisualCLX}
JvComponent, JvTypes;
type
TJvArrowButton = class(TJvGraphicControl)
private
FGroupIndex: Integer;
FGlyph: TObject;
FDown: Boolean;
FArrowClick: Boolean;
FPressBoth: Boolean;
FArrowWidth: Integer;
FAllowAllUp: Boolean;
FLayout: TButtonLayout;
FSpacing: Integer;
FFillFont: TFont;
FMargin: Integer;
FFlat: Boolean;
FMouseInControl: Boolean;
FDropDown: TPopupMenu;
FDropOnButtonClick: Boolean;
FOnDrop: TNotifyEvent;
procedure GlyphChanged(Sender: TObject);
procedure UpdateExclusive;
function GetGlyph: TBitmap;
procedure SetGlyph(Value: TBitmap);
function GetNumGlyphs: TNumGlyphs;
procedure SetNumGlyphs(Value: TNumGlyphs);
procedure SetDown(Value: Boolean);
procedure SetFlat(Value: Boolean);
procedure SetAllowAllUp(Value: Boolean);
procedure SetGroupIndex(Value: Integer);
procedure SetLayout(Value: TButtonLayout);
procedure SetSpacing(Value: Integer);
procedure SetMargin(Value: Integer);
procedure SetArrowWidth(Value: Integer);
procedure SetFillFont(Value: TFont);
procedure UpdateTracking;
procedure CMButtonPressed(var Msg: TCMButtonPressed); message CM_BUTTONPRESSED;
{$IFDEF VCL}
procedure WMLButtonDblClk(var Msg: TWMLButtonDown); message WM_LBUTTONDBLCLK;
procedure CMSysColorChange(var Msg: TMessage); message CM_SYSCOLORCHANGE;
{$ENDIF VCL}
protected
FState: TButtonState;
{$IFDEF VCL}
function GetPalette: HPALETTE; override;
{$ENDIF VCL}
procedure Loaded; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); override;
procedure Paint; override;
procedure MouseEnter(Control: TControl); override;
procedure MouseLeave(Control: TControl); override;
function WantKey(Key: Integer; Shift: TShiftState;
const KeyText: WideString): Boolean; override;
procedure EnabledChanged; override;
procedure FontChanged; override;
procedure TextChanged; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Align;
property Action;
property Anchors;
property Constraints;
property AllowAllUp: Boolean read FAllowAllUp write SetAllowAllUp default False;
property ArrowWidth: Integer read FArrowWidth write SetArrowWidth default 13;
property GroupIndex: Integer read FGroupIndex write SetGroupIndex default 0;
property Down: Boolean read FDown write SetDown default False;
property DropDown: TPopupMenu read FDropDown write FDropDown;
property DropOnButtonClick: Boolean read FDropOnButtonClick write FDropOnButtonClick default False;
property Caption;
property Enabled;
property Flat: Boolean read FFlat write SetFlat default False;
property Font;
property FillFont: TFont read FFillFont write SetFillFont;
property Glyph: TBitmap read GetGlyph write SetGlyph;
property Layout: TButtonLayout read FLayout write SetLayout default blGlyphLeft;
property Margin: Integer read FMargin write SetMargin default -1;
property NumGlyphs: TNumGlyphs read GetNumGlyphs write SetNumGlyphs default 1;
property ParentFont default True;
property ParentShowHint;
property PressBoth: Boolean read FPressBoth write FPressBoth default True;
property ShowHint;
property Spacing: Integer read FSpacing write SetSpacing default 4;
property Visible;
property OnDrop: TNotifyEvent read FOnDrop write FOnDrop;
property OnClick;
property OnDblClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
end;
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvArrowButton.pas $';
Revision: '$Revision: 11043 $';
Date: '$Date: 2006-11-26 08:21:48 +0100 (dim., 26 nov. 2006) $';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
implementation
uses
SysUtils, Consts, Forms,
JvConsts, JvThemes, JvJCLUtils;
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;
{$IFDEF VisualCLX} override; {$ENDIF}
procedure Delete(Index: Integer);
property Count: Integer read FCount;
end;
TGlyphCache = class(TObject)
private
FGlyphLists: TList;
public
constructor Create;
destructor Destroy; override;
function GetList(AWidth, AHeight: Integer): TGlyphList;
procedure ReturnList(var List: TGlyphList);
function Empty: Boolean;
end;
TButtonGlyph = class(TObject)
private
FOriginal: TBitmap;
FGlyphList: TGlyphList;
FIndexs: 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);
procedure CalcButtonLayout(Canvas: TCanvas; const Client: TRect;
const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
Margin, Spacing: Integer; var GlyphPos: TPoint; var TextBounds: TRect);
public
constructor Create;
destructor Destroy; override;
{ return the text rectangle }
function Draw(Canvas: TCanvas; const Client: TRect; const Offset: TPoint;
const Caption: string; Layout: TButtonLayout; Margin, Spacing: Integer;
State: TButtonState; Transparent: Boolean): TRect;
property Glyph: TBitmap read FOriginal write SetGlyph;
property NumGlyphs: TNumGlyphs read FNumGlyphs write SetNumGlyphs;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
procedure DrawLine(Canvas: TCanvas; X, Y, X2, Y2: Integer);
begin
Canvas.MoveTo(X, Y);
Canvas.LineTo(X2, Y2);
end;
// (rom) best move to JCL
procedure GrayBitmap(Bmp: TBitmap);
var
I, J, W, H: Integer;
ColT: TColor;
Col: TColor;
begin
if Bmp.Empty then
Exit;
W := Bmp.Width;
H := Bmp.Height;
ColT := Bmp.Canvas.Pixels[0, 0];
// (rom) speed up by using Scanline
for I := 0 to W do
for J := 0 to H do
begin
Col := Bmp.Canvas.Pixels[I, J];
if (Col <> clWhite) and (Col <> ColT) then
Col := clBlack
else
Col := ColT;
Bmp.Canvas.Pixels[I, J] := Col;
end;
end;
//=== { 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 := TGlyphList(FGlyphLists[I]);
if (AWidth = Result.Width) and (AHeight = Result.Height) then
Exit;
end;
Result := TGlyphList.CreateSize(AWidth, AHeight);
FGlyphLists.Add(Result);
end;
procedure TGlyphCache.ReturnList(var List: TGlyphList);
begin
if (List <> nil) and (List.Count = 0) then
begin
FGlyphLists.Remove(List);
FreeAndNil(List);
end;
end;
function TGlyphCache.Empty: Boolean;
begin
Result := FGlyphLists.Count = 0;
end;
var
GlyphCache: TGlyphCache = nil;
Pattern: TBitmap = nil;
ButtonCount: Integer = 0;
//=== { TButtonGlyph } =======================================================
procedure CreateBrushPattern;
var
X, Y: Integer;
begin
Pattern.Free; // (rom) just to be sure
Pattern := TBitmap.Create;
Pattern.Width := 8;
Pattern.Height := 8;
with Pattern.Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clBtnFace;
FillRect(Rect(0, 0, Pattern.Width, Pattern.Height));
for Y := 0 to 7 do
for X := 0 to 7 do
if (Y mod 2) = (X mod 2) then { toggles between even/odd pixles }
Pixels[X, Y] := clBtnHighlight; { on even/odd rows }
end;
end;
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
FIndexs[I] := -1;
if GlyphCache = nil then
GlyphCache := TGlyphCache.Create;
end;
destructor TButtonGlyph.Destroy;
begin
FOriginal.Free;
Invalidate;
if Assigned(GlyphCache) and GlyphCache.Empty then
FreeAndNil(GlyphCache);
inherited Destroy;
end;
procedure TButtonGlyph.Invalidate;
var
I: TButtonState;
begin
for I := Low(TButtonState) to High(TButtonState) do
begin
if FIndexs[I] <> -1 then
FGlyphList.Delete(FIndexs[I]);
FIndexs[I] := -1;
end;
GlyphCache.ReturnList(FGlyphList);
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;
var
TmpImage, DDB, MonoBmp: TBitmap;
IWidth, IHeight: Integer;
IRect, ORect: TRect;
I: TButtonState;
DestDC: HDC;
begin
if (State = bsDown) and (NumGlyphs < 3) then
State := bsUp;
Result := FIndexs[State];
if Result <> -1 then
Exit;
if (FOriginal.Width = 0) 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;
{$IFDEF VCL}
TmpImage.Palette := CopyPalette(FOriginal.Palette);
{$ENDIF VCL}
I := State;
if Ord(I) >= NumGlyphs then
I := bsUp;
ORect := Rect(Ord(I) * IWidth, 0, (Ord(I) + 1) * IWidth, IHeight);
case State of
bsUp, bsDown, bsExclusive:
begin
TmpImage.Canvas.CopyRect(IRect, FOriginal.Canvas, ORect);
if FOriginal.TransparentMode = tmFixed then
FIndexs[State] := FGlyphList.AddMasked(TmpImage, FTransparentColor)
else
FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault);
end;
bsDisabled:
begin
MonoBmp := nil;
DDB := nil;
try
MonoBmp := TBitmap.Create;
DDB := TBitmap.Create;
DDB.Assign(FOriginal);
{$IFDEF VCL}
DDB.HandleType := bmDDB;
{$ENDIF VCL}
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);
GrayBitmap(MonoBmp);
{$IFDEF VCL}
HandleType := bmDDB;
{$ENDIF VCL}
Canvas.Brush.Color := clBlack;
Width := IWidth;
if Monochrome then
begin
Canvas.Font.Color := clWhite;
Monochrome := False;
Canvas.Brush.Color := clWhite;
end;
Monochrome := True;
end;
with TmpImage.Canvas do
begin
Brush.Color := clBtnFace;
FillRect(IRect);
Brush.Color := clBtnHighlight;
SetTextColor(Handle, clBlack);
SetBkColor(Handle, clWhite);
BitBlt(Handle, 1, 1, IWidth, IHeight,
MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax);
Brush.Color := clBtnShadow;
SetTextColor(Handle, clBlack);
SetBkColor(Handle, clWhite);
BitBlt(Handle, 0, 0, IWidth, IHeight,
MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax);
end;
end;
finally
DDB.Free;
MonoBmp.Free;
end;
FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault);
end;
end;
finally
TmpImage.Free;
end;
Result := FIndexs[State];
FOriginal.Dormant;
end;
procedure TButtonGlyph.DrawButtonGlyph(Canvas: TCanvas; const GlyphPos: TPoint;
State: TButtonState; Transparent: Boolean);
var
Index: Integer;
begin
if (FOriginal = nil) or (FOriginal.Width = 0) or (FOriginal.Height = 0) then
Exit;
Index := CreateButtonGlyph(State);
with GlyphPos do
{$IFDEF VCL}
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);
{$ENDIF VCL}
{$IFDEF VisualCLX}
// (ahuser) transparent not really supported under CLX
if Transparent or (State = bsExclusive) then
begin
FGlyphList.Draw(Canvas, X, Y, Index, itImage, True);
end
else
FGlyphList.Draw(Canvas, X, Y, Index, itImage, True);
{$ENDIF VisualCLX}
end;
procedure TButtonGlyph.DrawButtonText(Canvas: TCanvas; const Caption: string;
TextBounds: TRect; State: TButtonState);
var
S: string;
begin
S := Caption;
with Canvas do
begin
Brush.Style := bsClear;
if State = bsDisabled then
begin
OffsetRect(TextBounds, 1, 1);
Font.Color := clBtnHighlight;
DrawText(Canvas, S, -1, TextBounds, 0);
OffsetRect(TextBounds, -1, -1);
Font.Color := clBtnShadow;
DrawText(Canvas, S, -1, TextBounds, 0);
end
else
DrawText(Canvas, S, -1, TextBounds, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
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);
var
TextPos: TPoint;
ClientSize, GlyphSize, TextSize: TPoint;
TotalSize: TPoint;
S: string;
begin
{ calculate the item sizes }
ClientSize := Point(Client.Right - Client.Left, Client.Bottom -
Client.Top);
if FOriginal <> nil then
GlyphSize := Point(FOriginal.Width div FNumGlyphs, FOriginal.Height)
else
GlyphSize := Point(0, 0);
if Length(Caption) > 0 then
begin
TextBounds := Rect(0, 0, Client.Right - Client.Left, 0);
S := Caption;
DrawText(Canvas, S, -1, TextBounds, DT_CALCRECT);
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): TRect;
var
GlyphPos: TPoint;
begin
CalcButtonLayout(Canvas, Client, Offset, Caption, Layout, Margin, Spacing,
GlyphPos, Result);
DrawButtonGlyph(Canvas, GlyphPos, State, Transparent);
DrawButtonText(Canvas, Caption, Result, State);
end;
//=== { TJvArrowButton } =====================================================
constructor TJvArrowButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetBounds(0, 0, 42, 25);
ControlStyle := [csCaptureMouse, csOpaque, csDoubleClicks];
{$IFDEF VCL}
IncludeThemeStyle(Self, [csParentBackground]);
{$ENDIF VCL}
FGlyph := TButtonGlyph.Create;
TButtonGlyph(FGlyph).OnChange := GlyphChanged;
FFillFont := TFont.Create;
FFillFont.Assign(Font);
FAllowAllUp := False;
FArrowWidth := 13;
FGroupIndex := 0;
ParentFont := True;
FDown := False;
FFlat := False;
FLayout := blGlyphLeft;
FMargin := -1;
FSpacing := 4;
FPressBoth := True;
Inc(ButtonCount);
end;
destructor TJvArrowButton.Destroy;
begin
TButtonGlyph(FGlyph).Free;
FFillFont.Free;
Dec(ButtonCount);
if ButtonCount = 0 then
FreeAndNil(Pattern);
inherited Destroy;
end;
procedure TJvArrowButton.Paint;
const
DownStyles: array [Boolean] of Integer = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
FillStyles: array [Boolean] of Integer = (BF_MIDDLE, 0);
var
PaintRect: TRect;
DrawFlags: Integer;
Offset: TPoint;
DivX, DivY: Integer;
Push: Boolean;
begin
if not Enabled then
FState := bsDisabled
else
if FState = bsDisabled then
begin
if Down and (GroupIndex <> 0) then
FState := bsExclusive
else
FState := bsUp;
end;
if FMouseInControl then
Canvas.Font := FillFont
else
Canvas.Font := Self.Font;
PaintRect := Rect(0, 0, Width - ArrowWidth, Height);
if FArrowClick and not Down then
FState := bsUp;
if not Flat then
begin
DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
if (FState in [bsDown, bsExclusive]) then
DrawFlags := DrawFlags or DFCS_PUSHED;
if IsMouseOver(Self) then
DrawFlags := DrawFlags or DFCS_HOT;
DrawThemedFrameControl(Self, Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);
end
else
begin
if (FState in [bsDown, bsExclusive]) or
(FMouseInControl and (FState <> bsDisabled)) or
(csDesigning in ComponentState) then
DrawEdge(Canvas.Handle, PaintRect, DownStyles[FState in [bsDown, bsExclusive]],
FillStyles[Flat] or BF_RECT);
InflateRect(PaintRect, -1, -1);
end;
if FState in [bsDown, bsExclusive] then
begin
if (FState = bsExclusive) and (not Flat or not FMouseInControl) then
begin
if Pattern = nil then
CreateBrushPattern;
Canvas.Brush.Bitmap := Pattern;
Canvas.FillRect(PaintRect);
end;
Offset.X := 1;
Offset.Y := 1;
end
else
begin
Offset.X := 0;
Offset.Y := 0;
end;
{ draw image: }
TButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption, Layout, Margin,
Spacing, FState, Flat);
{ calculate were to put arrow part }
PaintRect := Rect(Width - ArrowWidth, 0, Width, Height);
{$IFDEF JVCLThemesEnabled}
if ThemeServices.ThemesEnabled then
Dec(PaintRect.Left);
{$ENDIF JVCLThemesEnabled}
Push := FArrowClick or (PressBoth and (FState in [bsDown, bsExclusive]));
if Push then
begin
Offset.X := 1;
Offset.Y := 1;
end
else
begin
Offset.X := 0;
Offset.Y := 0;
end;
if not Flat then
begin
DrawFlags := DFCS_BUTTONPUSH; // or DFCS_ADJUSTRECT;
if Push then
DrawFlags := DrawFlags or DFCS_PUSHED;
if IsMouseOver(Self) then
DrawFlags := DrawFlags or DFCS_HOT;
DrawThemedFrameControl(Self, Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags);
end
else
if FMouseInControl and Enabled or (csDesigning in ComponentState) then
DrawEdge(Canvas.Handle, PaintRect, DownStyles[Push],
FillStyles[Flat] or BF_RECT);
{ find middle pixel }
with PaintRect do
begin
DivX := Right - Left;
DivX := DivX div 2;
DivY := Bottom - Top;
DivY := DivY div 2;
Bottom := Bottom - (DivY + DivX div 2) + 1;
Top := Top + (DivY + DivX div 2) + 1;
Left := Left + (DivX div 2);
Right := (Right - DivX div 2);
end;
if not Flat then
Dec(Offset.X);
OffsetRect(PaintRect, Offset.X, Offset.Y);
if Enabled then
Canvas.Pen.Color := clBlack
else
Canvas.Pen.Color := clBtnShadow;
{ Draw arrow }
while PaintRect.Left < PaintRect.Right + 1 do
begin
DrawLine(Canvas, PaintRect.Left, PaintRect.Bottom, PaintRect.Right, PaintRect.Bottom);
InflateRect(PaintRect, -1, 1);
end;
end;
procedure TJvArrowButton.UpdateTracking;
var
P: TPoint;
begin
if Flat then
if Enabled then
begin
GetCursorPos(P);
FMouseInControl := not (FindDragTarget(P, True) = Self);
{$IFDEF VCL}
if FMouseInControl then
Perform(CM_MOUSELEAVE, 0, 0)
else
Perform(CM_MOUSEENTER, 0, 0);
{$ENDIF VCL}
{$IFDEF VisualCLX}
if FMouseInControl then
MouseLeave(Self)
else
MouseEnter(Self);
{$ENDIF VisualCLX}
end;
end;
procedure TJvArrowButton.Loaded;
var
State: TButtonState;
begin
inherited Loaded;
if Enabled then
State := bsUp
else
State := bsDisabled;
TButtonGlyph(FGlyph).CreateButtonGlyph(State);
end;
procedure TJvArrowButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Pnt: TPoint;
{$IFDEF VCL}
Msg: TMsg;
{$ENDIF VCL}
begin
inherited MouseDown(Button, Shift, X, Y);
if not Enabled then
Exit;
FArrowClick := (X >= Width - ArrowWidth) and (X <= Width) and (Y >= 0) and (Y <= Height) or DropOnButtonClick;
if Button = mbLeft then
begin
if not Down then
FState := bsDown
else
FState := bsExclusive;
Repaint; // Invalidate;
end;
if Assigned(FDropDown) and FArrowClick then
begin
Pnt := ClientToScreen(Point(0, Height));
DropDown.Popup(Pnt.X, Pnt.Y);
{$IFDEF VCL}
while PeekMessage(Msg, HWND_DESKTOP, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) do
{nothing};
if GetCapture <> 0 then
SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
{$ENDIF VCL}
{$IFDEF VisualCLX}
repeat
Application.ProcessMessages;
until IsWindowVisible(DropDown.Handle) = False;
{$ENDIF VisualCLX}
end;
if FArrowClick then
if Assigned(FOnDrop) then
FOnDrop(Self);
FArrowClick := False;
Repaint;
end;
procedure TJvArrowButton.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
DoClick: Boolean;
begin
inherited MouseUp(Button, Shift, X, Y);
if not Enabled then
begin
FState := bsUp;
Repaint;
end;
DoClick := (X >= 0) and (X <= Width - ArrowWidth) and (Y >= 0) and (Y <= Height) and not DropOnButtonClick;
if GroupIndex = 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 Down);
if Down then
Repaint;
end
else
begin
if Down then
FState := bsExclusive;
Repaint;
end;
if DoClick then
Click;
UpdateTracking;
Repaint;
end;
{$IFDEF VCL}
function TJvArrowButton.GetPalette: HPALETTE;
begin
Result := Glyph.Palette;
end;
{$ENDIF VCL}
function TJvArrowButton.GetGlyph: TBitmap;
begin
Result := TButtonGlyph(FGlyph).Glyph;
end;
procedure TJvArrowButton.SetGlyph(Value: TBitmap);
begin
TButtonGlyph(FGlyph).Glyph := Value;
Invalidate;
end;
function TJvArrowButton.GetNumGlyphs: TNumGlyphs;
begin
Result := TButtonGlyph(FGlyph).NumGlyphs;
end;
procedure TJvArrowButton.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 TJvArrowButton.GlyphChanged(Sender: TObject);
begin
Invalidate;
end;
procedure TJvArrowButton.UpdateExclusive;
var
Msg: TCMButtonPressed;
begin
if (GroupIndex <> 0) and (Parent <> nil) then
begin
Msg.Msg := CM_BUTTONPRESSED;
Msg.Index := GroupIndex;
Msg.Control := Self;
Msg.Result := 0;
Parent.Broadcast(Msg);
end;
end;
procedure TJvArrowButton.SetDown(Value: Boolean);
begin
if GroupIndex = 0 then
Value := False;
if Value <> FDown then
begin
if FDown and (not AllowAllUp) 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 TJvArrowButton.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 TJvArrowButton.SetGroupIndex(Value: Integer);
begin
if FGroupIndex <> Value then
begin
FGroupIndex := Value;
UpdateExclusive;
end;
end;
procedure TJvArrowButton.SetLayout(Value: TButtonLayout);
begin
if FLayout <> Value then
begin
FLayout := Value;
Invalidate;
end;
end;
procedure TJvArrowButton.SetMargin(Value: Integer);
begin
if (Value <> FMargin) and (Value >= -1) then
begin
FMargin := Value;
Invalidate;
end;
end;
procedure TJvArrowButton.SetArrowWidth(Value: Integer);
begin
if FArrowWidth <> Value then
begin
FArrowWidth := Value;
Repaint;
end;
end;
procedure TJvArrowButton.SetFillFont(Value: TFont);
begin
FFillFont.Assign(Value);
Repaint;
end;
procedure TJvArrowButton.SetSpacing(Value: Integer);
begin
if Value <> FSpacing then
begin
FSpacing := Value;
Invalidate;
end;
end;
procedure TJvArrowButton.SetAllowAllUp(Value: Boolean);
begin
if FAllowAllUp <> Value then
begin
FAllowAllUp := Value;
UpdateExclusive;
end;
end;
procedure TJvArrowButton.EnabledChanged;
const
NewState: array [Boolean] of TButtonState = (bsDisabled, bsUp);
begin
inherited EnabledChanged;
TButtonGlyph(FGlyph).CreateButtonGlyph(NewState[Enabled]);
UpdateTracking;
Repaint;
end;
procedure TJvArrowButton.CMButtonPressed(var Msg: TCMButtonPressed);
var
Sender: TJvArrowButton;
{$IFDEF JVCLThemesEnabled}
R: TRect;
{$ENDIF JVCLThemesEnabled}
begin
if Msg.Index = GroupIndex then
begin
Sender := TJvArrowButton(Msg.Control);
if Sender <> Self then
begin
if Sender.Down and Down then
begin
FDown := False;
FState := bsUp;
{$IFDEF JVCLThemesEnabled}
if ThemeServices.ThemesEnabled and Enabled and not Flat then
begin
R := BoundsRect;
InvalidateRect(Parent.Handle, @R, True);
end
else
{$ENDIF JVCLThemesEnabled}
Invalidate;
end;
FAllowAllUp := Sender.AllowAllUp;
end;
end;
end;
function TJvArrowButton.WantKey(Key: Integer; Shift: TShiftState;
const KeyText: WideString): Boolean;
begin
Result := IsAccel(Key, Caption) and Enabled and (Shift * KeyboardShiftStates = [ssAlt]);
if Result then
Click
else
Result := inherited WantKey(Key, Shift, KeyText);
end;
procedure TJvArrowButton.FontChanged;
begin
inherited FontChanged;
Invalidate;
end;
procedure TJvArrowButton.TextChanged;
begin
inherited TextChanged;
Invalidate;
end;
{$IFDEF VCL}
procedure TJvArrowButton.WMLButtonDblClk(var Msg: TWMLButtonDown);
begin
inherited;
if Down then
DblClick;
end;
procedure TJvArrowButton.CMSysColorChange(var Msg: TMessage);
begin
with TButtonGlyph(FGlyph) do
begin
Invalidate;
CreateButtonGlyph(FState);
end;
end;
{$ENDIF VCL}
procedure TJvArrowButton.MouseEnter(Control: TControl);
{$IFDEF JVCLThemesEnabled}
var
R: TRect;
{$ENDIF JVCLThemesEnabled}
begin
inherited MouseEnter(Control);
if Flat and not FMouseInControl and Enabled then
begin
FMouseInControl := True;
Repaint;
end;
{$IFDEF JVCLThemesEnabled}
if ThemeServices.ThemesEnabled and Enabled and not Flat then
begin
R := BoundsRect;
InvalidateRect(Parent.Handle, @R, True);
end;
{$ENDIF JVCLThemesEnabled}
end;
procedure TJvArrowButton.MouseLeave(Control: TControl);
{$IFDEF JVCLThemesEnabled}
var
R: TRect;
{$ENDIF JVCLThemesEnabled}
begin
inherited MouseLeave(Control);
if Flat and FMouseInControl and Enabled then
begin
FMouseInControl := False;
Invalidate;
end;
{$IFDEF JVCLThemesEnabled}
if ThemeServices.ThemesEnabled and Enabled and not Flat then
begin
R := BoundsRect;
InvalidateRect(Parent.Handle, @R, True);
end;
{$ENDIF JVCLThemesEnabled}
end;
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
end.