unit TB2Toolbar; {$MODE Delphi} { Toolbar2000 Copyright (C) 1998-2008 by Jordan Russell All rights reserved. The contents of this file are subject to the "Toolbar2000 License"; you may not use or distribute this file except in compliance with the "Toolbar2000 License". A copy of the "Toolbar2000 License" may be found in TB2k-LICENSE.txt or at: http://www.jrsoftware.org/files/tb2k/TB2k-LICENSE.txt Alternatively, the contents of this file may be used under the terms of the GNU General Public License (the "GPL"), in which case the provisions of the GPL are applicable instead of those in the "Toolbar2000 License". A copy of the GPL may be found in GPL-LICENSE.txt or at: http://www.jrsoftware.org/files/tb2k/GPL-LICENSE.txt If you wish to allow use of your version of this file only under the terms of the GPL and not to allow others to use your version of this file under the "Toolbar2000 License", indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under either the "Toolbar2000 License" or the GPL. $jrsoftware: tb2k/Source/TB2Toolbar.pas,v 1.126 2008/06/23 18:05:47 jr Exp $ } interface {$I TB2Ver.inc} uses {$IFDEF JR_D9} Types, {$ENDIF} LCLIntf, LCLType, LMessages, Messages, SysUtils, Classes, Graphics, Controls, Forms, ImgList, Menus, ActnList, TB2Item, TB2Dock; type TTBCustomToolbar = class; TTBChevronItem = class; TTBChevronItemClass = class of TTBChevronItem; TTBToolbarViewClass = class of TTBToolbarView; TTBToolbarView = class(TTBView) private FToolbar: TTBCustomToolbar; protected procedure AutoSize(AWidth, AHeight: Integer); override; procedure DoUpdatePositions(var ASize: TPoint); override; function GetChevronItem: TTBCustomItem; override; function GetMDIButtonsItem: TTBCustomItem; override; function GetMDISystemMenuItem: TTBCustomItem; override; public constructor Create(AOwner: TComponent; AParentView: TTBView; AParentItem: TTBCustomItem; AWindow: TWinControl; AIsToolbar, ACustomizing, AUsePriorityList: Boolean); override; function GetFont: TFont; override; procedure InvalidatePositions; override; end; TTBChevronPriorityForNewItems = (tbcpHighest, tbcpLowest); TTBCustomToolbar = class(TTBCustomDockableWindow, ITBItems) private FBaseSize: TPoint; FChevronItem: TTBChevronItem; FChevronMoveItems: Boolean; FChevronPriorityForNewItems: TTBChevronPriorityForNewItems; FDisableAlignArrange: Integer; FFloatingWidth: Integer; FIgnoreMouseLeave: Boolean; FItem: TTBRootItem; FLastWrappedLines: Integer; FMenuBar: Boolean; FOnShortCut: TShortCutEvent; FProcessShortCuts: Boolean; FMainWindowHookInstalled: Boolean; FShrinkMode: TTBShrinkMode; FSizeData: TObject; FSystemFont: Boolean; FUpdateActions: Boolean; procedure CancelHover; function CalcChevronOffset(const ADock: TTBDock; const AOrientation: TTBViewOrientation): Integer; function CalcWrapOffset(const ADock: TTBDock): Integer; function CreateWrapper(Index: Integer; Ctl: TControl): TTBControlItem; function FindWrapper(Ctl: TControl): TTBControlItem; function GetChevronHint: String; function GetImages: TCustomImageList; function GetItems: TTBCustomItem; function GetLinkSubitems: TTBCustomItem; function GetOptions: TTBItemOptions; procedure InstallMainWindowHook; function IsChevronHintStored: Boolean; class function MainWindowHook(var Message: TMessage): Boolean; {$IFDEF CLR} static; {$ENDIF} procedure SetChevronHint(const Value: String); procedure SetChevronMoveItems(Value: Boolean); procedure SetChevronPriorityForNewItems(Value: TTBChevronPriorityForNewItems); procedure SetFloatingWidth(Value: Integer); procedure SetImages(Value: TCustomImageList); procedure SetLinkSubitems(Value: TTBCustomItem); procedure SetMainWindowHook; procedure SetMenuBar(Value: Boolean); procedure SetOptions(Value: TTBItemOptions); procedure SetProcessShortCuts(Value: Boolean); procedure SetShrinkMode(Value: TTBShrinkMode); procedure SetSystemFont(Value: Boolean); procedure UninstallMainWindowHook; procedure UpdateViewProperties; procedure CMColorChanged(var Message: TMessage); message CM_COLORCHANGED; {$IFNDEF CLR} procedure CMControlChange(var Message: TCMControlChange); message CM_CONTROLCHANGE; procedure CMControlListChange(var Message: TCMControlListChange); message CM_CONTROLLISTCHANGE; {$ENDIF} procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED; procedure CMDialogChar(var Message: TCMDialogChar); message CM_DIALOGCHAR; procedure CMDialogKey(var Message: TCMDialogKey); message CM_DIALOGKEY; procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW; procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE; procedure CMShowHintChanged(var Message: TMessage); message CM_SHOWHINTCHANGED; procedure CMWinIniChange(var Message: TWMWinIniChange); message CM_WININICHANGE; procedure WMCancelMode(var Message: TWMCancelMode); message WM_CANCELMODE; procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT; procedure WMMouseLeave(var Message: TMessage); message WM_MOUSELEAVE; procedure WMNCMouseMove(var Message: TWMNCMouseMove); message WM_NCMOUSEMOVE; {$IFNDEF JR_D5} procedure WMRButtonUp(var Message: TWMRButtonUp); message WM_RBUTTONUP; {$ENDIF} procedure WMSetCursor(var Message: TWMSetCursor); message WM_SETCURSOR; procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND; protected FMDIButtonsItem: TTBCustomItem; FMDISystemMenuItem: TTBCustomItem; FView: TTBToolbarView; procedure AlignControls(AControl: TControl; var Rect: TRect); override; procedure BuildPotentialSizesList(SizesList: TList); dynamic; {$IFDEF CLR} procedure ControlChange(Inserting: Boolean; Child: TControl); override; procedure ControlListChange(Inserting: Boolean; Child: TControl); override; {$ENDIF} procedure ControlExistsAtPos(const P: TPoint; var ControlExists: Boolean); override; function DoArrange(CanMoveControls: Boolean; PreviousDockType: TTBDockType; NewFloating: Boolean; NewDock: TTBDock): TPoint; override; {$IFDEF JR_D5} procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override; {$ENDIF} procedure GetBaseSize(var ASize: TPoint); override; procedure GetMinBarSize(var MinimumSize: TPoint); procedure GetMinShrinkSize(var AMinimumSize: Integer); override; function GetShrinkMode: TTBShrinkMode; override; function GetChevronItemClass: TTBChevronItemClass; dynamic; function GetItemClass: TTBRootItemClass; dynamic; function GetViewClass: TTBToolbarViewClass; dynamic; procedure Loaded; override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure Paint; override; procedure ResizeBegin(ASizeHandle: TTBSizeHandle); override; procedure ResizeTrack(var Rect: TRect; const OrigRect: TRect); override; procedure ResizeTrackAccept; override; procedure ResizeEnd; override; procedure SetChildOrder(Child: TComponent; Order: Integer); override; property SystemFont: Boolean read FSystemFont write SetSystemFont default True; property OnShortCut: TShortCutEvent read FOnShortCut write FOnShortCut; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure BeginUpdate; procedure EndUpdate; procedure CreateWrappersForAllControls; procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; procedure GetTabOrderList(List: TList); override; procedure InitiateAction; override; function IsShortCut(var Message: TWMKey): Boolean; function KeyboardOpen(Key: Char; RequirePrimaryAccel: Boolean): Boolean; procedure ReadPositionData(const Data: TTBReadPositionData); override; procedure WritePositionData(const Data: TTBWritePositionData); override; property ChevronHint: String read GetChevronHint write SetChevronHint stored IsChevronHintStored; property ChevronMoveItems: Boolean read FChevronMoveItems write SetChevronMoveItems default True; property ChevronPriorityForNewItems: TTBChevronPriorityForNewItems read FChevronPriorityForNewItems write SetChevronPriorityForNewItems default tbcpHighest; property FloatingWidth: Integer read FFloatingWidth write SetFloatingWidth default 0; property Images: TCustomImageList read GetImages write SetImages; property Items: TTBRootItem read FItem; property LinkSubitems: TTBCustomItem read GetLinkSubitems write SetLinkSubitems; property Options: TTBItemOptions read GetOptions write SetOptions default []; property MenuBar: Boolean read FMenuBar write SetMenuBar default False; property ProcessShortCuts: Boolean read FProcessShortCuts write SetProcessShortCuts default False; property ShrinkMode: TTBShrinkMode read FShrinkMode write SetShrinkMode default tbsmChevron; property UpdateActions: Boolean read FUpdateActions write FUpdateActions default True; property View: TTBToolbarView read FView; published property Hint stored False; { Hint is set dynamically; don't save it } end; TTBToolbar = class(TTBCustomToolbar) published property ActivateParent; property Align; property Anchors; property AutoResize; property BorderStyle; property Caption; property ChevronHint; property ChevronMoveItems; property ChevronPriorityForNewItems; property CloseButton; property CloseButtonWhenDocked; property Color; property CurrentDock; property DefaultDock; property DockableTo; property DockMode; property DockPos; property DockRow; property DragHandleStyle; property FloatingMode; property FloatingWidth; property Font; property FullSize; property HideWhenInactive; property Images; property Items; property LastDock; property LinkSubitems; property MenuBar; property Options; property ParentFont; property ParentShowHint; property PopupMenu; property ProcessShortCuts; property Resizable; property ShowCaption; property ShowHint; property ShrinkMode; property SmoothDrag; property Stretch; property SystemFont; property TabOrder; property UpdateActions; property UseLastDock; property Visible; property OnClose; property OnCloseQuery; {$IFDEF JR_D5} property OnContextPopup; {$ENDIF} property OnDragDrop; property OnDragOver; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnMove; property OnRecreated; property OnRecreating; property OnDockChanged; property OnDockChanging; property OnDockChangingHidden; property OnResize; property OnShortCut; property OnVisibleChanged; end; { TTBChevronItem & TTBChevronItemViewer } TTBChevronItem = class(TTBCustomItem) private FToolbar: TTBCustomToolbar; function GetDefaultHint: String; protected function GetChevronParentView: TTBView; override; function GetItemViewerClass(AView: TTBView): TTBItemViewerClass; override; public constructor Create(AOwner: TComponent); override; end; TTBChevronItemViewer = class(TTBItemViewer) protected procedure Paint(const Canvas: TCanvas; const ClientAreaRect: TRect; IsSelected, IsPushed, UseDisabledShadow: Boolean); override; end; const tbChevronSize = 12; implementation uses {$IFDEF CLR} System.Runtime.InteropServices, System.Text, {$ENDIF} TB2Consts, TB2Common, TB2Hook; const { Constants for TTBCustomToolbar-specific registry values. Do not localize! } rvFloatRightX = 'FloatRightX'; DockTypeToOrientation: array[TTBDockType] of TTBViewOrientation = (tbvoHorizontal, tbvoFloating, tbvoHorizontal, tbvoVertical); type { Used internally by the TTBCustomToolbar.Resize* methods } TToolbarSizeData = class public SizeHandle: TTBSizeHandle; NewSizes: TList; { List of valid new sizes. Items are casted into TSmallPoints } OrigWidth, OrigHeight, NCXDiff: Integer; CurRightX: Integer; DisableSensCheck, OpSide: Boolean; DistanceToSmallerSize, DistanceToLargerSize: Integer; end; procedure HookProc(Code: THookProcCode; Wnd: HWND; WParam: WPARAM; LParam: LPARAM); var Msg: {$IFNDEF CLR} PMsg {$ELSE} TMsg {$ENDIF}; MainForm: TForm; begin { Work around an annoying Windows or VCL bug. If you close all MDI child windows, the MDI client window gets the focus, and when it has the focus, pressing Alt+[char] doesn't send a WM_SYSCOMMAND message to the form for some reason. It seems silly to have to use a hook for this, but I don't see a better workaround. Also, route Alt+- to the main form so that when an MDI child form is maximized, Alt+- brings up the TB2k MDI system menu instead of the system's. } if Code = hpGetMessage then begin {$IFNDEF CLR} Msg := PMsg(LParam); {$ELSE} Msg := TMsg(Marshal.PtrToStructure(IntPtr(LParam), TypeOf(TMsg))); {$ENDIF} if (Msg.message = WM_SYSCHAR) and (Msg.hwnd <> 0) then begin { Note: On Windows NT/2000/XP, even though we install the hook using SetWindowsHookExW, Msg.wParam may either be an ANSI character or a Unicode character, due to an apparent bug on these platforms. It is an ANSI character when the message passes through a separate SetWindowsHookExA-installed WH_GETMESSAGE hook first, and that hook calls us via CallNextHookEx. Windows apparently "forgets" to convert the character from ANSI back to Unicode in this case. We can't convert the character code because there seems to be no way to detect whether it is ANSI or Unicode. So we can't really do much with Msg.wParam, apart from comparing it against character codes that are the same between ANSI and Unicode, such as '-'. } MainForm := Application.MainForm; if Assigned(MainForm) and MainForm.HandleAllocated and (GetCapture = 0) and ((Msg.hwnd = MainForm.ClientHandle) or ((Msg.wParam = Ord('-')) and (MainForm.ClientHandle <> 0) and IsChild(MainForm.ClientHandle, Msg.hwnd))) then begin { Redirect the message to the main form. Note: Unfortunately, due to a bug in Windows NT 4.0 (and not 2000/XP/9x/Me), modifications to the message don't take effect if another WH_GETMESSAGE hook has been installed above this one. (The bug is that CallNextHookEx copies lParam^ to a local buffer, but does not propogate the changes made by the hook back to lParam^ when it returns.) I don't know of any clean workaround, other than to ensure other WH_GETMESSAGE hooks are installed *before* Toolbar2000's. } Msg.hwnd := MainForm.Handle; {$IFDEF CLR} Marshal.StructureToPtr(TObject(Msg), IntPtr(LParam), False); {$ENDIF} end; end; end; end; constructor TTBChevronItem.Create(AOwner: TComponent); begin inherited; FToolbar := AOwner as TTBCustomToolbar; ItemStyle := ItemStyle + [tbisSubMenu, tbisNoAutoOpen]; Hint := GetDefaultHint; Caption := EscapeAmpersands(GetShortHint(Hint)); end; function TTBChevronItem.GetChevronParentView: TTBView; begin Result := FToolbar.FView; end; function TTBChevronItem.GetDefaultHint: String; begin Result := STBChevronItemMoreButtonsHint; end; function TTBChevronItem.GetItemViewerClass(AView: TTBView): TTBItemViewerClass; begin Result := TTBChevronItemViewer; end; procedure TTBChevronItemViewer.Paint(const Canvas: TCanvas; const ClientAreaRect: TRect; IsSelected, IsPushed, UseDisabledShadow: Boolean); const HorzPattern: array[0..15] of Byte = ($CC, 0, $66, 0, $33, 0, $66, 0, $CC, 0, 0, 0, 0, 0, 0, 0); VertPattern: array[0..15] of Byte = ($88, 0, $D8, 0, $70, 0, $20, 0, $88, 0, $D8, 0, $70, 0, $20, 0); var DC: HDC; R2: TRect; TempBmp: TBitmap; procedure DrawPattern(const Color, Offset: Integer); begin SelectObject(DC, GetSysColorBrush(Color)); BitBlt(DC, R2.Left, R2.Top + Offset, R2.Right - R2.Left, R2.Bottom - R2.Top, TempBmp.Canvas.Handle, 0, 0, $00E20746 {ROP_DSPDxax}); end; begin DC := Canvas.Handle; R2 := ClientAreaRect; if Item.Enabled then begin if IsPushed then DrawEdge(DC, R2, BDR_SUNKENOUTER, BF_RECT) else if IsSelected and not(csDesigning in Item.ComponentState) then DrawEdge(DC, R2, BDR_RAISEDINNER, BF_RECT); end; if View.Orientation <> tbvoVertical then begin R2.Top := 4; R2.Bottom := R2.Top + 5; Inc(R2.Left, 2); R2.Right := R2.Left + 8; end else begin R2.Left := R2.Right - 9; R2.Right := R2.Left + 5; Inc(R2.Top, 2); R2.Bottom := R2.Top + 8; end; if IsPushed then OffsetRect(R2, 1, 1); TempBmp := TBitmap.Create; try if View.Orientation = tbvoVertical then TempBmp.Handle := CreateMonoBitmap(8, 8, VertPattern) else TempBmp.Handle := CreateMonoBitmap(8, 8, HorzPattern); SetTextColor(DC, clBlack); SetBkColor(DC, clWhite); if Item.Enabled then DrawPattern(COLOR_BTNTEXT, 0) else begin DrawPattern(COLOR_BTNHIGHLIGHT, 1); DrawPattern(COLOR_BTNSHADOW, 0); end; finally TempBmp.Free; end; end; { TTBToolbarView } constructor TTBToolbarView.Create(AOwner: TComponent; AParentView: TTBView; AParentItem: TTBCustomItem; AWindow: TWinControl; AIsToolbar, ACustomizing, AUsePriorityList: Boolean); begin FToolbar := AOwner as TTBCustomToolbar; inherited; end; procedure TTBToolbarView.AutoSize(AWidth, AHeight: Integer); begin FToolbar.FBaseSize := BaseSize; if FToolbar.IsAutoResized then FToolbar.ChangeSize(AWidth, AHeight); end; procedure TTBToolbarView.DoUpdatePositions(var ASize: TPoint); begin { Reset CurrentSize because it probably won't be valid after positions are recalculated [2001-06-24] } FToolbar.CurrentSize := 0; FToolbar.GetMinBarSize(ASize); { On FullSize toolbars, increase ASize.X/Y to WrapOffset so that right-aligned items always appear at the right edge even when the toolbar isn't wrapping } if FToolbar.FullSize then begin if (Orientation = tbvoHorizontal) and (ASize.X < WrapOffset) then ASize.X := WrapOffset else if (Orientation = tbvoVertical) and (ASize.Y < WrapOffset) then ASize.Y := WrapOffset; end; { Increment FDisableAlignArrange so that we don't recursively arrange when CalculatePositions moves controls } Inc(FToolbar.FDisableAlignArrange); try inherited; finally Dec(FToolbar.FDisableAlignArrange); end; end; procedure TTBToolbarView.InvalidatePositions; begin { Reset CurrentSize because it probably won't be valid after positions are recalculated [2001-06-04] } FToolbar.CurrentSize := 0; inherited; end; function TTBToolbarView.GetFont: TFont; begin if not FToolbar.SystemFont then Result := FToolbar.Font else Result := inherited GetFont; end; function TTBToolbarView.GetChevronItem: TTBCustomItem; begin Result := FToolbar.FChevronItem; end; function TTBToolbarView.GetMDIButtonsItem: TTBCustomItem; begin Result := FToolbar.FMDIButtonsItem; end; function TTBToolbarView.GetMDISystemMenuItem: TTBCustomItem; begin Result := FToolbar.FMDISystemMenuItem; end; { TTBCustomToolbar } type {}TTBCustomItemAccess = class(TTBCustomItem); TTBItemViewerAccess = class(TTBItemViewer); constructor TTBCustomToolbar.Create(AOwner: TComponent); begin inherited; ControlStyle := ControlStyle + [csAcceptsControls, csActionClient] - [csCaptureMouse]; DockableWindowStyles := DockableWindowStyles - [tbdsResizeEightCorner, tbdsResizeClipCursor]; FItem := GetItemClass.Create(Self); FItem.ParentComponent := Self; FChevronItem := GetChevronItemClass.Create(Self); FChevronItem.LinkSubitems := FItem; FChevronMoveItems := True; FView := GetViewClass.Create(Self, nil, FItem, Self, True, False, not(csDesigning in ComponentState)); FView.BackgroundColor := clBtnFace; FUpdateActions := True; FShrinkMode := tbsmChevron; FSystemFont := True; Color := clBtnFace; SetBounds(Left, Top, 23, 22);{} end; destructor TTBCustomToolbar.Destroy; begin { Call Destroying to ensure csDestroying is in ComponentState now. Only needed for Delphi 4 and earlier since Delphi 5 calls Destroying in TComponent.BeforeDestruction } Destroying; UninstallHookProc(Self, HookProc); UninstallMainWindowHook; FreeAndNil(FView); FreeAndNil(FChevronItem); FreeAndNil(FItem); inherited; end; function TTBCustomToolbar.GetItems: TTBCustomItem; begin Result := FItem; end; function TTBCustomToolbar.GetItemClass: TTBRootItemClass; begin Result := TTBRootItem; end; function TTBCustomToolbar.GetViewClass: TTBToolbarViewClass; begin Result := TTBToolbarView; end; function TTBCustomToolbar.GetChevronItemClass: TTBChevronItemClass; begin Result := TTBChevronItem; end; procedure TTBCustomToolbar.CreateWrappersForAllControls; { Create wrappers for any controls that don't already have them } var L: TList; I, J, C: Integer; begin if ControlCount = 0 then Exit; L := TList.Create; try L.Capacity := ControlCount; for I := 0 to ControlCount-1 do L.Add(Controls[I]); C := FItem.Count-1; for I := 0 to C do if FItem[I] is TTBControlItem then begin J := L.IndexOf(TTBControlItem(FItem[I]).Control); if J <> -1 then L[J] := nil; end; for I := 0 to L.Count-1 do if Assigned(L[I]) then CreateWrapper(FItem.Count, TControl(L[I])); finally L.Free; end; end; procedure TTBCustomToolbar.Loaded; begin CreateWrappersForAllControls; inherited; end; procedure TTBCustomToolbar.GetChildren(Proc: TGetChildProc; Root: TComponent); begin TTBCustomItemAccess(TTBCustomItem(FItem)).GetChildren(Proc, Root); inherited; end; procedure TTBCustomToolbar.SetChildOrder(Child: TComponent; Order: Integer); begin if Child is TTBCustomItem then TTBCustomItemAccess(TTBCustomItem(FItem)).SetChildOrder(Child, Order); end; procedure TTBCustomToolbar.AlignControls(AControl: TControl; var Rect: TRect); { VCL calls this whenever any child controls in the toolbar are moved, sized, inserted, etc., and also when the toolbar is resized. } begin if FDisableAlignArrange = 0 then Arrange; end; procedure TTBCustomToolbar.InitiateAction; begin inherited; {}{ also add this to popupmenu(?) } { Update visible top-level items } if FUpdateActions then FView.InitiateActions; end; procedure TTBCustomToolbar.CMColorChanged(var Message: TMessage); begin { Synchronize FView.BackgroundColor with the new color } if Assigned(FView) then FView.BackgroundColor := Color; inherited; end; function TTBCustomToolbar.CreateWrapper(Index: Integer; Ctl: TControl): TTBControlItem; var I: Integer; S: String; begin Result := TTBControlItem.Create(Owner); Result.Control := Ctl; if (csDesigning in ComponentState) and Assigned(Owner) then begin { Needs a name for compatibility with form inheritance } I := 1; while True do begin S := Format('TBControlItem%d', [I]); if Owner.FindComponent(S) = nil then Break; Inc(I); end; Result.Name := S; end; FItem.Insert(Index, Result); end; function TTBCustomToolbar.FindWrapper(Ctl: TControl): TTBControlItem; var I: Integer; Item: TTBCustomItem; begin Result := nil; for I := 0 to FItem.Count-1 do begin Item := FItem[I]; if (Item is TTBControlItem) and (TTBControlItem(Item).Control = Ctl) then begin Result := TTBControlItem(Item); Break; end; end; end; {$IFNDEF CLR} procedure TTBCustomToolbar.CMControlChange(var Message: TCMControlChange); {$ELSE} procedure TTBCustomToolbar.ControlChange(Inserting: Boolean; Child: TControl); {$ENDIF} { A CM_CONTROLCHANGE handler must be used instead of a CM_CONTROLLISTCHANGE handler because when a CM_CONTROLLISTCHANGE message is sent it is relayed to *all* parents. CM_CONTROLCHANGE messages are only sent to the immediate parent. } procedure HandleControlChange(Inserting: Boolean; Child: TControl); begin { Don't automatically create TTBControlItem wrappers if the component is loading or being updated to reflect changes in an ancestor form, because wrappers will be streamed in } if Inserting and not(csLoading in ComponentState) and not(csUpdating in ComponentState) and (FindWrapper(Child) = nil) then CreateWrapper(FItem.Count, Child); end; begin inherited; {$IFNDEF CLR} HandleControlChange(Message.Inserting, Message.Control); {$ELSE} HandleControlChange(Inserting, Child); {$ENDIF} end; {$IFNDEF CLR} procedure TTBCustomToolbar.CMControlListChange(var Message: TCMControlListChange); {$ELSE} procedure TTBCustomToolbar.ControlListChange(Inserting: Boolean; Child: TControl); {$ENDIF} { Don't handle deletions inside CM_CONTROLCHANGE because CM_CONTROLCHANGE is sent *before* a control begins removing itself from its parent. (It used to handle both insertions and deletions inside CM_CONTROLCHANGE but this caused AV's.) } procedure HandleControlListChange(Inserting: Boolean; Child: TControl); var Item: TTBControlItem; begin if not Inserting and Assigned(FItem) then begin while True do begin Item := FindWrapper(Child); if Item = nil then Break; { The control is being removed the control, not necessarily destroyed, so set DontFreeControl to True } Item.DontFreeControl := True; Item.Free; end; end; end; begin inherited; {$IFNDEF CLR} HandleControlListChange(Message.Inserting, Message.Control); {$ELSE} HandleControlListChange(Inserting, Child); {$ENDIF} end; procedure TTBCustomToolbar.CMHintShow(var Message: TCMHintShow); { Since the items on a toolbar aren't "real" controls, it needs a CM_HINTSHOW handler for their hints to be displayed. } begin FView.HandleHintShowMessage(Message); end; procedure TTBCustomToolbar.CMShowHintChanged(var Message: TMessage); begin inherited; if ShowHint then FView.Style := FView.Style + [vsAlwaysShowHints] else FView.Style := FView.Style - [vsAlwaysShowHints]; end; procedure TTBCustomToolbar.WMGetObject(var Message: TMessage); begin if not FView.HandleWMGetObject(Message) then inherited; end; procedure TTBCustomToolbar.WMSetCursor(var Message: TWMSetCursor); var P: TPoint; Viewer: TTBItemViewer; Cursor: HCURSOR; begin if not(csDesigning in ComponentState) and (Message.CursorWnd = WindowHandle) and (Smallint(Message.HitTest) = HTCLIENT) then begin { Note: This should not change the selection, because we can receive this message during a modal loop if a user sets "Screen.Cursor := crDefault" inside a submenu's OnClick handler (which really isn't recommended, as it won't necessarily restore the cursor we set originally). } GetCursorPos(P); P := ScreenToClient(P); Viewer := FView.ViewerFromPoint(P); if Assigned(Viewer) then begin Cursor := 0; Dec(P.X, Viewer.BoundsRect.Left); Dec(P.Y, Viewer.BoundsRect.Top); TTBItemViewerAccess(Viewer).GetCursor(P, Cursor); if Cursor <> 0 then begin SetCursor(Cursor); Message.Result := 1; Exit; end; end; end; inherited; end; procedure TTBCustomToolbar.WMSysCommand(var Message: TWMSysCommand); var ConvertedKey: Char; begin if FMenuBar and CanFocus then with Message do if (CmdType and $FFF0 = SC_KEYMENU) and (Key <> VK_SPACE) and (GetCapture = 0) then begin {$IFNDEF CLR} {$IFDEF JR_WIDESTR} { Under Unicode Win32 VCL, no conversion required } WideChar(ConvertedKey) := WideChar(Key); {$ELSE} if Win32Platform = VER_PLATFORM_WIN32_NT then begin { On Windows NT 4/2000/XP, Key is a wide character, so we have to convert it. Pressing Alt+N in a Russian input locale, for example, results in a Key value of $0442. This could perhaps be considered a bug in Windows NT since the character codes in other messages such as WM_SYSCHAR aren't left in Unicode form. The conversion isn't done with the system code page, but rather with the code page of the currently active input locale, like Windows does when sending WM_(SYS)CHAR messages. } if WideCharToMultiByte(GetInputLocaleCodePage, 0, @WideChar(Key), 1, @AnsiChar(ConvertedKey), 1, nil, nil) <> 1 then Exit; { shouldn't fail, but if it does, we can't continue } end else begin { On Windows 95/98/Me, Key is not a wide character. } AnsiChar(ConvertedKey) := AnsiChar(Key); end; {$ENDIF} {$ELSE} if Marshal.SystemDefaultCharSize = 2 then begin { Strings are Unicode on .NET, so no need to downconvert to ANSI } ConvertedKey := WideChar(Key); end else begin { On Windows 98/Me, we have to convert ANSI->Unicode, using the code page of the currently active input locale } ConvertedKey := Encoding.GetEncoding(GetInputLocaleCodePage). GetChars([Byte(Key)])[0]; end; {$ENDIF} if not KeyboardOpen(ConvertedKey, False) then begin if Key = Ord('-') then Exit; MessageBeep(0); end; Result := 1; end; end; procedure TTBCustomToolbar.Paint; var R: TRect; begin { Draw dotted border in design mode on undocked toolbars } if not Docked and (csDesigning in ComponentState) then with Canvas do begin R := ClientRect; Pen.Style := psDot; Pen.Color := clBtnShadow; Brush.Style := bsClear; Rectangle(R.Left, R.Top, R.Right, R.Bottom); Pen.Style := psSolid; end; FView.DrawSubitems(Canvas); end; procedure TTBCustomToolbar.CMDialogKey(var Message: TCMDialogKey); begin if not(csDesigning in ComponentState) and (Message.CharCode = VK_MENU) and FMenuBar and CanFocus then FView.SetAccelsVisibility(True); inherited; end; procedure TTBCustomToolbar.CMDialogChar(var Message: TCMDialogChar); begin { On toolbars that aren't menu bars, handle CM_DIALOGCHAR instead of WM_SYSCOMMAND. Note: We have to check for csDesigning because on Delphi 2005/2006 we get CM_DIALOG* messages if Alt+[key] is pressed while a form with a toolbar is open in the embedded designer, and a tab other than Design is currently selected (e.g., Code). } if not(csDesigning in ComponentState) and not FMenuBar and CanFocus and (Message.CharCode <> 0) then if KeyboardOpen(Chr(Message.CharCode), True) then begin Message.Result := 1; Exit; end; inherited; end; procedure TTBCustomToolbar.CancelHover; begin if not MouseCapture then FView.UpdateSelection(Point(Low(Integer), Low(Integer)), True); end; procedure TTBCustomToolbar.CMMouseLeave(var Message: TMessage); begin CancelHover; inherited; end; {$IFDEF JR_D5} procedure TTBCustomToolbar.DoContextPopup(MousePos: TPoint; var Handled: Boolean); begin CancelHover; inherited; end; {$ENDIF} {$IFNDEF JR_D5} { Delphi 4 and earlier don't have a DoContextPopup method; we instead have to trap WM_RBUTTONUP to determine if a popup menu (might) be displayed } procedure TTBCustomToolbar.WMRButtonUp(var Message: TWMRButtonUp); begin CancelHover; inherited; end; {$ENDIF} procedure TTBCustomToolbar.MouseMove(Shift: TShiftState; X, Y: Integer); var P: TPoint; Item: TTBCustomItem; begin if not(csDesigning in ComponentState) then begin P := ClientToScreen(Point(X, Y)); FView.UpdateSelection(P, True); if Assigned(FView.Selected) then begin Item := FView.Selected.Item; if not(tboLongHintInMenuOnly in Item.EffectiveOptions) then Hint := Item.Hint else Hint := ''; end else Hint := ''; end; { Call TrackMouseEvent to be sure that we are notified when the mouse leaves the window. We won't get a CM_MOUSELEAVE message if the mouse moves directly from the toolbar to another application's window } CallTrackMouseEvent(Handle, TME_LEAVE); inherited; end; procedure TTBCustomToolbar.WMCancelMode(var Message: TWMCancelMode); begin inherited; { We can receive a WM_CANCELMODE message during a modal loop if a dialog pops up. Respond by hiding menus to make it look like the modal loop has returned, even though it really hasn't yet. Note: Similar code in TTBModalHandler.WndProc. } if vsModal in FView.State then FView.CancelMode; end; procedure TTBCustomToolbar.WMMouseLeave(var Message: TMessage); begin { A WM_MOUSELEAVE handler is necessary because the control won't get a CM_MOUSELEAVE message if the user presses Alt+Space. Also, CM_MOUSELEAVE messages are also not sent if the application is in a Application.ProcessMessages loop. } if not FIgnoreMouseLeave then CancelHover; inherited; end; procedure TTBCustomToolbar.WMNCMouseMove(var Message: TWMNCMouseMove); begin Hint := ''; CancelHover; inherited; end; function TTBCustomToolbar.KeyboardOpen(Key: Char; RequirePrimaryAccel: Boolean): Boolean; var I: TTBItemViewer; IsOnlyItemWithAccel: Boolean; begin Result := False; { Sanity check: Bail out early if re-entered } if vsModal in FView.State then Exit; I := nil; FView.SetAccelsVisibility(True); try if Key = #0 then begin I := FView.FirstSelectable; if I = nil then Exit; FView.Selected := I; FView.EnterToolbarLoop([]); end else begin I := FView.NextSelectableWithAccel(nil, Key, RequirePrimaryAccel, IsOnlyItemWithAccel); if (I = nil) or not I.Item.Enabled then Exit; if IsOnlyItemWithAccel then begin FView.Selected := I; FView.EnterToolbarLoop([tbetExecuteSelected]); end else if FMenuBar then begin FView.Selected := I; FView.EnterToolbarLoop([]); end else Exit; end; Result := True; finally if Assigned(I) then FView.SetAccelsVisibility(False); end; end; procedure TTBCustomToolbar.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var OldParent: TWinControl; P: TPoint; Item: TTBCustomItem; begin OldParent := Parent; inherited; if Parent <> OldParent then { if the inherited handler (TTBDockableWindow.MouseDown) changed the Parent (due to the toolbar moving between docks), nothing else should be done } Exit; if not(csDesigning in ComponentState) and (Button = mbLeft) then begin P := ClientToScreen(Point(X, Y)); FView.UpdateSelection(P, True); if Assigned(FView.Selected) then begin Item := FView.Selected.Item; if not(tbisClicksTransparent in TTBCustomItemAccess(Item).ItemStyle) then begin FIgnoreMouseLeave := True; try FView.EnterToolbarLoop([tbetMouseDown]); finally FIgnoreMouseLeave := False; end; end; end; end; end; procedure TTBCustomToolbar.CMFontChanged(var Message: TMessage); begin inherited; if not FSystemFont then Arrange; end; function TTBCustomToolbar.GetChevronHint: String; begin Result := FChevronItem.Hint; end; procedure TTBCustomToolbar.SetChevronHint(const Value: String); begin FChevronItem.Hint := Value; FChevronItem.Caption := EscapeAmpersands(GetShortHint(Value)); end; procedure TTBCustomToolbar.SetChevronMoveItems(Value: Boolean); begin if FChevronMoveItems <> Value then begin FChevronMoveItems := Value; FView.UsePriorityList := Value and not(csDesigning in ComponentState); end; end; procedure TTBCustomToolbar.SetChevronPriorityForNewItems(Value: TTBChevronPriorityForNewItems); begin FChevronPriorityForNewItems := Value; FView.NewViewersGetHighestPriority := (Value = tbcpHighest); end; function TTBCustomToolbar.IsChevronHintStored: Boolean; begin Result := (FChevronItem.Hint <> FChevronItem.GetDefaultHint); end; function TTBCustomToolbar.GetImages: TCustomImageList; begin Result := FItem.SubMenuImages; end; procedure TTBCustomToolbar.SetImages(Value: TCustomImageList); begin FItem.SubMenuImages := Value; end; function TTBCustomToolbar.GetLinkSubitems: TTBCustomItem; begin Result := FItem.LinkSubitems; end; procedure TTBCustomToolbar.SetLinkSubitems(Value: TTBCustomItem); begin FItem.LinkSubitems := Value; end; procedure TTBCustomToolbar.SetMenuBar(Value: Boolean); begin if FMenuBar <> Value then begin FMenuBar := Value; if Value then begin ControlStyle := ControlStyle + [csMenuEvents]; FView.Style := FView.Style + [vsMenuBar, vsUseHiddenAccels]; end else begin ControlStyle := ControlStyle - [csMenuEvents]; FView.Style := FView.Style - [vsMenuBar, vsUseHiddenAccels]; end; if not(csLoading in ComponentState) then begin FullSize := Value; if Value then ShrinkMode := tbsmWrap else ShrinkMode := tbsmChevron; CloseButton := not Value; ProcessShortCuts := Value; end; if Value and not(csDesigning in ComponentState) then InstallHookProc(Self, HookProc, [hpGetMessage]) else UninstallHookProc(Self, HookProc); SetMainWindowHook; end; end; function TTBCustomToolbar.GetOptions: TTBItemOptions; begin Result := FItem.Options; end; procedure TTBCustomToolbar.SetOptions(Value: TTBItemOptions); begin FItem.Options := Value; end; procedure TTBCustomToolbar.SetProcessShortCuts(Value: Boolean); begin if FProcessShortCuts <> Value then begin FProcessShortCuts := Value; SetMainWindowHook; end; end; procedure TTBCustomToolbar.SetSystemFont(Value: Boolean); begin if FSystemFont <> Value then begin FSystemFont := Value; Arrange; end; end; procedure TTBCustomToolbar.SetShrinkMode(Value: TTBShrinkMode); begin if FShrinkMode <> Value then begin FShrinkMode := Value; if Docked then CurrentDock.ArrangeToolbars else if not Floating then Arrange; end; end; procedure TTBCustomToolbar.SetFloatingWidth(Value: Integer); begin if FFloatingWidth <> Value then begin FFloatingWidth := Value; if Floating then begin UpdateViewProperties; Arrange; end; end; end; function TTBCustomToolbar.CalcWrapOffset(const ADock: TTBDock): Integer; begin if ADock = nil then Result := FFloatingWidth else begin if FShrinkMode = tbsmWrap then begin if not(ADock.Position in [dpLeft, dpRight]) then Result := ADock.Width - ADock.NonClientWidth - NonClientWidth else Result := ADock.Height - ADock.NonClientHeight - NonClientHeight; end else Result := 0; end; end; function TTBCustomToolbar.CalcChevronOffset(const ADock: TTBDock; const AOrientation: TTBViewOrientation): Integer; begin if (FShrinkMode = tbsmChevron) and Docked and (ADock = CurrentDock) then begin Result := CurrentSize; { Subtract non-client size } if AOrientation <> tbvoVertical then Dec(Result, NonClientWidth) else Dec(Result, NonClientHeight); if Result < 0 then Result := 0; { in case CurrentSize wasn't properly initialized yet } end else Result := 0; end; procedure TTBCustomToolbar.UpdateViewProperties; var DT: TTBDockType; begin DT := TBGetDockTypeOf(CurrentDock, Floating); FView.Orientation := DockTypeToOrientation[DT]; FView.ChevronSize := tbChevronSize; if Assigned(CurrentDock) or Floating then begin FView.ChevronOffset := CalcChevronOffset(CurrentDock, FView.Orientation); FView.WrapOffset := CalcWrapOffset(CurrentDock); end else begin FView.ChevronOffset := 0; FView.WrapOffset := 0; { Only enable chevron/wrapping when the width of the toolbar is fixed } if not AutoResize or ((akLeft in Anchors) and (akRight in Anchors)) then begin if FShrinkMode = tbsmChevron then FView.ChevronOffset := Width - NonClientWidth else if FShrinkMode = tbsmWrap then FView.WrapOffset := Width - NonClientWidth; end; end; end; {}{DOCKING STUFF} procedure TTBCustomToolbar.ReadPositionData(const Data: TTBReadPositionData); begin inherited; with Data do FloatingWidth := ReadIntProc(Name, rvFloatRightX, 0, ExtraData); end; procedure TTBCustomToolbar.WritePositionData(const Data: TTBWritePositionData); begin inherited; with Data do WriteIntProc(Name, rvFloatRightX, FFloatingWidth, ExtraData); end; procedure TTBCustomToolbar.GetMinBarSize(var MinimumSize: TPoint); var WH: Integer; begin MinimumSize.X := 0; MinimumSize.Y := 0; if Docked then begin WH := CurrentDock.GetMinRowSize(EffectiveDockRow, Self); if not(CurrentDock.Position in [dpLeft, dpRight]) then MinimumSize.Y := WH else MinimumSize.X := WH; end; end; procedure TTBCustomToolbar.GetBaseSize(var ASize: TPoint); begin FView.ValidatePositions; ASize := FBaseSize; end; function TTBCustomToolbar.DoArrange(CanMoveControls: Boolean; PreviousDockType: TTBDockType; NewFloating: Boolean; NewDock: TTBDock): TPoint; var DT: TTBDockType; O: TTBViewOrientation; TempBaseSize: TPoint; begin //outputdebugstring (pchar(format('%s.DoArrange(%d)', [Name, Ord(CanMoveControls)]))); if CanMoveControls then begin UpdateViewProperties; Result := FView.UpdatePositions; end else begin DT := TBGetDockTypeOf(NewDock, NewFloating); O := DockTypeToOrientation[DT]; Result.X := 0; Result.Y := 0; FView.CalculatePositions(False, O, CalcWrapOffset(NewDock), CalcChevronOffset(NewDock, O), tbChevronSize, TempBaseSize, Result, FLastWrappedLines); end; end; procedure TTBCustomToolbar.ControlExistsAtPos(const P: TPoint; var ControlExists: Boolean); var P2: TPoint; begin inherited; if not ControlExists and not(csDesigning in ComponentState) then begin P2 := ClientToScreen(P); FView.UpdateSelection(P2, True); if Assigned(FView.Selected) and not(tbisClicksTransparent in TTBCustomItemAccess(FView.Selected.Item).ItemStyle) then ControlExists := True; end; end; procedure TTBCustomToolbar.BuildPotentialSizesList(SizesList: TList); var Margins: TRect; MinX, SaveWrapX: Integer; X, PrevWrappedLines: Integer; S: TPoint; S2: TSmallPoint; begin View.GetMargins(tbvoFloating, Margins); MinX := Margins.Left + Margins.Right; SaveWrapX := FFloatingWidth; try { Add the widest size to the list } FFloatingWidth := 0; S := DoArrange(False, dtNotDocked, True, nil); SizesList.Add(TListItemType(PointToSmallPoint(S))); { Calculate and add rest of sizes to the list } PrevWrappedLines := 1; X := S.X-1; while X >= MinX do begin FFloatingWidth := X; S := DoArrange(False, dtNotDocked, True, nil); if S.X > X then { if it refuses to go any smaller } Break else if X = S.X then begin S2 := PointToSmallPoint(S); if FLastWrappedLines <> PrevWrappedLines then SizesList.Add(TListItemType(S2)) else SizesList[SizesList.Count-1] := TListItemType(S2); PrevWrappedLines := FLastWrappedLines; Dec(X); end else X := S.X; end; finally FFloatingWidth := SaveWrapX; end; end; function CompareSizesX(Item1, Item2: TListItemType): Integer; begin { Sorts in descending order } Result := TSmallPoint(Item2).X - TSmallPoint(Item1).X; end; function CompareSizesY(Item1, Item2: TListItemType): Integer; begin { Sorts in descending order } Result := TSmallPoint(Item2).Y - TSmallPoint(Item1).Y; end; procedure TTBCustomToolbar.ResizeBegin(ASizeHandle: TTBSizeHandle); const MaxDistance = 12; var I, NewSize: Integer; S, N: TSmallPoint; P: TPoint; begin inherited; FSizeData := TToolbarSizeData.Create; with TToolbarSizeData(FSizeData) do begin SizeHandle := ASizeHandle; OrigWidth := Parent.Width; OrigHeight := Parent.Height; NCXDiff := ClientToScreen(Point(0, 0)).X - Parent.Left; CurRightX := FFloatingWidth; DisableSensCheck := False; OpSide := False; NewSizes := TList.Create; BuildPotentialSizesList(NewSizes); for I := 0 to NewSizes.Count-1 do begin P := SmallPointToPoint(TSmallPoint(NewSizes[I])); AddFloatingNCAreaToSize(P); NewSizes[I] := TListItemType(PointToSmallPoint(P)); end; if ASizeHandle in [twshTop, twshBottom] then NewSizes.Sort(CompareSizesY) else NewSizes.Sort(CompareSizesX); { Calculate distance in pixels to the nearest potential sizes smaller and larger than the current size, up to a maximum of MaxDistance pixels. } DistanceToSmallerSize := 0; DistanceToLargerSize := 0; for I := 0 to NewSizes.Count-1 do begin S := TSmallPoint(NewSizes[I]); if (S.X = OrigWidth) and (S.Y = OrigHeight) then begin if I > 0 then begin N := TSmallPoint(NewSizes[I-1]); if ASizeHandle in [twshLeft, twshRight] then NewSize := N.X - S.X else NewSize := N.Y - S.Y; if NewSize > MaxDistance then NewSize := MaxDistance; DistanceToLargerSize := NewSize; end; if I < NewSizes.Count-1 then begin N := TSmallPoint(NewSizes[I+1]); if ASizeHandle in [twshLeft, twshRight] then NewSize := S.X - N.X else NewSize := S.Y - N.Y; if NewSize > MaxDistance then NewSize := MaxDistance; DistanceToSmallerSize := NewSize; end; Break; end; end; end; end; procedure TTBCustomToolbar.ResizeTrack(var Rect: TRect; const OrigRect: TRect); var Pos: TPoint; NewOpSide: Boolean; Reverse: Boolean; I: Integer; P: TSmallPoint; begin inherited; with TToolbarSizeData(FSizeData) do begin Pos.X := Rect.Right - Rect.Left; Pos.Y := Rect.Bottom - Rect.Top; { Like Office, don't change from the original size until the mouse is moved a reasonable distance left/up or right/down. Without this, dragging the mouse just one pixel in either direction would cause the toolbar to change sizes. } if SizeHandle in [twshLeft, twshRight] then NewOpSide := Pos.X < OrigWidth else NewOpSide := Pos.Y < OrigHeight; if (not DisableSensCheck) or (OpSide <> NewOpSide) then begin DisableSensCheck := False; OpSide := NewOpSide; if SizeHandle in [twshLeft, twshRight] then begin if (Pos.X > OrigWidth-DistanceToSmallerSize) and (Pos.X < OrigWidth+DistanceToLargerSize) then Pos.X := OrigWidth; end else begin if (Pos.Y > OrigHeight-DistanceToSmallerSize) and (Pos.Y < OrigHeight+DistanceToLargerSize) then Pos.Y := OrigHeight; end; end; Rect := OrigRect; if SizeHandle in [twshLeft, twshRight] then Reverse := Pos.X > OrigWidth else Reverse := Pos.Y > OrigHeight; if not Reverse then I := 0 else I := NewSizes.Count-1; while True do begin P := TSmallPoint(NewSizes[I]); if SizeHandle in [twshLeft, twshRight] 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 SizeHandle = twshRight 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); Break; 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 SizeHandle = twshBottom 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); Break; end; end; if not Reverse then Inc(I) else Dec(I); end; end; end; procedure TTBCustomToolbar.ResizeTrackAccept; begin inherited; FloatingWidth := TToolbarSizeData(FSizeData).CurRightX; end; procedure TTBCustomToolbar.ResizeEnd; begin inherited; if Assigned(FSizeData) then begin with TToolbarSizeData(FSizeData) do FreeAndNil(NewSizes); FreeAndNil(FSizeData); end; end; function TTBCustomToolbar.GetShrinkMode: TTBShrinkMode; begin Result := FShrinkMode; end; procedure TTBCustomToolbar.GetMinShrinkSize(var AMinimumSize: Integer); var I: TTBItemViewer; begin I := FView.HighestPriorityViewer; if Assigned(I) then begin if not(CurrentDock.Position in [dpLeft, dpRight]) then AMinimumSize := I.BoundsRect.Right - I.BoundsRect.Left else AMinimumSize := I.BoundsRect.Bottom - I.BoundsRect.Top; end; if not(CurrentDock.Position in [dpLeft, dpRight]) then Inc(AMinimumSize, NonClientWidth) else Inc(AMinimumSize, NonClientHeight); Inc(AMinimumSize, tbChevronSize); end; procedure TTBCustomToolbar.BeginUpdate; begin FView.BeginUpdate; end; procedure TTBCustomToolbar.EndUpdate; begin FView.EndUpdate; end; procedure TTBCustomToolbar.GetTabOrderList(List: TList); var CtlList: TList; I, J: Integer; CtlI, CtlJ: TWinControl; begin inherited; { Remove off-edge items and their children from List } CtlList := TList.Create; try FView.GetOffEdgeControlList(CtlList); for I := 0 to CtlList.Count-1 do begin CtlI := TWinControl(CtlList[I]); J := 0; while J < List.Count do begin CtlJ := TWinControl(List[J]); if (CtlJ = CtlI) or CtlI.ContainsControl(CtlJ) then List.Delete(J) else Inc(J); end; end; finally CtlList.Free; end; end; procedure TTBCustomToolbar.CMWinIniChange(var Message: TWMWinIniChange); begin inherited; if {$IFNDEF CLR}TMessage{$ENDIF}(Message).WParam = SPI_SETNONCLIENTMETRICS then begin TBInitToolbarSystemFont; Arrange; end; end; function TTBCustomToolbar.IsShortCut(var Message: TWMKey): Boolean; begin Result := False; if Assigned(FOnShortCut) then FOnShortCut(Message, Result); Result := Result or FItem.IsShortCut(Message); end; var HookCount: Integer; HookList: TList; class function TTBCustomToolbar.MainWindowHook(var Message: TMessage): Boolean; function GetActiveForm: TCustomForm; var Wnd: HWND; Ctl: TWinControl; begin { Note: We don't use Screen.ActiveCustomForm because when an EXE calls a DLL that shows a modal form, Screen.ActiveCustomForm doesn't change in the EXE; it remains set to the last form that was active in the EXE. Use FindControl(GetActiveWindow) instead to avoid this problem; it will return nil when a form in another module is active. } Result := nil; Wnd := GetActiveWindow; if Wnd <> 0 then begin Ctl := FindControl(Wnd); if Assigned(Ctl) and (Ctl is TCustomForm) then Result := TCustomForm(Ctl); end; end; function HandleShortCutOnForm(const Form: TCustomForm): Boolean; var I: Integer; Toolbar: TTBCustomToolbar; {$IFDEF CLR} KeyMsg: TWMKey; {$ENDIF} begin Result := False; if Form = nil then Exit; for I := 0 to HookList.Count-1 do begin Toolbar := TTBCustomToolbar(HookList[I]); if Toolbar.ProcessShortCuts and (TBGetToolWindowParentForm(Toolbar) = Form) and IsWindowEnabled(Form.Handle) then begin {$IFNDEF CLR} if Toolbar.IsShortCut(TWMKey(Message)) then begin {$ELSE} KeyMsg := TWMKey.Create(Message); if Toolbar.IsShortCut(KeyMsg) then begin {$ENDIF} Message.Result := 1; Result := True; Exit; end; end; end; end; function TraverseControls(Container: TWinControl): Boolean; var I: Integer; Control: TControl; begin Result := False; if Container.Showing then for I := 0 to Container.ControlCount - 1 do begin Control := Container.Controls[I]; if Control.Visible and Control.Enabled then begin if (csMenuEvents in Control.ControlStyle) and ((Control is TTBDock) or (Control is TTBCustomToolbar)) and (Control.Perform(WM_SYSCOMMAND, TMessage(Message).WParam, TMessage(Message).LParam) <> 0) or (Control is TWinControl) and TraverseControls(TWinControl(Control)) then begin Result := True; Exit; end; end; end; end; var ActiveForm: TCustomForm; ActiveMDIChild: TForm; begin Result := False; if (Message.Msg = CM_APPKEYDOWN) and Assigned(HookList) then begin { Process shortcuts on toolbars. Search forms in this order: 1. If the active form is an MDI parent form, the active MDI child form if it has the focus. 2. The active form. 3. The main form. } ActiveForm := GetActiveForm; if Assigned(ActiveForm) and (ActiveForm is TForm) and (TForm(ActiveForm).FormStyle = fsMDIForm) then begin ActiveMDIChild := TForm(ActiveForm).ActiveMDIChild; { Don't search the child form if a control on the MDI parent form is currently focused (i.e. Screen.ActiveCustomForm <> ActiveMDIChild) } if Assigned(ActiveMDIChild) and (Screen.ActiveCustomForm = ActiveMDIChild) and HandleShortCutOnForm(ActiveMDIChild) then begin Result := True; Exit; end; end; if HandleShortCutOnForm(ActiveForm) then Result := True else begin if (Application.MainForm <> ActiveForm) and HandleShortCutOnForm(Application.MainForm) then Result := True; end; end else if Message.Msg = CM_APPSYSCOMMAND then begin { Handle "Alt or Alt+[key] pressed on secondary form" case. If there's a menu bar on the active form we want the keypress to go to it instead of to the main form (the VCL's default handling). } ActiveForm := GetActiveForm; if Assigned(ActiveForm) and IsWindowEnabled(ActiveForm.Handle) and IsWindowVisible(ActiveForm.Handle) and TraverseControls(ActiveForm) then begin Message.Result := 1; Result := True; end; end; end; procedure TTBCustomToolbar.SetMainWindowHook; begin if (ProcessShortCuts or MenuBar) and not(csDesigning in ComponentState) then InstallMainWindowHook else UninstallMainWindowHook; end; procedure TTBCustomToolbar.InstallMainWindowHook; begin if FMainWindowHookInstalled then Exit; if HookCount = 0 then Application.HookMainWindow(MainWindowHook); Inc(HookCount); AddToList(HookList, Self); FMainWindowHookInstalled := True; end; procedure TTBCustomToolbar.UninstallMainWindowHook; begin if not FMainWindowHookInstalled then Exit; FMainWindowHookInstalled := False; RemoveFromList(HookList, Self); Dec(HookCount); if HookCount = 0 then Application.UnhookMainWindow(MainWindowHook); end; end.