{----------------------------------------------------------------------------- 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.delphi-jedi.org 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$ unit JvArrowButton; {$I jvcl.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} Classes, Windows, Messages, Controls, Graphics, Buttons, Menus, Types, CommCtrl, JvComponent, JvTypes; type {$IFDEF RTL230_UP} [ComponentPlatformsAttribute(pidWin32 or pidWin64)] {$ENDIF RTL230_UP} TJvArrowButton = class(TJvGraphicControl) private FGroupIndex: Integer; 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; FVerticalAlignment: TVerticalAlignment; FAlignment: TAlignment; FFlatArrowColor: TColor; FFlatArrowDisabledColor: TColor; FSplittedButton: Boolean; procedure GlyphChanged(Sender: TObject); procedure UpdateExclusive; function GetGlyph: TBitmap; procedure SetGlyph(Value: TBitmap); function GetNumGlyphs: TNumGlyphs; procedure SetNumGlyphs(Value: TNumGlyphs); procedure SetDown(Value: Boolean); procedure SetFlat(Value: Boolean); procedure SetAllowAllUp(Value: Boolean); procedure SetGroupIndex(Value: Integer); procedure SetLayout(Value: TButtonLayout); procedure SetSpacing(Value: Integer); procedure SetMargin(Value: Integer); procedure SetArrowWidth(Value: Integer); procedure SetFillFont(Value: TFont); procedure UpdateTracking; procedure CMButtonPressed(var Msg: TCMButtonPressed); message CM_BUTTONPRESSED; procedure WMLButtonDblClk(var Msg: TWMLButtonDown); message WM_LBUTTONDBLCLK; procedure CMSysColorChange(var Msg: TMessage); message CM_SYSCOLORCHANGE; procedure SetAlignment(const Value: TAlignment); procedure SetVerticalAlignment(const Value: TVerticalAlignment); procedure SetFlatArrowColor(const Value: TColor); procedure SetFlatArrowDisabledColor(const Value: TColor); procedure SetSplittedButton(const Value: Boolean); protected FState: TButtonState; FGlyph: TObject; function GetPalette: HPALETTE; override; 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): Boolean; override; procedure EnabledChanged; override; procedure FontChanged; override; procedure TextChanged; override; Procedure CreateButtonGlyph; Virtual; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property Align; property Alignment: TAlignment read FAlignment write SetAlignment default taCenter; 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 FlatArrowColor: TColor read FFlatArrowColor write SetFlatArrowColor default clBlack; property FlatArrowDisabledColor: TColor read FFlatArrowDisabledColor write SetFlatArrowDisabledColor default clBtnShadow; 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 0; 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 SplittedButton: Boolean read FSplittedButton write SetSplittedButton default True; property VerticalAlignment: TVerticalAlignment read FVerticalAlignment write SetVerticalAlignment default taVerticalCenter; property Visible; property OnDrop: TNotifyEvent read FOnDrop write FOnDrop; property OnClick; property OnDblClick; property OnMouseDown; property OnMouseMove; property OnMouseUp; end; { David Tetard: These moved here: } TGlyphList = class(TImageList) private FUsed: TBits; FCount: Integer; FRefCount: Integer; function AllocateIndex: Integer; public constructor CreateSize(AWidth, AHeight: Integer); destructor Destroy; override; function AddMasked(Image: TBitmap; MaskColor: TColor): Integer; procedure Delete(Index: Integer); procedure AddReference; function RemoveReference: Integer; property Count: Integer read FCount; end; TButtonGlyph = class(TObject) private FOriginal: TBitmap; FGlyphList: TGlyphList; FIndexs: array [TButtonState] of Integer; FTransparentColor: TColor; FNumGlyphs: TNumGlyphs; FOnChange: TNotifyEvent; { Added by David Tetard: } FButtonLayout: TButtonLayout; FCanvas: TCanvas; FCaption: String; FMargin: Integer; FSpacing: Integer; FState: TButtonState; FTextAlignment: TAlignment; FTextVerticalAlignment: TVerticalAlignment; FTransparent: Boolean; function CreateButtonGlyph(State: TButtonState): Integer; procedure GlyphChanged(Sender: TObject); procedure Invalidate; procedure SetGlyph(Value: TBitmap); procedure SetNumGlyphs(Value: TNumGlyphs); Protected FArrowButton: TJvArrowButton; Function GetCaptionSize(Const Client: TRect; Var TextBounds : TRect) : TPoint; Virtual; Function GetGlyphSize : TPoint; Virtual; procedure DrawButtonGlyph(const GlyphPos: TPoint); Virtual; procedure DrawButtonText(TextBounds: TRect); Virtual; procedure CalcButtonLayout(Const Client: TRect; const Offset: Integer; var GlyphPos: TPoint; var TextBounds: TRect); Virtual; public constructor Create(AArrowButton: TJvArrowButton); destructor Destroy; override; Procedure Draw(Const Client: TRect; Const Offset: Integer); property Glyph: TBitmap read FOriginal write SetGlyph; property NumGlyphs: TNumGlyphs read FNumGlyphs write SetNumGlyphs; property OnChange: TNotifyEvent read FOnChange write FOnChange; { Added by David Tetard: } Property Canvas : TCanvas Read FCanvas Write FCanvas; Property Caption : String Read FCaption Write FCaption; Property Layout : TButtonLayout Read FButtonLayout Write FButtonLayout; Property Margin : Integer Read FMargin Write FMargin; Property Spacing : Integer Read FSpacing Write FSpacing; Property State: TButtonState Read FState Write FState; Property TextAlignment: TAlignment Read FTextAlignment Write FTextAlignment; Property TextVerticalAlignment: TVerticalAlignment Read FTextVerticalAlignment Write FTextVerticalAlignment; Property Transparent : Boolean Read FTransparent Write FTransparent; end; {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL$'; Revision: '$Revision$'; Date: '$Date$'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation uses SysUtils, Forms, ActnList, ImgList, Math, JvConsts, JvThemes, JvJCLUtils; type 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; 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; procedure TGlyphList.AddReference; begin Inc(FRefCount); end; function TGlyphList.RemoveReference: Integer; begin Dec(FRefCount); Result := FRefCount; 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 begin Result.AddReference; Exit; end; end; Result := TGlyphList.CreateSize(AWidth, AHeight); FGlyphLists.Add(Result); Result.AddReference; end; procedure TGlyphCache.ReturnList(var List: TGlyphList); begin if (List <> nil) and (List.RemoveReference = 0) then begin FGlyphLists.Remove(List); FreeAndNil(List); end else List := nil; 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(AArrowButton: TJvArrowButton); var I: TButtonState; begin inherited Create; FArrowButton := AArrowButton; 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) and (FGlyphList <> nil) 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; TmpImage.Palette := CopyPalette(FOriginal.Palette); I := State; if Ord(I) >= NumGlyphs then I := bsUp; ORect := Rect(Ord(I) * IWidth, 0, (Ord(I) + 1) * IWidth, IHeight); case State of bsUp, bsDown, bsExclusive: begin TmpImage.Canvas.CopyRect(IRect, FOriginal.Canvas, ORect); if FOriginal.TransparentMode = tmFixed then FIndexs[State] := FGlyphList.AddMasked(TmpImage, FTransparentColor) else FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault); end; bsDisabled: begin MonoBmp := nil; DDB := nil; try MonoBmp := TBitmap.Create; DDB := TBitmap.Create; DDB.Assign(FOriginal); DDB.HandleType := bmDDB; if NumGlyphs > 1 then with TmpImage.Canvas do begin { Change white & gray to clBtnHighlight and clBtnShadow } CopyRect(IRect, DDB.Canvas, ORect); MonoBmp.Monochrome := True; MonoBmp.Width := IWidth; MonoBmp.Height := IHeight; { Convert white to clBtnHighlight } DDB.Canvas.Brush.Color := clWhite; MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect); Brush.Color := clBtnHighlight; DestDC := Handle; SetTextColor(DestDC, clBlack); SetBkColor(DestDC, clWhite); BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax); { Convert gray to clBtnShadow } DDB.Canvas.Brush.Color := clGray; MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect); Brush.Color := clBtnShadow; DestDC := Handle; SetTextColor(DestDC, clBlack); SetBkColor(DestDC, clWhite); BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax); { Convert transparent color to clBtnFace } DDB.Canvas.Brush.Color := ColorToRGB(FTransparentColor); MonoBmp.Canvas.CopyRect(IRect, DDB.Canvas, ORect); Brush.Color := clBtnFace; DestDC := Handle; SetTextColor(DestDC, clBlack); SetBkColor(DestDC, clWhite); BitBlt(DestDC, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax); end else begin { Create a disabled version } with MonoBmp do begin Assign(FOriginal); GrayBitmap(MonoBmp); HandleType := bmDDB; Canvas.Brush.Color := clBlack; Width := IWidth; if Monochrome then begin Canvas.Font.Color := clWhite; Monochrome := False; Canvas.Brush.Color := clWhite; end; Monochrome := True; end; with TmpImage.Canvas do begin Brush.Color := clBtnFace; FillRect(IRect); Brush.Color := clBtnHighlight; SetTextColor(Handle, clBlack); SetBkColor(Handle, clWhite); BitBlt(Handle, 1, 1, IWidth, IHeight, MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax); Brush.Color := clBtnShadow; SetTextColor(Handle, clBlack); SetBkColor(Handle, clWhite); BitBlt(Handle, 0, 0, IWidth, IHeight, MonoBmp.Canvas.Handle, 0, 0, ROP_DSPDxax); end; end; finally DDB.Free; MonoBmp.Free; end; FIndexs[State] := FGlyphList.AddMasked(TmpImage, clDefault); end; end; finally TmpImage.Free; end; Result := FIndexs[State]; FOriginal.Dormant; end; {procedure TButtonGlyph.DrawButtonGlyph(Canvas: TCanvas; const GlyphPos: TPoint; State: TButtonState; Transparent: Boolean);} procedure TButtonGlyph.DrawButtonGlyph(Const GlyphPos: TPoint); var Index: Integer; CustomAction: TCustomAction; ActionList: TCustomActionList; ImageList: TCustomImageList; begin ImageList := nil; Index := -1; if (FOriginal <> nil) and not FOriginal.Empty then begin Index := CreateButtonGlyph(FState); ImageList := FGlyphList; end else begin if (FArrowButton.Action is TCustomAction) then begin CustomAction := TCustomAction(FArrowButton.Action); ActionList := CustomAction.ActionList; if (ActionList.Images <> nil) and (CustomAction.ImageIndex >= 0) and (CustomAction.ImageIndex < ActionList.Images.Count) then begin ImageList := ActionList.Images; Index := CustomAction.ImageIndex; end; end; end; if Assigned(ImageList) then if FTransparent or (State = bsExclusive) then ImageList_DrawEx(ImageList.Handle, Index, FCanvas.Handle, GlyphPos.X, GlyphPos.Y, 0, 0, clNone, clNone, ILD_Transparent) else ImageList_DrawEx(ImageList.Handle, Index, FCanvas.Handle, GlyphPos.X, GlyphPos.Y, 0, 0, ColorToRGB(clBtnFace), clNone, ILD_Normal); end; { Rewriten by David Tetard: } (*procedure TButtonGlyph.DrawButtonText(Canvas: TCanvas; const Caption: string; TextBounds: TRect; State: TButtonState; Alignment: TAlignment; VerticalAlignment: TVerticalAlignment);*) procedure TButtonGlyph.DrawButtonText(TextBounds: TRect); const AlignFlags: array[TAlignment] of Integer = (DT_LEFT, DT_RIGHT, DT_CENTER); VerticalAlignFlags: array[TVerticalAlignment] of Integer = (DT_TOP, DT_BOTTOM, DT_VCENTER); begin FCanvas.Brush.Style := bsClear; if FState = bsDisabled then begin OffsetRect(TextBounds, 1, 1); FCanvas.Font.Color := clBtnHighlight; DrawText(FCanvas, FCaption, -1, TextBounds, AlignFlags[FTextAlignment] or VerticalAlignFlags[FTextVerticalAlignment] or DT_SINGLELINE); OffsetRect(TextBounds, -1, -1); FCanvas.Font.Color := clBtnShadow; DrawText(FCanvas, Caption, -1, TextBounds, AlignFlags[FTextAlignment] or VerticalAlignFlags[FTextVerticalAlignment] or DT_SINGLELINE); end else DrawText(FCanvas, Caption, -1, TextBounds, AlignFlags[FTextAlignment] or VerticalAlignFlags[FTextVerticalAlignment] or DT_SINGLELINE); end; { David Tetard: added } function TButtonGlyph.GetCaptionSize(const Client: TRect; var TextBounds: TRect): TPoint; begin if Length(Caption) > 0 then Begin TextBounds := Rect(0, 0, Client.Width, 0); DrawText(FCanvas, FCaption, -1, TextBounds, DT_CALCRECT); Result := Point(TextBounds.Width, TextBounds.Height); End Else Begin TextBounds := Rect(0, 0, 0, 0); Result := Point(0, 0); End; end; { Added by David Tetard: } function TButtonGlyph.GetGlyphSize: TPoint; Var ActionList: TCustomActionList; CustomAction: TCustomAction; begin if (FOriginal <> nil) and not FOriginal.Empty then begin Result := Point(FOriginal.Width div FNumGlyphs, FOriginal.Height) end else begin Result := Point(0, 0); if (FArrowButton.Action is TCustomAction) then begin CustomAction := TCustomAction(FArrowButton.Action); ActionList := CustomAction.ActionList; if (ActionList.Images <> nil) and (CustomAction.ImageIndex >= 0) and (CustomAction.ImageIndex < ActionList.Images.Count) then Result := Point(ActionList.Images.Width, ActionList.Images.Height); end; end; end; { Rewriten by David Tetard: } (*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; Alignment: TAlignment; VerticalAlignment: TVerticalAlignment);*) procedure TButtonGlyph.CalcButtonLayout(const Client: TRect; const Offset: Integer; var GlyphPos: TPoint; var TextBounds: TRect); var aSpacing : Integer; ClientSize : TPoint; Delta : Integer; DrawSize : TPoint; GlyphSize : TPoint; TextPos: TPoint; TextSize: TPoint; TotalSize : TPoint; begin ClientSize := Point(Client.Width, Client.Height); GlyphSize := GetGlyphSize; TextSize := GetCaptionSize(Client, TextBounds); TextPos.Create(0, 0); GlyphPos.Create(0, 0); TotalSize := Point(ClientSize.X - 2 * FMargin, ClientSize.Y - 2 * FMargin); // Do not use any spacing if the caption or glyph are empty: if (TextSize.X = 0) Or (TextSize.Y = 0) or (GlyphSize.X = 0) or (GlyphSize.Y = 0) then aSpacing := 0 else aSpacing := FSpacing; // Place the glyph and text relative to each other: DrawSize.Create(0, 0); case FButtonLayout of blGlyphLeft: begin TextPos.X := GlyphSize.X + aSpacing; TextPos.Y := Max(0, (GlyphSize.Y - TextSize.Y) Div 2); GlyphPos.X := 0; GlyphPos.Y := Max(0, (TextSize.Y - GlyphSize.Y) div 2); DrawSize.Create(GlyphSize.X + aSpacing + TextSize.X, Max(GlyphSize.Y, TextSize.Y)); end; blGlyphTop: begin TextPos.Y := GlyphSize.Y + aSpacing; TextPos.X := Max(0, (GlyphSize.X - TextSize.X) div 2); GlyphPos.Y := 0; GlyphPos.X := Max(0, (TextSize.X - GlyphSize.X) div 2); DrawSize.Create(Max(GlyphSize.X, TextSize.X), GlyphSize.Y + TextSize.Y + aSpacing); end; blGlyphRight: begin TextPos.X := 0; TextPos.Y := Max(0, (GlyphSize.Y - TextSize.Y) Div 2); GlyphPos.X := TextSize.X + aSpacing; GlyphPos.Y := Max(0, (TextSize.Y - GlyphSize.Y) div 2); DrawSize.Create(GlyphSize.X + aSpacing + TextSize.X, Max(GlyphSize.Y, TextSize.Y)); end; blGlyphBottom: begin TextPos.Y := 0; TextPos.X := Max(0, (GlyphSize.X - TextSize.X) div 2); GlyphPos.Y := TextSize.Y + aSpacing; GlyphPos.X := Max(0, (TextSize.X - GlyphSize.X) div 2); DrawSize.Create(Max(GlyphSize.X, TextSize.X), GlyphSize.Y + TextSize.Y + aSpacing); end; end; // Set the horizontal alignment: case FTextAlignment of // taLeftJustify: Nothing to do. taCenter: begin Delta := (TotalSize.X - DrawSize.x) Div 2; TextPos.Offset(Delta, 0); GlyphPos.Offset(Delta, 0); end; taRightJustify: begin Delta := TotalSize.X - DrawSize.x; TextPos.Offset(Delta, 0); GlyphPos.Offset(Delta, 0); end; end; // Set the vertical alignment: case FTextVerticalAlignment of // taAlignTop: Nothing to do. taVerticalCenter: begin Delta := (TotalSize.Y - DrawSize.Y) Div 2; TextPos.Offset(0, Delta); GlyphPos.Offset(0, Delta); end; taAlignBottom: begin Delta := TotalSize.Y - DrawSize.y; TextPos.Offset(0, Delta); GlyphPos.Offset(0, Delta); end; end; // Apply the Margin; TextPos.Offset(FMargin + Offset, FMargin + Offset); GlyphPos.Offset(FMargin + Offset, FMargin + Offset); TextBounds.Create(TextPos.X, TextPos.Y, TextPos.X + TextSize.X, TextPos.Y + TextSize.y); End; { Rewriten by David Tetard: } (*function TButtonGlyph.Draw(Canvas: TCanvas; const Client: TRect; const Offset: TPoint; const Caption: string; Layout: TButtonLayout; Margin, Spacing: Integer; State: TButtonState; Transparent: Boolean; TextAlignment: TAlignment; TextVerticalAlignment: TVerticalAlignment): TRect;*) Procedure TButtonGlyph.Draw(const Client: TRect; const Offset: Integer); var GlyphPos: TPoint; TextBounds : TRect; begin CalcButtonLayout(Client, Offset, GlyphPos, TextBounds); DrawButtonGlyph(GlyphPos); DrawButtonText(TextBounds); end; //=== { TJvArrowButton } ===================================================== constructor TJvArrowButton.Create(AOwner: TComponent); begin inherited Create(AOwner); SetBounds(0, 0, 42, 25); ControlStyle := [csCaptureMouse, {csOpaque, }csDoubleClicks]; IncludeThemeStyle(Self, [csParentBackground]); { David Tetard: Instead of creating the TButtonGlyph here, it's created in that procedure that can be overriden: } CreateButtonGlyph; TButtonGlyph(FGlyph).OnChange := GlyphChanged; TButtonGlyph(FGlyph).Canvas := Canvas; FFillFont := TFont.Create; FFillFont.Assign(Font); FFlatArrowColor := clBlack; FFlatArrowDisabledColor := clBtnShadow; FAllowAllUp := False; FArrowWidth := 13; FGroupIndex := 0; ParentFont := True; FDown := False; FFlat := False; FLayout := blGlyphLeft; FAlignment := taCenter; FVerticalAlignment := taVerticalCenter; FMargin := -1; FSpacing := 4; FSplittedButton := True; FPressBoth := True; Inc(ButtonCount); end; destructor TJvArrowButton.Destroy; begin FGlyph.Free; FFillFont.Free; Dec(ButtonCount); if ButtonCount = 0 then FreeAndNil(Pattern); inherited Destroy; end; { David Tetard: This virtual method can be overriden to change the class of the FGlyph: } Procedure TJvArrowButton.CreateButtonGlyph; Begin FGlyph := TButtonGlyph.Create(Self); 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: Integer; DivX, DivY: Integer; Push: Boolean; {$IFDEF JVCLThemesEnabled} Details: TThemedElementDetails; {$ENDIF JVCLThemesEnabled} begin FMouseInControl := IsMouseOver(Self); 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; if SplittedButton then PaintRect := Rect(0, 0, Width - ArrowWidth, Height) else PaintRect := Rect(0, 0, Width, Height); if FArrowClick and not Down then FState := bsUp; if not Flat then begin DrawFlags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT; if not Enabled and not (csDesigning in ComponentState) then DrawFlags := DrawFlags or DFCS_INACTIVE else if (FState in [bsDown, bsExclusive]) or (not SplittedButton and FArrowClick) then DrawFlags := DrawFlags or DFCS_PUSHED; if FMouseInControl and not (csDesigning in ComponentState) then DrawFlags := DrawFlags or DFCS_HOT; DrawThemedFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags); end else begin if (FState in [bsDown, bsExclusive]) or (FMouseInControl and (FState <> bsDisabled)) or (csDesigning in ComponentState) then begin {$IFDEF JVCLThemesEnabled} if StyleServices.Enabled then begin Details := StyleServices.GetElementDetails(ttbButtonNormal); if not Enabled and (csDesigning in ComponentState) then Details := StyleServices.GetElementDetails(ttbButtonDisabled) else if (FState in [bsDown, bsExclusive]) or (not SplittedButton and FArrowClick) then Details := StyleServices.GetElementDetails(ttbButtonPressed) else if FMouseInControl and (FState <> bsDisabled) or (csDesigning in ComponentState) then Details := StyleServices.GetElementDetails(ttbButtonHot); StyleServices.DrawElement(Canvas.Handle, Details, PaintRect); StyleServices.GetElementContentRect(Canvas.Handle, Details, PaintRect, PaintRect); end else {$ENDIF JVCLThemesEnabled} DrawEdge(Canvas.Handle, PaintRect, DownStyles[FState in [bsDown, bsExclusive]], FillStyles[Flat] or BF_RECT); end; InflateRect(PaintRect, -1, -1); end; if not SplittedButton then PaintRect := Rect(0, 0, Width - ArrowWidth, Height); 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 := 1; end else Offset := 0; { draw image: } { Modified by David Tetard: } TButtonGlyph(FGlyph).Caption := Caption; TButtonGlyph(FGlyph).Layout := Layout; TButtonGlyph(FGlyph).Margin := Margin; TButtonGlyph(FGlyph).Spacing := Spacing; TButtonGlyph(FGlyph).TextAlignment := Alignment; TButtonGlyph(FGlyph).TextVerticalAlignment := VerticalAlignment; TButtonGlyph(FGlyph).Transparent := Flat {$IFDEF JVCLThemesEnabled} or StyleServices.Enabled {$ENDIF}; TButtonGlyph(FGlyph).State := FState; (*TButtonGlyph(FGlyph).Draw(Canvas, PaintRect, Offset, Caption, Layout, Margin, Spacing, FState, Flat {$IFDEF JVCLThemesEnabled} or StyleServices.Enabled {$ENDIF}, FAlignment, FVerticalAlignment);*) TButtonGlyph(FGlyph).Draw(PaintRect, Offset); { calculate were to put arrow part } PaintRect := Rect(Width - ArrowWidth, 0, Width, Height); {$IFDEF JVCLThemesEnabled} if StyleServices.Enabled then Dec(PaintRect.Left); {$ENDIF JVCLThemesEnabled} Push := FArrowClick or (PressBoth and (FState in [bsDown, bsExclusive])); if Push then Offset := 1 else Offset := 0; if FSplittedButton then begin if not Flat then begin DrawFlags := DFCS_BUTTONPUSH; // or DFCS_ADJUSTRECT; if not Enabled and not (csDesigning in ComponentState) then DrawFlags := DrawFlags or DFCS_INACTIVE else if Push then DrawFlags := DrawFlags or DFCS_PUSHED else if FMouseInControl and not (csDesigning in ComponentState) then DrawFlags := DrawFlags or DFCS_HOT; DrawThemedFrameControl(Canvas.Handle, PaintRect, DFC_BUTTON, DrawFlags); end else if FMouseInControl and Enabled or (csDesigning in ComponentState) then begin {$IFDEF JVCLThemesEnabled} if StyleServices.Enabled then begin if not Enabled and (csDesigning in ComponentState) then Details := StyleServices.GetElementDetails(ttbButtonDisabled) else if FState in [bsDown, bsExclusive] then Details := StyleServices.GetElementDetails(ttbButtonPressed) else if FMouseInControl and (FState <> bsDisabled) or (csDesigning in ComponentState) then Details := StyleServices.GetElementDetails(ttbButtonHot); StyleServices.DrawElement(Canvas.Handle, Details, PaintRect); end else {$ENDIF JVCLThemesEnabled} DrawEdge(Canvas.Handle, PaintRect, DownStyles[Push], FillStyles[Flat] or BF_RECT); end; end; { find middle pixel } DivX := PaintRect.Right - PaintRect.Left; DivX := DivX div 2; DivY := PaintRect.Bottom - PaintRect.Top; DivY := DivY div 2; PaintRect.Bottom := PaintRect.Bottom - (DivY + DivX div 2) + 1; PaintRect.Top := PaintRect.Top + (DivY + DivX div 2) + 1; PaintRect.Left := PaintRect.Left + (DivX div 2); PaintRect.Right := (PaintRect.Right - DivX div 2); OffsetRect(PaintRect, Offset, Offset); if not SplittedButton and (not Flat or (FMouseInControl and Enabled)) then begin { Draw vertical 'bar' } Canvas.Pen.Color := clBtnShadow; DrawLine(Canvas, Width - ArrowWidth - 1 + Offset, 4, Width - ArrowWidth - 1 + Offset, Height - 4); Canvas.Pen.Color := clBtnHighlight; DrawLine(Canvas, Width - ArrowWidth + Offset, 4, Width - ArrowWidth + Offset, Height - 4); Dec(PaintRect.Right, 1); end; if Flat and (not FMouseInControl or (csDesigning in ComponentState)) then begin if Enabled then Canvas.Pen.Color := FFlatArrowColor else Canvas.Pen.Color := FFlatArrowDisabledColor; end else begin if Enabled and not (csDesigning in ComponentState) then Canvas.Pen.Color := clBlack else Canvas.Pen.Color := clBtnShadow; end; { 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); if FMouseInControl then Perform(CM_MOUSELEAVE, 0, 0) else Perform(CM_MOUSEENTER, 0, 0); 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; Msg: TMsg; 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 DropDown.PopupComponent := Self; Pnt := ClientToScreen(Point(0, Height)); DropDown.Popup(Pnt.X, Pnt.Y); while PeekMessage(Msg, HWND_DESKTOP, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) do {nothing}; if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0); 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; function TJvArrowButton.GetPalette: HPALETTE; begin Result := Glyph.Palette; end; 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.SetFlatArrowColor(const Value: TColor); begin if Value <> FFlatArrowColor then begin FFlatArrowColor := Value; Invalidate; end; end; procedure TJvArrowButton.SetFlatArrowDisabledColor(const Value: TColor); begin if Value <> FFlatArrowDisabledColor then begin FFlatArrowDisabledColor := Value; 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) then begin FMargin := Max(Value, 0); 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 := Max(0, Value); Invalidate; end; end; procedure TJvArrowButton.SetSplittedButton(const Value: Boolean); begin if Value <> FSplittedButton then begin FSplittedButton := Value; Invalidate; end; end; procedure TJvArrowButton.SetAlignment(const Value: TAlignment); begin if FAlignment <> Value then begin FAlignment := Value; Invalidate; end; end; procedure TJvArrowButton.SetVerticalAlignment(const Value: TVerticalAlignment); begin if FVerticalAlignment <> Value then begin FVerticalAlignment := 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 StyleServices.Enabled and Enabled and not Flat then begin R := BoundsRect; Windows.InvalidateRect(Parent.Handle, {$IFNDEF COMPILER12_UP}@{$ENDIF ~COMPILER12_UP}R, True); end else {$ENDIF JVCLThemesEnabled} Invalidate; end; FAllowAllUp := Sender.AllowAllUp; end; end; end; function TJvArrowButton.WantKey(Key: Integer; Shift: TShiftState): Boolean; begin Result := IsAccel(Key, Caption) and Enabled and (Shift * KeyboardShiftStates = [ssAlt]); if Result then Click else Result := inherited WantKey(Key, Shift); end; procedure TJvArrowButton.FontChanged; begin inherited FontChanged; Invalidate; end; procedure TJvArrowButton.TextChanged; begin inherited TextChanged; Invalidate; end; 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; 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 StyleServices.Enabled and Enabled and not Flat then begin R := BoundsRect; Windows.InvalidateRect(Parent.Handle, {$IFNDEF COMPILER12_UP}@{$ENDIF ~COMPILER12_UP}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 StyleServices.Enabled and Enabled and not Flat then begin R := BoundsRect; Windows.InvalidateRect(Parent.Handle, {$IFNDEF COMPILER12_UP}@{$ENDIF ~COMPILER12_UP}R, True); end; {$ENDIF JVCLThemesEnabled} end; {$IFDEF UNITVERSIONING} initialization RegisterUnitVersion(HInstance, UnitVersioning); finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.