2021-09-12 21:54:38 +02:00

7136 lines
224 KiB
Plaintext

unit TB97;
{
Toolbar97 version 1.63 (with Delphi 4 support)
Copyright (C) 1998 by Jordan Russell
e-mail: jordanr@iname.com
home page: http://www.connect.net/jordanr/
(alternate address: http://www.digicron.com/jordanr/)
*PLEASE NOTE* Before making any bug reports please first verify you are
using the latest version by checking my home page. And if
you do report a bug, please, if applicable, include a code
sample.
You are free to use Toolbar97 in compiled form for any purpose. However,
use in commercial or shareware applications requires registration. The
Toolbar97 source code or DCU, in whole or in part, modified or unmodified,
may not be redistributed for profit or as part of another commercial or
shareware software package without express written permission from me.
This code is distributed "as is" without any warranties, express or implied.
Notes:
- I cannot support modified versions of this code. So if you encounter a
possible bug while using a modified version, always first revert back to
the my original code before making an attempt to contact me.
- While debugging the toolbar code you might want to enable the
'TB97DisableLock' conditional define, as described below.
- In the WM_NCPAINT handlers, GetWindowRect is used to work around a possible
VCL problem. The Width, Height, and BoundsRect properties are sometimes
wrong. So it avoids any use of these properties in the WM_NCPAINT handlers.
- In case you are unsure of its meaning, NewStyleControls is a VCL variable
set to True at application startup if the user is running Windows 95 or NT
4.0 or later.
}
{$IFNDEF WIN32} Delphi 1 is not supported. {$ENDIF}
{$ALIGN ON}
{$BOOLEVAL OFF}
{$LONGSTRINGS ON}
{$WRITEABLECONST ON}
{x$DEFINE TB97DisableLock}
{ Remove the 'x' to enable the define. It will disable calls to
LockWindowUpdate, which it calls to disable screen updates while dragging.
You should temporarily enable that while debugging so you are able to see
your code window if you have something like a breakpoint that's set inside
the dragging routines }
{ Determine Delphi/C++Builder version }
{$IFNDEF VER90} { if it's not Delphi 2.0 }
{$IFNDEF VER93} { and it's not C++Builder 1.0 }
{$DEFINE TB97Delphi3orHigher} { then it must be Delphi 3 or C++Builder 3 }
{$ENDIF}
{$ENDIF}
interface
uses
Windows, Messages, Classes, Controls, Forms, Menus, Graphics, Buttons,
StdCtrls, ExtCtrls;
const
Toolbar97Version = '1.63';
WM_TB97DoneCreating = WM_USER + 5038; { used internally }
WM_TB97DoneCreating_Magic = $73A590F4; { used internally }
WM_TB97PaintDockedNCArea = WM_USER + 5039; { used internally }
type
{ TDock97 }
TDockBoundLinesValues = (blTop, blBottom, blLeft, blRight);
TDockBoundLines = set of TDockBoundLinesValues;
TDockPosition = (dpTop, dpBottom, dpLeft, dpRight);
TDockType = (dtNotDocked, dtTopBottom, dtLeftRight);
TDockableTo = set of TDockPosition;
TCustomToolWindow97 = class;
TInsertRemoveEvent = procedure(Sender: TObject; Inserting: Boolean;
Bar: TCustomToolWindow97) of object;
TRequestDockEvent = procedure(Sender: TObject; Bar: TCustomToolWindow97;
var Accept: Boolean) of object;
TDock97 = class(TCustomControl)
private
{ Property values }
FPosition: TDockPosition;
FAllowDrag: Boolean;
FBoundLines: TDockBoundLines;
FBkg, FBkgCache: TBitmap;
FBkgTransparent, FBkgOnToolbars: Boolean;
FFixAlign: Boolean;
FLimitToOneRow: Boolean;
FOnInsertRemoveBar: TInsertRemoveEvent;
FOnRequestDock: TRequestDockEvent;
FOnResize: TNotifyEvent;
{ Internal }
DisableArrangeToolbars: Integer; { Increment to disable ArrangeToolbars }
DockList: TList; { List of the visible toolbars docked. Items are casted in TCustomToolWindow97's.
But, at design time, all docked toolbars are here regardless of visibility }
RowSizes: TList; { List of the width or height of each row, depending on what Position is set to.
Items are casted info Longint's }
{ Property access methods }
procedure SetAllowDrag (Value: Boolean);
procedure SetBackground (Value: TBitmap);
procedure SetBackgroundOnToolbars (Value: Boolean);
procedure SetBackgroundTransparent (Value: Boolean);
procedure SetBoundLines (Value: TDockBoundLines);
procedure SetFixAlign (Value: Boolean);
procedure SetPosition (Value: TDockPosition);
function GetToolbarCount: Integer;
function GetToolbars (Index: Integer): TCustomToolWindow97;
{ Internal }
procedure FreeRowInfo;
function GetRowOf (const XY: Integer; var Before: Boolean): Integer;
function GetDesignModeRowOf (const XY: Integer): Integer;
function GetHighestRow: Integer;
function GetNumberOfToolbarsOnRow (const Row: Integer;
const NotIncluding: TCustomToolWindow97): Integer;
procedure RemoveBlankRows;
procedure InsertRowBefore (const BeforeRow: Integer);
procedure BuildRowInfo;
procedure ChangeDockList (const Insert: Boolean; const Bar: TCustomToolWindow97;
const IsVisible: Boolean);
procedure ChangeWidthHeight (const IsClientWidthAndHeight: Boolean;
NewWidth, NewHeight: Integer);
procedure ArrangeToolbars;
procedure DrawBackground (const DC: HDC;
const IntersectClippingRect: TRect; const ExcludeClippingRect: PRect;
const DrawRect: TRect);
procedure InvalidateBackgrounds;
procedure BackgroundChanged (Sender: TObject);
function UsingBackground: Boolean;
{ Messages }
procedure CMColorChanged (var Message: TMessage); message CM_COLORCHANGED;
procedure CMSysColorChange (var Message: TMessage); message CM_SYSCOLORCHANGE;
procedure WMMove (var Message: TWMMove); message WM_MOVE;
procedure WMSize (var Message: TWMSize); message WM_SIZE;
procedure WMNCCalcSize (var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCPaint (var Message: TMessage); message WM_NCPAINT;
protected
procedure AlignControls (AControl: TControl; var Rect: TRect); override;
function GetPalette: HPALETTE; override;
procedure Loaded; override;
procedure SetParent (AParent: TWinControl); override;
procedure Paint; override;
procedure VisibleChanging; override;
public
constructor Create (AOwner: TComponent); override;
procedure CreateParams (var Params: TCreateParams); override;
destructor Destroy; override;
function GetRowSize (const Row: Integer;
const DefaultToolbar: TCustomToolWindow97): Integer;
property ToolbarCount: Integer read GetToolbarCount;
property Toolbars[Index: Integer]: TCustomToolWindow97 read GetToolbars;
published
property AllowDrag: Boolean read FAllowDrag write SetAllowDrag default True;
property Background: TBitmap read FBkg write SetBackground;
property BackgroundOnToolbars: Boolean read FBkgOnToolbars write SetBackgroundOnToolbars default True;
property BackgroundTransparent: Boolean read FBkgTransparent write SetBackgroundTransparent default False;
property BoundLines: TDockBoundLines read FBoundLines write SetBoundLines default [];
property Color default clBtnFace;
property FixAlign: Boolean read FFixAlign write SetFixAlign default False;
property LimitToOneRow: Boolean read FLimitToOneRow write FLimitToOneRow default False;
property PopupMenu;
property Position: TDockPosition read FPosition write SetPosition default dpTop;
property OnInsertRemoveBar: TInsertRemoveEvent read FOnInsertRemoveBar write FOnInsertRemoveBar;
property OnRequestDock: TRequestDockEvent read FOnRequestDock write FOnRequestDock;
property OnResize: TNotifyEvent read FOnResize write FOnResize;
end;
{ TCustomToolWindow97 }
TToolWindowArrangeType = (atNone, atMoveControls, atMoveControlsAndResize);
TToolWindowParams = record
CallAlignControls, ResizeEightCorner, ResizeClipCursor: Boolean;
end;
TPositionReadIntProc = function(const ToolbarName, Value: String; const Default: Longint;
const ExtraData: Pointer): Longint;
TPositionReadStringProc = function(const ToolbarName, Value, Default: String;
const ExtraData: Pointer): String;
TPositionWriteIntProc = procedure(const ToolbarName, Value: String; const Data: Longint;
const ExtraData: Pointer);
TPositionWriteStringProc = procedure(const ToolbarName, Value, Data: String;
const ExtraData: Pointer);
TCustomToolWindow97 = class(TCustomControl)
private
{ Property variables }
FDockPos, FDockRow: Integer;
FDocked: Boolean;
FDockedTo, FDefaultDock: TDock97;
FOnClose, FOnDockChanged, FOnDockChanging, FOnRecreated, FOnRecreating,
FOnResize, FOnVisibleChanged: TNotifyEvent;
FActivateParent, FHideWhenInactive, FCloseButton, FFullSize, FResizable,
FDragHandle: Boolean;
FDockableTo: TDockableTo;
FParams: TToolWindowParams;
{ Misc. }
FUpdatingBounds, { Incremented while internally changing the bounds. This allows
it to move the toolbar freely in design mode and prevents the
SizeChanging protected method from begin called }
FDisableArrangeControls, { Incremented to disable ArrangeControls }
FHidden: Integer; { Incremented while the toolbar is temporarily hidden }
FArrangeNeeded: Boolean;
FInactiveCaption: Boolean; { True when the caption of the toolbar is currently the inactive color }
FFloatingTopLeft: TPoint;
{ When floating. These are not used (and FloatParent isn't created) in design mode }
FloatParent: TWinControl; { The actual Parent of the toolbar when it is floating }
MDIParentForm: TForm; { Either the owner form, or the MDI parent if the owner form is an MDI child form }
NotOnScreen: Boolean; { True if the toolbar is currently hidden from view.
This is True while the toolbar is creating or when the application is deactivated }
CloseButtonDown: Boolean; { True if Close button is currently depressed }
{ Property access methods }
procedure SetCloseButton (Value: Boolean);
procedure SetDefaultDock (Value: TDock97);
procedure SetDockedTo (Value: TDock97);
procedure SetDockPos (Value: Integer);
procedure SetDockRow (Value: Integer);
procedure SetDragHandle (Value: Boolean);
procedure SetFullSize (Value: Boolean);
procedure SetResizable (Value: Boolean);
{ Internal }
procedure MoveOnScreen (const OnlyIfFullyOffscreen: Boolean);
procedure CustomArrangeControls (const ArrangeType: TToolWindowArrangeType;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint);
procedure ArrangeControls;
procedure DrawDraggingOutline (const DC: HDC; const NewRect, OldRect: PRect;
const NewDocking, OldDocking: Boolean);
class function NewMainWindowHook (var Message: TMessage): Boolean;
procedure BeginMoving (const InitX, InitY: Integer);
procedure BeginSizing (const HitTestValue: Integer; var Accept: Boolean;
var NewRect: TRect);
procedure DrawFloatingNCArea (const Clip: HRGN; const RedrawBorder, RedrawCaption, RedrawCloseButton: Boolean);
procedure DrawDockedNCArea (const Clip: HRGN);
procedure InvalidateDockedNCArea;
procedure ValidateDockedNCArea;
procedure SetNotOnScreen (const Value: Boolean);
procedure SetInactiveCaption (const Value: Boolean);
{ Messages }
procedure CMColorChanged (var Message: TMessage); message CM_COLORCHANGED;
procedure CMTextChanged (var Message: TMessage); message CM_TEXTCHANGED;
procedure CMShowingChanged (var Message: TMessage); message CM_SHOWINGCHANGED;
procedure CMVisibleChanged (var Message: TMessage); message CM_VISIBLECHANGED;
procedure WMActivate (var Message: TWMActivate); message WM_ACTIVATE;
procedure WMClose (var Message: TWMClose); message WM_CLOSE;
procedure WMGetMinMaxInfo (var Message: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
procedure WMMove (var Message: TWMMove); message WM_MOVE;
procedure WMMouseActivate (var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
procedure WMNCCalcSize (var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCHitTest (var Message: TWMNCHitTest); message WM_NCHITTEST;
procedure WMNCLButtonDown (var Message: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
procedure WMNCPaint (var Message: TMessage); message WM_NCPAINT;
procedure WMTB97PaintDockedNCArea (var Message: TMessage); message WM_TB97PaintDockedNCArea;
procedure WMSize (var Message: TWMSize); message WM_SIZE;
protected
property ActivateParent: Boolean read FActivateParent write FActivateParent default True;
property Color default clBtnFace;
property CloseButton: Boolean read FCloseButton write SetCloseButton default True;
property DefaultDock: TDock97 read FDefaultDock write SetDefaultDock;
property DockableTo: TDockableTo read FDockableTo write FDockableTo default [dpTop, dpBottom, dpLeft, dpRight];
property DockedTo: TDock97 read FDockedTo write SetDockedTo;
property DockPos: Integer read FDockPos write SetDockPos default -1;
property DockRow: Integer read FDockRow write SetDockRow default 0;
property DragHandle: Boolean read FDragHandle write SetDragHandle default True;
property FullSize: Boolean read FFullSize write SetFullSize default False;
property HideWhenInactive: Boolean read FHideWhenInactive write FHideWhenInactive default True;
property Params: TToolWindowParams read FParams;
property Resizable: Boolean read FResizable write SetResizable default True;
property OnClose: TNotifyEvent read FOnClose write FOnClose;
property OnDockChanged: TNotifyEvent read FOnDockChanged write FOnDockChanged;
property OnDockChanging: TNotifyEvent read FOnDockChanging write FOnDockChanging;
property OnRecreated: TNotifyEvent read FOnRecreated write FOnRecreated;
property OnRecreating: TNotifyEvent read FOnRecreating write FOnRecreating;
property OnResize: TNotifyEvent read FOnResize write FOnResize;
property OnVisibleChanged: TNotifyEvent read FOnVisibleChanged write FOnVisibleChanged;
{ Overridden methods }
procedure AlignControls (AControl: TControl; var Rect: TRect); override;
procedure CreateParams (var Params: TCreateParams); override;
function GetPalette: HPALETTE; override;
procedure Loaded; override;
procedure MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure Notification (AComponent: TComponent; Operation: TOperation); override;
procedure Paint; override;
function PaletteChanged (Foreground: Boolean): Boolean; override;
procedure SetParent (AParent: TWinControl); override;
{ Methods accessible to descendants }
procedure ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer); dynamic;
procedure DoneReadingPositionData; dynamic;
procedure WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer); dynamic;
procedure GetParams (var Params: TToolWindowParams); dynamic;
procedure ResizeBegin (HitTestValue: Integer); dynamic;
procedure ResizeTrack (var Rect: TRect; const OrigRect: TRect); dynamic;
procedure ResizeEnd (Accept: Boolean); dynamic;
procedure GetBarSize (var ASize: Integer; const DockType: TDockType); virtual; abstract;
procedure GetDockRowSize (var AHeightOrWidth: Integer);
procedure GetMinimumSize (var AClientWidth, AClientHeight: Integer); virtual; abstract;
procedure InitializeOrdering; dynamic;
procedure OrderControls (const CanMoveControls: Boolean;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint); virtual; abstract;
procedure SizeChanging (const AWidth, AHeight: Integer); virtual;
public
property Docked: Boolean read FDocked;
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
procedure SetBounds (ALeft, ATop, AWidth, AHeight: Integer); override;
procedure BeginUpdate;
procedure EndUpdate;
published
property Height stored False;
property Width stored False;
property ClientHeight stored True;
property ClientWidth stored True;
end;
{ TCustomToolbar97 }
TCustomToolbar97 = class(TCustomToolWindow97)
private
FFloatingRightX: Integer;
SizeData: Pointer;
{ Lists }
SlaveInfo, { List of slave controls. Items are pointers to TSlaveInfo's }
GroupInfo, { List of the control "groups". List items are pointers to TGroupInfo's }
LineSeps, { List of the Y locations of line separators. Items are casted in TLineSep's }
OrderList: TList; { List of the child controls, arranged using the current "OrderIndex" values }
{ Property access methods }
function GetOrderIndex (Control: TControl): Integer;
procedure SetOrderIndex (Control: TControl; Value: Integer);
{ Internal }
function ShouldBeVisible (const Control: TControl; const LeftOrRight: Boolean;
const SetIt: Boolean): Boolean;
procedure FreeGroupInfo (const List: TList);
procedure BuildGroupInfo (const List: TList; const TranslateSlave: Boolean;
const OldDockType, NewDockType: TDockType);
{ Messages }
procedure CMControlListChange (var Message: TCMControlListChange); message CM_CONTROLLISTCHANGE;
protected
procedure Paint; override;
procedure ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer); override;
procedure WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer); override;
procedure GetParams (var Params: TToolWindowParams); override;
procedure ResizeBegin (HitTestValue: Integer); override;
procedure ResizeTrack (var Rect: TRect; const OrigRect: TRect); override;
procedure ResizeEnd (Accept: Boolean); override;
procedure GetBarSize (var ASize: Integer; const DockType: TDockType); override;
procedure GetMinimumSize (var AClientWidth, AClientHeight: Integer); override;
procedure InitializeOrdering; override;
procedure OrderControls (const CanMoveControls: Boolean;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint); override;
public
property OrderIndex[Control: TControl]: Integer read GetOrderIndex write SetOrderIndex;
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
procedure SetSlaveControl (const ATopBottom, ALeftRight: TControl);
published
property ClientHeight stored False;
property ClientWidth stored False;
end;
{ TToolbar97 }
TToolbar97 = class(TCustomToolbar97)
published
property ActivateParent;
property Caption;
property Color;
property CloseButton;
property DefaultDock;
property DockableTo;
property DockedTo;
property DockPos;
property DockRow;
property DragHandle;
property FullSize;
property HideWhenInactive;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property Visible;
property OnClose;
property OnDragDrop;
property OnDragOver;
property OnRecreated;
property OnRecreating;
property OnDockChanged;
property OnDockChanging;
property OnResize;
property OnVisibleChanged;
end;
{ TToolWindow97 }
TToolWindow97 = class(TCustomToolWindow97)
private
FMinClientWidth, FMinClientHeight: Integer;
FBarHeight, FBarWidth: Integer;
protected
procedure CreateParams (var Params: TCreateParams); override;
procedure ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer); override;
procedure WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer); override;
procedure GetBarSize (var ASize: Integer; const DockType: TDockType); override;
procedure GetMinimumSize (var AClientWidth, AClientHeight: Integer); override;
procedure OrderControls (const CanMoveControls: Boolean;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint); override;
procedure SizeChanging (const AWidth, AHeight: Integer); override;
public
constructor Create (AOwner: TComponent); override;
published
property ActivateParent;
property Caption;
property Color;
property CloseButton;
property DefaultDock;
property DockableTo;
property DockedTo;
property DockPos;
property DockRow;
property DragHandle;
property FullSize;
property HideWhenInactive;
property MinClientHeight: Integer read FMinClientHeight write FMinClientHeight default 32;
property MinClientWidth: Integer read FMinClientWidth write FMinClientWidth default 32;
property ParentShowHint;
property PopupMenu;
property Resizable;
property ShowHint;
property TabOrder;
property Visible;
property OnClose;
property OnDragDrop;
property OnDragOver;
property OnDockChanged;
property OnDockChanging;
property OnRecreated;
property OnRecreating;
property OnResize;
property OnVisibleChanged;
end;
{ TToolbarSep97 }
TToolbarSepSize = 1..MaxInt;
TToolbarSep97 = class(TGraphicControl)
private
FBlank: Boolean;
FSizeHorz, FSizeVert: TToolbarSepSize;
procedure SetBlank (Value: Boolean);
procedure SetSizeHorz (Value: TToolbarSepSize);
procedure SetSizeVert (Value: TToolbarSepSize);
protected
procedure MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure Paint; override;
procedure SetParent (AParent: TWinControl); override;
public
constructor Create (AOwner: TComponent); override;
published
{ These two properties don't need to be stored since it automatically gets
resized based on the setting of SizeHorz and SizeVert }
property Width stored False;
property Height stored False;
property Blank: Boolean read FBlank write SetBlank default False;
property SizeHorz: TToolbarSepSize read FSizeHorz write SetSizeHorz default 6;
property SizeVert: TToolbarSepSize read FSizeVert write SetSizeVert default 6;
end;
{ TToolbarButton97 }
TButtonDisplayMode = (dmBoth, dmGlyphOnly, dmTextOnly);
TButtonState97 = (bsUp, bsDisabled, bsDown, bsExclusive, bsMouseIn);
TNumGlyphs97 = 1..5;
TToolbarButton97 = class(TGraphicControl)
private
FAllowAllUp: Boolean;
FDisplayMode: TButtonDisplayMode;
FDown: Boolean;
FDropdownArrow: Boolean;
FDropdownCombo: Boolean;
FDropdownMenu: TPopupMenu;
FFlat: Boolean;
FGlyph: Pointer;
FGroupIndex: Integer;
FLayout: TButtonLayout;
FMargin: Integer;
FModalResult: TModalResult;
FNoBorder: Boolean;
FOldDisabledStyle: Boolean;
FOpaque: Boolean;
FRepeating: Boolean;
FRepeatDelay, FRepeatInterval: Integer;
FShowBorderWhenInactive: Boolean;
FSpacing: Integer;
FWordWrap: Boolean;
FOnMouseEnter, FOnMouseExit: TNotifyEvent;
{ Internal }
FInClick: Boolean;
FMouseInControl: Boolean;
FMouseIsDown: Boolean;
FMenuIsDown: Boolean;
FHooked: Boolean;
FUsesDropdown: Boolean;
FRepeatTimer: TTimer;
procedure GlyphChanged(Sender: TObject);
procedure UpdateExclusive;
procedure SetAllowAllUp (Value: Boolean);
function GetCallDormant: Boolean;
procedure SetCallDormant (Value: Boolean);
procedure SetDown (Value: Boolean);
procedure SetDisplayMode (Value: TButtonDisplayMode);
procedure SetDropdownArrow (Value: Boolean);
procedure SetDropdownCombo (Value: Boolean);
procedure SetDropdownMenu (Value: TPopupMenu);
procedure SetFlat (Value: Boolean);
function GetGlyph: TBitmap;
procedure SetGlyph (Value: TBitmap);
function GetGlyphMask: TBitmap;
procedure SetGlyphMask (Value: TBitmap);
procedure SetGroupIndex (Value: Integer);
procedure SetLayout (Value: TButtonLayout);
procedure SetMargin (Value: Integer);
procedure SetNoBorder (Value: Boolean);
function GetNumGlyphs: TNumGlyphs97;
procedure SetNumGlyphs (Value: TNumGlyphs97);
procedure SetOldDisabledStyle (Value: Boolean);
procedure SetOpaque (Value: Boolean);
procedure SetSpacing (Value: Integer);
procedure SetWordWrap (Value: Boolean);
procedure UpdateTracking;
procedure Redraw (const Erase: Boolean);
function PointInButton (X, Y: Integer): Boolean;
procedure ButtonMouseTimerHandler (Sender: TObject);
procedure RepeatTimerHandler (Sender: TObject);
class function DeactivateHook (var Message: TMessage): Boolean;
procedure WMLButtonDblClk (var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK;
procedure CMEnabledChanged (var Message: TMessage); message CM_ENABLEDCHANGED;
procedure CMButtonPressed (var Message: TMessage); message CM_BUTTONPRESSED;
procedure CMDialogChar (var Message: TCMDialogChar); message CM_DIALOGCHAR;
procedure CMFontChanged (var Message: TMessage); message CM_FONTCHANGED;
procedure CMTextChanged (var Message: TMessage); message CM_TEXTCHANGED;
procedure CMSysColorChange (var Message: TMessage); message CM_SYSCOLORCHANGE;
procedure CMMouseLeave (var Message: TMessage); message CM_MOUSELEAVE;
procedure WMCancelMode (var Message: TWMCancelMode); message WM_CANCELMODE;
protected
FState: TButtonState97;
function GetPalette: HPALETTE; override;
procedure Loaded; override;
procedure Notification (AComponent: TComponent; Operation: TOperation); 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
property CallDormant: Boolean read GetCallDormant write SetCallDormant;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Click; override;
procedure MouseEntered;
procedure MouseLeft;
published
property AllowAllUp: Boolean read FAllowAllUp write SetAllowAllUp default False;
property GroupIndex: Integer read FGroupIndex write SetGroupIndex default 0;
property DisplayMode: TButtonDisplayMode read FDisplayMode write SetDisplayMode default dmBoth;
property Down: Boolean read FDown write SetDown default False;
property DragCursor;
property DragMode;
property DropdownArrow: Boolean read FDropdownArrow write SetDropdownArrow default True;
property DropdownCombo: Boolean read FDropdownCombo write SetDropdownCombo default False;
property DropdownMenu: TPopupMenu read FDropdownMenu write SetDropdownMenu;
property Caption;
property Enabled;
property Flat: Boolean read FFlat write SetFlat default True;
property Font;
property Glyph: TBitmap read GetGlyph write SetGlyph;
property GlyphMask: TBitmap read GetGlyphMask write SetGlyphMask;
property Layout: TButtonLayout read FLayout write SetLayout default blGlyphLeft;
property Margin: Integer read FMargin write SetMargin default -1;
property ModalResult: TModalResult read FModalResult write FModalResult default 0;
property NoBorder: Boolean read FNoBorder write SetNoBorder default False;
property NumGlyphs: TNumGlyphs97 read GetNumGlyphs write SetNumGlyphs default 1;
property OldDisabledStyle: Boolean read FOldDisabledStyle write SetOldDisabledStyle default False;
property Opaque: Boolean read FOpaque write SetOpaque default True;
property ParentFont;
property ParentShowHint;
property Repeating: Boolean read FRepeating write FRepeating default False;
property RepeatDelay: Integer read FRepeatDelay write FRepeatDelay default 400;
property RepeatInterval: Integer read FRepeatInterval write FRepeatInterval default 100;
property ShowBorderWhenInactive: Boolean read FShowBorderWhenInactive write FShowBorderWhenInactive default False;
property ShowHint;
property Spacing: Integer read FSpacing write SetSpacing default 4;
property Visible;
property WordWrap: Boolean read FWordWrap write SetWordWrap default False;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnMouseDown;
property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter;
property OnMouseExit: TNotifyEvent read FOnMouseExit write FOnMouseExit;
property OnMouseMove;
property OnMouseUp;
property OnStartDrag;
end;
{ TEdit97 }
TEdit97 = class(TCustomEdit)
private
MouseInControl: Boolean;
procedure RedrawBorder (const Clip: HRGN);
procedure NewAdjustHeight;
procedure CMEnabledChanged (var Message: TMessage); message CM_ENABLEDCHANGED;
procedure CMFontChanged (var Message: TMessage); message CM_FONTCHANGED;
procedure CMMouseEnter (var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave (var Message: TMessage); message CM_MOUSELEAVE;
procedure WMSetFocus (var Message: TWMSetFocus); message WM_SETFOCUS;
procedure WMKillFocus (var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMNCCalcSize (var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCPaint (var Message: TMessage); message WM_NCPAINT;
protected
procedure Loaded; override;
public
constructor Create (AOwner: TComponent); override;
published
property CharCase;
property DragCursor;
property DragMode;
property Enabled;
property Font;
property HideSelection;
{$IFDEF TB97Delphi3orHigher}
property ImeMode;
property ImeName;
{$ENDIF}
property MaxLength;
property OEMConvert;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PasswordChar;
property PopupMenu;
property ReadOnly;
property ShowHint;
property TabOrder;
property TabStop;
property Text;
property Visible;
property OnChange;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDrag;
end;
procedure RegLoadToolbarPositions (const Form: TForm; const BaseRegistryKey: String);
procedure RegSaveToolbarPositions (const Form: TForm; const BaseRegistryKey: String);
procedure IniLoadToolbarPositions (const Form: TForm; const Filename: String);
procedure IniSaveToolbarPositions (const Form: TForm; const Filename: String);
procedure CustomLoadToolbarPositions (const Form: TForm;
const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer);
procedure CustomSaveToolbarPositions (const Form: TForm;
const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer);
procedure AddFloatingNCAreaToRect (var R: TRect; const Resizable: Boolean);
function GetDockTypeOf (const Control: TDock97): TDockType;
procedure Register;
var
ButtonMouseInControl: TToolbarButton97 = nil;
implementation
uses
CommCtrl, Registry, IniFiles, SysUtils, Consts;
const
{ Exception messages }
STB97DockNotFormOwner = 'TDock97 must be owned by a form';
STB97DockParentNotAllowed = 'A TDock97 control cannot be placed inside a tool window or another TDock97';
STB97DockCannotHide = 'Cannot hide a TDock97';
STB97DockCannotChangePosition = 'Cannot change Position of a TDock97 if it already contains controls';
STB97ToolwinNotFormOwner = 'Tool windows must be owned by a form';
STB97ToolwinNameNotSet = 'Cannot save tool window''s position because Name property is not set';
STB97ToolwinDockedToNameNotSet = 'Cannot save tool window''s position because DockedTo''s Name property not set';
STB97ToolwinParentNotAllowed = 'A tool window can only be placed on a TDock97 or directly on the form';
STB97ToolbarControlNotChildOfToolbar = 'Control is not a child of the toolbar';
STB97SepParentNotAllowed = 'TToolbarSep97 can only be placed on a TToolbar97';
{ All spacing & margin values are here. It's recommended that you don't
try changing any of this! }
LineSpacing = 6;
DropdownComboWidth = 11;
TopMarginNotDocked = 2;
TopMargin: array[Boolean] of Integer = (TopMarginNotDocked, 0);
BottomMarginNotDocked = 1;
BottomMargin: array[Boolean] of Integer = (BottomMarginNotDocked, 0);
LeftMarginNotDocked = 4;
LeftMargin: array[Boolean] of Integer = (LeftMarginNotDocked, 0);
RightMarginNotDocked = 4;
RightMargin: array[Boolean] of Integer = (RightMarginNotDocked, 0);
DockedBorderSize = 2;
DockedBorderSize2 = DockedBorderSize*2;
DragHandleSize = 9;
DefaultBarWidthHeight = 8;
ForceDockAtTopRow = 0;
ForceDockAtLeftPos = -8;
PositionLeftOrRight = [dpLeft, dpRight];
{ Constants for TCustomToolWindow97 registry values/data.
Don't localize any of these names! }
rvRev = 'Rev';
rdCurrentRev = 2;
rvVisible = 'Visible';
rvDockedTo = 'DockedTo';
rdDockedToFloating = '+';
rvDockRow = 'DockRow';
rvDockPos = 'DockPos';
rvFloatLeft = 'FloatLeft';
rvFloatTop = 'FloatTop';
{ TCustomToolbar97 specific }
rvFloatRightX = 'FloatRightX';
{ TToolWindow97 specific }
rvClientWidth = 'ClientWidth';
rvClientHeight = 'ClientHeight';
type
{ Used internally by the TCustomToolbar97.Resize* procedures }
PToolbar97SizeData = ^TToolbar97SizeData;
TToolbar97SizeData = record
HitTest: Integer;
NewSizes: TList; { List of valid new sizes. Items are casted into TSmallPoints }
CurRightX: Integer;
DisableSensCheck, OpSide: Boolean;
SizeSens: Integer;
end;
{ Used in TCustomToolbar97.GroupInfo lists }
PGroupInfo = ^TGroupInfo;
TGroupInfo = record
GroupWidth, { Width in pixels of the group, if all controls were
lined up left-to-right }
GroupHeight: Integer; { Heights in pixels of the group, if all controls were
lined up top-to-bottom }
Members: TList;
end;
{ Used in TCustomToolbar97.SlaveInfo lists }
PSlaveInfo = ^TSlaveInfo;
TSlaveInfo = record
LeftRight,
TopBottom: TControl;
end;
{ Used in TCustomToolbar97.LineSeps lists }
TLineSep = packed record
Y: SmallInt;
Blank: Boolean;
Unused: Boolean;
end;
{ Use by CompareControls }
PCompareExtra = ^TCompareExtra;
TCompareExtra = record
Toolbar: TCustomToolbar97;
ComparePositions: Boolean;
CurDockType: TDockType;
end;
TFloatParent = class(TWinControl)
protected
procedure CreateParams (var Params: TCreateParams); override;
end;
THookedFormID = (hkParentForm, hkChildForm);
PHookedFormInfo = ^THookedFormInfo;
THookedFormInfo = record
Form: TForm;
ID: THookedFormID;
InstalledMainHook: Boolean;
SaveActiveControl: HWND;
RefCount: Integer;
end;
PMainHookedFormInfo = ^TMainHookedFormInfo;
TMainHookedFormInfo = record
Form: TForm;
RefCount: Integer;
end;
procedure InstallHooks (const AID: THookedFormID; const AForm: TForm;
const InstallMainHook: Boolean); forward;
procedure UninstallHooks (const AID: THookedFormID; const AForm: TForm); forward;
var
HookedForms, MainHookedForms, DoneCreatingList: TList;
CWPHookHandle: HHOOK;
ButtonHookRefCount: Longint = 0;
{ See TToolbarButton97.ButtonMouseTimerHandler for info on this }
ButtonMouseTimer: TTimer = nil;
procedure Register;
begin
RegisterComponents ('Toolbar97',
[TDock97, TToolbar97, TToolWindow97, TToolbarButton97, TToolbarSep97, TEdit97]);
end;
{ Misc. functions }
function GetSmallCaptionHeight: Integer;
{ Returns height of the caption of a small window }
begin
if NewStyleControls then
Result := GetSystemMetrics(SM_CYSMCAPTION)
else
{ Win 3.x doesn't support small captions, so, like Office 97, use the size
of normal captions minus one }
Result := GetSystemMetrics(SM_CYCAPTION) - 1;
end;
function GetBorderSize (const Resizable: Boolean): TPoint;
{ Returns size of a thick border. Note that, depending on the Windows version,
this may not be the same as the actual window metrics since it draws its
own border }
const
XMetrics: array[Boolean] of Integer = (SM_CXDLGFRAME, SM_CXFRAME);
YMetrics: array[Boolean] of Integer = (SM_CYDLGFRAME, SM_CYFRAME);
begin
Result.X := GetSystemMetrics(XMetrics[Resizable]);
Result.Y := GetSystemMetrics(YMetrics[Resizable]);
end;
procedure AddFloatingNCAreaToRect (var R: TRect; const Resizable: Boolean);
begin
with GetBorderSize(Resizable) do begin
Dec (R.Left, X);
Inc (R.Right, X);
Inc (R.Bottom, GetSmallCaptionHeight + Y*2);
end;
end;
procedure AddDockedNCAreaToSize (var S: TPoint; const LeftRight, DragHandle: Boolean);
begin
if not LeftRight then begin
Inc (S.X, DockedBorderSize2 + (Ord(DragHandle) * DragHandleSize));
Inc (S.Y, DockedBorderSize2);
end
else begin
Inc (S.X, DockedBorderSize2);
Inc (S.Y, DockedBorderSize2 + (Ord(DragHandle) * DragHandleSize));
end;
end;
(* not currently used
function GetDragFullWindows: Boolean;
var
S: BOOL;
begin
Result := False;
if SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, @S, 0) then
Result := S;
end;
*)
function GetDesktopArea: TRect;
{ Returns a rectangle of the screen. But, under Win95 and NT 4.0, it excludes
the area taken up by the taskbar. }
begin
if not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
{ SPI_GETWORKAREA is only supported by Win95 and NT 4.0. So it fails under
Win 3.x. In that case, return a rectangle of the entire screen }
Result := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN));
end;
function GetMDIParent (const Form: TForm): TForm;
{ Returns the parent of the specified MDI child form. But, if Form isn't a
MDI child, it simply returns Form. }
var
I, J: Integer;
begin
Result := Form;
if Form = nil then Exit;
if Form.FormStyle = fsMDIChild then
for I := 0 to Screen.FormCount-1 do
with Screen.Forms[I] do begin
if FormStyle <> fsMDIForm then Continue;
for J := 0 to MDIChildCount-1 do
if MDIChildren[J] = Form then begin
Result := Screen.Forms[I];
Exit;
end;
end;
end;
function GetDockTypeOf (const Control: TDock97): TDockType;
begin
if Control = nil then
Result := dtNotDocked
else begin
if not(Control.Position in PositionLeftOrRight) then
Result := dtTopBottom
else
Result := dtLeftRight;
end;
end;
procedure ShowHideFloatParents (const Form: TForm; const AppActive: Boolean);
var
HideFloatingToolbars: Boolean;
I: Integer;
ParentForm: TForm;
begin
{ First call ShowHideFloatParent on child forms }
for I := 0 to Form.MDIChildCount-1 do
ShowHideFloatParents (Form.MDIChildren[I], AppActive);
{ Hide any child toolbars if: the application is not active or is
minimized, or the form (or its MDI parent) is not visible or is minimized }
HideFloatingToolbars := IsIconic(Application.Handle) or
not IsWindowVisible(Form.Handle) or IsIconic(Form.Handle);
ParentForm := GetMDIParent(Form);
if ParentForm <> Form then
HideFloatingToolbars := HideFloatingToolbars or
not IsWindowVisible(ParentForm.Handle) or IsIconic(ParentForm.Handle);
for I := 0 to Form.ComponentCount-1 do
if Form.Components[I] is TCustomToolWindow97 then
with TCustomToolWindow97(Form.Components[I]) do begin
SetNotOnScreen (not Docked and (HideFloatingToolbars or (FHideWhenInactive and not AppActive)));
SetInactiveCaption (not Docked and (not FHideWhenInactive and not AppActive));
end;
end;
function FormCallWndProcHook (Code: Integer; WParam: WPARAM; LParam: LPARAM): LRESULT;
stdcall;
var
I: Integer;
Wnd: HWND;
Ctl: TWinControl;
begin
if Code = HC_ACTION then begin
case PCWPStruct(LParam).Message of
WM_DESTROY, WM_SETFOCUS, WM_WINDOWPOSCHANGED: begin
for I := 0 to HookedForms.Count-1 do
with PHookedFormInfo(HookedForms.List[I])^ do begin { uses List property for speed }
Wnd := PCWPStruct(LParam).hwnd;
case PCWPStruct(LParam).Message of
WM_DESTROY:
if Wnd = SaveActiveControl then
SaveActiveControl := 0;
WM_SETFOCUS: begin
if Form.HandleAllocated and IsChild(Form.Handle, Wnd) then begin
SaveActiveControl := 0;
while True do begin
Ctl := FindControl(Wnd);
if Ctl <> nil then begin
if (Ctl <> Form) and Ctl.HandleAllocated and
(GetParentForm(Ctl) = Form) then
SaveActiveControl := Ctl.Handle;
Break;
end;
Wnd := GetParent(Wnd);
if Wnd = 0 then Break;
end;
Break;
end;
end;
WM_WINDOWPOSCHANGED:
if Form.HandleAllocated and (Wnd = Form.Handle) then
ShowHideFloatParents (Form, Application.Active);
end;
end;
end;
end;
end;
Result := CallNextHookEx(CWPHookHandle, Code, WParam, LParam);
end;
procedure InstallHooks (const AID: THookedFormID; const AForm: TForm;
const InstallMainHook: Boolean);
var
I: Integer;
Info: PHookedFormInfo;
AlreadyExists: Boolean;
MainInfo: PMainHookedFormInfo;
begin
for I := 0 to HookedForms.Count-1 do
with PHookedFormInfo(HookedForms[I])^ do
{ If AForm already exists in list with the same ID, only increment
the reference count }
if (ID = AID) and (Form = AForm) then begin
Inc (RefCount);
Exit;
end;
New (Info);
try
with Info^ do begin
Form := AForm;
ID := AID;
InstalledMainHook := InstallMainHook;
if InstallMainHook then begin
AlreadyExists := False;
for I := 0 to MainHookedForms.Count-1 do
with PMainHookedFormInfo(MainHookedForms[I])^ do
if (Form = AForm) then begin
Inc (RefCount);
AlreadyExists := True;
Break;
end;
if not AlreadyExists then begin
New (MainInfo);
with MainInfo^ do begin
Form := AForm;
RefCount := 1;
end;
MainHookedForms.Add (MainInfo);
if MainHookedForms.Count = 1 then
Application.HookMainWindow (TCustomToolWindow97.NewMainWindowHook);
end;
end;
SaveActiveControl := 0;
RefCount := 1;
end;
HookedForms.Add (Info);
if HookedForms.Count = 1 then
CWPHookHandle := SetWindowsHookEx(WH_CALLWNDPROC, FormCallWndProcHook, 0, GetCurrentThreadId);
except
Dispose (Info);
raise;
end;
end;
procedure UninstallHooks (const AID: THookedFormID; const AForm: TForm);
var
I, J: Integer;
begin
for I := HookedForms.Count-1 downto 0 do
with PHookedFormInfo(HookedForms[I])^ do
if (ID = AID) and (Form = AForm) then begin
Dec (RefCount);
if RefCount = 0 then begin
if InstalledMainHook then begin
for J := MainHookedForms.Count-1 downto 0 do
with PMainHookedFormInfo(MainHookedForms[J])^ do
if (Form = AForm) then begin
Dec (RefCount);
if RefCount = 0 then begin
Dispose (PMainHookedFormInfo(MainHookedForms[J]));
MainHookedForms.Delete (J);
if MainHookedForms.Count = 0 then
Application.UnhookMainWindow (TCustomToolWindow97.NewMainWindowHook);
end;
end;
end;
Dispose (PHookedFormInfo(HookedForms[I]));
HookedForms.Delete (I);
if HookedForms.Count = 0 then begin
UnhookWindowsHookEx (CWPHookHandle);
CWPHookHandle := 0;
end;
end;
end;
end;
type
TListSortExCompare = function (const Item1, Item2, ExtraData: Pointer): Integer;
procedure ListSortEx (const List: TList; const Compare: TListSortExCompare;
const ExtraData: Pointer);
{ Similar to TList.Sort, but lets you pass a user-defined ExtraData pointer }
procedure QuickSortEx (L: Integer; const R: Integer);
var
I, J: Integer;
P: Pointer;
begin
repeat
I := L;
J := R;
P := List[(L + R) shr 1];
repeat
while Compare(List[I], P, ExtraData) < 0 do Inc(I);
while Compare(List[J], P, ExtraData) > 0 do Dec(J);
if I <= J then
begin
List.Exchange (I, J);
Inc (I);
Dec (J);
end;
until I > J;
if L < J then QuickSortEx (L, J);
L := I;
until I >= R;
end;
begin
if List.Count > 1 then
QuickSortEx (0, List.Count-1);
end;
procedure ProcessPaintMessages;
{ Dispatches all pending WM_PAINT messages. In effect, this is like an
'UpdateWindow' on all visible windows }
var
Msg: TMsg;
begin
while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_NOREMOVE) do begin
case Integer(GetMessage(Msg, 0, WM_PAINT, WM_PAINT)) of
-1: Break; { if GetMessage failed }
0: begin
{ Repost WM_QUIT messages }
PostQuitMessage (Msg.WParam);
Break;
end;
end;
DispatchMessage (Msg);
end;
end;
{$IFNDEF TB97Delphi3orHigher}
type
PMaxLogPalette = ^TMaxLogPalette;
TMaxLogPalette = packed record
palVersion: Word;
palNumEntries: Word;
palPalEntry: array[Byte] of TPaletteEntry;
end;
function CopyPalette (Palette: HPALETTE): HPALETTE;
var
PaletteSize: Integer;
LogPal: TMaxLogPalette;
begin
Result := 0;
if Palette = 0 then Exit;
PaletteSize := 0;
if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit;
if PaletteSize = 0 then Exit;
with LogPal do begin
palVersion := $0300;
palNumEntries := PaletteSize;
GetPaletteEntries (Palette, 0, PaletteSize, palPalEntry);
end;
Result := CreatePalette(PLogPalette(@LogPal)^);
end;
{$ENDIF}
{ TDock97 - internal }
constructor TDock97.Create (AOwner: TComponent);
begin
inherited;
if not(AOwner is TForm) then
raise EInvalidOperation.Create(STB97DockNotFormOwner);
{ because TCustomToolWindow97 depends on docks being in the form's component list }
FAllowDrag := True;
FBkgOnToolbars := True;
DockList := TList.Create;
RowSizes := TList.Create;
Inc (DisableArrangeToolbars);
try
ControlStyle := ControlStyle +
[csAcceptsControls, csNoStdEvents] -
[csClickEvents, csCaptureMouse, csOpaque];
FBkg := TBitmap.Create;
FBkg.OnChange := BackgroundChanged;
Position := dpTop;
Color := clBtnFace;
finally
Dec (DisableArrangeToolbars);
end;
{ Rearranging was disabled, so manually rearrange it now }
ArrangeToolbars;
end;
procedure TDock97.CreateParams (var Params: TCreateParams);
begin
inherited;
{ Disable complete redraws when size changes. CS_H/VREDRAW cause flicker
and are not necessary for this control at run time }
if not(csDesigning in ComponentState) then
with Params.WindowClass do
Style := Style and not(CS_HREDRAW or CS_VREDRAW);
end;
destructor TDock97.Destroy;
begin
FBkgCache.Free;
FBkg.Free;
FreeRowInfo;
RowSizes.Free;
DockList.Free;
inherited;
end;
procedure TDock97.SetParent (AParent: TWinControl);
begin
if (AParent is TCustomToolWindow97) or (AParent is TDock97) then
raise EInvalidOperation.Create(STB97DockParentNotAllowed);
inherited;
end;
procedure TDock97.VisibleChanging;
begin
if Visible then
raise EInvalidOperation.Create(STB97DockCannotHide);
inherited;
end;
procedure TDock97.FreeRowInfo;
begin
if Assigned(RowSizes) then
RowSizes.Clear;
end;
procedure TDock97.BuildRowInfo;
var
R, I, Size, HighestSize: Integer;
begin
FreeRowInfo;
for R := 0 to GetHighestRow do begin
HighestSize := DefaultBarWidthHeight;
for I := 0 to DockList.Count-1 do begin
with TCustomToolWindow97(DockList[I]) do begin
if FDockRow <> R then Continue;
GetBarSize (Size, GetDockTypeOf(Self));
if Size > HighestSize then HighestSize := Size;
end;
end;
RowSizes.Add (Pointer(HighestSize));
end;
end;
function TDock97.GetRowSize (const Row: Integer;
const DefaultToolbar: TCustomToolWindow97): Integer;
begin
if Row < RowSizes.Count then
Result := Longint(RowSizes[Row])
else begin
{ If it's out of bounds }
if DefaultToolbar = nil then
Result := 0
else
DefaultToolbar.GetBarSize (Result, GetDockTypeOf(Self));
end;
end;
function TDock97.GetRowOf (const XY: Integer; var Before: Boolean): Integer;
{ Returns row number of the specified coordinate. Before is set to True if it
was close to being in between two rows. }
var
HighestRow, R, CurY, NextY: Integer;
begin
Result := 0; Before := False;
HighestRow := GetHighestRow;
CurY := 0;
for R := 0 to HighestRow+1 do begin
if R <= HighestRow then
NextY := CurY + GetRowSize(R, nil) + DockedBorderSize2
else
NextY := High(NextY);
if XY <= CurY+5 then begin
Result := R;
Before := True;
Break;
end;
if (XY >= CurY+5) and (XY <= NextY-5) then begin
Result := R;
Break;
end;
CurY := NextY;
end;
end;
function TDock97.GetDesignModeRowOf (const XY: Integer): Integer;
{ Similar to GetRowOf, but is a little different to accomidate design mode
better }
var
HighestRowPlus1, R, CurY, NextY: Integer;
begin
Result := 0;
HighestRowPlus1 := GetHighestRow+1;
CurY := 0;
for R := 0 to HighestRowPlus1 do begin
Result := R;
if R = HighestRowPlus1 then Break;
NextY := CurY + GetRowSize(R, nil) + DockedBorderSize2;
if XY < NextY then
Break;
CurY := NextY;
end;
end;
function TDock97.GetHighestRow: Integer;
{ Returns highest used row number, or -1 if no rows are used }
var
I: Integer;
begin
Result := -1;
for I := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[I]) do
if FDockRow > Result then
Result := FDockRow;
end;
function TDock97.GetNumberOfToolbarsOnRow (const Row: Integer;
const NotIncluding: TCustomToolWindow97): Integer;
{ Returns number of toolbars on the specified row. The toolbar specified by
"NotIncluding" is not included in the count. }
var
I: Integer;
begin
Result := 0;
for I := 0 to DockList.Count-1 do
if (TCustomToolWindow97(DockList[I]).FDockRow = Row) and
(DockList[I] <> NotIncluding) then
Inc (Result);
end;
procedure TDock97.RemoveBlankRows;
{ Deletes any blank row numbers, adjusting the docked toolbars' FDockRow as
needed }
var
HighestRow, R, I: Integer;
RowIsEmpty: Boolean;
begin
HighestRow := GetHighestRow;
R := 0;
while R <= HighestRow do begin
RowIsEmpty := True;
for I := 0 to DockList.Count-1 do
if TCustomToolWindow97(DockList[I]).FDockRow = R then begin
RowIsEmpty := False;
Break;
end;
if RowIsEmpty then begin
{ Shift all ones higher than R back one }
for I := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[I]) do
if FDockRow > R then
Dec (FDockRow);
Dec (HighestRow);
end;
Inc (R);
end;
end;
procedure TDock97.InsertRowBefore (const BeforeRow: Integer);
{ Inserts a blank row before BeforeRow, adjusting all the docked toolbars'
FDockRow as needed }
var
I: Integer;
begin
for I := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[I]) do
if FDockRow >= BeforeRow then
Inc (FDockRow);
end;
procedure TDock97.ChangeWidthHeight (const IsClientWidthAndHeight: Boolean;
NewWidth, NewHeight: Integer);
{ Same as setting Width/Height or ClientWidth/ClientHeight directly, but does
not lose Align position. }
begin
if IsClientWidthAndHeight then begin
Inc (NewWidth, Width-ClientWidth);
Inc (NewHeight, Height-ClientHeight);
end;
case Align of
alTop, alLeft:
SetBounds (Left, Top, NewWidth, NewHeight);
alBottom:
SetBounds (Left, Top-NewHeight+Height, NewWidth, NewHeight);
alRight:
SetBounds (Left-NewWidth+Width, Top, NewWidth, NewHeight);
end;
end;
procedure TDock97.AlignControls (AControl: TControl; var Rect: TRect);
begin
ArrangeToolbars;
end;
function CompareDockRowPos (const Item1, Item2, ExtraData: Pointer): Integer; far;
begin
if TCustomToolWindow97(Item1).FDockRow <> TCustomToolWindow97(Item2).FDockRow then
Result := TCustomToolWindow97(Item1).FDockRow - TCustomToolWindow97(Item2).FDockRow
else
Result := TCustomToolWindow97(Item1).FDockPos - TCustomToolWindow97(Item2).FDockPos;
end;
procedure TDock97.ArrangeToolbars;
{ The main procedure to arrange all the toolbars docked to it }
var
LeftRight: Boolean;
EmptySize: Integer;
HighestRow, R, CurDockPos, CurRowPixel, I, J, K: Integer;
HighestRowSize, CurRowSize: Integer;
begin
if (DisableArrangeToolbars > 0) or (csLoading in ComponentState) then
Exit;
{ Work around VCL alignment bug when docking toolbars taller or wider than
the client height or width of the form. }
if not(csDesigning in ComponentState) and HandleAllocated then
SetWindowPos (Handle, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
LeftRight := Position in PositionLeftOrRight;
if DockList.Count = 0 then begin
EmptySize := Ord(FFixAlign);
if csDesigning in ComponentState then
EmptySize := 9;
if not LeftRight then
ChangeWidthHeight (False, Width, EmptySize)
else
ChangeWidthHeight (False, EmptySize, Height);
Exit;
end;
{ Ensure list is in correct ordering according to DockRow/DockPos }
ListSortEx (DockList, CompareDockRowPos, nil);
{ If LimitToOneRow is True, only use the first row }
if FLimitToOneRow then
for I := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[I]) do
FDockRow := 0;
{ Remove any blank rows }
RemoveBlankRows;
{ Find highest row number }
HighestRow := GetHighestRow;
{ Arrange, first without actually moving the toolbars onscreen }
R := 0;
while R <= HighestRow do begin
CurDockPos := 0;
for I := 0 to DockList.Count-1 do begin
with TCustomToolWindow97(DockList[I]) do begin
if FDockRow <> R then Continue;
if FullSize then
{ If FullSize, make sure there aren't any other toolbars on the same
row. If there are, shift them down a row. }
for J := 0 to DockList.Count-1 do
if (J <> I) and (TCustomToolWindow97(DockList[J]).FDockRow = R) then begin
for K := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[K]) do
if (K <> I) and (FDockRow >= R) then begin
Inc (FDockRow);
if FDockRow > HighestRow then
HighestRow := FDockRow;
end;
Break;
end;
if FDockPos <= CurDockPos then
FDockPos := CurDockPos
else
CurDockPos := FDockPos;
if not LeftRight then
Inc (CurDockPos, Width)
else
Inc (CurDockPos, Height);
end;
end;
Inc (R);
end;
{ Rebuild the RowInfo, since rows numbers may have shifted }
BuildRowInfo;
{ Try to move all the toolbars that are offscreen to a fully visible position }
for R := 0 to HighestRow do begin
for I := 0 to DockList.Count-1 do begin
with TCustomToolWindow97(DockList[I]) do begin
if FDockRow <> R then Continue;
if FullSize then
FDockPos := 0
else
for J := DockList.Count-1 downto I do begin
with TCustomToolWindow97(DockList[J]) do begin
if FDockRow <> R then Continue;
if not LeftRight then begin
if FDockPos+Width > Self.ClientWidth then begin
Dec (TCustomToolWindow97(DockList[I]).FDockPos,
((FDockPos+Width) - Self.ClientWidth));
Break;
end;
end
else begin
if FDockPos+Height > Self.ClientHeight then begin
Dec (TCustomToolWindow97(DockList[I]).FDockPos,
((FDockPos+Height) - Self.ClientHeight));
Break;
end;
end;
end;
end;
end;
end;
end;
{ Arrange again, this time actually moving the toolbars }
CurRowPixel := 0;
for R := 0 to HighestRow do begin
CurDockPos := 0;
HighestRowSize := DefaultBarWidthHeight;
for I := 0 to DockList.Count-1 do begin
with TCustomToolWindow97(DockList[I]) do begin
if FDockRow <> R then Continue;
CurRowSize := DockedBorderSize2 + GetRowSize(FDockRow, TCustomToolWindow97(DockList[I]));
if CurRowSize > HighestRowSize then
HighestRowSize := CurRowSize;
if FDockPos <= CurDockPos then
FDockPos := CurDockPos
else
CurDockPos := FDockPos;
Inc (FUpdatingBounds);
try
if not LeftRight then begin
J := Width;
if FullSize then J := Self.ClientWidth;
SetBounds (CurDockPos, CurRowPixel, J, CurRowSize)
end
else begin
J := Height;
if FullSize then J := Self.ClientHeight;
SetBounds (CurRowPixel, CurDockPos, CurRowSize, J);
end;
finally
Dec (FUpdatingBounds);
end;
if not LeftRight then
Inc (CurDockPos, Width)
else
Inc (CurDockPos, Height);
end;
end;
Inc (CurRowPixel, HighestRowSize);
end;
{ Set the size of the dock }
if not LeftRight then
ChangeWidthHeight (True, ClientWidth, CurRowPixel)
else
ChangeWidthHeight (True, CurRowPixel, ClientHeight);
end;
procedure TDock97.ChangeDockList (const Insert: Boolean;
const Bar: TCustomToolWindow97; const IsVisible: Boolean);
{ Inserts or removes Bar. It inserts only if IsVisible is True, or is in
design mode }
var
Modified: Boolean;
begin
Modified := False;
if Insert then begin
{ Delete if already exists }
if DockList.IndexOf(Bar) <> -1 then
DockList.Remove (Bar);
{ Only add to dock list if visible }
if (csDesigning in ComponentState) or IsVisible then begin
DockList.Add (Bar);
Modified := True;
end;
end
else begin
if DockList.IndexOf(Bar) <> -1 then begin
DockList.Remove (Bar);
Modified := True;
end;
end;
if Modified then begin
ArrangeToolbars;
{ This corrects a problem in past versions when toolbar is shown after it
was initially hidden }
Bar.ArrangeControls;
if Assigned(FOnInsertRemoveBar) then
FOnInsertRemoveBar (Self, Insert, Bar);
end;
end;
procedure TDock97.Loaded;
begin
inherited;
{ Rearranging is disabled while the component is loading, so now that it's
loaded, rearrange it. }
ArrangeToolbars;
end;
function TDock97.GetPalette: HPALETTE;
begin
Result := FBkg.Palette;
end;
procedure TDock97.DrawBackground (const DC: HDC;
const IntersectClippingRect: TRect; const ExcludeClippingRect: PRect;
const DrawRect: TRect);
var
UseBmp: TBitmap;
R2: TRect;
SaveIndex: Integer;
begin
UseBmp := FBkg;
{ When FBkgTransparent is True, it keeps a cached copy of the
background that has the transparent color already translated. Without the
cache, redraws can be very slow.
Note: The cache is cleared in the OnChange event of FBkg }
if FBkgTransparent then begin
if FBkgCache = nil then begin
FBkgCache := TBitmap.Create;
with FBkgCache do begin
Palette := CopyPalette(FBkg.Palette);
Width := FBkg.Width;
Height := FBkg.Height;
Canvas.Brush.Color := Self.Color;
Canvas.BrushCopy (Rect(0, 0, Width, Height), FBkg,
Rect(0, 0, Width, Height), FBkg.Canvas.Pixels[0, Height-1] or $02000000);
end;
end;
UseBmp := FBkgCache;
end;
SaveIndex := SaveDC(DC);
try
with IntersectClippingRect do
IntersectClipRect (DC, Left, Top, Right, Bottom);
if Assigned(ExcludeClippingRect) then
with ExcludeClippingRect^ do
ExcludeClipRect (DC, Left, Top, Right, Bottom);
if UseBmp.Palette <> 0 then begin
SelectPalette (DC, UseBmp.Palette, True);
RealizePalette (DC);
end;
R2 := DrawRect;
while R2.Left < R2.Right do begin
while R2.Top < R2.Bottom do begin
BitBlt (DC, R2.Left, R2.Top, UseBmp.Width, UseBmp.Height,
UseBmp.Canvas.Handle, 0, 0, SRCCOPY);
Inc (R2.Top, UseBmp.Height);
end;
R2.Top := DrawRect.Top;
Inc (R2.Left, UseBmp.Width);
end;
finally
{ Restores the clipping region and palette back }
RestoreDC (DC, SaveIndex);
end;
end;
procedure TDock97.Paint;
var
R, R2: TRect;
P1, P2: TPoint;
begin
inherited;
with Canvas do begin
R := ClientRect;
{ Draw dotted border in design mode }
if csDesigning in ComponentState then begin
Pen.Style := psDot;
Pen.Color := clBtnShadow;
Brush.Style := bsClear;
Rectangle (R.Left, R.Top, R.Right, R.Bottom);
Pen.Style := psSolid;
InflateRect (R, -1, -1);
end;
{ Draw the Background }
if UsingBackground then begin
R2 := ClientRect;
{ Make up for nonclient area }
P1 := ClientToScreen(Point(0, 0));
P2 := Parent.ClientToScreen(BoundsRect.TopLeft);
Dec (R2.Left, Left + (P1.X-P2.X));
Dec (R2.Top, Top + (P1.Y-P2.Y));
DrawBackground (Canvas.Handle, R, nil, R2);
end;
end;
end;
procedure TDock97.WMMove (var Message: TWMMove);
begin
inherited;
if UsingBackground then
InvalidateBackgrounds;
end;
procedure TDock97.WMSize (var Message: TWMSize);
begin
inherited;
ArrangeToolbars;
if Assigned(FOnResize) then
FOnResize (Self);
end;
procedure TDock97.WMNCCalcSize (var Message: TWMNCCalcSize);
begin
inherited;
with Message.CalcSize_Params^.rgrc[0] do begin
{ Don't add a border when width or height is zero (or one in case of
FixAlign=True) }
if ((Right-Left) <= 1) or ((Bottom-Top) <= 1) then
Exit;
if blTop in BoundLines then Inc (Top);
if blBottom in BoundLines then Dec (Bottom);
if blLeft in BoundLines then Inc (Left);
if blRight in BoundLines then Dec (Right);
end;
end;
procedure TDock97.WMNCPaint (var Message: TMessage);
var
R, R2: TRect;
DC: HDC;
NewClipRgn: HRGN;
HighlightPen, ShadowPen, SavePen: HPEN;
begin
{ This works around WM_NCPAINT problem described at top of source code }
{no! R := Rect(0, 0, Width, Height);}
GetWindowRect (Handle, R); OffsetRect (R, -R.Left, -R.Top);
{ Don't draw border when width or height is zero (or one in case of
FixAlign=True) }
if ((R.Right-R.Left) <= 1) or ((R.Bottom-R.Top) <= 1) then
Exit;
DC := GetWindowDC(Handle);
try
{ Use update region }
if (Message.WParam <> 0) and (Message.WParam <> 1) then begin
GetWindowRect (Handle, R2);
if SelectClipRgn(DC, Message.WParam) = ERROR then begin
NewClipRgn := CreateRectRgnIndirect(R2);
SelectClipRgn (DC, NewClipRgn);
DeleteObject (NewClipRgn);
end;
OffsetClipRgn (DC, -R2.Left, -R2.Top);
end;
{ Draw BoundLines }
HighlightPen := CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));
ShadowPen := CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
if blTop in BoundLines then begin
SavePen := SelectObject(DC, ShadowPen);
MoveToEx (DC, R.Left, R.Top, nil); LineTo (DC, R.Right, R.Top);
SelectObject (DC, SavePen);
end;
if blLeft in BoundLines then begin
SavePen := SelectObject(DC, ShadowPen);
MoveToEx (DC, R.Left, R.Top, nil); LineTo (DC, R.Left, R.Bottom);
SelectObject (DC, SavePen);
end;
if blBottom in BoundLines then begin
SavePen := SelectObject(DC, HighlightPen);
MoveToEx (DC, R.Left, R.Bottom-1, nil); LineTo (DC, R.Right, R.Bottom-1);
SelectObject (DC, SavePen);
end;
if blRight in BoundLines then begin
SavePen := SelectObject(DC, HighlightPen);
MoveToEx (DC, R.Right-1, R.Top, nil); LineTo (DC, R.Right-1, R.Bottom);
SelectObject (DC, SavePen);
end;
DeleteObject (ShadowPen);
DeleteObject (HighlightPen);
finally
ReleaseDC (Handle, DC);
end;
end;
procedure TDock97.CMColorChanged (var Message: TMessage);
begin
if UsingBackground then
{ Erase the cache }
BackgroundChanged (FBkg);
inherited;
end;
procedure TDock97.CMSysColorChange (var Message: TMessage);
begin
inherited;
if UsingBackground then
{ Erase the cache }
BackgroundChanged (FBkg);
end;
{ TDock97 - property access methods }
procedure TDock97.SetAllowDrag (Value: Boolean);
var
I: Integer;
begin
if FAllowDrag <> Value then begin
FAllowDrag := Value;
for I := 0 to ControlCount-1 do
if (Controls[I] is TCustomToolWindow97) and
TCustomToolWindow97(Controls[I]).HandleAllocated then
{ Recalculate the non-client area }
SetWindowPos (TCustomToolWindow97(Controls[I]).Handle, 0, 0, 0, 0, 0,
SWP_FRAMECHANGED or SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER);
end;
end;
procedure TDock97.SetBackground (Value: TBitmap);
begin
FBkg.Assign (Value);
end;
function TDock97.UsingBackground: Boolean;
begin
Result := (FBkg.Width <> 0) and (FBkg.Height <> 0);
end;
procedure TDock97.InvalidateBackgrounds;
{ Called after background is changed }
var
I: Integer;
begin
Invalidate;
{ Synchronize child toolbars also }
for I := 0 to DockList.Count-1 do
with TCustomToolWindow97(DockList[I]) do begin
InvalidateDockedNCArea;
Invalidate;
end;
end;
procedure TDock97.BackgroundChanged (Sender: TObject);
begin
{ Erase the cache }
FBkgCache.Free;
FBkgCache := nil;
InvalidateBackgrounds;
end;
procedure TDock97.SetBackgroundOnToolbars (Value: Boolean);
begin
if FBkgOnToolbars <> Value then begin
FBkgOnToolbars := Value;
InvalidateBackgrounds;
end;
end;
procedure TDock97.SetBackgroundTransparent (Value: Boolean);
begin
if FBkgTransparent <> Value then begin
FBkgTransparent := Value;
if UsingBackground then
{ Erase the cache }
BackgroundChanged (FBkg);
end;
end;
procedure TDock97.SetBoundLines (Value: TDockBoundLines);
begin
if FBoundLines <> Value then begin
FBoundLines := Value;
{ Recalculate the non-client area }
SetWindowPos (Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER);
end;
end;
procedure TDock97.SetFixAlign (Value: Boolean);
begin
if FFixAlign <> Value then begin
FFixAlign := Value;
ArrangeToolbars;
end;
end;
procedure TDock97.SetPosition (Value: TDockPosition);
begin
if ControlCount <> 0 then
raise EInvalidOperation.Create(STB97DockCannotChangePosition);
FPosition := Value;
case Position of
dpTop: Align := alTop;
dpBottom: Align := alBottom;
dpLeft: Align := alLeft;
dpRight: Align := alRight;
end;
end;
function TDock97.GetToolbarCount: Integer;
begin
Result := DockList.Count;
end;
function TDock97.GetToolbars (Index: Integer): TCustomToolWindow97;
begin
Result := TCustomToolWindow97(DockList[Index]);
end;
{ TFloatParent - Internal }
procedure TFloatParent.CreateParams (var Params: TCreateParams);
begin
inherited;
with Params do begin
Style := WS_CHILD;
ExStyle := 0;
end;
end;
{ Global procedures }
procedure CustomLoadToolbarPositions (const Form: TForm;
const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer);
function FindDock (AName: String): TDock97;
var
I: Integer;
begin
Result := nil;
for I := 0 to Form.ComponentCount-1 do
if (Form.Components[I] is TDock97) and (Form.Components[I].Name = AName) then begin
Result := TDock97(Form.Components[I]);
Break;
end;
end;
procedure ReadValues (const Toolbar: TCustomToolWindow97; const NewDock: TDock97);
begin
with Toolbar do begin
FDockRow := ReadIntProc(Name, rvDockRow, FDockRow, ExtraData);
FDockPos := ReadIntProc(Name, rvDockPos, FDockPos, ExtraData);
FFloatingTopLeft.X := ReadIntProc(Name, rvFloatLeft, 0, ExtraData);
FFloatingTopLeft.Y := ReadIntProc(Name, rvFloatTop, 0, ExtraData);
ReadPositionData (ReadIntProc, ReadStringProc, ExtraData);
DockedTo := NewDock;
DoneReadingPositionData;
end;
end;
var
DocksDisabled: TList;
I: Integer;
ADock: TDock97;
DockedToName: String;
begin
DocksDisabled := TList.Create;
try
with Form do
for I := 0 to ComponentCount-1 do
if Components[I] is TDock97 then begin
Inc (TDock97(Components[I]).DisableArrangeToolbars);
DocksDisabled.Add (Components[I]);
end;
for I := 0 to Form.ComponentCount-1 do
if Form.Components[I] is TCustomToolWindow97 then
with TCustomToolWindow97(Form.Components[I]) do begin
if Name = '' then
raise Exception.Create (STB97ToolWinNameNotSet);
if ReadIntProc(Name, rvRev, 0, ExtraData) = rdCurrentRev then begin
Visible := ReadIntProc(Name, rvVisible, Ord(Visible), ExtraData) <> 0;
DockedToName := ReadStringProc(Name, rvDockedTo, '', ExtraData);
if DockedToName <> '' then begin
if DockedToName <> rdDockedToFloating then begin
ADock := FindDock(DockedToName);
if (ADock <> nil) and (ADock.FAllowDrag) then
ReadValues (TCustomToolWindow97(Form.Components[I]), ADock);
end
else begin
ReadValues (TCustomToolWindow97(Form.Components[I]), nil);
MoveOnScreen (True);
end;
end;
end;
end;
finally
for I := DocksDisabled.Count-1 downto 0 do begin
Dec (TDock97(DocksDisabled[I]).DisableArrangeToolbars);
TDock97(DocksDisabled[I]).ArrangeToolbars;
end;
DocksDisabled.Free;
end;
end;
procedure CustomSaveToolbarPositions (const Form: TForm;
const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer);
var
I: Integer;
N: String;
begin
for I := 0 to Form.ComponentCount-1 do
if Form.Components[I] is TCustomToolWindow97 then
with TCustomToolWindow97(Form.Components[I]) do begin
if Name = '' then
raise Exception.Create (STB97ToolwinNameNotSet);
if not Docked then
N := rdDockedToFloating
else begin
if DockedTo.FAllowDrag then begin
N := DockedTo.Name;
if N = '' then
raise Exception.Create (STB97ToolwinDockedToNameNotSet);
end
else
N := '';
end;
WriteIntProc (Name, rvRev, rdCurrentRev, ExtraData);
WriteIntProc (Name, rvVisible, Ord(Visible), ExtraData);
with TCustomToolWindow97(Form.Components[I]) do begin
WriteStringProc (Name, rvDockedTo, N, ExtraData);
WriteIntProc (Name, rvDockRow, FDockRow, ExtraData);
WriteIntProc (Name, rvDockPos, FDockPos, ExtraData);
WriteIntProc (Name, rvFloatLeft, FFloatingTopLeft.X, ExtraData);
WriteIntProc (Name, rvFloatTop, FFloatingTopLeft.Y, ExtraData);
WritePositionData (WriteIntProc, WriteStringProc, ExtraData);
end;
end;
end;
function IniReadInt (const ToolbarName, Value: String; const Default: Longint;
const ExtraData: Pointer): Longint; far;
begin
Result := TIniFile(ExtraData).ReadInteger(ToolbarName, Value, Default);
end;
function IniReadString (const ToolbarName, Value, Default: String;
const ExtraData: Pointer): String; far;
begin
Result := TIniFile(ExtraData).ReadString(ToolbarName, Value, Default);
end;
procedure IniWriteInt (const ToolbarName, Value: String; const Data: Longint;
const ExtraData: Pointer); far;
begin
TIniFile(ExtraData).WriteInteger (ToolbarName, Value, Data);
end;
procedure IniWriteString (const ToolbarName, Value, Data: String;
const ExtraData: Pointer); far;
begin
TIniFile(ExtraData).WriteString (ToolbarName, Value, Data);
end;
procedure IniLoadToolbarPositions (const Form: TForm; const Filename: String);
var
Ini: TIniFile;
begin
Ini := TIniFile.Create(Filename);
try
CustomLoadToolbarPositions (Form, IniReadInt, IniReadString, Ini);
finally
Ini.Free;
end;
end;
procedure IniSaveToolbarPositions (const Form: TForm; const Filename: String);
var
Ini: TIniFile;
begin
Ini := TIniFile.Create(Filename);
try
CustomSaveToolbarPositions (Form, IniWriteInt, IniWriteString, Ini);
finally
Ini.Free;
end;
end;
function RegReadInt (const ToolbarName, Value: String; const Default: Longint;
const ExtraData: Pointer): Longint; far;
begin
Result := TRegIniFile(ExtraData).ReadInteger(ToolbarName, Value, Default);
end;
function RegReadString (const ToolbarName, Value, Default: String;
const ExtraData: Pointer): String; far;
begin
Result := TRegIniFile(ExtraData).ReadString(ToolbarName, Value, Default);
end;
procedure RegWriteInt (const ToolbarName, Value: String; const Data: Longint;
const ExtraData: Pointer); far;
begin
TRegIniFile(ExtraData).WriteInteger (ToolbarName, Value, Data);
end;
procedure RegWriteString (const ToolbarName, Value, Data: String;
const ExtraData: Pointer); far;
begin
TRegIniFile(ExtraData).WriteString (ToolbarName, Value, Data);
end;
procedure RegLoadToolbarPositions (const Form: TForm; const BaseRegistryKey: String);
var
Reg: TRegIniFile;
begin
Reg := TRegIniFile.Create(BaseRegistryKey);
try
CustomLoadToolbarPositions (Form, RegReadInt, RegReadString, Reg);
finally
Reg.Free;
end;
end;
procedure RegSaveToolbarPositions (const Form: TForm; const BaseRegistryKey: String);
var
Reg: TRegIniFile;
begin
Reg := TRegIniFile.Create(BaseRegistryKey);
try
CustomSaveToolbarPositions (Form, RegWriteInt, RegWriteString, Reg);
finally
Reg.Free;
end;
end;
{ TCustomToolWindow97 - Internal }
constructor TCustomToolWindow97.Create (AOwner: TComponent);
begin
inherited;
GetParams (FParams);
if not(AOwner is TForm) then
raise EInvalidOperation.Create(STB97ToolwinNotFormOwner);
{ because it frequently casts Owner into a TForm }
MDIParentForm := GetMDIParent(TForm(AOwner));
Inc (FDisableArrangeControls);
try
ControlStyle := ControlStyle +
[csAcceptsControls, csClickEvents, csDoubleClicks, csSetCaption] -
[csCaptureMouse{capturing is done manually}, csOpaque];
if not(csDesigning in ComponentState) then begin
FloatParent := TFloatParent.Create(TForm(AOwner));
FloatParent.Parent := MDIParentForm;
{ Set up the hooks for the parent form of the toolbar, and a main
window hook }
InstallHooks (hkParentForm, MDIParentForm, True);
{ Add a another hook if it's owner is an MDI child }
if TForm(AOwner).FormStyle = fsMDIChild then
InstallHooks (hkChildForm, TForm(AOwner), False);
{ Need to move it offscreen while loading to prevent any flashing as it's
updating }
SetNotOnScreen (True);
end
else
FloatParent := TForm(AOwner);
FInactiveCaption := not Application.Active;
FActivateParent := True;
FDockableTo := [dpTop, dpBottom, dpLeft, dpRight];
FCloseButton := True;
FDragHandle := True;
FResizable := True;
FHideWhenInactive := True;
FDockPos := -1;
Color := clBtnFace;
DockedTo := nil;
if not(csDesigning in ComponentState) then begin
{ Since SetNotOnScreen(True) was called, it needs to restore the toolbars
back by posting a message which will be processed once it's done
loading. }
if DoneCreatingList.IndexOf(Self) = -1 then { can't have duplicates }
DoneCreatingList.Add (Self);
PostMessage (Application.Handle, WM_TB97DoneCreating,
WM_TB97DoneCreating_Magic, WM_TB97DoneCreating_Magic);
end;
finally
Dec (FDisableArrangeControls);
end;
end;
destructor TCustomToolWindow97.Destroy;
begin
if not(csDesigning in ComponentState) then begin
DoneCreatingList.Remove (Self); { just in case }
UninstallHooks (hkParentForm, MDIParentForm);
if TForm(Owner).FormStyle = fsMDIChild then
UninstallHooks (hkChildForm, TForm(Owner));
end;
inherited;
end;
procedure TCustomToolWindow97.SetNotOnScreen (const Value: Boolean);
begin
if NotOnScreen <> Value then begin
NotOnScreen := Value;
{ Update the actual visibility of the toolbar by sending a
CM_SHOWINGCHANGED message. CM_SHOWINGCHANGED cannot be sent if the
handle has not been allocated yet, so check HandleAllocated first }
if HandleAllocated then
Perform (CM_SHOWINGCHANGED, 0, 0);
end;
end;
procedure TCustomToolWindow97.SetInactiveCaption (const Value: Boolean);
begin
if FInactiveCaption <> Value then begin
FInactiveCaption := Value;
DrawFloatingNCArea (0, False, True, False);
end;
end;
procedure TCustomToolWindow97.WMMove (var Message: TWMMove);
begin
inherited;
if Docked and DockedTo.UsingBackground then begin
{ Needs to redraw so that background is lined up with the dock at the
new position }
InvalidateDockedNCArea;
{ To minimize flicker, InvalidateRect is called with the Erase parameter
set to False instead of calling the Invalidate method }
if HandleAllocated then
InvalidateRect (Handle, nil, False);
end;
end;
procedure TCustomToolWindow97.WMSize (var Message: TWMSize);
begin
inherited;
if Assigned(FOnResize) then
FOnResize (Self);
end;
procedure TCustomToolWindow97.WMGetMinMaxInfo (var Message: TWMGetMinMaxInfo);
begin
inherited;
{ Because the window uses the WS_THICKFRAME style (but not for the usual
purpose), it must process the WM_GETMINMAXINFO message to remove the
minimum and maximum size limits it imposes by default. }
with Message.MinMaxInfo^ do begin
with ptMinTrackSize do begin
X := 1;
Y := 1;
{ Note to self: Don't put GetMinimumSize code here, since
ClientWidth/Height values are sometimes invalid during a RecreateWnd }
end;
with ptMaxTrackSize do begin
{ Because of the 16-bit (signed) size limitations of Windows 95,
Smallints must be used instead of Integers or Longints }
X := High(Smallint);
Y := High(Smallint);
end;
end;
end;
procedure TCustomToolWindow97.CMShowingChanged (var Message: TMessage);
const
ShowFlags: array[Boolean] of UINT = (
SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_NOACTIVATE or SWP_HIDEWINDOW,
SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_NOACTIVATE or SWP_SHOWWINDOW);
begin
{ inherited isn't called since TCustomToolWindow97 handles CM_SHOWINGCHANGED
itself. For reference, the original TWinControl implementation is:
const
ShowFlags: array[Boolean] of Word = (
SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_HIDEWINDOW,
SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_SHOWWINDOW);
begin
SetWindowPos(FHandle, 0, 0, 0, 0, 0, ShowFlags[FShowing]);
end;
}
SetWindowPos (Handle, 0, 0, 0, 0, 0, ShowFlags[Showing and not NotOnScreen]);
end;
procedure TCustomToolWindow97.CreateParams (var Params: TCreateParams);
begin
inherited;
if Parent = FloatParent then
with Params do begin
{ Note: WS_THICKFRAME and WS_BORDER styles are included to ensure that
sizing grips are displayed on child controls with scrollbars. The
thick frame or border is not drawn by Windows; TCustomToolWindow97
handles all border drawing by itself. }
if not(csDesigning in ComponentState) then
Style := WS_POPUP or WS_THICKFRAME or WS_BORDER
else
Style := Style or WS_THICKFRAME or WS_BORDER;
ExStyle := 0;
end;
end;
procedure TCustomToolWindow97.Notification (AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent = FDefaultDock) then
FDefaultDock := nil;
end;
procedure TCustomToolWindow97.MoveOnScreen (const OnlyIfFullyOffscreen: Boolean);
{ Moves the (floating) toolbar so that it is fully (or at least mostly) in
view on the screen }
var
R, S, Test: TRect;
begin
if not Docked then begin
R := BoundsRect;
S := GetDesktopArea;
if OnlyIfFullyOffscreen and IntersectRect(Test, R, S) then
Exit;
if R.Right > S.Right then
OffsetRect (R, S.Right - R.Right, 0);
if R.Bottom > S.Bottom then
OffsetRect (R, 0, S.Bottom - R.Bottom);
if R.Left < S.Left then
OffsetRect (R, S.Left - R.Left, 0);
if R.Top < S.Top then
OffsetRect (R, 0, S.Top - R.Top);
BoundsRect := R;
end;
end;
procedure TCustomToolWindow97.ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer);
begin
end;
procedure TCustomToolWindow97.DoneReadingPositionData;
begin
end;
procedure TCustomToolWindow97.WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer);
begin
end;
procedure TCustomToolWindow97.InitializeOrdering;
begin
end;
procedure TCustomToolWindow97.GetDockRowSize (var AHeightOrWidth: Integer);
begin
GetBarSize (AHeightOrWidth, GetDockTypeOf(DockedTo));
if Docked then
with DockedTo do begin
BuildRowInfo;
AHeightOrWidth := DockedTo.GetRowSize(FDockRow, Self);
end;
end;
procedure TCustomToolWindow97.SizeChanging (const AWidth, AHeight: Integer);
begin
end;
procedure TCustomToolWindow97.Loaded;
var
R: TRect;
begin
inherited;
{ Adjust coordinates if it was initially floating }
if not(csDesigning in ComponentState) and not Docked then begin
R := BoundsRect;
MapWindowPoints (TForm(Owner).Handle, 0, R, 2);
BoundsRect := R;
MoveOnScreen (False);
end;
InitializeOrdering;
{ Arranging of controls is disabled while component was loading, so rearrange
it now }
ArrangeControls;
end;
procedure TCustomToolWindow97.BeginUpdate;
begin
Inc (FDisableArrangeControls);
end;
procedure TCustomToolWindow97.EndUpdate;
begin
Dec (FDisableArrangeControls);
if FArrangeNeeded and (FDisableArrangeControls = 0) then
ArrangeControls;
end;
procedure TCustomToolWindow97.CustomArrangeControls (const ArrangeType: TToolWindowArrangeType;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint);
var
WH: Integer;
begin
if (FDisableArrangeControls > 0) or
{ Prevent flicker while loading or destroying }
(csLoading in ComponentState) or
{ Following line added in 1.53 to stop the access violations that 1.52 was
causing while destroying. }
(csDestroying in ComponentState) or
(Parent = nil) or
(Parent.HandleAllocated and (csDestroying in Parent.ComponentState)) then begin
FArrangeNeeded := True;
Exit;
end;
FArrangeNeeded := False;
NewClientSize.X := 0;
NewClientSize.Y := 0;
Inc (FDisableArrangeControls);
try
OrderControls (ArrangeType <> atNone, WasDockedTo, DockingTo, NewClientSize);
if ArrangeType = atMoveControlsAndResize then
with NewClientSize do begin
if Docked then begin
GetDockRowSize (WH);
if not(DockedTo.Position in PositionLeftOrRight) then begin
if WH > Y then Y := WH;
if FullSize then
X := DockedTo.ClientWidth - (Width-ClientWidth);
end
else begin
if WH > X then X := WH;
if FullSize then
Y := DockedTo.ClientHeight - (Height-ClientHeight);
end;
end;
if (ClientWidth <> X) or (ClientHeight <> Y) then begin
Inc (FUpdatingBounds);
try
SetBounds (Left, Top, (Width-ClientWidth) + X, (Height-ClientHeight) + Y);
finally
Dec (FUpdatingBounds);
end;
end;
end;
finally
Dec (FDisableArrangeControls);
end;
end;
procedure TCustomToolWindow97.ArrangeControls;
var
TempSize: TPoint;
begin
CustomArrangeControls (atMoveControlsAndResize, DockedTo, DockedTo, TempSize);
end;
procedure TCustomToolWindow97.AlignControls (AControl: TControl; var Rect: TRect);
{ VCL calls this whenever any child controls in the toolbar are moved, sized,
inserted, etc. It doesn't need to make use of the AControl and Rect
parameters. }
begin
if Params.CallAlignControls then
inherited;
ArrangeControls;
end;
procedure TCustomToolWindow97.SetBounds (ALeft, ATop, AWidth, AHeight: Integer);
begin
if (FUpdatingBounds = 0) and ((AWidth <> Width) or (AHeight <> Height)) then
SizeChanging (AWidth, AHeight);
{ This allows you to drag the toolbar around the dock at design time }
if (csDesigning in ComponentState) and not(csLoading in ComponentState) and
Docked and (FUpdatingBounds = 0) and ((ALeft <> Left) or (ATop <> Top)) then begin
if not(DockedTo.Position in PositionLeftOrRight) then begin
FDockRow := DockedTo.GetDesignModeRowOf(ATop+(Height div 2));
FDockPos := ALeft;
end
else begin
FDockRow := DockedTo.GetDesignModeRowOf(ALeft+(Width div 2));
FDockPos := ATop;
end;
inherited SetBounds (Left, Top, AWidth, AHeight); { only pass any size changes }
DockedTo.ArrangeToolbars; { let ArrangeToolbars take care of position changes }
end
else begin
inherited;
if not(csLoading in ComponentState) and not Docked and (FUpdatingBounds = 0) then
FFloatingTopLeft := BoundsRect.TopLeft;
end;
end;
procedure TCustomToolWindow97.SetParent (AParent: TWinControl);
begin
if not(csDesigning in ComponentState) and (AParent = TForm(Owner)) then
AParent := FloatParent;
if (AParent <> nil) and not(AParent is TDock97) and
not(AParent = Owner) and not(AParent is TFloatParent) then
raise EInvalidOperation.Create(STB97ToolwinParentNotAllowed);
if not(csDestroying in ComponentState) and Assigned(FOnRecreating) then
FOnRecreating (Self);
if Parent is TDock97 then
TDock97(Parent).ChangeDockList (False, Self, Visible or (FHidden <> 0));
{ Ensure that the handle is destroyed now so that any messages in the queue
get flushed. This is neccessary since existing messages may reference
FDockedTo or FDocked, which is changed below. }
inherited SetParent (nil);
{ ^ Note to self: SetParent is used instead of DestroyHandle because it does
additional processing }
if not(AParent is TDock97) then
FDockedTo := nil
else
FDockedTo := TDock97(AParent);
FDocked := FDockedTo <> nil;
try
inherited;
except
{ Failure is rare, but just in case, restore FDockedTo and FDocked back. }
if not(Parent is TDock97) then
FDockedTo := nil
else
FDockedTo := TDock97(Parent);
FDocked := FDockedTo <> nil;
raise;
end;
if Parent is TDock97 then
TDock97(Parent).ChangeDockList (True, Self, Visible or (FHidden <> 0));
if not(csDestroying in ComponentState) and Assigned(FOnRecreated) then
FOnRecreated (Self);
end;
function GetCaptionRect (const Control: TCustomToolWindow97;
const AdjustForBorder, MinusCloseButton: Boolean): TRect;
begin
Result := Rect(0, 0, Control.ClientWidth, GetSmallCaptionHeight-1);
if MinusCloseButton then
Dec (Result.Right, GetSmallCaptionHeight-1);
if AdjustForBorder then
with GetBorderSize(Control.Resizable) do
OffsetRect (Result, X, Y);
end;
function GetCloseButtonRect (const Control: TCustomToolWindow97;
const AdjustForBorder: Boolean): TRect;
begin
Result := Rect(0, 0, Control.ClientWidth, GetSmallCaptionHeight-1);
if AdjustForBorder then
with GetBorderSize(Control.Resizable) do
OffsetRect (Result, X, Y);
Result.Left := Result.Right - (GetSmallCaptionHeight-1);
end;
procedure TCustomToolWindow97.WMNCCalcSize (var Message: TWMNCCalcSize);
begin
{ Doesn't call inherited since it overrides the normal NC sizes }
Message.Result := 0;
with Message.CalcSize_Params^ do begin
if not Docked then begin
with GetBorderSize(Resizable) do
InflateRect (rgrc[0], -X, -Y);
Inc (rgrc[0].Top, GetSmallCaptionHeight);
end
else begin
InflateRect (rgrc[0], -DockedBorderSize, -DockedBorderSize);
if DockedTo.FAllowDrag and FDragHandle then begin
if not(DockedTo.Position in PositionLeftOrRight) then
Inc (rgrc[0].Left, DragHandleSize)
else
Inc (rgrc[0].Top, DragHandleSize);
end;
end;
end;
end;
procedure TCustomToolWindow97.DrawFloatingNCArea (const Clip: HRGN;
const RedrawBorder, RedrawCaption, RedrawCloseButton: Boolean);
{ Redraws all the non-client area (the border, title bar, and close button) of
the toolbar when it is floating. }
const
CaptionBkColors: array[Boolean] of Integer =
(COLOR_ACTIVECAPTION, COLOR_INACTIVECAPTION);
CaptionTextColors: array[Boolean] of Integer =
(COLOR_CAPTIONTEXT, COLOR_INACTIVECAPTIONTEXT);
procedure Win3DrawCaption (const DC: HDC; const R: TRect);
{ Emulates DrawCaption, which isn't supported in Win 3.x }
const
Ellipsis = '...';
var
R2: TRect;
SaveTextColor, SaveBkColor: TColorRef;
SaveFont: HFONT;
Cap: String;
function CaptionTextWidth: Integer;
var
Size: TSize;
begin
GetTextExtentPoint32 (DC, PChar(Cap), Length(Cap), Size);
Result := Size.cx;
end;
begin
R2 := R;
{ Fill the rectangle }
FillRect (DC, R2, GetSysColorBrush(CaptionBkColors[FInactiveCaption]));
Inc (R2.Left);
Dec (R2.Right);
SaveFont := SelectObject(DC, CreateFont(-11, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, 0, 0, 'MS Sans Serif'));
{ Add ellipsis to caption if necessary }
Cap := Caption;
if CaptionTextWidth > R2.Right-R2.Left then begin
Cap := Cap + Ellipsis;
while (CaptionTextWidth > R2.Right-R2.Left) and (Length(Cap) > 4) do
Delete (Cap, Length(Cap)-Length(Ellipsis), 1)
end;
{ Draw the text }
SaveBkColor := SetBkColor(DC, GetSysColor(CaptionBkColors[FInactiveCaption]));
SaveTextColor := SetTextColor(DC, GetSysColor(CaptionTextColors[FInactiveCaption]));
DrawText (DC, PChar(Cap), Length(Cap), R2, DT_SINGLELINE or DT_NOPREFIX or DT_VCENTER);
SetTextColor (DC, SaveTextColor);
SetBkColor (DC, SaveBkColor);
DeleteObject (SelectObject(DC, SaveFont));
end;
const
CloseButtonState: array[Boolean] of UINT = (0, DFCS_PUSHED);
ActiveCaptionFlags: array[Boolean] of UINT = (DC_ACTIVE, 0);
var
DC: HDC;
R: TRect;
NewClipRgn: HRGN;
NewDrawCaption: function(p1: HWND; p2: HDC; const p3: TRect; p4: UINT): BOOL; stdcall;
SavePen: HPEN;
SaveIndex: Integer;
I: Integer;
begin
if Docked then Exit;
DC := GetWindowDC(Handle);
try
{ Use update region }
if (Clip <> 0) and (Clip <> 1) then begin
GetWindowRect (Handle, R);
if SelectClipRgn(DC, Clip) = ERROR then begin
NewClipRgn := CreateRectRgnIndirect(R);
SelectClipRgn (DC, NewClipRgn);
DeleteObject (NewClipRgn);
end;
OffsetClipRgn (DC, -R.Left, -R.Top);
end;
{ Border }
if RedrawBorder then begin
{ This works around WM_NCPAINT problem described at top of source code }
{no! R := Rect(0, 0, Width, Height);}
GetWindowRect (Handle, R); OffsetRect (R, -R.Left, -R.Top);
for I := 1 to GetBorderSize(Resizable).X do
case I of
1: DrawEdge (DC, R, EDGE_RAISED, BF_RECT or BF_ADJUST);
2: ;
else
FrameRect (DC, R, GetSysColorBrush(COLOR_BTNFACE));
InflateRect (R, -1, -1);
end;
end;
if RedrawCaption and FCloseButton and RedrawCloseButton then
SaveIndex := SaveDC(DC)
else
SaveIndex := 0;
try
if SaveIndex <> 0 then
with GetCloseButtonRect(Self, True) do
{ Reduces flicker }
ExcludeClipRect (DC, Left, Top, Right, Bottom);
{ Caption }
if RedrawCaption then begin
R := GetCaptionRect(Self, True, FCloseButton);
if NewStyleControls then begin
{ Use a dynamic import of DrawCaption since it's Win95/NT 4.0 only.
Also note that Delphi's Win32 help for DrawCaption is totally wrong!
I got updated info from www.microsoft.com/msdn/sdk/ }
NewDrawCaption := GetProcAddress(GetModuleHandle(user32), 'DrawCaption');
NewDrawCaption (Handle, DC, R, DC_TEXT or DC_SMALLCAP or
ActiveCaptionFlags[FInactiveCaption]);
end
else
Win3DrawCaption (DC, R);
{ Line below caption }
R := GetCaptionRect(Self, True, False);
SavePen := SelectObject(DC, CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE)));
MoveToEx (DC, R.Left, R.Bottom, nil);
LineTo (DC, R.Right, R.Bottom);
DeleteObject (SelectObject(DC, SavePen));
end;
finally
if SaveIndex <> 0 then
RestoreDC (DC, SaveIndex);
end;
{ Close button }
if FCloseButton then begin
if RedrawCloseButton then begin
R := GetCloseButtonRect(Self, True);
InflateRect (R, -1, -1);
DrawFrameControl (DC, R, DFC_CAPTION, DFCS_CAPTIONCLOSE or
CloseButtonState[CloseButtonDown]);
end;
if RedrawCaption then begin
{ Caption-colored frame around close button }
R := GetCloseButtonRect(Self, True);
FrameRect (DC, R, GetSysColorBrush(CaptionBkColors[FInactiveCaption]));
end;
end;
finally
ReleaseDC (Handle, DC);
end;
end;
procedure TCustomToolWindow97.ValidateDockedNCArea;
var
Msg: TMsg;
begin
if HandleAllocated then
while PeekMessage(Msg, Handle, WM_TB97PaintDockedNCArea,
WM_TB97PaintDockedNCArea, PM_REMOVE or PM_NOYIELD) do ;
end;
procedure TCustomToolWindow97.InvalidateDockedNCArea;
begin
ValidateDockedNCArea;
if HandleAllocated then
PostMessage (Handle, WM_TB97PaintDockedNCArea, 0, 0);
end;
procedure TCustomToolWindow97.WMTB97PaintDockedNCArea (var Message: TMessage);
begin
DrawDockedNCArea (0);
end;
procedure TCustomToolWindow97.DrawDockedNCArea (const Clip: HRGN);
{ Redraws all the non-client area of the toolbar when it is docked. }
var
DC: HDC;
R: TRect;
NewClipRgn: HRGN;
DockType: TDockType;
X, Y: Integer;
R2, R3, R4: TRect;
P1, P2: TPoint;
Brush: HBRUSH;
Clr: TColorRef;
UsingBackground: Boolean;
procedure DrawRaisedEdge (R: TRect; const FillInterior: Boolean);
const
FillMiddle: array[Boolean] of UINT = (0, BF_MIDDLE);
begin
DrawEdge (DC, R, BDR_RAISEDINNER, BF_RECT or FillMiddle[FillInterior]);
end;
begin
ValidateDockedNCArea;
if not Docked then Exit;
DC := GetWindowDC(Handle);
try
{ Use update region }
if (Clip <> 0) and (Clip <> 1) then begin
GetWindowRect (Handle, R);
if SelectClipRgn(DC, Clip) = ERROR then begin
NewClipRgn := CreateRectRgnIndirect(R);
SelectClipRgn (DC, NewClipRgn);
DeleteObject (NewClipRgn);
end;
OffsetClipRgn (DC, -R.Left, -R.Top);
end;
{ This works around WM_NCPAINT problem described at top of source code }
{no! R := Rect(0, 0, Width, Height);}
GetWindowRect (Handle, R); OffsetRect (R, -R.Left, -R.Top);
if not(DockedTo.Position in PositionLeftOrRight) then
DockType := dtTopBottom
else
DockType := dtLeftRight;
Brush := CreateSolidBrush(ColorToRGB(Color));
{ Border }
DrawRaisedEdge (R, False);
R2 := R;
InflateRect (R2, -1, -1);
FrameRect (DC, R2, Brush);
UsingBackground := DockedTo.UsingBackground and DockedTo.FBkgOnToolbars;
{ Draw the Background }
if UsingBackground then begin
R2 := R;
P1 := DockedTo.ClientToScreen(Point(0, 0));
P2 := DockedTo.Parent.ClientToScreen(DockedTo.BoundsRect.TopLeft);
Dec (R2.Left, Left + DockedTo.Left + (P1.X-P2.X));
Dec (R2.Top, Top + DockedTo.Top + (P1.Y-P2.Y));
InflateRect (R, -1, -1);
GetWindowRect (Handle, R4);
R3 := ClientRect;
with ClientToScreen(Point(0, 0)) do
OffsetRect (R3, X-R4.Left, Y-R4.Top);
DockedTo.DrawBackground (DC, R, @R3, R2);
end;
{ The drag handle at the left, or top }
if DockedTo.FAllowDrag and FDragHandle then begin
Clr := GetSysColor(COLOR_BTNHIGHLIGHT);
if DockType <> dtLeftRight then begin
Y := ClientHeight+2;
if not UsingBackground then begin
FillRect (DC, Rect(2, 2, 4, Y), Brush);
FillRect (DC, Rect(10, 2, 11, Y), Brush);
end;
DrawRaisedEdge (Rect(4, 2, 7, Y), True);
SetPixelV (DC, 4, Y-1, Clr);
DrawRaisedEdge (Rect(7, 2, 10, Y), True);
SetPixelV (DC, 7, Y-1, Clr);
end
else begin
X := ClientWidth+2;
if not UsingBackground then begin
FillRect (DC, Rect(2, 2, X, 4), Brush);
FillRect (DC, Rect(2, 10, X, 11), Brush);
end;
DrawRaisedEdge (Rect(2, 4, X, 7), True);
SetPixelV (DC, X-1, 4, Clr);
DrawRaisedEdge (Rect(2, 7, X, 10), True);
SetPixelV (DC, X-1, 7, Clr);
end;
end;
DeleteObject (Brush);
finally
ReleaseDC (Handle, DC);
end;
end;
procedure TCustomToolWindow97.WMNCPaint (var Message: TMessage);
begin
{ Don't call inherited because it overrides the default NC painting }
if Docked then
DrawDockedNCArea (Message.WParam)
else
DrawFloatingNCArea (Message.WParam, True, True, True);
end;
procedure TCustomToolWindow97.Paint;
var
R, R2, R3: TRect;
P1, P2: TPoint;
begin
inherited;
if Docked and DockedTo.UsingBackground and DockedTo.FBkgOnToolbars then begin
R := ClientRect;
R2 := R;
P1 := DockedTo.ClientToScreen(Point(0, 0));
P2 := DockedTo.Parent.ClientToScreen(DockedTo.BoundsRect.TopLeft);
Dec (R2.Left, Left + DockedTo.Left + (P1.X-P2.X));
Dec (R2.Top, Top + DockedTo.Top + (P1.Y-P2.Y));
GetWindowRect (Handle, R3);
with ClientToScreen(Point(0, 0)) do begin
Inc (R2.Left, R3.Left-X);
Inc (R2.Top, R3.Top-Y);
end;
DockedTo.DrawBackground (Canvas.Handle, R, nil, R2);
end;
end;
function TCustomToolWindow97.GetPalette: HPALETTE;
begin
if Docked and DockedTo.UsingBackground then
Result := DockedTo.FBkg.Palette
else
Result := 0;
end;
function TCustomToolWindow97.PaletteChanged (Foreground: Boolean): Boolean;
begin
Result := inherited PaletteChanged(Foreground);
if Result and not Foreground then begin
{ There seems to be a bug in Delphi's palette handling. When the form is
inactive and another window realizes a palette, docked TToolbar97s
weren't getting redrawn. So this workaround code was added. }
InvalidateDockedNCArea;
Invalidate;
end;
end;
procedure DrawDragRect (const DC: HDC; const NewRect, OldRect: PRect;
const NewSize, OldSize: TSize; const Brush: HBRUSH; BrushLast: HBRUSH);
{ Draws a dragging outline, hiding the old one if neccessary. This is
completely flicker free, unlike the old DrawFocusRect method. In case
you're wondering, I got a lot of ideas from the MFC sources.
Either NewRect or OldRect can be nil or empty.
NOTE: If the specific DC had a clipping region, it will be gone when this
function exits. }
const
BlankRect: TRect = (Left: 0; Top: 0; Right: 0; Bottom: 0);
var
rgnNew, rgnOutside, rgnInside, rgnLast, rgnUpdate: HRGN;
R: TRect;
SaveBrush: HBRUSH;
begin
rgnLast := 0;
rgnUpdate := 0;
{ First, determine the update region and select it }
if NewRect = nil then begin
R := BlankRect;
rgnOutside := CreateRectRgnIndirect(R);
end
else begin
R := NewRect^;
rgnOutside := CreateRectRgnIndirect(R);
InflateRect (R, -NewSize.cx, -NewSize.cy);
IntersectRect (R, R, NewRect^);
end;
rgnInside := CreateRectRgnIndirect(R);
rgnNew := CreateRectRgnIndirect(BlankRect);
CombineRgn (rgnNew, rgnOutside, rgnInside, RGN_XOR);
if BrushLast = 0 then
BrushLast := Brush;
if OldRect <> nil then begin
{ Find difference between new region and old region }
rgnLast := CreateRectRgnIndirect(BlankRect);
with OldRect^ do
SetRectRgn (rgnOutside, Left, Top, Right, Bottom);
R := OldRect^;
InflateRect (R, -OldSize.cx, -OldSize.cy);
IntersectRect (R, R, OldRect^);
SetRectRgn (rgnInside, R.Left, R.Top, R.Right, R.Bottom);
CombineRgn (rgnLast, rgnOutside, rgnInside, RGN_XOR);
{ Only diff them if brushes are the same}
if Brush = BrushLast then begin
rgnUpdate := CreateRectRgnIndirect(BlankRect);
CombineRgn (rgnUpdate, rgnLast, rgnNew, RGN_XOR);
end;
end;
if (Brush <> BrushLast) and (OldRect <> nil) then begin
{ Brushes are different -- erase old region first }
SelectClipRgn (DC, rgnLast);
GetClipBox (DC, R);
SaveBrush := SelectObject(DC, BrushLast);
PatBlt (DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
SelectObject (DC, SaveBrush);
end;
{ Draw into the update/new region }
if rgnUpdate <> 0 then
SelectClipRgn (DC, rgnUpdate)
else
SelectClipRgn (DC, rgnNew);
GetClipBox (DC, R);
SaveBrush := SelectObject(DC, Brush);
PatBlt (DC, R.Left, R.Top, R.Right-R.Left, R.Bottom-R.Top, PATINVERT);
SelectObject (DC, SaveBrush);
{ Free regions }
if rgnNew <> 0 then DeleteObject (rgnNew);
if rgnOutside <> 0 then DeleteObject (rgnOutside);
if rgnInside <> 0 then DeleteObject (rgnInside);
if rgnLast <> 0 then DeleteObject (rgnLast);
if rgnUpdate <> 0 then DeleteObject (rgnUpdate);
{ Clean up DC }
SelectClipRgn (DC, 0);
end;
procedure TCustomToolWindow97.DrawDraggingOutline (const DC: HDC;
const NewRect, OldRect: PRect; const NewDocking, OldDocking: Boolean);
function CreateHalftoneBrush: HBRUSH;
const
Patterns: array[Boolean] of Word = ($5555, $AAAA);
var
I: Integer;
GrayPattern: array[0..7] of Word;
GrayBitmap: HBITMAP;
begin
for I := 0 to 7 do
GrayPattern[I] := Patterns[Odd(I)];
GrayBitmap := CreateBitmap(8, 8, 1, 1, @GrayPattern);
Result := CreatePatternBrush(GrayBitmap);
DeleteObject (GrayBitmap);
end;
var
NewSize, OldSize: TSize;
Brush: HBRUSH;
begin
Brush := CreateHalftoneBrush;
try
with GetBorderSize(Resizable) do begin
if NewDocking then NewSize.cx := 1 else NewSize.cx := X;
NewSize.cy := NewSize.cx;
if OldDocking then OldSize.cx := 1 else OldSize.cx := X;
OldSize.cy := OldSize.cx;
end;
DrawDragRect (DC, NewRect, OldRect, NewSize, OldSize, Brush, Brush);
finally
DeleteObject (Brush);
end;
end;
procedure TCustomToolWindow97.CMColorChanged (var Message: TMessage);
begin
{ Make sure non-client area is redrawn }
InvalidateDockedNCArea;
inherited; { the inherited handler calls Invalidate }
end;
procedure TCustomToolWindow97.CMTextChanged (var Message: TMessage);
begin
inherited;
{ Update the title bar to use the new Caption }
DrawFloatingNCArea (0, False, True, False);
end;
procedure TCustomToolWindow97.CMVisibleChanged (var Message: TMessage);
begin
if (FHidden = 0) and not(csDesigning in ComponentState) and Docked then
DockedTo.ChangeDockList (Visible, Self, Visible);
inherited;
if (FHidden = 0) and Assigned(FOnVisibleChanged) then
FOnVisibleChanged (Self);
end;
procedure TCustomToolWindow97.WMActivate (var Message: TWMActivate);
function GetParentToolWindow (Control: TControl): TCustomToolWindow97;
{ Returns the parent toolbar (direct or indirect) of the control, or nil if it
is not a child of a TCustomToolWindow97 }
begin
Result := nil;
while Control <> nil do begin
if Control is TCustomToolWindow97 then begin
Result := TCustomToolWindow97(Control);
Break;
end;
Control := Control.Parent;
end;
end;
function FindFirstFocusableNonToolWindowControl (const ParentControl: TWinControl): TWinControl;
var
List: TList;
I: Integer;
CurControl: TWinControl;
begin
Result := nil;
List := TList.Create;
try
with ParentControl do begin
GetTabOrderList (List);
for I := 0 to List.Count-1 do begin
CurControl := List[I];
if CurControl.TabStop and CurControl.CanFocus and
(GetParentToolWindow(CurControl) = nil) then begin
Result := CurControl;
Break;
end;
end;
end;
finally
List.Free;
end;
end;
var
Ctl: TWinControl;
I: Integer;
begin
if Docked then begin
inherited;
Exit;
end;
SendMessage (MDIParentForm.Handle, WM_NCACTIVATE, Ord(Message.Active <> WA_INACTIVE), 0);
inherited;
if Message.Active = WA_INACTIVE then begin
Ctl := FindFirstFocusableNonToolWindowControl(TForm(Owner));
for I := 0 to HookedForms.Count-1 do
with PHookedFormInfo(HookedForms[I])^ do
if Form = TForm(Owner) then begin
if SaveActiveControl <> 0 then
Ctl := FindControl(SaveActiveControl);
Break;
end;
if Ctl = nil then
{ Can't leave Ctl set to nil or the VCL will automatically pick a new
ActiveControl (which we don't want, because it could be another tool
window). So just set Ctl to FloatParent, which effectively leaves no
currently focused control on the form. }
Ctl := FloatParent;
TForm(Owner).ActiveControl := Ctl;
end;
end;
procedure TCustomToolWindow97.WMMouseActivate (var Message: TWMMouseActivate);
begin
if Docked or (csDesigning in ComponentState) then
inherited
else begin
{ When floating, prevent the toolbar from activating when clicked.
This is so it doesn't take the focus away from the window that had it }
Message.Result := MA_NOACTIVATE;
{ Similar to calling BringWindowToTop, but doesn't activate it }
SetWindowPos (Handle, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
{ Since it is returning MA_NOACTIVATE, activate the form instead. }
if FActivateParent and
(GetActiveWindow <> Handle) then begin
{ ^ Note to self: That must be in there so that double-clicks work
properly on controls like Edits }
SetActiveWindow (MDIParentForm.Handle);
if MDIParentForm <> TForm(Owner) then { if it's an MDI child form }
BringWindowToTop (TForm(Owner).Handle);
end;
end;
end;
procedure TCustomToolWindow97.BeginMoving (const InitX, InitY: Integer);
type
PDockedSize = ^TDockedSize;
TDockedSize = record
Dock: TDock97;
Size: TPoint;
end;
var
NewDockedSizes: TList; {items are pointers to TDockedSizes}
MouseOverDock: TDock97;
MoveRect: TRect;
PreventDocking: Boolean;
ScreenDC: HDC;
NPoint, DPoint: TPoint;
procedure Dropped;
var
NewDockRow: Integer;
Before: Boolean;
MoveRectClient: TRect;
C: Integer;
begin
if MouseOverDock <> nil then begin
MoveRectClient := MoveRect;
MapWindowPoints (0, MouseOverDock.Handle, MoveRectClient, 2);
if not(MouseOverDock.Position in PositionLeftOrRight) then
C := (MoveRectClient.Top+MoveRectClient.Bottom) div 2
else
C := (MoveRectClient.Left+MoveRectClient.Right) div 2;
NewDockRow := MouseOverDock.GetRowOf(C, Before);
if Before then
MouseOverDock.InsertRowBefore (NewDockRow)
else
if FullSize and
(MouseOverDock.GetNumberOfToolbarsOnRow(NewDockRow, Self) <> 0) then begin
Inc (NewDockRow);
MouseOverDock.InsertRowBefore (NewDockRow);
end;
FDockRow := NewDockRow;
if not(MouseOverDock.Position in PositionLeftOrRight) then
FDockPos := MoveRectClient.Left
else
FDockPos := MoveRectClient.Top;
DockedTo := MouseOverDock;
end
else begin
FFloatingTopLeft := MoveRect.TopLeft;
DockedTo := nil;
end;
{ Make sure it doesn't go completely off the screen }
MoveOnScreen (True);
end;
procedure MouseMoved;
var
OldMouseOverDock: TDock97;
OldMoveRect: TRect;
Pos: TPoint;
function CheckIfCanDockTo (Control: TDock97): Boolean;
const
DockSensX = 32;
DockSensY = 20;
var
R, S, Temp: TRect;
I: Integer;
Sens: Integer;
begin
with Control do begin
Result := False;
R := ClientRect;
MapWindowPoints (Handle, 0, R, 2);
for I := 0 to NewDockedSizes.Count-1 do
with PDockedSize(NewDockedSizes[I])^ do begin
if Dock <> Control then Continue;
S := Bounds(Pos.X-MulDiv(Size.X-1, NPoint.X, DPoint.X),
Pos.Y-MulDiv(Size.Y-1, NPoint.Y, DPoint.Y),
Size.X, Size.Y);
Break;
end;
if (R.Left = R.Right) or (R.Top = R.Bottom) then begin
if not(Control.Position in PositionLeftOrRight) then
InflateRect (R, 0, 1)
else
InflateRect (R, 1, 0);
end;
{ Like Office 97, distribute ~32 pixels of extra dock detection area
to the left side if the toolbar was grabbed at the left, both sides
if the toolbar was grabbed at the middle, or the right side if
toolbar was grabbed at the right. If outside, don't try to dock. }
Sens := MulDiv(DockSensX, NPoint.X, DPoint.X);
if (Pos.X < R.Left-(DockSensX-Sens)) or (Pos.X > R.Right-1+Sens) then
Exit;
{ Don't try to dock to the left or right if pointer is above or below
the boundaries of the dock }
if (Control.Position in PositionLeftOrRight) and
((Pos.Y < R.Top) or (Pos.Y >= R.Bottom)) then
Exit;
{ And also distribute ~20 pixels of extra dock detection area to
the top or bottom side }
Sens := MulDiv(DockSensY, NPoint.Y, DPoint.Y);
if (Pos.Y < R.Top-(DockSensY-Sens)) or (Pos.Y > R.Bottom-1+Sens) then
Exit;
Result := IntersectRect(Temp, R, S);
end;
end;
var
R: TRect;
D: TDockPosition;
I: Integer;
Accept: Boolean;
begin
OldMouseOverDock := MouseOverDock;
OldMoveRect := MoveRect;
GetCursorPos (Pos);
{ Check if it can dock }
MouseOverDock := nil;
if not PreventDocking then begin
{ Search through the form's controls and see if it can find a
Dock97. If it finds one, assign it to MouseOverDock. }
with TForm(Owner) do
for D := Low(D) to High(D) do
if D in DockableTo then
for I := 0 to ComponentCount-1 do
if (Components[I] is TDock97) and
(TDock97(Components[I]).Position = D) and
TDock97(Components[I]).FAllowDrag and
CheckIfCanDockTo(TDock97(Components[I])) then begin
MouseOverDock := TDock97(Components[I]);
Accept := True;
if Assigned(MouseOverDock.FOnRequestDock) then
MouseOverDock.FOnRequestDock (MouseOverDock, Self, Accept);
if Accept then
Break
else
MouseOverDock := nil;
end;
end;
{ If not docking, clip the point so it doesn't get dragged under the
taskbar }
if MouseOverDock = nil then begin
R := GetDesktopArea;
if Pos.X < R.Left then Pos.X := R.Left;
if Pos.X > R.Right then Pos.X := R.Right;
if Pos.Y < R.Top then Pos.Y := R.Top;
if Pos.Y > R.Bottom then Pos.Y := R.Bottom;
end;
for I := 0 to NewDockedSizes.Count-1 do
with PDockedSize(NewDockedSizes[I])^ do begin
if Dock <> MouseOverDock then Continue;
MoveRect := Bounds(Pos.X-MulDiv(Size.X-1, NPoint.X, DPoint.X),
Pos.Y-MulDiv(Size.Y-1, NPoint.Y, DPoint.Y),
Size.X, Size.Y);
Break;
end;
{ Make sure title bar (or at least part of the toolbar) is still accessible
if it's dragged almost completely off the screen. This prevents the
problem seen in Office 97 where you drag it offscreen so that only the
border is visible, sometimes leaving you no way to move it back short of
resetting the toolbar. }
if MouseOverDock = nil then begin
R := GetDesktopArea;
with GetBorderSize(Resizable) do
InflateRect (R, -(X+4), -(Y+4));
if MoveRect.Bottom < R.Top then
OffsetRect (MoveRect, 0, R.Top-MoveRect.Bottom);
if MoveRect.Top > R.Bottom then
OffsetRect (MoveRect, 0, R.Bottom-MoveRect.Top);
if MoveRect.Right < R.Left then
OffsetRect (MoveRect, R.Left-MoveRect.Right, 0);
if MoveRect.Left > R.Right then
OffsetRect (MoveRect, R.Right-MoveRect.Left, 0);
I := GetDesktopArea.Top - GetBorderSize(Resizable).Y - GetSmallCaptionHeight + 4;
if MoveRect.Top < I then
OffsetRect (MoveRect, 0, I-MoveRect.Top);
end;
{ Update the dragging outline }
DrawDraggingOutline (ScreenDC, @MoveRect, @OldMoveRect, MouseOverDock <> nil,
OldMouseOverDock <> nil);
end;
var
Accept: Boolean;
R: TRect;
Msg: TMsg;
NewDockedSize: PDockedSize;
I: Integer;
begin
Accept := False;
NPoint := Point(InitX, InitY);
{ Adjust for non-client area }
GetWindowRect (Handle, R);
R.BottomRight := ClientToScreen(Point(0, 0));
Dec (NPoint.X, R.Left-R.Right);
Dec (NPoint.Y, R.Top-R.Bottom);
DPoint := Point(Width-1, Height-1);
PreventDocking := GetKeyState(VK_CONTROL) < 0;
{ Set up potential sizes for each dock type }
NewDockedSizes := TList.Create;
try
SetRectEmpty (R);
CustomArrangeControls (atNone, DockedTo, nil, R.BottomRight);
AddFloatingNCAreaToRect (R, Resizable);
New (NewDockedSize);
try
with NewDockedSize^ do begin
Dock := nil;
Size := Point(R.Right-R.Left, R.Bottom-R.Top);
end;
NewDockedSizes.Add (NewDockedSize);
except
Dispose (NewDockedSize);
raise;
end;
with TForm(Owner) do
for I := 0 to ComponentCount-1 do begin
if not(Components[I] is TDock97) then Continue;
New (NewDockedSize);
try
with NewDockedSize^ do begin
Dock := TDock97(Components[I]);
if Components[I] <> DockedTo then
CustomArrangeControls (atNone, DockedTo, TDock97(Components[I]), Size)
else
Size := Self.ClientRect.BottomRight;
AddDockedNCAreaToSize (Size, TDock97(Components[I]).Position in PositionLeftOrRight,
FDragHandle);
end;
NewDockedSizes.Add (NewDockedSize);
except
Dispose (NewDockedSize);
raise;
end;
end;
{ Before locking, make sure all pending paint messages are processed }
ProcessPaintMessages;
{ This uses LockWindowUpdate to suppress all window updating so the
dragging outlines doesn't sometimes get garbled. (This is safe, and in
fact, is the main purpose of the LockWindowUpdate function)
IMPORTANT! While debugging you might want to enable the 'TB97DisableLock'
conditional define (see top of the source code). }
{$IFNDEF TB97DisableLock}
LockWindowUpdate (GetDesktopWindow);
{$ENDIF}
{ Get a DC of the entire screen. Works around the window update lock
by specifying DCX_LOCKWINDOWUPDATE. }
ScreenDC := GetDCEx(GetDesktopWindow, 0,
DCX_LOCKWINDOWUPDATE or DCX_CACHE or DCX_WINDOW);
try
SetCapture (Handle);
{ Initialize }
MouseOverDock := nil;
SetRectEmpty (MoveRect);
MouseMoved;
{ Stay in message loop until capture is lost. Capture is removed either
by this procedure manually doing it, or by an outside influence (like
a message box or menu popping up) }
while GetCapture = Handle do begin
case Integer(GetMessage(Msg, 0, 0, 0)) of
-1: Break; { if GetMessage failed }
0: begin
{ Repost WM_QUIT messages }
PostQuitMessage (Msg.WParam);
Break;
end;
end;
case Msg.Message of
WM_KEYDOWN, WM_KEYUP:
{ Ignore all keystrokes while dragging. But process Ctrl and Escape }
case Msg.WParam of
VK_CONTROL:
if PreventDocking <> (Msg.Message = WM_KEYDOWN) then begin
PreventDocking := Msg.Message = WM_KEYDOWN;
MouseMoved;
end;
VK_ESCAPE:
Break;
end;
WM_MOUSEMOVE:
{ Note to self: WM_MOUSEMOVE messages should never be dispatched
here to ensure no hints get shown during the drag process }
MouseMoved;
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
{ Make sure it doesn't begin another loop }
Break;
WM_LBUTTONUP: begin
Accept := True;
Break;
end;
WM_RBUTTONDOWN..WM_MBUTTONDBLCLK:
{ Ignore all other mouse up/down messages }
;
else
TranslateMessage (Msg);
DispatchMessage (Msg);
end;
end;
finally
{ Since it sometimes breaks out of the loop without capture being
released }
if GetCapture = Handle then
ReleaseCapture;
{ Hide dragging outline and release the DC }
DrawDraggingOutline (ScreenDC, nil, @MoveRect, False, MouseOverDock <> nil);
ReleaseDC (GetDesktopWindow, ScreenDC);
{ Release window update lock }
{$IFNDEF TB97DisableLock}
LockWindowUpdate (0);
{$ENDIF}
end;
{ Move to new position }
if Accept then
Dropped;
finally
for I := NewDockedSizes.Count-1 downto 0 do
Dispose (PDockedSize(NewDockedSizes[I]));
NewDockedSizes.Free;
end;
end;
procedure TCustomToolWindow97.MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
function ControlExistsAtPos (const P: TPoint): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to ControlCount-1 do
if not(Controls[I] is TToolbarSep97) and Controls[I].Visible and
PtInRect(Controls[I].BoundsRect, P) then begin
Result := True;
Break;
end;
end;
begin
inherited;
if (Button <> mbLeft) or
{ Ignore message if user clicked on a child control that was probably
disabled }
ControlExistsAtPos(Point(X, Y)) or
(Docked and not DockedTo.FAllowDrag) then
Exit;
{ Handle double click }
if ssDouble in Shift then begin
if Docked then
DockedTo := nil
else begin
FDockRow := ForceDockAtTopRow;
FDockPos := ForceDockAtLeftPos;
DockedTo := DefaultDock;
end;
Exit;
end;
BeginMoving (X, Y);
end;
procedure TCustomToolWindow97.WMNCHitTest (var Message: TWMNCHitTest);
var
P: TPoint;
BorderSize: TPoint;
C: Integer;
begin
inherited;
with Message do
if Docked then begin
if Result = HTNOWHERE then
Result := HTCLIENT;
end
else begin
P := SmallPointToPoint(Pos);
Dec (P.X, Left); Dec (P.Y, Top);
if Result <> HTCLIENT then begin
if PtInRect(GetCaptionRect(Self, True, False), P) then begin
if FCloseButton and PtInRect(GetCloseButtonRect(Self, True), P) then
Result := HTCLOSE
else
Result := HTCLIENT;
end
else begin
if Result in [HTLEFT..HTBOTTOMRIGHT] {set covers all resizing corners} then
Result := HTNOWHERE; { handles all resize hit-tests itself }
if Resizable then begin
BorderSize := GetBorderSize(Resizable);
C := BorderSize.X + (GetSmallCaptionHeight-1);
if not Params.ResizeEightCorner then begin
if (P.Y >= 0) and (P.Y < BorderSize.Y) then Result := HTTOP else
if (P.Y < Height) and (P.Y >= Height-BorderSize.Y-1) then Result := HTBOTTOM else
if (P.X >= 0) and (P.X < BorderSize.X) then Result := HTLEFT else
if (P.X < Width) and (P.X >= Width-BorderSize.X-1) then Result := HTRIGHT;
end
else begin
if (P.X >= 0) and (P.X < BorderSize.X) then begin
Result := HTLEFT;
if (P.Y < C) then Result := HTTOPLEFT else
if (P.Y >= Height-C) then Result := HTBOTTOMLEFT;
end
else
if (P.X < Width) and (P.X >= Width-BorderSize.X-1) then begin
Result := HTRIGHT;
if (P.Y < C) then Result := HTTOPRIGHT else
if (P.Y >= Height-C) then Result := HTBOTTOMRIGHT;
end
else
if (P.Y >= 0) and (P.Y < BorderSize.Y) then begin
Result := HTTOP;
if (P.X < C) then Result := HTTOPLEFT else
if (P.X >= Width-C) then Result := HTTOPRIGHT;
end
else
if (P.Y < Height) and (P.Y >= Height-BorderSize.Y-1) then begin
Result := HTBOTTOM;
if (P.X < C) then Result := HTBOTTOMLEFT else
if (P.X >= Width-C) then Result := HTBOTTOMRIGHT;
end;
end;
end;
end;
end;
end;
end;
procedure TCustomToolWindow97.WMNCLButtonDown (var Message: TWMNCLButtonDown);
procedure CloseButtonLoop;
var
Accept, NewCloseButtonDown: Boolean;
P: TPoint;
Msg: TMsg;
begin
Accept := False;
CloseButtonDown := True;
DrawFloatingNCArea (0, False, False, True);
SetCapture (Handle);
try
while GetCapture = Handle do begin
case Integer(GetMessage(Msg, 0, 0, 0)) of
-1: Break; { if GetMessage failed }
0: begin
{ Repost WM_QUIT messages }
PostQuitMessage (Msg.WParam);
Break;
end;
end;
case Msg.Message of
WM_KEYDOWN, WM_KEYUP:
{ Ignore all keystrokes while in a close button loop }
;
WM_MOUSEMOVE: begin
{ Note to self: WM_MOUSEMOVE messages should never be dispatched
here to ensure no hints get shown }
GetCursorPos (P);
Dec (P.X, Left); Dec (P.Y, Top);
NewCloseButtonDown := PtInRect(GetCloseButtonRect(Self, True), P);
if CloseButtonDown <> NewCloseButtonDown then begin
CloseButtonDown := NewCloseButtonDown;
DrawFloatingNCArea (0, False, False, True);
end;
end;
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
{ Make sure it doesn't begin another loop }
Break;
WM_LBUTTONUP: begin
if CloseButtonDown then
Accept := True;
Break;
end;
WM_RBUTTONDOWN..WM_MBUTTONDBLCLK:
{ Ignore all other mouse up/down messages }
;
else
TranslateMessage (Msg);
DispatchMessage (Msg);
end;
end;
finally
if GetCapture = Handle then
ReleaseCapture;
if CloseButtonDown <> False then begin
CloseButtonDown := False;
DrawFloatingNCArea (0, False, False, True);
end;
end;
if Accept then begin
{ Hide the window after close button is pushed }
Hide;
if Assigned(FOnClose) then
FOnClose (Self);
end;
end;
var
Accept: Boolean;
NewRect: TRect;
begin
case Message.HitTest of
HTLEFT..HTBOTTOMRIGHT:
if not Docked then begin
Accept := False;
SetRectEmpty (NewRect);
BeginSizing (Message.HitTest, Accept, NewRect);
if Accept then begin
Inc (FDisableArrangeControls);
try
BoundsRect := NewRect;
finally
Dec (FDisableArrangeControls);
end;
ArrangeControls;
{ Make sure it doesn't go completely off the screen }
MoveOnScreen (True);
end;
end;
HTCLOSE:
if not Docked then
CloseButtonLoop;
else
inherited;
end;
end;
procedure TCustomToolWindow97.GetParams (var Params: TToolWindowParams);
begin
with Params do begin
CallAlignControls := True;
ResizeEightCorner := True;
ResizeClipCursor := True;
end;
end;
procedure TCustomToolWindow97.ResizeBegin;
begin
end;
procedure TCustomToolWindow97.ResizeTrack (var Rect: TRect; const OrigRect: TRect);
begin
end;
procedure TCustomToolWindow97.ResizeEnd;
begin
end;
procedure TCustomToolWindow97.BeginSizing (const HitTestValue: Integer;
var Accept: Boolean; var NewRect: TRect);
var
DragX, DragY, ReverseX, ReverseY: Boolean;
MinWidth, MinHeight: Integer;
DragRect, OrigDragRect: TRect;
ScreenDC: HDC;
OrigPos, OldPos: TPoint;
procedure MouseMoved;
var
Pos: TPoint;
OldDragRect: TRect;
begin
GetCursorPos (Pos);
{ It needs to check if the cursor actually moved since last time. This is
because a call to LockWindowUpdate (apparently) generates a mouse move
message even when mouse hasn't moved. }
if (Pos.X = OldPos.X) and (Pos.Y = OldPos.Y) then Exit;
OldPos := Pos;
OldDragRect := DragRect;
DragRect := OrigDragRect;
if DragX then begin
if not ReverseX then Inc (DragRect.Right, Pos.X-OrigPos.X)
else Inc (DragRect.Left, Pos.X-OrigPos.X);
end;
if DragY then begin
if not ReverseY then Inc (DragRect.Bottom, Pos.Y-OrigPos.Y)
else Inc (DragRect.Top, Pos.Y-OrigPos.Y);
end;
if DragRect.Right-DragRect.Left < MinWidth then begin
if not ReverseX then DragRect.Right := DragRect.Left + MinWidth
else DragRect.Left := DragRect.Right - MinWidth;
end;
if DragRect.Bottom-DragRect.Top < MinHeight then begin
if not ReverseY then DragRect.Bottom := DragRect.Top + MinHeight
else DragRect.Top := DragRect.Bottom - MinHeight;
end;
ResizeTrack (DragRect, OrigDragRect);
DrawDraggingOutline (ScreenDC, @DragRect, @OldDragRect, False, False);
end;
var
Msg: TMsg;
R: TRect;
begin
Accept := False;
GetMinimumSize (MinWidth, MinHeight);
Inc (MinWidth, Width-ClientWidth);
Inc (MinHeight, Height-ClientHeight);
DragX := HitTestValue in [HTLEFT, HTRIGHT, HTTOPLEFT, HTTOPRIGHT, HTBOTTOMLEFT, HTBOTTOMRIGHT];
ReverseX := HitTestValue in [HTLEFT, HTTOPLEFT, HTBOTTOMLEFT];
DragY := HitTestValue in [HTTOP, HTTOPLEFT, HTTOPRIGHT, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT];
ReverseY := HitTestValue in [HTTOP, HTTOPLEFT, HTTOPRIGHT];
ResizeBegin (HitTestValue);
try
{ Before locking, make sure all pending paint messages are processed }
ProcessPaintMessages;
{ This uses LockWindowUpdate to suppress all window updating so the
dragging outlines doesn't sometimes get garbled. (This is safe, and in
fact, is the main purpose of the LockWindowUpdate function)
IMPORTANT! While debugging you might want to enable the 'TB97DisableLock'
conditional define (see top of the source code). }
{$IFNDEF TB97DisableLock}
LockWindowUpdate (GetDesktopWindow);
{$ENDIF}
{ Get a DC of the entire screen. Works around the window update lock
by specifying DCX_LOCKWINDOWUPDATE. }
ScreenDC := GetDCEx(GetDesktopWindow, 0,
DCX_LOCKWINDOWUPDATE or DCX_CACHE or DCX_WINDOW);
try
SetCapture (Handle);
if Params.ResizeClipCursor then begin
R := GetDesktopArea;
ClipCursor (@R);
end;
{ Initialize }
OrigDragRect := BoundsRect;
DragRect := OrigDragRect;
DrawDraggingOutline (ScreenDC, @DragRect, nil, False, False);
GetCursorPos (OrigPos);
OldPos := OrigPos;
{ Stay in message loop until capture is lost. Capture is removed either
by this procedure manually doing it, or by an outside influence (like
a message box or menu popping up) }
while GetCapture = Handle do begin
case Integer(GetMessage(Msg, 0, 0, 0)) of
-1: Break; { if GetMessage failed }
0: begin
{ Repost WM_QUIT messages }
PostQuitMessage (Msg.WParam);
Break;
end;
end;
case Msg.Message of
WM_KEYDOWN, WM_KEYUP:
{ Ignore all keystrokes while sizing except for Escape }
if Msg.WParam = VK_ESCAPE then
Break;
WM_MOUSEMOVE:
{ Note to self: WM_MOUSEMOVE messages should never be dispatched
here to ensure no hints get shown during the drag process }
MouseMoved;
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
{ Make sure it doesn't begin another loop }
Break;
WM_LBUTTONUP: begin
Accept := True;
Break;
end;
WM_RBUTTONDOWN..WM_MBUTTONDBLCLK:
{ Ignore all other mouse up/down messages }
;
else
TranslateMessage (Msg);
DispatchMessage (Msg);
end;
end;
finally
{ Since it sometimes breaks out of the loop without capture being
released }
if GetCapture = Handle then
ReleaseCapture;
ClipCursor (nil);
{ Hide dragging outline and release the DC }
DrawDraggingOutline (ScreenDC, nil, @DragRect, False, False);
ReleaseDC (GetDesktopWindow, ScreenDC);
{ Release window update lock }
{$IFNDEF TB97DisableLock}
LockWindowUpdate (0);
{$ENDIF}
end;
finally
ResizeEnd (Accept);
end;
if Accept then
NewRect := DragRect;
end;
procedure TCustomToolWindow97.WMClose (var Message: TWMClose);
begin
{ A floating toolbar does not use WM_CLOSE messages when its close button
is clicked, but Windows still sends a WM_CLOSE message if the user
presses Alt+F4 while one of the toolbar's controls is focused. Inherited
is not called since we do not want Windows' default processing - which
destroys the window. Instead, relay the message to the parent form. }
SendMessage (MDIParentForm.Handle, WM_CLOSE, 0, 0);
{ Note to self: MDIParentForm is used instead of TForm(Owner) since MDI
childs don't process Alt+F4 as Close }
end;
class function TCustomToolWindow97.NewMainWindowHook (var Message: TMessage): Boolean;
var
I: Integer;
begin
Result := False;
case Message.Msg of
CM_ACTIVATE, CM_DEACTIVATE:
for I := 0 to MainHookedForms.Count-1 do
{ Hide or restore toolbars when application is deactivated or activated }
ShowHideFloatParents (GetMDIParent(PMainHookedFormInfo(MainHookedForms[I]).Form),
Message.Msg = CM_ACTIVATE);
WM_TB97DoneCreating:
if (Message.WParam = WM_TB97DoneCreating_Magic) and
(Message.LParam = WM_TB97DoneCreating_Magic) then
for I := DoneCreatingList.Count-1 downto 0 do begin
ShowHideFloatParents (TCustomToolWindow97(DoneCreatingList[I]).MDIParentForm, Application.Active);
DoneCreatingList.Delete (I);
end;
end;
end;
{ TCustomToolWindow97 - property access methods }
procedure TCustomToolWindow97.SetCloseButton (Value: Boolean);
begin
if FCloseButton <> Value then begin
FCloseButton := Value;
{ Update the close button's visibility }
DrawFloatingNCArea (0, False, True, True);
end;
end;
procedure TCustomToolWindow97.SetDefaultDock (Value: TDock97);
begin
if FDefaultDock <> Value then begin
FDefaultDock := Value;
if Assigned(Value) then
Value.FreeNotification (Self);
end;
end;
procedure TCustomToolWindow97.SetDockedTo (Value: TDock97);
var
OldDockedTo: TDock97;
HiddenInced: Boolean;
TempSize: TPoint;
begin
OldDockedTo := DockedTo;
if Assigned(FOnDockChanging) and (Value <> OldDockedTo) then
FOnDockChanging (Self);
Inc (FUpdatingBounds);
try
if Assigned(Value) then
Inc (Value.DisableArrangeToolbars);
try
{ Before changing between docked and floating state (and vice-versa)
or between docks, hide the toolbar. This prevents any flashing while
it's being moved }
HiddenInced := False;
if not(csDesigning in ComponentState) and (Value <> OldDockedTo) and (Visible) then begin
Inc (FHidden);
HiddenInced := True;
if Assigned(OldDockedTo) then
{ Need to disable arranging of current dock so it doesn't lose it's
FDockRow/FDockPos it's going to set later }
Inc (OldDockedTo.DisableArrangeToolbars);
try
Hide; {must Hide AFTER incing Hidden}
finally
if Assigned(OldDockedTo) then
Dec (OldDockedTo.DisableArrangeToolbars);
end;
end;
try
if Value <> nil then begin
{ Must pre-arrange controls in new dock orientation before changing
the Parent }
if Parent <> nil then
CustomArrangeControls (atMoveControls, OldDockedTo, Value, TempSize);
if Parent <> Value then begin
Inc (FDisableArrangeControls);
try
Parent := Value;
finally
Dec (FDisableArrangeControls);
end;
end;
ArrangeControls;
{ Temporarily put it off the edge of the parent window when changing
parents so that no momentary "flicker" occurs when it shows the
toolbar again before it gets positioned }
if Value <> OldDockedTo then
SetBounds (-Width, -Height, Width, Height);
end
else begin
{ Must pre-arrange controls in new dock orientation before changing
the Parent }
if Parent <> nil then
CustomArrangeControls (atMoveControls, OldDockedTo, Value, TempSize);
Inc (FDisableArrangeControls);
try
if Parent <> FloatParent then
Parent := FloatParent;
SetBounds (FFloatingTopLeft.X, FFloatingTopLeft.Y, Width, Height);
MoveOnScreen (True);
finally
Dec (FDisableArrangeControls);
end;
ArrangeControls;
end;
finally
if HiddenInced then begin
Dec (FHidden);
Show;
end;
end;
finally
if Assigned(Value) then
Dec (Value.DisableArrangeToolbars);
end;
finally
Dec (FUpdatingBounds);
end;
if Assigned(Value) then
Value.ArrangeToolbars;
if Assigned(FOnDockChanged) and (Value <> OldDockedTo) then
FOnDockChanged (Self);
end;
procedure TCustomToolWindow97.SetDockPos (Value: Integer);
begin
FDockPos := Value;
if Docked then
DockedTo.ArrangeToolbars;
end;
procedure TCustomToolWindow97.SetDockRow (Value: Integer);
begin
FDockRow := Value;
if Docked then
DockedTo.ArrangeToolbars;
end;
procedure TCustomToolWindow97.SetDragHandle (Value: Boolean);
begin
if FDragHandle <> Value then begin
FDragHandle := Value;
if Docked and HandleAllocated then
SetWindowPos (Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER);
end;
end;
procedure TCustomToolWindow97.SetFullSize (Value: Boolean);
begin
if FFullSize <> Value then begin
FFullSize := Value;
ArrangeControls;
end;
end;
procedure TCustomToolWindow97.SetResizable (Value: Boolean);
begin
if FResizable <> Value then begin
FResizable := Value;
if not Docked then begin
{ Recalculate the non-client area }
SetWindowPos (Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or
SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER);
{ The VCL does not automatically realign child controls when the
non-client size changes, so do it manually }
Realign;
end;
end;
end;
{ TCustomToolbar97 }
constructor TCustomToolbar97.Create (AOwner: TComponent);
begin
inherited;
GroupInfo := TList.Create;
SlaveInfo := TList.Create;
LineSeps := TList.Create;
OrderList := TList.Create;
{ There hasn't been any child controls added yet, but call ArrangeControls
to initialize the toolbar's size }
ArrangeControls;
end;
destructor TCustomToolbar97.Destroy;
var
I: Integer;
begin
OrderList.Free;
LineSeps.Free;
if Assigned(SlaveInfo) then begin
for I := SlaveInfo.Count-1 downto 0 do
FreeMem (SlaveInfo.Items[I]);
SlaveInfo.Free;
end;
FreeGroupInfo (GroupInfo);
GroupInfo.Free;
inherited;
end;
procedure TCustomToolbar97.ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer);
begin
inherited;
FFloatingRightX := ReadIntProc(Name, rvFloatRightX, 0, ExtraData);
end;
procedure TCustomToolbar97.WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer);
begin
inherited;
WriteIntProc (Name, rvFloatRightX, FFloatingRightX, ExtraData);
end;
procedure TCustomToolbar97.GetMinimumSize (var AClientWidth, AClientHeight: Integer);
begin
AClientWidth := 0;
AClientHeight := 0;
end;
function CompareControls (const Item1, Item2, ExtraData: Pointer): Integer; far;
begin
with PCompareExtra(ExtraData)^ do
if ComparePositions then begin
if CurDockType <> dtLeftRight then
Result := TControl(Item1).Left - TControl(Item2).Left
else
Result := TControl(Item1).Top - TControl(Item2).Top;
end
else
with Toolbar.OrderList do
Result := IndexOf(Item1) - IndexOf(Item2);
end;
procedure TCustomToolbar97.InitializeOrdering;
var
Extra: TCompareExtra;
begin
inherited;
{ Initialize order of items in OrderList }
if not(csDesigning in ComponentState) then begin
with Extra do begin
Toolbar := Self;
ComparePositions := True;
CurDockType := GetDockTypeOf(DockedTo);
end;
ListSortEx (OrderList, CompareControls, @Extra);
end;
end;
procedure TCustomToolbar97.GetBarSize (var ASize: Integer; const DockType: TDockType);
var
I: Integer;
begin
ASize := DefaultBarWidthHeight;
for I := 0 to ControlCount-1 do
if not(Controls[I] is TToolbarSep97) then
with Controls[I] do begin
if ShouldBeVisible(Controls[I], DockType = dtLeftRight, False) then begin
if DockType = dtLeftRight then begin
if Width > ASize then ASize := Width;
end
else begin
if Height > ASize then ASize := Height;
end;
end;
end;
end;
procedure TCustomToolbar97.GetParams (var Params: TToolWindowParams);
begin
inherited;
with Params do begin
CallAlignControls := False;
ResizeEightCorner := False;
ResizeClipCursor := False;
end;
end;
procedure TCustomToolbar97.Paint;
var
S: Integer;
begin
inherited;
{ Long separators when not docked }
if not Docked then
for S := 0 to LineSeps.Count-1 do begin
with TLineSep(LineSeps[S]) do begin
if Blank then Continue;
Canvas.Pen.Color := clBtnShadow;
Canvas.MoveTo (1, Y-4); Canvas.LineTo (ClientWidth-1, Y-4);
Canvas.Pen.Color := clBtnHighlight;
Canvas.MoveTo (1, Y-3); Canvas.LineTo (ClientWidth-1, Y-3);
end;
end;
end;
function TCustomToolbar97.ShouldBeVisible (const Control: TControl;
const LeftOrRight: Boolean; const SetIt: Boolean): Boolean;
{ If Control is a master or slave control, it returns the appropriate visibility
setting based on the value of LeftOrRight, otherwise it simply returns the
current Visible setting. If SetIt is True, it automatically adjusts the
Visible properties of both the master and slave control. }
var
I: Integer;
begin
for I := 0 to SlaveInfo.Count-1 do
with PSlaveInfo(SlaveInfo[I])^ do
if TopBottom = Control then begin
Result := not LeftOrRight;
if SetIt then begin
TopBottom.Visible := Result;
LeftRight.Visible := not Result;
end;
Exit;
end
else
if LeftRight = Control then begin
Result := LeftOrRight;
if SetIt then begin
TopBottom.Visible := not Result;
LeftRight.Visible := Result;
end;
Exit;
end;
Result := Control.Visible;
end;
procedure TCustomToolbar97.FreeGroupInfo (const List: TList);
var
I: Integer;
L: PGroupInfo;
begin
if List = nil then Exit;
for I := List.Count-1 downto 0 do begin
L := List.Items[I];
if Assigned(L) then begin
L^.Members.Free;
FreeMem (L);
end;
List.Delete (I);
end;
end;
procedure TCustomToolbar97.BuildGroupInfo (const List: TList;
const TranslateSlave: Boolean; const OldDockType, NewDockType: TDockType);
var
I: Integer;
GI: PGroupInfo;
Children: TList; {items casted into TControls}
NewGroup: Boolean;
Extra: TCompareExtra;
begin
FreeGroupInfo (List);
if ControlCount = 0 then Exit;
Children := TList.Create;
try
for I := 0 to ControlCount-1 do
if (not TranslateSlave and Controls[I].Visible) or
(TranslateSlave and ShouldBeVisible(Controls[I], NewDockType = dtLeftRight, False)) then
Children.Add (Controls[I]);
with Extra do begin
Toolbar := Self;
CurDockType := OldDockType;
end;
if csDesigning in ComponentState then begin
Extra.ComparePositions := True;
ListSortEx (OrderList, CompareControls, @Extra);
end;
Extra.ComparePositions := csDesigning in ComponentState;
ListSortEx (Children, CompareControls, @Extra);
GI := nil;
NewGroup := True;
for I := 0 to Children.Count-1 do begin
if NewGroup then begin
NewGroup := False;
List.Add (AllocMem(SizeOf(TGroupInfo)));
{ Note: AllocMem initializes the newly allocated data to zero }
GI := List.Last;
GI^.Members := TList.Create;
end;
GI^.Members.Add (Children[I]);
if TControl(Children[I]) is TToolbarSep97 then
NewGroup := True
else begin
with TControl(Children[I]) do begin
Inc (GI^.GroupWidth, Width);
Inc (GI^.GroupHeight, Height);
end;
end;
end;
finally
Children.Free;
end;
end;
procedure TCustomToolbar97.OrderControls (const CanMoveControls: Boolean;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint);
{ This arranges the controls on the toolbar }
var
OldDockType, NewDockType: TDockType;
NewDocked: Boolean;
RightX, I: Integer;
CurBarSize, DockRowSize: Integer;
GInfo: TList;
AllowWrap: Boolean;
BarPosSize, MinPosPixels, MinRowPixels, CurPosPixel, CurLinePixel, G: Integer;
NewLine, Prec1Line: Boolean;
GI: PGroupInfo;
Member: TControl;
MemberIsSep: Boolean;
GroupPosSize, MemberPosSize: Integer;
PreviousSep: TToolbarSep97; PrevMinPosPixels: Integer;
NewLineSep: TLineSep;
label 1;
begin
OldDockType := GetDockTypeOf(WasDockedTo);
NewDockType := GetDockTypeOf(DockingTo);
NewDocked := Assigned(DockingTo);
RightX := FFloatingRightX;
if (NewDockType <> dtNotDocked) or (RightX = 0) then
RightX := High(RightX)
else begin
{ Make sure RightX isn't less than the smallest sized control + margins,
in case one of the *LoadToolbarPositions functions happened to read
a value too small. }
for I := 0 to ControlCount-1 do
if not(Controls[I] is TToolbarSep97) then
with Controls[I] do
if Width + (LeftMarginNotDocked+RightMarginNotDocked) > RightX then
RightX := Width + (LeftMarginNotDocked+RightMarginNotDocked);
end;
if CanMoveControls then
for I := 0 to ControlCount-1 do
if not(Controls[I] is TToolbarSep97) then
ShouldBeVisible (Controls[I], NewDockType = dtLeftRight, True);
GetBarSize (CurBarSize, NewDockType);
DockRowSize := CurBarSize;
if (DockingTo <> nil) and (DockingTo = DockedTo) then
GetDockRowSize (DockRowSize);
if CanMoveControls then
GInfo := GroupInfo
else
GInfo := TList.Create;
try
BuildGroupInfo (GInfo, not CanMoveControls, OldDockType, NewDockType);
if CanMoveControls then
LineSeps.Clear;
AllowWrap := not NewDocked;
if GInfo.Count <> 0 then begin
BarPosSize := CurBarSize;
MinPosPixels := 0;
CurPosPixel := 0;
CurLinePixel := TopMargin[NewDocked];
Prec1Line := True; NewLine := True;
PreviousSep := nil; PrevMinPosPixels := 0;
for G := 0 to GInfo.Count-1 do begin
GI := PGroupInfo(GInfo[G]);
if NewDockType <> dtLeftRight then
GroupPosSize := GI^.GroupWidth
else
GroupPosSize := GI^.GroupHeight;
if (not AllowWrap) or (Prec1Line) then begin
if NewLine then begin
NewLine := False;
Inc (CurPosPixel, LeftMargin[NewDocked])
end;
if CurPosPixel+GroupPosSize+RightMargin[NewDocked] > RightX then
goto 1; { I know it's sloppy to use a goto. But it's fast }
if CurPosPixel > MinPosPixels then MinPosPixels := CurPosPixel;
end
else begin
1:CurPosPixel := LeftMargin[NewDocked];
if CurPosPixel > MinPosPixels then MinPosPixels := CurPosPixel;
if (G <> 0) and (PGroupInfo(GInfo[G-1])^.Members.Count <> 0) then begin
Inc (CurLinePixel, BarPosSize+LineSpacing);
if Assigned(PreviousSep) then begin
MinPosPixels := PrevMinPosPixels;
if CanMoveControls then begin
PreviousSep.Width := 0;
LongInt(NewLineSep) := 0;
NewLineSep.Y := CurLinePixel;
NewLineSep.Blank := PreviousSep.Blank;
LineSeps.Add (Pointer(NewLineSep));
end;
end;
end;
end;
Prec1Line := True;
for I := 0 to GI^.Members.Count-1 do begin
Member := TControl(GI^.Members[I]);
MemberIsSep := Member is TToolbarSep97;
with Member do begin
if not MemberIsSep then begin
if NewDockType <> dtLeftRight then
MemberPosSize := Width
else
MemberPosSize := Height;
end
else begin
if NewDockType <> dtLeftRight then
MemberPosSize := TToolbarSep97(Member).SizeHorz
else
MemberPosSize := TToolbarSep97(Member).SizeVert;
end;
{ If RightX is passed, proceed to next line }
if not MemberIsSep and
(CurPosPixel+MemberPosSize+RightMargin[NewDocked] > RightX) then begin
CurPosPixel := LeftMargin[NewDocked];
if CurPosPixel > MinPosPixels then MinPosPixels := CurPosPixel;
Inc (CurLinePixel, CurBarSize);
Prec1Line := False;
end;
if NewDockType <> dtLeftRight then begin
if not MemberIsSep then begin
if CanMoveControls then
SetBounds (CurPosPixel, CurLinePixel+((DockRowSize-Height) div 2), Width, Height);
Inc (CurPosPixel, Width);
end
else begin
if CanMoveControls then
SetBounds (CurPosPixel, CurLinePixel, TToolbarSep97(Member).SizeHorz, DockRowSize);
Inc (CurPosPixel, TToolbarSep97(Member).SizeHorz);
end;
end
else begin
if not MemberIsSep then begin
if CanMoveControls then
SetBounds (CurLinePixel+((DockRowSize-Width) div 2), CurPosPixel, Width, Height);
Inc (CurPosPixel, Height);
end
else begin
if CanMoveControls then
SetBounds (CurLinePixel, CurPosPixel, DockRowSize, TToolbarSep97(Member).SizeVert);
Inc (CurPosPixel, TToolbarSep97(Member).SizeVert);
end;
end;
PrevMinPosPixels := MinPosPixels;
if not MemberIsSep then
PreviousSep := nil
else
PreviousSep := TToolbarSep97(Member);
if CurPosPixel > MinPosPixels then MinPosPixels := CurPosPixel;
end;
end;
end;
end
else begin
if Docked then begin
MinPosPixels := LeftMargin[NewDocked];
CurLinePixel := TopMargin[NewDocked];
Inc (MinPosPixels, DefaultBarWidthHeight);
BarPosSize := DockedTo.GetRowSize(FDockRow, Self);
end
else begin
MinPosPixels := LeftMargin[NewDocked] + DefaultBarWidthHeight;
CurLinePixel := TopMargin[NewDocked];
BarPosSize := DefaultBarWidthHeight;
end;
end;
if csDesigning in ComponentState then
Invalidate;
finally
if not CanMoveControls then begin
FreeGroupInfo (GInfo);
GInfo.Free;
end;
end;
Inc (MinPosPixels, RightMargin[NewDocked]);
MinRowPixels := CurLinePixel + BarPosSize + BottomMargin[NewDocked];
if NewDockType <> dtLeftRight then begin
NewClientSize.X := MinPosPixels;
NewClientSize.Y := MinRowPixels;
end
else begin
NewClientSize.X := MinRowPixels;
NewClientSize.Y := MinPosPixels;
end;
end;
procedure TCustomToolbar97.CMControlListChange (var Message: TCMControlListChange);
{ The VCL sends this message is sent whenever a child control is inserted into
or deleted from the toolbar }
var
I: Integer;
begin
inherited;
with Message, OrderList do begin
{ Delete any previous occurances of Control in OrderList. There shouldn't
be any if Inserting=True, but just to be safe, check anyway. }
while True do begin
I := IndexOf(Control);
if I = -1 then Break;
Delete (I);
end;
if Inserting then
Add (Control);
end;
end;
function CompareNewSizes (const Item1, Item2, ExtraData: Pointer): Integer; far;
begin
{ Sorts in descending order }
if ExtraData = nil then
Result := TSmallPoint(Item2).X - TSmallPoint(Item1).X
else
Result := TSmallPoint(Item2).Y - TSmallPoint(Item1).Y;
end;
procedure TCustomToolbar97.ResizeBegin (HitTestValue: Integer);
var
CurBarSize: Integer;
procedure BuildNewSizes (const YOrdering: Boolean);
{ Adds items to the NewSizes list. The list must be empty when this is called }
function AddNCAreaToSize (const P: TPoint): TPoint;
var
R: TRect;
begin
with R do begin
Top := 0; Left := 0;
BottomRight := P;
end;
AddFloatingNCAreaToRect (R, Resizable);
OffsetRect (R, -R.Left, -R.Top);
Result := R.BottomRight;
end;
var
SaveFloatingRightX: Integer;
Max, X, LastY, SkipTo: Integer;
S: TPoint;
S2: TSmallPoint;
begin
with PToolbar97SizeData(SizeData)^ do begin
SaveFloatingRightX := FFloatingRightX;
try
FFloatingRightX := 0;
CustomArrangeControls (atNone, nil, nil, S);
S2 := PointToSmallPoint(AddNCAreaToSize(S));
NewSizes.Add (Pointer(S2));
LastY := S.Y;
Max := S.X;
SkipTo := High(SkipTo);
for X := Max-1 downto LeftMarginNotDocked+CurBarSize+RightMarginNotDocked do begin
if X > SkipTo then Continue;
FFloatingRightX := X;
CustomArrangeControls (atNone, nil, nil, S);
if X = S.X then begin
if S.Y = LastY then
NewSizes.Delete (NewSizes.Count-1);
S2 := PointToSmallPoint(AddNCAreaToSize(S));
if NewSizes.IndexOf(Pointer(S2)) = -1 then
NewSizes.Add (Pointer(S2));
LastY := S.Y;
end
else
SkipTo := S.X;
end;
finally
FFloatingRightX := SaveFloatingRightX;
end;
ListSortEx (NewSizes, CompareNewSizes, Pointer(Longint(YOrdering)));
end;
end;
const
MaxSizeSens = 12;
var
I, NewSize: Integer;
S, N: TSmallPoint;
begin
inherited;
SizeData := AllocMem(SizeOf(TToolbar97SizeData));
with PToolbar97SizeData(SizeData)^ do begin
HitTest := HitTestValue;
CurRightX := FFloatingRightX;
DisableSensCheck := False;
OpSide := False;
GetBarSize (CurBarSize, dtNotDocked);
NewSizes := TList.Create;
BuildNewSizes (HitTestValue in [HTTOP, HTBOTTOM]);
SizeSens := MaxSizeSens;
{ Adjust sensitivity if it's too high }
for I := 0 to NewSizes.Count-1 do begin
Pointer(S) := NewSizes[I];
if (S.X = Width) and (S.Y = Height) then begin
if I > 0 then begin
Pointer(N) := NewSizes[I-1];
if HitTestValue in [HTLEFT, HTRIGHT] then
NewSize := N.X - S.X - 1
else
NewSize := N.Y - S.Y - 1;
if NewSize < SizeSens then SizeSens := NewSize;
end;
if I < NewSizes.Count-1 then begin
Pointer(N) := NewSizes[I+1];
if HitTestValue in [HTLEFT, HTRIGHT] then
NewSize := S.X - N.X - 1
else
NewSize := S.Y - N.Y - 1;
if NewSize < SizeSens then SizeSens := NewSize;
end;
Break;
end;
end;
if SizeSens < 0 then SizeSens := 0;
end;
end;
procedure TCustomToolbar97.ResizeTrack (var Rect: TRect; const OrigRect: TRect);
var
Pos: TPoint;
NCXDiff: Integer;
NewOpSide: Boolean;
Reverse: Boolean;
I: Integer;
P: TSmallPoint;
begin
inherited;
with PToolbar97SizeData(SizeData)^ do begin
GetCursorPos (Pos);
NCXDiff := ClientToScreen(Point(0, 0)).X - Left;
Dec (Pos.X, Left); Dec (Pos.Y, Top);
if HitTest = HTLEFT then
Pos.X := Width-Pos.X
else
if HitTest = HTTOP then
Pos.Y := Height-Pos.Y;
{ Adjust Pos to make up for the "sizing sensitivity", as seen in Office 97 }
if HitTest in [HTLEFT, HTRIGHT] then
NewOpSide := Pos.X < Width
else
NewOpSide := Pos.Y < Height;
if (not DisableSensCheck) or (OpSide <> NewOpSide) then begin
DisableSensCheck := False;
OpSide := NewOpSide;
if HitTest in [HTLEFT, HTRIGHT] then begin
if (Pos.X >= Width-SizeSens) and (Pos.X < Width+SizeSens) then
Pos.X := Width;
end
else begin
if (Pos.Y >= Height-SizeSens) and (Pos.Y < Height+SizeSens) then
Pos.Y := Height;
end;
end;
Rect := OrigRect;
if HitTest in [HTLEFT, HTRIGHT] then
Reverse := Pos.X > Width
else
Reverse := Pos.Y > Height;
if not Reverse then
I := NewSizes.Count-1
else
I := 0;
while True do begin
if (not Reverse and (I < 0)) or
(Reverse and (I >= NewSizes.Count)) then
Break;
Pointer(P) := NewSizes[I];
if HitTest in [HTLEFT, HTRIGHT] then begin
if (not Reverse and ((I = NewSizes.Count-1) or (Pos.X >= P.X))) or
(Reverse and ((I = 0) or (Pos.X < P.X))) then begin
if I = 0 then
CurRightX := 0
else
CurRightX := P.X - NCXDiff*2;
if HitTest = HTRIGHT then
Rect.Right := Rect.Left + P.X
else
Rect.Left := Rect.Right - P.X;
Rect.Bottom := Rect.Top + P.Y;
DisableSensCheck := not EqualRect(Rect, OrigRect);
end;
end
else begin
if (not Reverse and ((I = NewSizes.Count-1) or (Pos.Y >= P.Y))) or
(Reverse and ((I = 0) or (Pos.Y < P.Y))) then begin
if I = NewSizes.Count-1 then
CurRightX := 0
else
CurRightX := P.X - NCXDiff*2;
if HitTest = HTBOTTOM then
Rect.Bottom := Rect.Top + P.Y
else
Rect.Top := Rect.Bottom - P.Y;
Rect.Right := Rect.Left + P.X;
DisableSensCheck := not EqualRect(Rect, OrigRect);
end;
end;
if not Reverse then
Dec (I)
else
Inc (I);
end;
end;
end;
procedure TCustomToolbar97.ResizeEnd (Accept: Boolean);
begin
inherited;
if Assigned(SizeData) then begin
with PToolbar97SizeData(SizeData)^ do begin
if Accept then
FFloatingRightX := CurRightX;
NewSizes.Free;
end;
FreeMem (SizeData);
end;
end;
function TCustomToolbar97.GetOrderIndex (Control: TControl): Integer;
begin
Result := OrderList.IndexOf(Control);
if Result = -1 then
raise EInvalidOperation.Create(STB97ToolbarControlNotChildOfToolbar);
end;
procedure TCustomToolbar97.SetOrderIndex (Control: TControl; Value: Integer);
var
OldIndex: Integer;
begin
with OrderList do begin
OldIndex := IndexOf(Control);
if OldIndex = -1 then
raise EInvalidOperation.Create(STB97ToolbarControlNotChildOfToolbar);
if Value < 0 then Value := 0;
if Value >= Count then Value := Count-1;
if Value <> OldIndex then begin
Delete (OldIndex);
Insert (Value, Control);
ArrangeControls;
end;
end;
end;
procedure TCustomToolbar97.SetSlaveControl (const ATopBottom, ALeftRight: TControl);
var
NewVersion: PSlaveInfo;
begin
GetMem (NewVersion, SizeOf(TSlaveInfo));
with NewVersion^ do begin
TopBottom := ATopBottom;
LeftRight := ALeftRight;
end;
SlaveInfo.Add (NewVersion);
ArrangeControls;
end;
{ TDockableWindow - internal }
constructor TToolWindow97.Create (AOwner: TComponent);
var
R: TRect;
begin
inherited;
FMinClientWidth := 32;
FMinClientHeight := 32;
{ Initialize the client size to 32x32 }
R := GetClientRect;
SetBounds (Left, Top, Width - R.Right + 32, Height - R.Bottom + 32);
end;
procedure TToolWindow97.CreateParams (var Params: TCreateParams);
begin
inherited;
{ Disable complete redraws when size changes. CS_H/VREDRAW cause flicker
and are not necessary for this control at run time }
if not(csDesigning in ComponentState) then
with Params.WindowClass do
Style := Style and not(CS_HREDRAW or CS_VREDRAW);
end;
procedure TToolWindow97.ReadPositionData (const ReadIntProc: TPositionReadIntProc;
const ReadStringProc: TPositionReadStringProc; const ExtraData: Pointer);
begin
inherited;
{ Restore FBarWidth/FBarHeight variables }
if Resizable then begin
FBarWidth := ReadIntProc(Name, rvClientWidth, FBarWidth, ExtraData);
FBarHeight := ReadIntProc(Name, rvClientHeight, FBarHeight, ExtraData);
end;
end;
procedure TToolWindow97.WritePositionData (const WriteIntProc: TPositionWriteIntProc;
const WriteStringProc: TPositionWriteStringProc; const ExtraData: Pointer);
begin
inherited;
{ Write values of FBarWidth/FBarHeight }
WriteIntProc (Name, rvClientWidth, FBarWidth, ExtraData);
WriteIntProc (Name, rvClientHeight, FBarHeight, ExtraData);
end;
procedure TToolWindow97.GetMinimumSize (var AClientWidth, AClientHeight: Integer);
begin
AClientWidth := FMinClientWidth;
AClientHeight := FMinClientHeight;
end;
procedure TToolWindow97.SizeChanging (const AWidth, AHeight: Integer);
begin
FBarWidth := AWidth - (Width-ClientWidth);
FBarHeight := AHeight - (Height-ClientHeight);
end;
procedure TToolWindow97.GetBarSize (var ASize: Integer; const DockType: TDockType);
begin
if DockType <> dtLeftRight then
ASize := FBarHeight
else
ASize := FBarWidth;
end;
procedure TToolWindow97.OrderControls (const CanMoveControls: Boolean;
const WasDockedTo, DockingTo: TDock97; var NewClientSize: TPoint);
begin
NewClientSize.X := FBarWidth;
NewClientSize.Y := FBarHeight;
end;
{ TToolbarSep97 - internal }
constructor TToolbarSep97.Create (AOwner: TComponent);
begin
inherited;
FSizeHorz := 6;
FSizeVert := 6;
ControlStyle := ControlStyle - [csOpaque, csCaptureMouse];
end;
procedure TToolbarSep97.SetParent (AParent: TWinControl);
begin
if (AParent <> nil) and not(AParent is TCustomToolbar97) then
raise EInvalidOperation.Create(STB97SepParentNotAllowed);
inherited;
end;
procedure TToolbarSep97.SetBlank (Value: Boolean);
begin
if FBlank <> Value then begin
FBlank := Value;
Invalidate;
end;
end;
procedure TToolbarSep97.SetSizeHorz (Value: TToolbarSepSize);
begin
if FSizeHorz <> Value then begin
FSizeHorz := Value;
if Parent is TCustomToolbar97 then
TCustomToolbar97(Parent).ArrangeControls;
end;
end;
procedure TToolbarSep97.SetSizeVert (Value: TToolbarSepSize);
begin
if FSizeVert <> Value then begin
FSizeVert := Value;
if Parent is TCustomToolbar97 then
TCustomToolbar97(Parent).ArrangeControls;
end;
end;
procedure TToolbarSep97.Paint;
var
R: TRect;
Z: Integer;
begin
inherited;
if not(Parent is TCustomToolbar97) then Exit;
with Canvas do begin
{ Draw dotted border in design mode }
if csDesigning in ComponentState then begin
Pen.Style := psDot;
Pen.Color := clBtnShadow;
Brush.Style := bsClear;
R := ClientRect;
Rectangle (R.Left, R.Top, R.Right, R.Bottom);
Pen.Style := psSolid;
end;
if not FBlank then
if GetDockTypeOf(TCustomToolbar97(Parent).DockedTo) <> dtLeftRight then begin
Z := Width div 2;
Pen.Color := clBtnShadow;
MoveTo (Z-1, 0); LineTo (Z-1, Height);
Pen.Color := clBtnHighlight;
MoveTo (Z, 0); LineTo (Z, Height);
end
else begin
Z := Height div 2;
Pen.Color := clBtnShadow;
MoveTo (0, Z-1); LineTo (Width, Z-1);
Pen.Color := clBtnHighlight;
MoveTo (0, Z); LineTo (Width, Z);
end;
end;
end;
procedure TToolbarSep97.MouseDown (Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
P: TPoint;
begin
inherited;
if not(Parent is TCustomToolbar97) then Exit;
{ Relay the message to the parent toolbar }
P := Parent.ScreenToClient(ClientToScreen(Point(X, Y)));
TCustomToolbar97(Parent).MouseDown (Button, Shift, P.X, P.Y);
end;
{ TToolbarButton97 - internal }
type
TGlyphList = class(TImageList)
private
Used: TBits;
FCount: Integer;
function AllocateIndex: Integer;
public
constructor CreateSize (AWidth, AHeight: Integer);
destructor Destroy; override;
function Add (Image, Mask: TBitmap): Integer;
function AddMasked (Image: TBitmap; MaskColor: TColor): Integer;
procedure Delete (Index: Integer);
property Count: Integer read FCount;
end;
TGlyphCache = class
private
GlyphLists: TList;
public
constructor Create;
destructor Destroy; override;
function GetList(AWidth, AHeight: Integer): TGlyphList;
procedure ReturnList(List: TGlyphList);
function Empty: Boolean;
end;
TBoolInt = record
B: Boolean;
I: Integer;
end;
TButtonGlyph = class
private
FOriginal, FOriginalMask: TBitmap;
FCallDormant: Boolean;
FGlyphList: array[Boolean] of TGlyphList;
FIndexs: array[Boolean, TButtonState97] of Integer;
FTransparentColor: TColor;
FNumGlyphs: TNumGlyphs97;
FOnChange: TNotifyEvent;
FOldDisabledStyle: Boolean;
procedure GlyphChanged (Sender: TObject);
procedure SetGlyph (Value: TBitmap);
procedure SetGlyphMask (Value: TBitmap);
procedure SetNumGlyphs (Value: TNumGlyphs97);
procedure Invalidate;
function CreateButtonGlyph (State: TButtonState97): TBoolInt;
procedure DrawButtonGlyph (Canvas: TCanvas; const GlyphPos: TPoint;
State: TButtonState97);
procedure DrawButtonText (Canvas: TCanvas;
const Caption: string; TextBounds: TRect;
WordWrap: Boolean; State: TButtonState97);
procedure CalcButtonLayout (Canvas: TCanvas; const Client: TRect;
const Offset: TPoint; DrawGlyph, DrawCaption: Boolean;
const Caption: string; WordWrap: Boolean;
Layout: TButtonLayout; Margin, Spacing: Integer; DropArrow: Boolean;
var GlyphPos, ArrowPos: TPoint; var TextBounds: TRect);
public
constructor Create;
destructor Destroy; override;
{ returns the text rectangle }
function Draw (Canvas: TCanvas; const Client: TRect; const Offset: TPoint;
DrawGlyph, DrawCaption: Boolean; const Caption: string; WordWrap: Boolean;
Layout: TButtonLayout; Margin, Spacing: Integer; DropArrow: Boolean;
State: TButtonState97): TRect;
procedure DrawButtonDropArrow (Canvas: TCanvas;
const X, Y: Integer; State: TButtonState97);
property Glyph: TBitmap read FOriginal write SetGlyph;
property GlyphMask: TBitmap read FOriginalMask write SetGlyphMask;
property NumGlyphs: TNumGlyphs97 read FNumGlyphs write SetNumGlyphs;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
{ TGlyphList }
constructor TGlyphList.CreateSize(AWidth, AHeight: Integer);
begin
inherited CreateSize (AWidth, AHeight);
Used := TBits.Create;
end;
destructor TGlyphList.Destroy;
begin
Used.Free;
inherited;
end;
function TGlyphList.AllocateIndex: Integer;
begin
Result := Used.OpenBit;
if Result >= Used.Size then
begin
Result := inherited Add(nil, nil);
Used.Size := Result + 1;
end;
Used[Result] := True;
end;
function TGlyphList.Add (Image, Mask: TBitmap): Integer;
begin
Result := AllocateIndex;
Replace (Result, Image, Mask);
Inc (FCount);
end;
function TGlyphList.AddMasked (Image: TBitmap; MaskColor: TColor): Integer;
procedure BugfreeReplaceMasked (Index: Integer; NewImage: TBitmap; MaskColor: TColor);
procedure CheckImage (Image: TGraphic);
begin
if Image = nil then Exit;
if (Image.Height < Height) or (Image.Width < Width) then
raise EInvalidOperation.Create({$IFNDEF TB97Delphi3orHigher}LoadStr{$ENDIF}(SInvalidImageSize));
end;
var
TempIndex: Integer;
Image, Mask: TBitmap;
begin
if HandleAllocated then begin
CheckImage(NewImage);
TempIndex := inherited AddMasked(NewImage, MaskColor);
if TempIndex <> -1 then
try
Image := TBitmap.Create;
Mask := TBitmap.Create;
try
Image.Height := Height;
Image.Width := Width;
Mask.Monochrome := True;
{ ^ Prevents the "invisible glyph" problem when used with certain
color schemes. (Fixed in Delphi 3.01) }
Mask.Height := Height;
Mask.Width := Width;
ImageList_Draw (Handle, TempIndex, Image.Canvas.Handle, 0, 0, ILD_NORMAL);
ImageList_Draw (Handle, TempIndex, Mask.Canvas.Handle, 0, 0, ILD_MASK);
if not ImageList_Replace(Handle, Index, Image.Handle, Mask.Handle) then
raise EInvalidOperation.Create({$IFNDEF TB97Delphi3orHigher}LoadStr{$ENDIF}(SReplaceImage));
finally
Image.Free;
Mask.Free;
end;
finally
inherited Delete(TempIndex);
end
else
raise EInvalidOperation.Create({$IFNDEF TB97Delphi3orHigher}LoadStr{$ENDIF}(SReplaceImage));
end;
Change;
end;
begin
Result := AllocateIndex;
{ This works two very serious bugs in the Delphi 2/BCB and Delphi 3
implementations of the ReplaceMasked method. In the Delphi 2 and BCB
versions of the ReplaceMasked method, it incorrectly uses ILD_NORMAL as
the last parameter for the second ImageList_Draw call, in effect causing
all white colors to be considered transparent also. And in the Delphi 2/3
and BCB versions it doesn't set Monochrome to True on the Mask bitmap,
causing the bitmaps to be invisible on certain color schemes. }
BugfreeReplaceMasked (Result, Image, MaskColor);
Inc (FCount);
end;
procedure TGlyphList.Delete (Index: Integer);
begin
if Used[Index] then begin
Dec(FCount);
Used[Index] := False;
end;
end;
{ TGlyphCache }
constructor TGlyphCache.Create;
begin
inherited;
GlyphLists := TList.Create;
end;
destructor TGlyphCache.Destroy;
begin
GlyphLists.Free;
inherited;
end;
function TGlyphCache.GetList(AWidth, AHeight: Integer): TGlyphList;
var
I: Integer;
begin
for I := GlyphLists.Count - 1 downto 0 do begin
Result := GlyphLists[I];
with Result do
if (AWidth = Width) and (AHeight = Height) then Exit;
end;
Result := TGlyphList.CreateSize(AWidth, AHeight);
GlyphLists.Add(Result);
end;
procedure TGlyphCache.ReturnList(List: TGlyphList);
begin
if List = nil then Exit;
if List.Count = 0 then begin
GlyphLists.Remove(List);
List.Free;
end;
end;
function TGlyphCache.Empty: Boolean;
begin
Result := GlyphLists.Count = 0;
end;
var
GlyphCache: TGlyphCache = nil;
Pattern: TBitmap = nil;
PatternBtnFace, PatternBtnHighlight: TColor;
ButtonCount: Integer = 0;
procedure CreateBrushPattern;
var
X, Y: Integer;
begin
PatternBtnFace := GetSysColor(COLOR_BTNFACE);
PatternBtnHighlight := GetSysColor(COLOR_BTNHIGHLIGHT);
Pattern := TBitmap.Create;
with Pattern do begin
Width := 8;
Height := 8;
with Canvas do begin
Brush.Style := bsSolid;
Brush.Color := clBtnFace;
FillRect (Rect(0, 0, Width, Height));
for Y := 0 to 7 do
for X := 0 to 7 do
if Odd(Y) = Odd(X) then { toggles between even/odd pixels }
Pixels[X, Y] := clBtnHighlight; { on even/odd rows }
end;
end;
end;
{ TButtonGlyph }
constructor TButtonGlyph.Create;
var
B: Boolean;
I: TButtonState97;
begin
inherited;
FCallDormant := True;
FOriginal := TBitmap.Create;
FOriginal.OnChange := GlyphChanged;
FOriginalMask := TBitmap.Create;
FOriginalMask.OnChange := GlyphChanged;
FNumGlyphs := 1;
for B := False to True do
for I := Low(I) to High(I) do
FIndexs[B, I] := -1;
if GlyphCache = nil then
GlyphCache := TGlyphCache.Create;
end;
destructor TButtonGlyph.Destroy;
begin
FOriginalMask.Free;
FOriginal.Free;
Invalidate;
if Assigned(GlyphCache) and GlyphCache.Empty then begin
GlyphCache.Free;
GlyphCache := nil;
end;
inherited;
end;
procedure TButtonGlyph.Invalidate;
var
B: Boolean;
I: TButtonState97;
begin
for B := False to True do begin
for I := Low(I) to High(I) do begin
if FIndexs[B, I] <> -1 then FGlyphList[B].Delete (FIndexs[B, I]);
FIndexs[B, I] := -1;
end;
GlyphCache.ReturnList (FGlyphList[B]);
FGlyphList[B] := nil;
end;
end;
procedure TButtonGlyph.GlyphChanged (Sender: TObject);
begin
if (Sender = FOriginal) or (Sender = FOriginalMask) then begin
if (FOriginal.Width <> 0) and (FOriginal.Height <> 0) then
FTransparentColor := FOriginal.Canvas.Pixels[0, FOriginal.Height-1] or $02000000;
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
if Value.Width mod Value.Height = 0 then begin
Glyphs := Value.Width div Value.Height;
if Glyphs > High(TNumGlyphs97) then Glyphs := 1;
SetNumGlyphs (Glyphs);
end;
end;
end;
procedure TButtonGlyph.SetGlyphMask (Value: TBitmap);
begin
Invalidate;
FOriginalMask.Assign (Value);
end;
procedure TButtonGlyph.SetNumGlyphs (Value: TNumGlyphs97);
begin
Invalidate;
FNumGlyphs := Value;
GlyphChanged (Glyph);
end;
function TButtonGlyph.CreateButtonGlyph (State: TButtonState97): TBoolInt;
const
ROP_DSPDxax = $00E20746;
ROP_PSDPxax = $00B8074A;
ROP_DSna = $00220326; { D & ~S }
procedure GenerateMaskBitmapFromDIB (const MaskBitmap, SourceBitmap: TBitmap;
const SourceOffset, SourceSize: TPoint; TransColors: array of TColor);
{ This a special procedure meant for generating monochrome masks from
>4 bpp color DIB sections. Because each video driver seems to sport its own
interpretation of how to handle DIB sections, a workaround procedure like
this was necessary. }
type
TColorArray = array[0..536870910] of TColorRef;
var
Info: packed record
Header: TBitmapInfoHeader;
Colors: array[0..1] of TColorRef;
end;
W, H: Integer;
I, Y, X: Integer;
Pixels: ^TColorArray;
Pixel: ^TColorRef;
MonoPixels: Pointer;
MonoPixel, StartMonoPixel: ^Byte;
MonoScanLineSize, CurBit: Integer;
DC: HDC;
MaskBmp: HBITMAP;
begin
W := SourceBitmap.Width;
H := SourceBitmap.Height;
MonoScanLineSize := SourceSize.X div 8;
if MonoScanLineSize mod 4 <> 0 then { Compensate for scan line boundary }
Inc (MonoScanLineSize, 4 - (MonoScanLineSize mod 4));
MonoPixels := AllocMem(MonoScanLineSize * SourceSize.Y); { AllocMem is used because it initializes to zero }
try
GetMem (Pixels, W * H * 4);
try
FillChar (Info, SizeOf(Info), 0);
with Info do begin
with Header do begin
biSize := SizeOf(TBitmapInfoHeader);
biWidth := W;
biHeight := -H; { negative number makes it a top-down DIB }
biPlanes := 1;
biBitCount := 32;
{biCompression := BI_RGB;} { implied due to the FillChar zeroing }
end;
{Colors[0] := clBlack;} { implied due to the FillChar zeroing }
Colors[1] := clWhite;
end;
DC := CreateCompatibleDC(0);
GetDIBits (DC, SourceBitmap.Handle, 0, H, Pixels, PBitmapInfo(@Info)^,
DIB_RGB_COLORS);
DeleteDC (DC);
for I := 0 to High(TransColors) do
if TransColors[I] = -1 then
TransColors[I] := Pixels[W * (H-1)] and $FFFFFF;
{ ^ 'and' operation is necessary because the high byte is undefined }
MonoPixel := MonoPixels;
for Y := SourceOffset.Y to SourceOffset.Y+SourceSize.Y-1 do begin
StartMonoPixel := MonoPixel;
CurBit := 7;
Pixel := @Pixels[(Y * W) + SourceOffset.X];
for X := 0 to SourceSize.X-1 do begin
for I := 0 to High(TransColors) do
if Pixel^ and $FFFFFF = Cardinal(TransColors[I]) then begin
{ ^ 'and' operation is necessary because the high byte is undefined }
MonoPixel^ := MonoPixel^ or (1 shl CurBit);
Break;
end;
Dec (CurBit);
if CurBit < 0 then begin
Inc (Integer(MonoPixel));
CurBit := 7;
end;
Inc (Integer(Pixel), SizeOf(Longint)); { proceed to the next pixel }
end;
Integer(MonoPixel) := Integer(StartMonoPixel) + MonoScanLineSize;
end;
finally
FreeMem (Pixels);
end;
{ Write new bits into a new HBITMAP, and assign this handle to MaskBitmap }
MaskBmp := CreateBitmap(SourceSize.X, SourceSize.Y, 1, 1, nil);
with Info.Header do begin
biWidth := SourceSize.X;
biHeight := -SourceSize.Y; { negative number makes it a top-down DIB }
biPlanes := 1;
biBitCount := 1;
end;
DC := CreateCompatibleDC(0);
SetDIBits (DC, MaskBmp, 0, SourceSize.Y, MonoPixels, PBitmapInfo(@Info)^,
DIB_RGB_COLORS);
DeleteDC (DC);
finally
FreeMem (MonoPixels);
end;
MaskBitmap.Handle := MaskBmp;
end;
procedure GenerateMaskBitmap (const MaskBitmap, SourceBitmap: TBitmap;
const SourceOffset, SourceSize: TPoint; const TransColors: array of TColor);
{ Returns handle of a monochrome bitmap, with pixels in SourceBitmap of color
TransColor set to white in the resulting bitmap. All other colors of
SourceBitmap are set to black in the resulting bitmap. This uses the
regular ROP_DSPDxax BitBlt method. }
var
CanvasHandle: HDC;
SaveBkColor: TColorRef;
DC: HDC;
MaskBmp, SaveBmp: HBITMAP;
I: Integer;
const
ROP: array[Boolean] of DWORD = (SRCPAINT, SRCCOPY);
begin
CanvasHandle := SourceBitmap.Canvas.Handle;
MaskBmp := CreateBitmap(SourceSize.X, SourceSize.Y, 1, 1, nil);
DC := CreateCompatibleDC(0);
SaveBmp := SelectObject(DC, MaskBmp);
SaveBkColor := GetBkColor(CanvasHandle);
for I := 0 to High(TransColors) do begin
SetBkColor (CanvasHandle, ColorToRGB(TransColors[I]));
BitBlt (DC, 0, 0, SourceSize.X, SourceSize.Y, CanvasHandle,
SourceOffset.X, SourceOffset.Y, ROP[I = 0]);
end;
SetBkColor (CanvasHandle, SaveBkColor);
SelectObject (DC, SaveBmp);
DeleteDC (DC);
MaskBitmap.Handle := MaskBmp;
end;
procedure ReplaceBitmapColorsFromMask (const MaskBitmap, DestBitmap: TBitmap;
const DestOffset, DestSize: TPoint; const ReplaceColor: TColor);
var
DestDC: HDC;
SaveBrush: HBRUSH;
SaveTextColor, SaveBkColor: TColorRef;
begin
DestDC := DestBitmap.Canvas.Handle;
SaveBrush := SelectObject(DestDC, CreateSolidBrush(ColorToRGB(ReplaceColor)));
SaveTextColor := SetTextColor(DestDC, clBlack);
SaveBkColor := SetBkColor(DestDC, clWhite);
BitBlt (DestDC, DestOffset.X, DestOffset.Y, DestSize.X, DestSize.Y,
MaskBitmap.Canvas.Handle, 0, 0, ROP_DSPDxax);
SetBkColor (DestDC, SaveBkColor);
SetTextColor (DestDC, SaveTextColor);
DeleteObject (SelectObject(DestDC, SaveBrush));
end;
function CopyBitmapToDDB (const SourceBitmap: TBitmap): TBitmap;
{ Makes a device-dependent duplicate of SourceBitmap. The color palette,
if any, is preserved. }
var
SB: HBITMAP;
SavePalette: HPALETTE;
DC: HDC;
BitmapInfo: packed record
Header: TBitmapInfoHeader;
Colors: array[0..255] of TColorRef;
end;
Bits: Pointer;
begin
Result := TBitmap.Create;
try
Result.Palette := CopyPalette(SourceBitmap.Palette);
Result.Width := SourceBitmap.Width;
Result.Height := SourceBitmap.Height;
SB := SourceBitmap.Handle;
if SB = 0 then Exit; { it would have a null handle if its width or height was zero }
SavePalette := 0;
DC := CreateCompatibleDC(0);
try
if Result.Palette <> 0 then begin
SavePalette := SelectPalette(DC, Result.Palette, False);
RealizePalette (DC);
end;
BitmapInfo.Header.biSize := SizeOf(TBitmapInfoHeader);
BitmapInfo.Header.biBitCount := 0; { instructs GetDIBits not to fill in the color table }
{ First retrieve the BitmapInfo header only }
if GetDIBits(DC, SB, 0, 0, nil, PBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS) <> 0 then begin
GetMem (Bits, BitmapInfo.Header.biSizeImage);
try
{ Then read the actual bits }
if GetDIBits(DC, SB, 0, SourceBitmap.Height, Bits, PBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS) <> 0 then
{ And copy them to the resulting bitmap }
SetDIBits (DC, Result.Handle, 0, SourceBitmap.Height, Bits, PBitmapInfo(@BitmapInfo)^, DIB_RGB_COLORS);
finally
FreeMem (Bits);
end;
end;
finally
if SavePalette <> 0 then SelectPalette (DC, SavePalette, False);
DeleteDC (DC);
end;
except
Result.Free;
raise;
end;
end;
const
ROPs: array[Boolean] of DWORD = (ROP_PSDPxax, ROP_DSPDxax);
var
TmpImage, DDB, MonoBmp: TBitmap;
I: TButtonState97;
B: Boolean;
AddPixels, IWidth, IHeight, IWidthA, IHeightA: Integer;
IRect, IRectA, SourceRect, R: TRect;
DC: HDC;
UsesMask: Boolean;
{$IFDEF TB97Delphi3orHigher}
IsHighColorDIB: Boolean;
{$ELSE}
const
IsHighColorDIB = False;
{$ENDIF}
begin
if (State <> bsDisabled) and (Ord(State) >= NumGlyphs) then
State := bsUp;
Result.B := True;
Result.I := FIndexs[True, State];
if Result.I = -1 then begin
Result.B := False;
Result.I := FIndexs[False, State];
end;
if Result.I <> -1 then Exit;
if (FOriginal.Width or FOriginal.Height) = 0 then Exit;
UsesMask := (FOriginalMask.Width <> 0) and (FOriginalMask.Height <> 0);
B := State <> bsDisabled;
{ + AddPixels is to make sure the highlight color on generated disabled glyphs
doesn't get cut off }
AddPixels := Ord(State = bsDisabled);
IWidth := FOriginal.Width div FNumGlyphs + AddPixels;
IHeight := FOriginal.Height + AddPixels;
IRect := Rect(0, 0, IWidth, IHeight);
IWidthA := IWidth - AddPixels;
IHeightA := IHeight - AddPixels;
IRectA := Rect(0, 0, IWidthA, IHeightA);
if FGlyphList[B] = nil then begin
if GlyphCache = nil then
GlyphCache := TGlyphCache.Create;
FGlyphList[B] := GlyphCache.GetList(IWidth, IHeight);
end;
{$IFDEF TB97Delphi3orHigher}
IsHighColorDIB := FOriginal.PixelFormat > pf4bit;
{$ENDIF}
TmpImage := TBitmap.Create;
try
TmpImage.Width := IWidth;
TmpImage.Height := IHeight;
TmpImage.Canvas.Brush.Color := clBtnFace;
TmpImage.Palette := CopyPalette(FOriginal.Palette);
I := State;
if Ord(I) >= NumGlyphs then I := bsUp;
SourceRect := Bounds(Ord(I) * IWidthA, 0, IWidthA, IHeightA);
if State <> bsDisabled then begin
TmpImage.Canvas.CopyRect (IRectA, FOriginal.Canvas, SourceRect);
if not UsesMask then begin
{$IFDEF TB97Delphi3orHigher}
{ Use clDefault instead of FTransparentColor whereever possible to
ensure compatibility with all video drivers when using high-color
(> 4 bpp) DIB glyphs }
FIndexs[B, State] := FGlyphList[B].AddMasked(TmpImage, clDefault);
{$ELSE}
FIndexs[B, State] := FGlyphList[B].AddMasked(TmpImage, FTransparentColor);
{$ENDIF}
end
else begin
MonoBmp := TBitmap.Create;
try
MonoBmp.Monochrome := True;
MonoBmp.Width := IWidth;
MonoBmp.Height := IHeight;
MonoBmp.Canvas.CopyRect (IRectA, FOriginalMask.Canvas, SourceRect);
FIndexs[B, State] := FGlyphList[B].Add(TmpImage, MonoBmp);
finally
MonoBmp.Free;
end;
end;
end
else begin
MonoBmp := TBitmap.Create;
try
{ Uses the CopyBitmapToDDB to work around a Delphi 3 flaw. If you copy
a DIB to a second bitmap via Assign, change the HandleType of the
second bitmap to bmDDB, then try to read the Handle property, Delphi
converts it back to a DIB. }
DDB := CopyBitmapToDDB(FOriginal);
try
if NumGlyphs > 1 then
with TmpImage.Canvas do begin
CopyRect (IRectA, DDB.Canvas, SourceRect);
{ Convert white to clBtnHighlight }
if not IsHighColorDIB then
GenerateMaskBitmap (MonoBmp, DDB, SourceRect.TopLeft,
IRectA.BottomRight, [GetNearestColor(FOriginal.Canvas.Handle, clWhite)])
else
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal, SourceRect.TopLeft,
IRectA.BottomRight, [clWhite]);
ReplaceBitmapColorsFromMask (MonoBmp, TmpImage, IRectA.TopLeft,
IRectA.BottomRight, clBtnHighlight);
{ Convert gray to clBtnShadow }
if not IsHighColorDIB then
GenerateMaskBitmap (MonoBmp, DDB, SourceRect.TopLeft,
IRectA.BottomRight, [GetNearestColor(FOriginal.Canvas.Handle, clGray)])
else
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal, SourceRect.TopLeft,
IRectA.BottomRight, [clGray]);
ReplaceBitmapColorsFromMask (MonoBmp, TmpImage, IRectA.TopLeft,
IRectA.BottomRight, clBtnShadow);
if not UsesMask then begin
{ Generate the transparent mask in MonoBmp. The reason why
it doesn't just use a mask color is because the mask needs
to be of the glyph -before- the clBtnHighlight/Shadow were
translated }
if not IsHighColorDIB then
GenerateMaskBitmap (MonoBmp, DDB,
SourceRect.TopLeft, IRectA.BottomRight, FTransparentColor)
else
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal,
SourceRect.TopLeft, IRectA.BottomRight, [-1]);
end
else
MonoBmp.Canvas.CopyRect (IRectA, FOriginalMask.Canvas, SourceRect);
with MonoBmp do begin
Width := Width + AddPixels;
Height := Height + AddPixels;
{ Set the additional bottom and right row on disabled glyph
masks to white so that it always shines through, since the
bottom and right row on TmpImage was left uninitialized }
Canvas.Pen.Color := clWhite;
Canvas.PolyLine ([Point(0, Height-1), Point(Width-1, Height-1),
Point(Width-1, -1)]);
end;
FIndexs[B, State] := FGlyphList[B].Add(TmpImage, MonoBmp);
end
else begin
{ Create a disabled version }
if FOldDisabledStyle then begin
{ "Old" TSpeedButton style }
if not UsesMask then begin
if IsHighColorDIB then
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal,
SourceRect.TopLeft, IRectA.BottomRight, [clBlack])
else begin
with MonoBmp do begin
Assign (DDB); { must be a DDB for this to work right }
Canvas.Brush.Color := clBlack;
Monochrome := True;
end;
end;
end
else begin
MonoBmp.Assign (DDB); { must be a DDB for this to work right }
with TBitmap.Create do
try
Monochrome := True;
Width := FOriginalMask.Width;
Height := FOriginalMask.Height;
R := Rect(0, 0, Width, Height);
Canvas.CopyRect (R, FOriginalMask.Canvas, R);
DC := Canvas.Handle;
with MonoBmp.Canvas do begin
BitBlt (Handle, 0, 0, IWidthA, IHeightA, DC,
SourceRect.Left, SourceRect.Top, ROP_DSna);
BitBlt (Handle, 0, 0, IWidthA, IHeightA, DC,
SourceRect.Left, SourceRect.Top, SRCPAINT);
end;
finally
Free;
end;
MonoBmp.Canvas.Brush.Color := clBlack;
MonoBmp.Monochrome := True;
end;
end
else begin
{ The new Office 97 / MFC look }
if not UsesMask then begin
with TmpImage.Canvas do begin
if not IsHighColorDIB then
GenerateMaskBitmap (MonoBmp, DDB, IRectA.TopLeft,
IRectA.BottomRight, [FTransparentColor, clWhite, clSilver])
else
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal,
SourceRect.TopLeft, IRectA.BottomRight, [-1, clWhite, clSilver]);
end;
end
else begin
{ Generate the mask in MonoBmp. Make clWhite and clSilver transparent. }
if not IsHighColorDIB then
GenerateMaskBitmap (MonoBmp, DDB, SourceRect.TopLeft,
IRectA.BottomRight, [clWhite, clSilver])
else
GenerateMaskBitmapFromDIB (MonoBmp, FOriginal, SourceRect.TopLeft,
IRectA.BottomRight, [clWhite, clSilver]);
{ and all the white colors in FOriginalMask }
with TBitmap.Create do
try
Monochrome := True;
Width := FOriginalMask.Width;
Height := FOriginalMask.Height;
R := Rect(0, 0, Width, Height);
Canvas.CopyRect (R, FOriginalMask.Canvas, R);
DC := Canvas.Handle;
with MonoBmp.Canvas do begin
BitBlt (Handle, 0, 0, IWidthA, IHeightA, DC,
SourceRect.Left, SourceRect.Top, ROP_DSna);
BitBlt (Handle, 0, 0, IWidthA, IHeightA, DC,
SourceRect.Left, SourceRect.Top, SRCPAINT);
end;
finally
Free;
end;
end;
end;
with TmpImage.Canvas do begin
Brush.Color := clBtnFace;
FillRect (IRect);
Brush.Color := clBtnHighlight;
DC := Handle;
SetTextColor (DC, clBlack);
SetBkColor (DC, clWhite);
BitBlt (DC, 1, 1, IWidthA, IHeightA,
MonoBmp.Canvas.Handle, 0, 0, ROPs[FOldDisabledStyle]);
Brush.Color := clBtnShadow;
DC := Handle;
SetTextColor (DC, clBlack);
SetBkColor (DC, clWhite);
BitBlt (DC, 0, 0, IWidthA, IHeightA,
MonoBmp.Canvas.Handle, 0, 0, ROPs[FOldDisabledStyle]);
end;
FIndexs[B, State] := FGlyphList[B].AddMasked(TmpImage, clBtnFace);
end;
finally
DDB.Free;
end;
finally
MonoBmp.Free;
end;
end;
finally
TmpImage.Free;
end;
Result.B := B;
Result.I := FIndexs[B, State];
{ Note: Due to a bug in graphics.pas, Delphi 2's VCL crashes if Dormant is
called on an empty bitmap, so to prevent this it must check Handle first }
if {$IFNDEF TB97Delphi3orHigher} (FOriginal.Handle <> 0) and {$ENDIF}
FCallDormant then
FOriginal.Dormant;
{$IFNDEF TB97Delphi3orHigher} if FOriginalMask.Handle <> 0 then {$ENDIF}
FOriginalMask.Dormant;
end;
procedure TButtonGlyph.DrawButtonGlyph (Canvas: TCanvas; const GlyphPos: TPoint;
State: TButtonState97);
var
Index: TBoolInt;
begin
if (FOriginal = nil) or (FOriginal.Width = 0) or (FOriginal.Height = 0) then
Exit;
Index := CreateButtonGlyph(State);
ImageList_DrawEx (FGlyphList[Index.B].Handle, Index.I, Canvas.Handle,
GlyphPos.X, GlyphPos.Y, 0, 0, CLR_NONE, CLR_NONE, ILD_TRANSPARENT);
end;
procedure TButtonGlyph.DrawButtonText (Canvas: TCanvas; const Caption: string;
TextBounds: TRect; WordWrap: Boolean; State: TButtonState97);
var
Format: UINT;
begin
Format := DT_CENTER or DT_VCENTER;
if not WordWrap then
Format := Format or DT_SINGLELINE
else
Format := Format or DT_WORDBREAK;
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, Format);
OffsetRect (TextBounds, -1, -1);
Font.Color := clBtnShadow;
DrawText (Handle, PChar(Caption), Length(Caption), TextBounds, Format);
end
else
DrawText (Handle, PChar(Caption), Length(Caption), TextBounds, Format);
end;
end;
procedure TButtonGlyph.DrawButtonDropArrow (Canvas: TCanvas;
const X, Y: Integer; State: TButtonState97);
begin
with Canvas do begin
if State = bsDisabled then begin
Pen.Color := clBtnHighlight;
Brush.Color := clBtnHighlight;
Polygon ([Point(X+5, Y+1), Point(X+9, Y+1), Point(X+7, Y+3)]);
Pen.Color := clBtnShadow;
Brush.Color := clBtnShadow;
Polygon ([Point(X+4, Y), Point(X+8, Y), Point(X+6, Y+2)]);
end
else begin
Pen.Color := Font.Color;
Brush.Color := Font.Color;
Polygon ([Point(X+4, Y), Point(X+8, Y), Point(X+6, Y+2)]);
end;
end;
end;
procedure TButtonGlyph.CalcButtonLayout (Canvas: TCanvas; const Client: TRect;
const Offset: TPoint; DrawGlyph, DrawCaption: Boolean; const Caption: string;
WordWrap: Boolean; Layout: TButtonLayout; Margin, Spacing: Integer;
DropArrow: Boolean; var GlyphPos, ArrowPos: TPoint; var TextBounds: TRect);
var
TextPos: TPoint;
ClientSize, GlyphSize, TextSize, ArrowSize: TPoint;
TotalSize: TPoint;
Format: UINT;
Margin1, Spacing1: Integer;
LayoutLeftOrRight: Boolean;
begin
{ calculate the item sizes }
ClientSize := Point(Client.Right-Client.Left, Client.Bottom-Client.Top);
if DrawGlyph and (FOriginal <> nil) then
GlyphSize := Point(FOriginal.Width div FNumGlyphs, FOriginal.Height)
else
GlyphSize := Point(0, 0);
if DropArrow then
ArrowSize := Point(9, 3)
else
ArrowSize := Point(0, 0);
LayoutLeftOrRight := Layout in [blGlyphLeft, blGlyphRight];
if not LayoutLeftOrRight and ((GlyphSize.X = 0) or (GlyphSize.Y = 0)) then begin
Layout := blGlyphLeft;
LayoutLeftOrRight := True;
end;
if DrawCaption and (Caption <> '') then begin
TextBounds := Rect(0, 0, Client.Right-Client.Left, 0);
if LayoutLeftOrRight then
Dec (TextBounds.Right, ArrowSize.X);
Format := DT_CALCRECT;
if WordWrap then begin
Format := Format or DT_WORDBREAK;
Margin1 := 4;
if LayoutLeftOrRight and (GlyphSize.X <> 0) and (GlyphSize.Y <> 0) then begin
if Spacing = -1 then
Spacing1 := 4
else
Spacing1 := Spacing;
Dec (TextBounds.Right, GlyphSize.X + Spacing1);
if Margin <> -1 then
Margin1 := Margin
else
if Spacing <> -1 then
Margin1 := Spacing;
end;
Dec (TextBounds.Right, Margin1 * 2);
end;
DrawText (Canvas.Handle, PChar(Caption), Length(Caption), TextBounds, Format);
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 LayoutLeftOrRight 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 - ArrowSize.X + 1) div 2;
TextPos.X := (ClientSize.X - TextSize.X + 1) div 2;
if (GlyphSize.X = 0) or (GlyphSize.Y = 0) then
ArrowPos.X := TextPos.X + TextSize.X
else
ArrowPos.X := GlyphPos.X + GlyphSize.X;
end;
{ if there is no text or no bitmap, then Spacing is irrelevant }
if (TextSize.X = 0) or (TextSize.Y = 0) or
(GlyphSize.X = 0) or (GlyphSize.Y = 0) then
Spacing := 0;
{ adjust Margin and Spacing }
if Margin = -1 then begin
if Spacing = -1 then begin
TotalSize := Point(GlyphSize.X + TextSize.X + ArrowSize.X,
GlyphSize.Y + TextSize.Y);
if LayoutLeftOrRight 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 + ArrowSize.X,
GlyphSize.Y + Spacing + TextSize.Y);
if LayoutLeftOrRight 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 + ArrowSize.X),
ClientSize.Y - (Margin + GlyphSize.Y));
if LayoutLeftOrRight 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;
ArrowPos.X := TextPos.X + TextSize.X;
end;
blGlyphRight: begin
ArrowPos.X := ClientSize.X - Margin - ArrowSize.X;
GlyphPos.X := ArrowPos.X - 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;
if (GlyphSize.X = 0) or (GlyphSize.Y = 0) then
ArrowPos.Y := TextPos.Y + (TextSize.Y - ArrowSize.Y) div 2
else
ArrowPos.Y := GlyphPos.Y + (GlyphSize.Y - ArrowSize.Y) div 2;
{ fixup the result variables }
with GlyphPos do begin
Inc (X, Client.Left + Offset.X);
Inc (Y, Client.Top + Offset.Y);
end;
with ArrowPos 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; DrawGlyph, DrawCaption: Boolean; const Caption: string;
WordWrap: Boolean; Layout: TButtonLayout; Margin, Spacing: Integer;
DropArrow: Boolean; State: TButtonState97): TRect;
var
GlyphPos, ArrowPos: TPoint;
begin
CalcButtonLayout (Canvas, Client, Offset, DrawGlyph, DrawCaption, Caption,
WordWrap, Layout, Margin, Spacing, DropArrow, GlyphPos, ArrowPos, Result);
if DrawGlyph then
DrawButtonGlyph (Canvas, GlyphPos, State);
if DrawCaption then
DrawButtonText (Canvas, Caption, Result, WordWrap, State);
if DropArrow then
DrawButtonDropArrow (Canvas, ArrowPos.X, ArrowPos.Y, State);
end;
{ TDropdownList }
type
TDropdownList = class(TComponent)
private
List: TList;
Window: HWND;
procedure WndProc (var Message: TMessage);
protected
procedure Notification (AComponent: TComponent; Operation: TOperation); override;
public
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
procedure AddMenu (Menu: TPopupMenu);
end;
var
DropdownList: TDropdownList;
constructor TDropdownList.Create (AOwner: TComponent);
begin
inherited;
List := TList.Create;
end;
destructor TDropdownList.Destroy;
begin
List.Free;
inherited;
end;
procedure TDropdownList.WndProc (var Message: TMessage);
{ This procedure is based on code from TPopupList.WndProc (menus.pas) }
var
I: Integer;
MenuItem: TMenuItem;
FindKind: TFindItemKind;
ContextID: Integer;
begin
try
with List do
case Message.Msg of
WM_COMMAND:
for I := 0 to Count-1 do
if TPopupMenu(Items[I]).DispatchCommand(TWMCommand(Message).ItemID) then
Exit;
WM_INITMENUPOPUP:
for I := 0 to Count-1 do
if TPopupMenu(Items[I]).DispatchPopup(TWMInitMenuPopup(Message).MenuPopup) then
Exit;
WM_MENUSELECT:
with TWMMenuSelect(Message) do begin
FindKind := fkCommand;
if MenuFlag and MF_POPUP <> 0 then
FindKind := fkHandle;
for I := 0 to Count-1 do begin
MenuItem := TPopupMenu(Items[I]).FindItem(IDItem, FindKind);
if MenuItem <> nil then begin
Application.Hint := MenuItem.Hint;
Exit;
end;
end;
Application.Hint := '';
end;
WM_HELP:
with TWMHelp(Message).HelpInfo^ do begin
for I := 0 to Count-1 do
if TPopupMenu(Items[I]).Handle = hItemHandle then begin
ContextID := TPopupMenu(Items[I]).GetHelpContext(iCtrlID, True);
if ContextID = 0 then
ContextID := TPopupMenu(Items[I]).GetHelpContext(hItemHandle, False);
if Screen.ActiveForm = nil then Exit;
if (biHelp in Screen.ActiveForm.BorderIcons) then
Application.HelpCommand (HELP_CONTEXTPOPUP, ContextID)
else
Application.HelpContext (ContextID);
Exit;
end;
end;
end;
with Message do
Result := DefWindowProc(Window, Msg, wParam, lParam);
except
Application.HandleException (Self);
end;
end;
procedure TDropdownList.AddMenu (Menu: TPopupMenu);
begin
if List.IndexOf(Menu) = -1 then begin
if List.Count = 0 then
Window := AllocateHWnd(WndProc);
Menu.FreeNotification (DropdownList);
List.Add (Menu);
end;
end;
procedure TDropdownList.Notification (AComponent: TComponent; Operation: TOperation);
begin
inherited;
if Operation = opRemove then begin
List.Remove (AComponent);
if List.Count = 0 then
DeallocateHWnd (Window);
end;
end;
{ TToolbarButton97 }
constructor TToolbarButton97.Create (AOwner: TComponent);
begin
inherited;
if not(csDesigning in ComponentState) then begin
if ButtonHookRefCount = 0 then
Application.HookMainWindow (TToolbarButton97.DeactivateHook);
Inc (ButtonHookRefCount);
FHooked := True;
end;
SetBounds (Left, Top, 23, 22);
ControlStyle := [csCaptureMouse, csDoubleClicks, csOpaque];
FGlyph := TButtonGlyph.Create;
TButtonGlyph(FGlyph).OnChange := GlyphChanged;
ParentFont := True;
FFlat := True;
FOpaque := True;
FSpacing := 4;
FMargin := -1;
FLayout := blGlyphLeft;
FDropdownArrow := True;
FRepeatDelay := 400;
FRepeatInterval := 100;
Inc (ButtonCount);
end;
destructor TToolbarButton97.Destroy;
begin
if ButtonMouseInControl = Self then begin
ButtonMouseTimer.Enabled := False;
ButtonMouseInControl := nil;
end;
TButtonGlyph(FGlyph).Free;
Dec (ButtonCount);
if ButtonCount = 0 then begin
Pattern.Free;
Pattern := nil;
end;
if FHooked then begin
Dec (ButtonHookRefCount);
if ButtonHookRefCount = 0 then
Application.UnhookMainWindow (TToolbarButton97.DeactivateHook);
end;
inherited;
end;
procedure TToolbarButton97.Paint;
const
EdgeStyles: array[Boolean, Boolean] of UINT = (
(EDGE_RAISED, EDGE_SUNKEN),
(BDR_RAISEDINNER, BDR_SUNKENOUTER));
FlagStyles: array[Boolean] of UINT = (BF_RECT or BF_SOFT or BF_MIDDLE, BF_RECT);
var
Bmp: TBitmap;
DrawCanvas: TCanvas;
PaintRect, R: TRect;
Offset: TPoint;
begin
if FOpaque or not FFlat then
Bmp := TBitmap.Create
else
Bmp := nil;
try
if FOpaque or not FFlat then begin
Bmp.Width := Width;
Bmp.Height := Height;
DrawCanvas := Bmp.Canvas;
with DrawCanvas do begin
Brush.Color := Self.Color;
FillRect (ClientRect);
end;
end
else
DrawCanvas := Canvas;
if not Enabled then begin
FState := bsDisabled;
FMouseIsDown := False;
end
else
if FState = bsDisabled then
if FDown and (GroupIndex <> 0) then
FState := bsExclusive
else
FState := bsUp;
DrawCanvas.Font := Self.Font;
PaintRect := Rect(0, 0, Width, Height);
if ((not FNoBorder) and
((not FFlat) or (FState in [bsDown, bsExclusive]) or
(FMouseInControl and (FState <> bsDisabled)))) or
(csDesigning in ComponentState) then begin
if DropdownCombo and FUsesDropdown then begin
R := PaintRect;
R.Left := R.Right - DropdownComboWidth;
Dec (R.Right, 2);
DrawEdge (DrawCanvas.Handle, R,
EdgeStyles[FFlat, (FState in [bsDown, bsExclusive]) and FMenuIsDown],
FlagStyles[FFlat]);
Dec (PaintRect.Right, DropdownComboWidth);
end;
DrawEdge (DrawCanvas.Handle, PaintRect,
EdgeStyles[FFlat, (FState in [bsDown, bsExclusive]) and (not(DropdownCombo and FUsesDropdown) or not FMenuIsDown)],
FlagStyles[FFlat]);
end
else
if DropdownCombo and FUsesDropdown then
Dec (PaintRect.Right, DropdownComboWidth);
if not FNoBorder then begin
if FFlat then
InflateRect (PaintRect, -1, -1)
else
InflateRect (PaintRect, -2, -2);
end;
if (FState in [bsDown, bsExclusive]) and (not(DropdownCombo and FUsesDropdown) or not FMenuIsDown) then begin
if (FState = bsExclusive) and (not FFlat or not FMouseInControl) then begin
if Pattern = nil then CreateBrushPattern;
DrawCanvas.Brush.Bitmap := Pattern;
DrawCanvas.FillRect(PaintRect);
end;
Offset.X := 1;
Offset.Y := 1;
end
else begin
Offset.X := 0;
Offset.Y := 0;
end;
TButtonGlyph(FGlyph).Draw (DrawCanvas, PaintRect, Offset,
FDisplayMode <> dmTextOnly, FDisplayMode <> dmGlyphOnly,
Caption, FWordWrap, FLayout, FMargin, FSpacing,
FDropdownArrow and not FDropdownCombo and FUsesDropdown, FState);
if FDropdownCombo and FUsesDropdown then
TButtonGlyph(FGlyph).DrawButtonDropArrow (DrawCanvas, Width-DropdownComboWidth-2,
Height div 2 - 1, FState);
if FOpaque or not FFlat then
Canvas.Draw (0, 0, Bmp);
finally
if FOpaque or not FFlat then
Bmp.Free;
end;
end;
procedure TToolbarButton97.UpdateTracking;
var
P: TPoint;
begin
if Enabled then begin
GetCursorPos (P);
{ Use FindDragTarget instead of PtInRect since we want to check based on
the Z order }
FMouseInControl := not (FindDragTarget(P, True) = Self);
if FMouseInControl then
MouseLeft
else
MouseEntered;
end;
end;
procedure TToolbarButton97.Loaded;
var
State: TButtonState97;
begin
inherited;
if Enabled then
State := bsUp
else
State := bsDisabled;
TButtonGlyph(FGlyph).CreateButtonGlyph (State);
end;
procedure TToolbarButton97.Notification (AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (AComponent = FDropdownMenu) and (Operation = opRemove) then
FDropdownMenu := nil;
end;
function TToolbarButton97.PointInButton (X, Y: Integer): Boolean;
begin
Result := (X >= 0) and (X < ClientWidth-(DropdownComboWidth * Ord(FDropdownCombo and FUsesDropdown))) and
(Y >= 0) and (Y < ClientHeight);
end;
procedure TToolbarButton97.MouseDown (Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
if not Enabled then begin
inherited;
Exit;
end;
if Button <> mbLeft then begin
MouseEntered;
inherited;
end
else begin
{ We know mouse has to be over the control if the mouse went down. }
if not FMouseInControl then begin
{ Doesn't call MouseEntered since the redrawing it does is unnecessary
here }
FMouseInControl := True;
if Assigned(FOnMouseEnter) then
FOnMouseEnter (Self);
end;
FMenuIsDown := FUsesDropdown and (not FDropdownCombo or (X >= Width-DropdownComboWidth));
try
if not FDown then begin
FState := bsDown;
Redraw (True);
end
else
if FAllowAllUp then
Redraw (True);
if not FMenuIsDown then
FMouseIsDown := True;
inherited;
if FMenuIsDown then
Click
else
if FRepeating then begin
inherited Click;
if not Assigned(FRepeatTimer) then
FRepeatTimer := TTimer.Create(Self);
FRepeatTimer.Enabled := False;
FRepeatTimer.Interval := FRepeatDelay;
FRepeatTimer.OnTimer := RepeatTimerHandler;
FRepeatTimer.Enabled := True;
end;
finally
FMenuIsDown := False;
end;
end;
end;
procedure TToolbarButton97.MouseMove (Shift: TShiftState; X, Y: Integer);
function GetActiveForm: {$IFDEF TB97Delphi3orHigher} TCustomForm {$ELSE} TForm {$ENDIF};
{ Returns the active top-level form }
var
Ctl: TWinControl;
begin
Result := nil;
Ctl := FindControl(GetActiveWindow);
if Assigned(Ctl) then begin
Result := GetParentForm(Ctl);
if Result is TForm then
Result := GetMDIParent(TForm(Result));
end;
end;
var
P: TPoint;
NewState: TButtonState97;
PtInButton: Boolean;
begin
inherited;
{ Check if mouse just entered the control. It works better to check this
in MouseMove rather than using CM_MOUSEENTER, since the VCL doesn't send
a CM_MOUSEENTER in all cases
Use FindDragTarget instead of PtInRect since we want to check based on
the Z order }
P := ClientToScreen(Point(X, Y));
if (ButtonMouseInControl <> Self) and (FindDragTarget(P, True) = Self) then begin
if Assigned(ButtonMouseInControl) then
ButtonMouseInControl.MouseLeft;
{ Like Office 97, only draw the active borders when the application is active }
if FShowBorderWhenInactive or Application.Active then begin
ButtonMouseInControl := Self;
ButtonMouseTimer.OnTimer := ButtonMouseTimerHandler;
ButtonMouseTimer.Enabled := True;
MouseEntered;
end;
end;
if FMouseIsDown then begin
PtInButton := PointInButton(X, Y);
if PtInButton and Assigned(FRepeatTimer) then
FRepeatTimer.Enabled := True;
if FDown then
NewState := bsExclusive
else begin
if PtInButton then
NewState := bsDown
else
NewState := bsUp;
end;
if NewState <> FState then begin
FState := NewState;
Redraw (True);
end;
end;
end;
procedure TToolbarButton97.RepeatTimerHandler (Sender: TObject);
var
P: TPoint;
begin
FRepeatTimer.Interval := FRepeatInterval;
GetCursorPos (P);
P := ScreenToClient(P);
if Repeating and FMouseIsDown and MouseCapture and PointInButton(P.X, P.Y) then
inherited Click
else
FRepeatTimer.Enabled := False;
end;
procedure TToolbarButton97.WMCancelMode (var Message: TWMCancelMode);
begin
FRepeatTimer.Free;
FRepeatTimer := nil;
if FMouseIsDown then begin
FMouseIsDown := False;
if FGroupIndex <> 0 then begin
if FDown then
FState := bsExclusive;
end;
MouseLeft;
end;
{ Delphi's default processing of WM_CANCELMODE sends a "fake" WM_LBUTTONUP
message to the control, so inherited must only be called after setting
FMouseIsDown to False }
inherited;
end;
procedure TToolbarButton97.MouseUp (Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
DoClick: Boolean;
begin
FRepeatTimer.Free;
FRepeatTimer := nil;
{ Remove active border when right button is clicked }
if (Button = mbRight) and Enabled then begin
FMouseIsDown := False;
MouseLeft;
end;
inherited;
if (Button = mbLeft) and FMouseIsDown then begin
FMouseIsDown := False;
DoClick := PointInButton(X, Y);
if FGroupIndex <> 0 then begin
if DoClick then
SetDown (not FDown)
else begin
if FDown then
FState := bsExclusive;
end;
end;
if DoClick and not FRepeating then
Click
else begin
if FState = bsDown then
FState := bsUp;
UpdateTracking;
end;
end;
end;
procedure TToolbarButton97.Click;
const
{ TPM_RIGHTBUTTON works better on Windows 3.x }
ButtonFlags: array[Boolean] of UINT = (TPM_RIGHTBUTTON, TPM_LEFTBUTTON);
AlignFlags: array[TPopupAlignment] of UINT = (TPM_LEFTALIGN, TPM_RIGHTALIGN,
TPM_CENTERALIGN);
var
SaveAlignment: TPopupAlignment;
PopupPoint: TPoint;
RepostList: TList; {pointers to TMsg's}
Msg: TMsg;
Repost: Boolean;
I: Integer;
P: TPoint;
Form: {$IFDEF TB97Delphi3orHigher} TCustomForm {$ELSE} TForm {$ENDIF};
begin
FInClick := True;
try
if FState in [bsUp, bsMouseIn] then begin
FState := bsDown;
Redraw (True);
end;
{ Stop tracking }
MouseLeft;
if (not FUsesDropdown) or (FDropdownCombo and not FMenuIsDown) then begin
Form := GetParentForm(Self);
if Form <> nil then Form.ModalResult := ModalResult;
inherited;
end
else begin
{ It must release its capture before displaying the popup menu since
this control uses csCaptureMouse. If it doesn't, the VCL seems to
get confused and think the mouse is still captured even after the
popup menu is displayed, causing mouse problems after the menu is
dismissed. }
MouseCapture := False;
try
SaveAlignment := DropdownMenu.Alignment;
try
DropdownMenu.Alignment := paLeft;
PopupPoint := Point(0, Height);
if (Parent is TCustomToolWindow97) and
(GetDockTypeOf(TCustomToolWindow97(Parent).DockedTo) = dtLeftRight) then begin
{ Drop out right or left side }
if TCustomToolWindow97(Parent).DockedTo.Position = dpLeft then
PopupPoint := Point(Width, 0)
else begin
PopupPoint := Point(0, 0);
DropdownMenu.Alignment := paRight;
end;
end;
PopupPoint := ClientToScreen(PopupPoint);
with DropdownMenu do begin
PopupComponent := Self;
{ Starting with version 1.54, this avoids using the Popup method
of TPopupMenu because it uses the "track right button" flag
(which disallowed the "click and drag" selecting motion many
people are accustomed to). }
if Assigned(OnPopup) then
OnPopup (DropdownMenu);
TrackPopupMenu (Handle, AlignFlags[Alignment] or ButtonFlags[NewStyleControls],
PopupPoint.X, PopupPoint.Y, 0, DropdownList.Window, nil);
end;
finally
DropdownMenu.Alignment := SaveAlignment;
end;
finally
{ To prevent a mouse click from redisplaying the menu, filter all
mouse up/down messages, and repost the ones that don't need
removing. This is sort of bulky, but it's the only way I could
find that works perfectly and like Office 97. }
RepostList := TList.Create;
try
while PeekMessage(Msg, 0, WM_LBUTTONDOWN, WM_MBUTTONDBLCLK,
PM_REMOVE or PM_NOYIELD) do
{ ^ The WM_LBUTTONDOWN to WM_MBUTTONDBLCLK range encompasses all
of the DOWN and DBLCLK messages for the three buttons }
with Msg do begin
Repost := True;
case Message of
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK,
WM_RBUTTONDOWN, WM_RBUTTONDBLCLK,
WM_MBUTTONDOWN, WM_MBUTTONDBLCLK: begin
P := SmallPointToPoint(TSmallPoint(lParam));
Windows.ClientToScreen (hwnd, P);
if FindDragTarget(P, True) = Self then
Repost := False;
end;
end;
if Repost then begin
RepostList.Add (AllocMem(SizeOf(TMsg)));
PMsg(RepostList.Last)^ := Msg;
end;
end;
finally
for I := 0 to RepostList.Count-1 do begin
with PMsg(RepostList[I])^ do
PostMessage (hwnd, message, wParam, lParam);
FreeMem (RepostList[I]);
end;
RepostList.Free;
end;
end;
end;
finally
FInClick := False;
if FState = bsDown then
FState := bsUp;
{ Need to check if it's destroying in case the OnClick handler freed
the button. If it doesn't check this here, it can sometimes cause an
access violation }
if not(csDestroying in ComponentState) then begin
Redraw (True);
UpdateTracking;
end;
end;
end;
function TToolbarButton97.GetPalette: HPALETTE;
begin
Result := Glyph.Palette;
end;
function TToolbarButton97.GetGlyph: TBitmap;
begin
Result := TButtonGlyph(FGlyph).Glyph;
end;
procedure TToolbarButton97.SetGlyph (Value: TBitmap);
begin
TButtonGlyph(FGlyph).Glyph := Value;
Redraw (True);
end;
function TToolbarButton97.GetGlyphMask: TBitmap;
begin
Result := TButtonGlyph(FGlyph).GlyphMask;
end;
procedure TToolbarButton97.SetGlyphMask (Value: TBitmap);
begin
TButtonGlyph(FGlyph).GlyphMask := Value;
Redraw (True);
end;
function TToolbarButton97.GetNumGlyphs: TNumGlyphs97;
begin
Result := TButtonGlyph(FGlyph).NumGlyphs;
end;
procedure TToolbarButton97.SetNumGlyphs (Value: TNumGlyphs97);
begin
if Value < Low(TNumGlyphs97) then
Value := Low(TNumGlyphs97)
else
if Value > High(TNumGlyphs97) then
Value := High(TNumGlyphs97);
if Value <> TButtonGlyph(FGlyph).NumGlyphs then begin
TButtonGlyph(FGlyph).NumGlyphs := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.GlyphChanged(Sender: TObject);
begin
Redraw (True);
end;
procedure TToolbarButton97.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 TToolbarButton97.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
FState := bsExclusive
else
FState := bsUp;
Redraw (True);
if Value then UpdateExclusive;
end;
end;
procedure TToolbarButton97.SetFlat (Value: Boolean);
begin
if FFlat <> Value then begin
FFlat := Value;
if FOpaque or not FFlat then
ControlStyle := ControlStyle + [csOpaque]
else
ControlStyle := ControlStyle - [csOpaque];
Redraw (True);
end;
end;
procedure TToolbarButton97.SetGroupIndex (Value: Integer);
begin
if FGroupIndex <> Value then begin
FGroupIndex := Value;
UpdateExclusive;
end;
end;
procedure TToolbarButton97.SetLayout (Value: TButtonLayout);
begin
if FLayout <> Value then begin
FLayout := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetMargin (Value: Integer);
begin
if (FMargin <> Value) and (Value >= -1) then begin
FMargin := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetNoBorder (Value: Boolean);
begin
if FNoBorder <> Value then begin
FNoBorder := Value;
Invalidate;
end;
end;
procedure TToolbarButton97.SetOldDisabledStyle (Value: Boolean);
begin
if FOldDisabledStyle <> Value then begin
FOldDisabledStyle := Value;
with TButtonGlyph(FGlyph) do begin
FOldDisabledStyle := Value;
Invalidate;
end;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetOpaque (Value: Boolean);
begin
if FOpaque <> Value then begin
FOpaque := Value;
if FOpaque or not FFlat then
ControlStyle := ControlStyle + [csOpaque]
else
ControlStyle := ControlStyle - [csOpaque];
Invalidate;
end;
end;
procedure TToolbarButton97.Redraw (const Erase: Boolean);
var
AddedOpaque: Boolean;
begin
if FOpaque or not FFlat or not Erase then begin
{ Temporarily add csOpaque to the style. This prevents Invalidate from
erasing, which isn't needed when Erase is false. }
AddedOpaque := False;
if not(csOpaque in ControlStyle) then begin
AddedOpaque := True;
ControlStyle := ControlStyle + [csOpaque];
end;
try
Invalidate;
finally
if AddedOpaque then
ControlStyle := ControlStyle - [csOpaque];
end;
end
else
if not(FOpaque or not FFlat) then
Invalidate;
end;
procedure TToolbarButton97.SetSpacing (Value: Integer);
begin
if Value <> FSpacing then begin
FSpacing := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetAllowAllUp (Value: Boolean);
begin
if FAllowAllUp <> Value then begin
FAllowAllUp := Value;
UpdateExclusive;
end;
end;
procedure TToolbarButton97.SetDropdownMenu (Value: TPopupMenu);
begin
if FDropdownMenu <> Value then begin
FDropdownMenu := Value;
FUsesDropdown := Assigned(Value);
if Assigned(Value) then begin
Value.FreeNotification (Self);
DropdownList.AddMenu (Value);
end;
if FDropdownArrow then
Redraw (True);
end;
end;
procedure TToolbarButton97.SetWordWrap (Value: Boolean);
begin
if FWordWrap <> Value then begin
FWordWrap := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetDropdownArrow (Value: Boolean);
begin
if FDropdownArrow <> Value then begin
FDropdownArrow := Value;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetDropdownCombo (Value: Boolean);
var
W: Integer;
begin
if FDropdownCombo <> Value then begin
FDropdownCombo := Value;
if not(csLoading in ComponentState) then begin
if Value then
Width := Width + DropdownComboWidth
else begin
W := Width - DropdownComboWidth;
if W < 1 then W := 1;
Width := W;
end;
end;
Redraw (True);
end;
end;
procedure TToolbarButton97.SetDisplayMode (Value: TButtonDisplayMode);
begin
if FDisplayMode <> Value then begin
FDisplayMode := Value;
Redraw (True);
end;
end;
function TToolbarButton97.GetCallDormant: Boolean;
begin
Result := TButtonGlyph(FGlyph).FCallDormant;
end;
procedure TToolbarButton97.SetCallDormant (Value: Boolean);
begin
TButtonGlyph(FGlyph).FCallDormant := Value;
end;
procedure TToolbarButton97.WMLButtonDblClk (var Message: TWMLButtonDblClk);
begin
inherited;
if FDown then DblClick;
end;
procedure TToolbarButton97.CMEnabledChanged (var Message: TMessage);
const
NewState: array[Boolean] of TButtonState97 = (bsDisabled, bsUp);
begin
TButtonGlyph(FGlyph).CreateButtonGlyph (NewState[Enabled]);
UpdateTracking;
Redraw (True);
end;
procedure TToolbarButton97.CMButtonPressed (var Message: TMessage);
var
Sender: TToolbarButton97;
begin
{ UpdateExclusive broadcasts these messages }
if Message.WParam = FGroupIndex then begin
Sender := TToolbarButton97(Message.LParam);
if Sender <> Self then begin
if Sender.Down and FDown then begin
FDown := False;
FState := bsUp;
Redraw (True);
end;
FAllowAllUp := Sender.AllowAllUp;
end;
end;
end;
procedure TToolbarButton97.CMDialogChar (var Message: TCMDialogChar);
begin
with Message do
if IsAccel(CharCode, Caption) and Enabled and Visible then begin
{ NOTE: There is a bug in TSpeedButton where accelerator keys are still
processed even when the button is not visible. The 'and Visible'
corrects it, so TToolbarButton97 doesn't have this problem. }
Click;
Result := 1;
end
else
inherited;
end;
procedure TToolbarButton97.CMFontChanged (var Message: TMessage);
begin
Redraw (True);
end;
procedure TToolbarButton97.CMTextChanged (var Message: TMessage);
begin
Redraw (True);
end;
procedure TToolbarButton97.CMSysColorChange (var Message: TMessage);
begin
inherited;
if Assigned(Pattern) and
((PatternBtnFace <> TColor(GetSysColor(COLOR_BTNFACE))) or
(PatternBtnHighlight <> TColor(GetSysColor(COLOR_BTNHIGHLIGHT)))) then begin
Pattern.Free;
Pattern := nil;
end;
with TButtonGlyph(FGlyph) do begin
Invalidate;
CreateButtonGlyph (FState);
end;
end;
procedure TToolbarButton97.MouseEntered;
begin
if Enabled and not FMouseInControl then begin
FMouseInControl := True;
if FState = bsUp then
FState := bsMouseIn;
Redraw (FDown);
if Assigned(FOnMouseEnter) then
FOnMouseEnter (Self);
end;
end;
procedure TToolbarButton97.MouseLeft;
begin
if Enabled and FMouseInControl and not FMouseIsDown then begin
if (FState = bsMouseIn) or (not FInClick and (FState = bsDown)) then
FState := bsUp;
FMouseInControl := False;
Redraw (True);
if ButtonMouseInControl = Self then begin
ButtonMouseTimer.Enabled := False;
ButtonMouseInControl := nil;
end;
if Assigned(FOnMouseExit) then
FOnMouseExit (Self);
end;
end;
procedure TToolbarButton97.CMMouseLeave (var Message: TMessage);
begin
inherited;
MouseLeft;
end;
procedure TToolbarButton97.ButtonMouseTimerHandler (Sender: TObject);
var
P: TPoint;
begin
{ The button mouse timer is used to periodically check if mouse has left.
Normally it receives a CM_MOUSELEAVE, but the VCL does not send a
CM_MOUSELEAVE if the mouse is moved quickly from the button to another
application's window. For some reason, this problem doesn't seem to occur
on Windows NT 4 -- only 95 and 3.x.
The timer (which ticks 8 times a second) is only enabled when the
application is active and the mouse is over a button, so it uses virtually
no processing power.
For something interesting to try: If you want to know just how often this
is called, try putting a Beep call in here }
GetCursorPos (P);
if FindDragTarget(P, True) <> Self then
MouseLeft;
end;
class function TToolbarButton97.DeactivateHook (var Message: TMessage): Boolean;
begin
Result := False;
{ Hide any active border when application is deactivated }
if (Message.Msg = CM_DEACTIVATE) and Assigned(ButtonMouseInControl) and
not ButtonMouseInControl.FShowBorderWhenInactive then
ButtonMouseInControl.MouseLeft;
end;
{ TEdit97 - internal }
constructor TEdit97.Create (AOwner: TComponent);
begin
inherited;
AutoSize := False;
Ctl3D := False;
BorderStyle := bsNone;
ControlStyle := ControlStyle - [csFramed]; {fixes a VCL bug with Win 3.x}
Height := 19;
end;
procedure TEdit97.CMMouseEnter (var Message: TMessage);
begin
inherited;
MouseInControl := True;
RedrawBorder (0);
end;
procedure TEdit97.CMMouseLeave (var Message: TMessage);
begin
inherited;
MouseInControl := False;
RedrawBorder (0);
end;
procedure TEdit97.NewAdjustHeight;
var
DC: HDC;
SaveFont: HFONT;
Metrics: TTextMetric;
begin
DC := GetDC(0);
SaveFont := SelectObject(DC, Font.Handle);
GetTextMetrics (DC, Metrics);
SelectObject (DC, SaveFont);
ReleaseDC (0, DC);
Height := Metrics.tmHeight + 6;
end;
procedure TEdit97.Loaded;
begin
inherited;
if not(csDesigning in ComponentState) then
NewAdjustHeight;
end;
procedure TEdit97.CMEnabledChanged (var Message: TMessage);
const
EnableColors: array[Boolean] of TColor = (clBtnFace, clWindow);
begin
inherited;
Color := EnableColors[Enabled];
end;
procedure TEdit97.CMFontChanged (var Message: TMessage);
begin
inherited;
if not((csDesigning in ComponentState) and (csLoading in ComponentState)) then
NewAdjustHeight;
end;
procedure TEdit97.WMSetFocus (var Message: TWMSetFocus);
begin
inherited;
if not(csDesigning in ComponentState) then
RedrawBorder (0);
end;
procedure TEdit97.WMKillFocus (var Message: TWMKillFocus);
begin
inherited;
if not(csDesigning in ComponentState) then
RedrawBorder (0);
end;
procedure TEdit97.WMNCCalcSize (var Message: TWMNCCalcSize);
begin
inherited;
InflateRect (Message.CalcSize_Params^.rgrc[0], -3, -3);
end;
procedure TEdit97.WMNCPaint (var Message: TMessage);
begin
inherited;
RedrawBorder (Message.WParam);
end;
procedure TEdit97.RedrawBorder (const Clip: HRGN);
var
DC: HDC;
R: TRect;
NewClipRgn: HRGN;
BtnFaceBrush, WindowBrush: HBRUSH;
begin
DC := GetWindowDC(Handle);
try
{ Use update region }
if (Clip <> 0) and (Clip <> 1) then begin
GetWindowRect (Handle, R);
if SelectClipRgn(DC, Clip) = ERROR then begin
NewClipRgn := CreateRectRgnIndirect(R);
SelectClipRgn (DC, NewClipRgn);
DeleteObject (NewClipRgn);
end;
OffsetClipRgn (DC, -R.Left, -R.Top);
end;
{ This works around WM_NCPAINT problem described at top of source code }
{no! R := Rect(0, 0, Width, Height);}
GetWindowRect (Handle, R); OffsetRect (R, -R.Left, -R.Top);
BtnFaceBrush := GetSysColorBrush(COLOR_BTNFACE);
WindowBrush := GetSysColorBrush(COLOR_WINDOW);
if ((csDesigning in ComponentState) and Enabled) or
(not(csDesigning in ComponentState) and
(Focused or (MouseInControl and not(Screen.ActiveControl is TEdit97)))) then begin
DrawEdge (DC, R, BDR_SUNKENOUTER, BF_RECT or BF_ADJUST);
with R do begin
FillRect (DC, Rect(Left, Top, Left+1, Bottom-1), BtnFaceBrush);
FillRect (DC, Rect(Left, Top, Right-1, Top+1), BtnFaceBrush);
end;
DrawEdge (DC, R, BDR_SUNKENINNER, BF_BOTTOMRIGHT);
InflateRect (R, -1, -1);
FrameRect (DC, R, WindowBrush);
end
else begin
FrameRect (DC, R, BtnFaceBrush);
InflateRect (R, -1, -1);
FrameRect (DC, R, BtnFaceBrush);
InflateRect (R, -1, -1);
FrameRect (DC, R, WindowBrush);
end;
finally
ReleaseDC (Handle, DC);
end;
end;
const
Sig: PChar = '- Toolbar97 version ' + Toolbar97Version +
{$IFDEF VER90} '/D2'+ {$ENDIF} {$IFDEF VER93} '/CB1'+ {$ENDIF}
{$IFDEF VER100} '/D3'+ {$ENDIF} {$IFDEF VER110} '/CB3'+ {$ENDIF}
' by Jordan Russell -';
initialization
Sig := Sig;
HookedForms := TList.Create;
MainHookedForms := TList.Create;
DoneCreatingList := TList.Create;
DropdownList := TDropdownList.Create(nil);
ButtonMouseTimer := TTimer.Create(nil);
ButtonMouseTimer.Enabled := False;
ButtonMouseTimer.Interval := 125; { 8 times a second }
finalization
ButtonMouseTimer.Free;
DropdownList.Free;
DoneCreatingList.Free;
MainHookedForms.Free;
HookedForms.Free;
end.