6985 lines
217 KiB
ObjectPascal
6985 lines
217 KiB
ObjectPascal
unit TB2Item;
|
|
|
|
{$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/TB2Item.pas,v 1.313 2008/09/19 16:35:48 jr Exp $
|
|
}
|
|
|
|
interface
|
|
|
|
{$I TB2Ver.inc}
|
|
{x$DEFINE TB2K_NO_ANIMATION}
|
|
{ Enabling the above define disables all menu animation. For debugging
|
|
purpose only. }
|
|
{x$DEFINE TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
{ Enabling the above define forces it to use clBtnFace for the menu color
|
|
instead of clMenu, and disables the use of flat menu borders on Windows
|
|
XP with themes enabled. }
|
|
|
|
uses
|
|
LCLIntf, LCLType, LMessages, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
|
|
{$IFDEF CLR} TB2OleMarshal, {$ENDIF}
|
|
StdCtrls, CommCtrl, Menus, ActnList, ImgList, TB2Anim;
|
|
|
|
type
|
|
TTBCustomItem = class;
|
|
TTBCustomItemClass = class of TTBCustomItem;
|
|
TTBCustomItemActionLink = class;
|
|
TTBCustomItemActionLinkClass = class of TTBCustomItemActionLink;
|
|
TTBItemViewer = class;
|
|
TTBItemViewerClass = class of TTBItemViewer;
|
|
TTBPopupWindow = class;
|
|
TTBPopupWindowClass = class of TTBPopupWindow;
|
|
TTBView = class;
|
|
|
|
TTBDoneAction = (tbdaNone, tbdaCancel, tbdaClickItem, tbdaOpenSystemMenu,
|
|
tbdaHelpContext);
|
|
TTBDoneActionData = record
|
|
DoneAction: TTBDoneAction;
|
|
{ tbdaClickItem-specific fields: }
|
|
ClickItem: TTBCustomItem;
|
|
Sound: Boolean;
|
|
{ tbdaOpenSystemMenu-specific fields: }
|
|
Wnd: HWND;
|
|
Key: Word;
|
|
{ tbdaHelpContext-specific fields: }
|
|
ContextID: Integer;
|
|
end;
|
|
TTBInsertItemProc = procedure(AParent: TComponent; AItem: TTBCustomItem) of object;
|
|
TTBItemChangedAction = (tbicInserted, tbicDeleting, tbicSubitemsChanged,
|
|
tbicSubitemsBeginUpdate, tbicSubitemsEndUpdate, tbicInvalidate,
|
|
tbicInvalidateAndResize, tbicRecreateItemViewers, tbicNameChanged,
|
|
tbicSubMenuImagesChanged);
|
|
TTBItemChangedProc = procedure(Sender: TTBCustomItem; Relayed: Boolean;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem) of object;
|
|
TTBItemDisplayMode = (nbdmDefault, nbdmTextOnly, nbdmTextOnlyInMenus, nbdmImageAndText);
|
|
TTBItemOption = (tboDefault, tboDropdownArrow, tboImageAboveCaption,
|
|
tboLongHintInMenuOnly, tboNoAutoHint, tboNoRotation, tboSameWidth,
|
|
tboShowHint, tboToolbarStyle, tboToolbarSize);
|
|
TTBItemOptions = set of TTBItemOption;
|
|
TTBItemStyle = set of (tbisSubmenu, tbisSelectable, tbisSeparator,
|
|
tbisEmbeddedGroup, tbisClicksTransparent, tbisCombo, tbisNoAutoOpen,
|
|
tbisSubitemsEditable, tbisNoLineBreak, tbisRightAlign, tbisDontSelectFirst,
|
|
tbisRedrawOnSelChange, tbisRedrawOnMouseOverChange);
|
|
TTBPopupAlignment = (tbpaLeft, tbpaRight, tbpaCenter);
|
|
TTBPopupEvent = procedure(Sender: TTBCustomItem; FromLink: Boolean) of object;
|
|
TTBSelectEvent = procedure(Sender: TTBCustomItem; Viewer: TTBItemViewer;
|
|
Selecting: Boolean) of object;
|
|
|
|
ETBItemError = class(Exception);
|
|
|
|
TTBImageChangeLink = class(TChangeLink)
|
|
private
|
|
FLastWidth, FLastHeight: Integer;
|
|
end;
|
|
{$IFNDEF JR_D5}
|
|
TImageIndex = type Integer;
|
|
{$ENDIF}
|
|
|
|
TTBCustomItem = class(TComponent)
|
|
private
|
|
FActionLink: TTBCustomItemActionLink;
|
|
FAutoCheck: Boolean;
|
|
FCaption: String;
|
|
FChecked: Boolean;
|
|
FDisplayMode: TTBItemDisplayMode;
|
|
FEnabled: Boolean;
|
|
FEffectiveOptions: TTBItemOptions;
|
|
FGroupIndex: Integer;
|
|
FHelpContext: THelpContext;
|
|
FHint: String;
|
|
FImageIndex: TImageIndex;
|
|
FImages: TCustomImageList;
|
|
FImagesChangeLink: TTBImageChangeLink;
|
|
FItems: TList;
|
|
FItemStyle: TTBItemStyle;
|
|
FLinkParents: TList;
|
|
FMaskOptions: TTBItemOptions;
|
|
FOptions: TTBItemOptions;
|
|
FInheritOptions: Boolean;
|
|
FNotifyList: TList;
|
|
FOnClick: TNotifyEvent;
|
|
FOnPopup: TTBPopupEvent;
|
|
FOnSelect: TTBSelectEvent;
|
|
FParent: TTBCustomItem;
|
|
FParentComponent: TComponent;
|
|
FRadioItem: Boolean;
|
|
FShortCut: TShortCut;
|
|
FSubMenuImages: TCustomImageList;
|
|
FSubMenuImagesChangeLink: TTBImageChangeLink;
|
|
FLinkSubitems: TTBCustomItem;
|
|
FVisible: Boolean;
|
|
|
|
procedure DoActionChange(Sender: TObject);
|
|
function ChangeImages(var AImages: TCustomImageList;
|
|
const Value: TCustomImageList; var AChangeLink: TTBImageChangeLink): Boolean;
|
|
class procedure ClickWndProc(var Message: TMessage); {$IFDEF CLR} static; {$ENDIF}
|
|
function FindItemWithShortCut(AShortCut: TShortCut;
|
|
var ATopmostParent: TTBCustomItem): TTBCustomItem;
|
|
function FixOptions(const AOptions: TTBItemOptions): TTBItemOptions;
|
|
function GetAction: TBasicAction;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TTBCustomItem;
|
|
procedure ImageListChangeHandler(Sender: TObject);
|
|
procedure InternalNotify(Ancestor: TTBCustomItem; NestingLevel: Integer;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
{$IFDEF JR_D6}
|
|
function IsAutoCheckStored: Boolean;
|
|
{$ENDIF}
|
|
function IsCaptionStored: Boolean;
|
|
function IsCheckedStored: Boolean;
|
|
function IsEnabledStored: Boolean;
|
|
function IsHelpContextStored: Boolean;
|
|
function IsHintStored: Boolean;
|
|
function IsImageIndexStored: Boolean;
|
|
function IsOnClickStored: Boolean;
|
|
function IsShortCutStored: Boolean;
|
|
function IsVisibleStored: Boolean;
|
|
procedure Notify(Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
procedure RefreshOptions;
|
|
procedure SetAction(Value: TBasicAction);
|
|
procedure SetCaption(Value: String);
|
|
procedure SetChecked(Value: Boolean);
|
|
procedure SetDisplayMode(Value: TTBItemDisplayMode);
|
|
procedure SetEnabled(Value: Boolean);
|
|
procedure SetGroupIndex(Value: Integer);
|
|
procedure SetImageIndex(Value: TImageIndex);
|
|
procedure SetImages(Value: TCustomImageList);
|
|
procedure SetInheritOptions(Value: Boolean);
|
|
procedure SetLinkSubitems(Value: TTBCustomItem);
|
|
procedure SetMaskOptions(Value: TTBItemOptions);
|
|
procedure SetOptions(Value: TTBItemOptions);
|
|
procedure SetRadioItem(Value: Boolean);
|
|
procedure SetSubMenuImages(Value: TCustomImageList);
|
|
procedure SetVisible(Value: Boolean);
|
|
procedure SubMenuImagesChanged;
|
|
procedure TurnSiblingsOff;
|
|
protected
|
|
procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); dynamic;
|
|
procedure Change(NeedResize: Boolean); virtual;
|
|
function CreatePopup(const ParentView: TTBView; const ParentViewer: TTBItemViewer;
|
|
const PositionAsSubmenu, SelectFirstItem, Customizing: Boolean;
|
|
const APopupPoint: TPoint; const Alignment: TTBPopupAlignment): TTBPopupWindow; virtual;
|
|
procedure DoPopup(Sender: TTBCustomItem; FromLink: Boolean); virtual;
|
|
procedure EnabledChanged; virtual;
|
|
function GetActionLinkClass: TTBCustomItemActionLinkClass; dynamic;
|
|
function GetChevronParentView: TTBView; virtual;
|
|
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
|
|
function GetItemViewerClass(AView: TTBView): TTBItemViewerClass; virtual;
|
|
function GetPopupWindowClass: TTBPopupWindowClass; virtual;
|
|
class procedure IndexError;
|
|
procedure Loaded; override;
|
|
function NeedToRecreateViewer(AViewer: TTBItemViewer): Boolean; virtual;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
function OpenPopup(const SelectFirstItem, TrackRightButton: Boolean;
|
|
const PopupPoint: TPoint; const Alignment: TTBPopupAlignment;
|
|
const ReturnClickedItemOnly: Boolean): TTBCustomItem;
|
|
procedure RecreateItemViewers;
|
|
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
|
|
procedure SetName(const NewName: TComponentName); override;
|
|
{$IFNDEF CLR}
|
|
procedure SetParentComponent(Value: TComponent); override;
|
|
{$ENDIF}
|
|
|
|
property ActionLink: TTBCustomItemActionLink read FActionLink write FActionLink;
|
|
property ItemStyle: TTBItemStyle read FItemStyle write FItemStyle;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function HasParent: Boolean; override;
|
|
function GetParentComponent: TComponent; override;
|
|
|
|
procedure Add(AItem: TTBCustomItem);
|
|
procedure Clear;
|
|
procedure Click; virtual;
|
|
function ContainsItem(AItem: TTBCustomItem): Boolean;
|
|
procedure Delete(Index: Integer);
|
|
function GetItemStyle: TTBItemStyle;
|
|
function GetShortCutText: String;
|
|
function IndexOf(AItem: TTBCustomItem): Integer;
|
|
procedure InitiateAction; virtual;
|
|
procedure Insert(NewIndex: Integer; AItem: TTBCustomItem);
|
|
function IsShortCut(var Message: TWMKey): Boolean;
|
|
procedure Move(CurIndex, NewIndex: Integer);
|
|
function Popup(X, Y: Integer; TrackRightButton: Boolean;
|
|
Alignment: TTBPopupAlignment = tbpaLeft;
|
|
ReturnClickedItemOnly: Boolean = False): TTBCustomItem;
|
|
procedure PostClick;
|
|
procedure RegisterNotification(ANotify: TTBItemChangedProc);
|
|
procedure Remove(Item: TTBCustomItem);
|
|
{$IFDEF CLR}
|
|
procedure SetParentComponent(Value: TComponent); override;
|
|
{$ENDIF}
|
|
procedure UnregisterNotification(ANotify: TTBItemChangedProc);
|
|
procedure ViewBeginUpdate;
|
|
procedure ViewEndUpdate;
|
|
|
|
property Action: TBasicAction read GetAction write SetAction;
|
|
property AutoCheck: Boolean read FAutoCheck write FAutoCheck {$IFDEF JR_D6} stored IsAutoCheckStored {$ENDIF} default False;
|
|
property Caption: String read FCaption write SetCaption stored IsCaptionStored;
|
|
property Count: Integer read GetCount;
|
|
property Checked: Boolean read FChecked write SetChecked stored IsCheckedStored default False;
|
|
property DisplayMode: TTBItemDisplayMode read FDisplayMode write SetDisplayMode default nbdmDefault;
|
|
property EffectiveOptions: TTBItemOptions read FEffectiveOptions;
|
|
property Enabled: Boolean read FEnabled write SetEnabled stored IsEnabledStored default True;
|
|
property GroupIndex: Integer read FGroupIndex write SetGroupIndex default 0;
|
|
property HelpContext: THelpContext read FHelpContext write FHelpContext stored IsHelpContextStored default 0;
|
|
property Hint: String read FHint write FHint stored IsHintStored;
|
|
property ImageIndex: TImageIndex read FImageIndex write SetImageIndex stored IsImageIndexStored default -1;
|
|
property Images: TCustomImageList read FImages write SetImages;
|
|
property InheritOptions: Boolean read FInheritOptions write SetInheritOptions default True;
|
|
property Items[Index: Integer]: TTBCustomItem read GetItem; default;
|
|
property LinkSubitems: TTBCustomItem read FLinkSubitems write SetLinkSubitems;
|
|
property MaskOptions: TTBItemOptions read FMaskOptions write SetMaskOptions default [];
|
|
property Options: TTBItemOptions read FOptions write SetOptions default [];
|
|
property Parent: TTBCustomItem read FParent;
|
|
property ParentComponent: TComponent read FParentComponent write FParentComponent;
|
|
property RadioItem: Boolean read FRadioItem write SetRadioItem default False;
|
|
property ShortCut: TShortCut read FShortCut write FShortCut stored IsShortCutStored default 0;
|
|
property SubMenuImages: TCustomImageList read FSubMenuImages write SetSubMenuImages;
|
|
property Visible: Boolean read FVisible write SetVisible stored IsVisibleStored default True;
|
|
property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
|
|
property OnPopup: TTBPopupEvent read FOnPopup write FOnPopup;
|
|
property OnSelect: TTBSelectEvent read FOnSelect write FOnSelect;
|
|
end;
|
|
|
|
TTBCustomItemActionLink = class(TActionLink)
|
|
protected
|
|
FClient: TTBCustomItem;
|
|
procedure AssignClient(AClient: TObject); override;
|
|
{$IFDEF JR_D6}
|
|
function IsAutoCheckLinked: Boolean; virtual;
|
|
{$ENDIF}
|
|
function IsCaptionLinked: Boolean; override;
|
|
function IsCheckedLinked: Boolean; override;
|
|
function IsEnabledLinked: Boolean; override;
|
|
function IsHelpContextLinked: Boolean; override;
|
|
function IsHintLinked: Boolean; override;
|
|
function IsImageIndexLinked: Boolean; override;
|
|
function IsShortCutLinked: Boolean; override;
|
|
function IsVisibleLinked: Boolean; override;
|
|
function IsOnExecuteLinked: Boolean; override;
|
|
{$IFDEF JR_D6}
|
|
procedure SetAutoCheck(Value: Boolean); override;
|
|
{$ENDIF}
|
|
procedure SetCaption(const Value: String); override;
|
|
procedure SetChecked(Value: Boolean); override;
|
|
procedure SetEnabled(Value: Boolean); override;
|
|
procedure SetHelpContext(Value: THelpContext); override;
|
|
procedure SetHint(const Value: String); override;
|
|
procedure SetImageIndex(Value: Integer); override;
|
|
procedure SetShortCut(Value: TShortCut); override;
|
|
procedure SetVisible(Value: Boolean); override;
|
|
procedure SetOnExecute(Value: TNotifyEvent); override;
|
|
end;
|
|
|
|
{$IFNDEF CLR}
|
|
TTBBaseAccObject = class(TInterfacedObject, IDispatch)
|
|
{$ELSE}
|
|
TTBBaseAccObject = class(TTBStandardOleMarshalObject)
|
|
{$ENDIF}
|
|
public
|
|
procedure ClientIsDestroying; virtual; abstract;
|
|
{$IFNDEF CLR}
|
|
{ IDispatch }
|
|
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
|
|
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
|
|
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
|
|
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
|
|
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
|
|
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
TTBItemViewer = class
|
|
private
|
|
FBoundsRect: TRect;
|
|
FClipped: Boolean;
|
|
FGroupLevel: Integer;
|
|
FItem: TTBCustomItem;
|
|
FOffEdge: Boolean;
|
|
FShow: Boolean;
|
|
FView: TTBView;
|
|
procedure AccSelect(const AExecute: Boolean);
|
|
function GetIndex: Integer;
|
|
protected
|
|
FAccObjectInstance: TTBBaseAccObject;
|
|
procedure CalcSize(const Canvas: TCanvas; var AWidth, AHeight: Integer);
|
|
virtual;
|
|
function CaptionShown: Boolean; dynamic;
|
|
function DoExecute: Boolean; virtual;
|
|
procedure DrawItemCaption(const Canvas: TCanvas; ARect: TRect;
|
|
const ACaption: String; ADrawDisabledShadow: Boolean; AFormat: UINT); virtual;
|
|
procedure Entering; virtual;
|
|
function GetAccRole: Integer; virtual;
|
|
function GetAccValue(var Value: WideString): Boolean; virtual;
|
|
function GetCaptionText: String; virtual;
|
|
procedure GetCursor(const Pt: TPoint; var ACursor: HCURSOR); virtual;
|
|
function GetImageList: TCustomImageList;
|
|
function ImageShown: Boolean;
|
|
function IsRotated: Boolean;
|
|
function IsToolbarSize: Boolean;
|
|
function IsPtInButtonPart(X, Y: Integer): Boolean; virtual;
|
|
procedure KeyDown(var Key: Word; Shift: TShiftState); virtual;
|
|
procedure Leaving; virtual;
|
|
procedure LosingCapture; virtual;
|
|
procedure MouseDown(Shift: TShiftState; X, Y: Integer;
|
|
var MouseDownOnMenu: Boolean); virtual;
|
|
procedure MouseMove(X, Y: Integer); virtual;
|
|
procedure MouseUp(X, Y: Integer; MouseWasDownOnMenu: Boolean); virtual;
|
|
procedure MouseWheel(WheelDelta: Integer; X, Y: Integer); virtual;
|
|
procedure Paint(const Canvas: TCanvas; const ClientAreaRect: TRect;
|
|
IsSelected, IsPushed, UseDisabledShadow: Boolean); virtual;
|
|
procedure PostAccSelect(const AExecute: Boolean);
|
|
function UsesSameWidth: Boolean; virtual;
|
|
public
|
|
State: set of (tbisInvalidated, tbisLineSep);
|
|
property BoundsRect: TRect read FBoundsRect;
|
|
property Clipped: Boolean read FClipped;
|
|
property Index: Integer read GetIndex;
|
|
property Item: TTBCustomItem read FItem;
|
|
property OffEdge: Boolean read FOffEdge;
|
|
property Show: Boolean read FShow;
|
|
property View: TTBView read FView;
|
|
constructor Create(AView: TTBView; AItem: TTBCustomItem; AGroupLevel: Integer); virtual;
|
|
destructor Destroy; override;
|
|
procedure Execute(AGivePriority: Boolean);
|
|
function GetAccObject: TTBBaseAccObject;
|
|
function GetHintText: String;
|
|
function IsAccessible: Boolean;
|
|
function IsToolbarStyle: Boolean;
|
|
function ScreenToClient(const P: TPoint): TPoint;
|
|
end;
|
|
TTBViewOrientation = (tbvoHorizontal, tbvoVertical, tbvoFloating);
|
|
TTBEnterToolbarLoopOptions = set of (tbetMouseDown, tbetExecuteSelected,
|
|
tbetFromMSAA);
|
|
TTBViewState = set of (vsModal, vsMouseInWindow, vsDrawInOrder, vsOppositePopup,
|
|
vsIgnoreFirstMouseUp, vsShowAccels, vsDropDownMenus, vsNoAnimation);
|
|
TTBViewStyle = set of (vsMenuBar, vsUseHiddenAccels, vsAlwaysShowHints);
|
|
TTBViewTimerID = (tiOpen, tiClose, tiScrollUp, tiScrollDown);
|
|
|
|
TTBViewClass = class of TTBView;
|
|
TTBView = class(TComponent)
|
|
private
|
|
FViewers: TList; { at front to minimize code size }
|
|
FActiveTimers: set of TTBViewTimerID;
|
|
FBackgroundColor: TColor;
|
|
FBaseSize: TPoint;
|
|
FCapture: Boolean;
|
|
FCaptureWnd: HWND;
|
|
FChevronOffset: Integer;
|
|
FChevronParentView: TTBView;
|
|
FChevronSize: Integer;
|
|
FCurParentItem: TTBCustomItem;
|
|
FCustomizing: Boolean;
|
|
FDoneActionData: TTBDoneActionData;
|
|
FInternalViewersAtEnd: Integer;
|
|
FInternalViewersAtFront: Integer;
|
|
FIsPopup: Boolean;
|
|
FIsToolbar: Boolean;
|
|
FMaxHeight: Integer;
|
|
FMonitorRect: TRect;
|
|
FMouseOverSelected: Boolean;
|
|
FNewViewersGetHighestPriority: Boolean;
|
|
FOpenViewer: TTBItemViewer;
|
|
FOpenViewerView: TTBView;
|
|
FOpenViewerWindow: TTBPopupWindow;
|
|
FParentView: TTBView;
|
|
FParentItem: TTBCustomItem;
|
|
FPriorityList: TList;
|
|
FOrientation: TTBViewOrientation;
|
|
FScrollOffset: Integer;
|
|
FSelected: TTBItemViewer;
|
|
FSelectedViaMouse: Boolean;
|
|
FShowDownArrow: Boolean;
|
|
FShowUpArrow: Boolean;
|
|
FState: TTBViewState;
|
|
FStyle: TTBViewStyle;
|
|
FUpdating: Integer;
|
|
FUsePriorityList: Boolean;
|
|
FValidated: Boolean;
|
|
FWindow: TWinControl;
|
|
FWrapOffset: Integer;
|
|
|
|
procedure DeletingViewer(Viewer: TTBItemViewer);
|
|
procedure DrawItem(Viewer: TTBItemViewer; DrawTo: TCanvas; Offscreen: Boolean);
|
|
procedure FreeViewers;
|
|
function GetViewer(Index: Integer): TTBItemViewer;
|
|
function GetViewerCount: Integer; {$IFDEF JR_D9} inline; {$ENDIF}
|
|
procedure ImagesChanged;
|
|
function InsertItemViewers(const NewIndex: Integer;
|
|
const AItem: TTBCustomItem; const AGroupLevel: Integer;
|
|
const AddToPriorityList, TopOfPriorityList: Boolean): Integer;
|
|
procedure ItemNotification(Ancestor: TTBCustomItem; Relayed: Boolean;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
procedure LinkNotification(Ancestor: TTBCustomItem; Relayed: Boolean;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
procedure RecreateItemViewer(const I: Integer);
|
|
procedure Scroll(ADown: Boolean);
|
|
procedure SetCustomizing(Value: Boolean);
|
|
procedure SetSelected(Value: TTBItemViewer);
|
|
procedure SetUsePriorityList(Value: Boolean);
|
|
procedure StartTimer(const ATimer: TTBViewTimerID; const Interval: Integer);
|
|
procedure StopAllTimers;
|
|
procedure StopTimer(const ATimer: TTBViewTimerID);
|
|
procedure UpdateCurParentItem;
|
|
protected
|
|
FAccObjectInstance: TTBBaseAccObject;
|
|
procedure AutoSize(AWidth, AHeight: Integer); virtual;
|
|
function CalculatePositions(const CanMoveControls: Boolean;
|
|
const AOrientation: TTBViewOrientation;
|
|
AWrapOffset, AChevronOffset, AChevronSize: Integer;
|
|
var ABaseSize, TotalSize: TPoint;
|
|
var AWrappedLines: Integer): Boolean;
|
|
procedure DoUpdatePositions(var ASize: TPoint); virtual;
|
|
function GetChevronItem: TTBCustomItem; virtual;
|
|
procedure GetMargins(AOrientation: TTBViewOrientation; var Margins: TRect);
|
|
virtual;
|
|
function GetMDIButtonsItem: TTBCustomItem; virtual;
|
|
function GetMDISystemMenuItem: TTBCustomItem; virtual;
|
|
function GetParentToolbarView: TTBView;
|
|
function GetRootView: TTBView;
|
|
function HandleWMGetObject(var Message: TMessage): Boolean;
|
|
procedure InitiateActions;
|
|
procedure KeyDown(var Key: Word; Shift: TShiftState); virtual;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
procedure SetAccelsVisibility(AShowAccels: Boolean);
|
|
public
|
|
constructor Create(AOwner: TComponent; AParentView: TTBView;
|
|
AParentItem: TTBCustomItem; AWindow: TWinControl;
|
|
AIsToolbar, ACustomizing, AUsePriorityList: Boolean); reintroduce; virtual;
|
|
destructor Destroy; override;
|
|
procedure BeginUpdate;
|
|
procedure CancelCapture;
|
|
procedure CancelChildPopups;
|
|
procedure CancelMode;
|
|
procedure CloseChildPopups;
|
|
function ContainsView(AView: TTBView): Boolean;
|
|
procedure DrawSubitems(ACanvas: TCanvas);
|
|
procedure EndModal;
|
|
procedure EndModalWithClick(AViewer: TTBItemViewer);
|
|
procedure EndModalWithHelp(AContextID: Integer);
|
|
procedure EndModalWithSystemMenu(AWnd: HWND; AKey: Word);
|
|
procedure EndUpdate;
|
|
procedure EnterToolbarLoop(Options: TTBEnterToolbarLoopOptions);
|
|
procedure ExecuteSelected(AGivePriority: Boolean);
|
|
function Find(Item: TTBCustomItem): TTBItemViewer;
|
|
function FirstSelectable: TTBItemViewer;
|
|
function GetAccObject: TTBBaseAccObject;
|
|
function GetCaptureWnd: HWND;
|
|
function GetFont: TFont; virtual;
|
|
procedure GetOffEdgeControlList(const List: TList);
|
|
procedure GivePriority(AViewer: TTBItemViewer);
|
|
procedure HandleHintShowMessage(var Message: TCMHintShow);
|
|
function HighestPriorityViewer: TTBItemViewer;
|
|
procedure Invalidate(AViewer: TTBItemViewer);
|
|
procedure InvalidatePositions; virtual;
|
|
function IndexOf(AViewer: TTBItemViewer): Integer;
|
|
function IsModalEnding: Boolean;
|
|
function NextSelectable(CurViewer: TTBItemViewer; GoForward: Boolean): TTBItemViewer;
|
|
function NextSelectableWithAccel(CurViewer: TTBItemViewer; Key: Char;
|
|
RequirePrimaryAccel: Boolean; var IsOnlyItemWithAccel: Boolean): TTBItemViewer;
|
|
procedure NotifyFocusEvent;
|
|
function OpenChildPopup(const SelectFirstItem: Boolean): Boolean;
|
|
procedure RecreateAllViewers;
|
|
procedure ScrollSelectedIntoView;
|
|
procedure Select(Value: TTBItemViewer; ViaMouse: Boolean);
|
|
procedure SetCapture;
|
|
procedure TryValidatePositions;
|
|
procedure UpdateSelection(const P: TPoint; const AllowNewSelection: Boolean);
|
|
function UpdatePositions: TPoint;
|
|
procedure ValidatePositions;
|
|
function ViewerFromPoint(const P: TPoint): TTBItemViewer;
|
|
|
|
property BackgroundColor: TColor read FBackgroundColor write FBackgroundColor;
|
|
property BaseSize: TPoint read FBaseSize;
|
|
property Capture: Boolean read FCapture;
|
|
property ChevronOffset: Integer read FChevronOffset write FChevronOffset;
|
|
property ChevronSize: Integer read FChevronSize write FChevronSize;
|
|
property Customizing: Boolean read FCustomizing write SetCustomizing;
|
|
property IsPopup: Boolean read FIsPopup;
|
|
property IsToolbar: Boolean read FIsToolbar;
|
|
property MouseOverSelected: Boolean read FMouseOverSelected;
|
|
property NewViewersGetHighestPriority: Boolean read FNewViewersGetHighestPriority
|
|
write FNewViewersGetHighestPriority;
|
|
property ParentView: TTBView read FParentView;
|
|
property ParentItem: TTBCustomItem read FParentItem;
|
|
property OpenViewer: TTBItemViewer read FOpenViewer;
|
|
property OpenViewerView: TTBView read FOpenViewerView;
|
|
property Orientation: TTBViewOrientation read FOrientation write FOrientation;
|
|
property Selected: TTBItemViewer read FSelected write SetSelected;
|
|
property SelectedViaMouse: Boolean read FSelectedViaMouse;
|
|
property State: TTBViewState read FState;
|
|
property Style: TTBViewStyle read FStyle write FStyle;
|
|
property UsePriorityList: Boolean read FUsePriorityList write SetUsePriorityList;
|
|
property Viewers[Index: Integer]: TTBItemViewer read GetViewer;
|
|
property ViewerCount: Integer read GetViewerCount;
|
|
property Window: TWinControl read FWindow;
|
|
property WrapOffset: Integer read FWrapOffset write FWrapOffset;
|
|
end;
|
|
|
|
TTBRootItemClass = class of TTBRootItem;
|
|
TTBRootItem = class(TTBCustomItem);
|
|
{ same as TTBCustomItem, except there's a property editor for it }
|
|
|
|
TTBItem = class(TTBCustomItem)
|
|
published
|
|
property Action;
|
|
property AutoCheck;
|
|
property Caption;
|
|
property Checked;
|
|
property DisplayMode;
|
|
property Enabled;
|
|
property GroupIndex;
|
|
property HelpContext;
|
|
property Hint;
|
|
property ImageIndex;
|
|
property Images;
|
|
property InheritOptions;
|
|
property MaskOptions;
|
|
property Options;
|
|
property RadioItem;
|
|
property ShortCut;
|
|
property Visible;
|
|
|
|
property OnClick;
|
|
property OnSelect;
|
|
end;
|
|
|
|
TTBGroupItem = class(TTBCustomItem)
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property InheritOptions;
|
|
property LinkSubitems;
|
|
property MaskOptions;
|
|
property Options;
|
|
end;
|
|
|
|
TTBSubmenuItem = class(TTBCustomItem)
|
|
private
|
|
function GetDropdownCombo: Boolean;
|
|
procedure SetDropdownCombo(Value: Boolean);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property Action;
|
|
property AutoCheck;
|
|
property Caption;
|
|
property Checked;
|
|
//property DisplayAsToolbar;
|
|
property DisplayMode;
|
|
property DropdownCombo: Boolean read GetDropdownCombo write SetDropdownCombo default False;
|
|
property Enabled;
|
|
property GroupIndex;
|
|
property HelpContext;
|
|
property Hint;
|
|
property ImageIndex;
|
|
property Images;
|
|
property InheritOptions;
|
|
property LinkSubitems;
|
|
property MaskOptions;
|
|
property Options;
|
|
property RadioItem;
|
|
property ShortCut;
|
|
property SubMenuImages;
|
|
property Visible;
|
|
|
|
property OnClick;
|
|
property OnPopup;
|
|
property OnSelect;
|
|
end;
|
|
|
|
TTBSeparatorItem = class(TTBCustomItem)
|
|
private
|
|
FBlank: Boolean;
|
|
procedure SetBlank(Value: Boolean);
|
|
protected
|
|
function GetItemViewerClass(AView: TTBView): TTBItemViewerClass; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
published
|
|
property Blank: Boolean read FBlank write SetBlank default False;
|
|
property Hint;
|
|
property Visible;
|
|
end;
|
|
|
|
TTBSeparatorItemViewer = class(TTBItemViewer)
|
|
protected
|
|
procedure CalcSize(const Canvas: TCanvas;
|
|
var AWidth, AHeight: Integer); override;
|
|
procedure Paint(const Canvas: TCanvas; const ClientAreaRect: TRect;
|
|
IsSelected, IsPushed, UseDisabledShadow: Boolean); override;
|
|
function UsesSameWidth: Boolean; override;
|
|
end;
|
|
|
|
TTBControlItem = class(TTBCustomItem)
|
|
private
|
|
FControl: TControl;
|
|
FDontFreeControl: Boolean;
|
|
procedure SetControl(Value: TControl);
|
|
protected
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
property DontFreeControl: Boolean read FDontFreeControl write FDontFreeControl;
|
|
published
|
|
property Control: TControl read FControl write SetControl;
|
|
end;
|
|
|
|
TTBPopupView = class(TTBView)
|
|
protected
|
|
procedure AutoSize(AWidth, AHeight: Integer); override;
|
|
public
|
|
function GetFont: TFont; override;
|
|
end;
|
|
|
|
ITBPopupWindow = interface
|
|
['{E45CBE74-1ECF-44CB-B064-6D45B1924708}']
|
|
end;
|
|
|
|
TTBPopupWindow = class(TCustomControl, ITBPopupWindow)
|
|
private
|
|
FAccelsVisibilitySet: Boolean;
|
|
FAnimationDirection: TTBAnimationDirection;
|
|
FView: TTBView;
|
|
procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
|
|
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
|
|
procedure WMClose(var Message: TWMClose); message WM_CLOSE;
|
|
procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
|
|
procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT;
|
|
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
|
|
procedure WMNCPaint(var Message: TMessage); message WM_NCPAINT;
|
|
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
|
|
procedure WMPrint(var Message: TMessage); message WM_PRINT;
|
|
procedure WMPrintClient(var Message: {$IFNDEF CLR} TMessage {$ELSE} TWMPrintClient {$ENDIF}); message WM_PRINTCLIENT;
|
|
protected
|
|
procedure CreateParams(var Params: TCreateParams); override;
|
|
procedure CreateWnd; override;
|
|
procedure DestroyWindowHandle; override;
|
|
function GetViewClass: TTBViewClass; dynamic;
|
|
procedure Paint; override;
|
|
procedure PaintScrollArrows; virtual;
|
|
public
|
|
constructor CreatePopupWindow(AOwner: TComponent; const AParentView: TTBView;
|
|
const AItem: TTBCustomItem; const ACustomizing: Boolean); virtual;
|
|
destructor Destroy; override;
|
|
|
|
property View: TTBView read FView;
|
|
end;
|
|
|
|
ITBItems = interface
|
|
['{A5C0D7CC-3EC4-4090-A0F8-3D03271877EA}']
|
|
function GetItems: TTBCustomItem;
|
|
end;
|
|
|
|
TTBItemContainer = class(TComponent, ITBItems)
|
|
private
|
|
FItem: TTBRootItem;
|
|
function GetImages: TCustomImageList;
|
|
function GetItems: TTBCustomItem;
|
|
procedure SetImages(Value: TCustomImageList);
|
|
protected
|
|
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
property Items: TTBRootItem read FItem;
|
|
published
|
|
property Images: TCustomImageList read GetImages write SetImages;
|
|
end;
|
|
|
|
TTBPopupMenu = class(TPopupMenu, ITBItems)
|
|
private
|
|
FItem: TTBRootItem;
|
|
//procedure SetItems(Value: TTBCustomItem);
|
|
function GetImages: TCustomImageList;
|
|
function GetItems: TTBCustomItem;
|
|
function GetLinkSubitems: TTBCustomItem;
|
|
function GetOptions: TTBItemOptions;
|
|
procedure RootItemClick(Sender: TObject);
|
|
procedure SetImages(Value: TCustomImageList);
|
|
procedure SetLinkSubitems(Value: TTBCustomItem);
|
|
procedure SetOptions(Value: TTBItemOptions);
|
|
protected
|
|
{$IFNDEF JR_D5}
|
|
procedure DoPopup(Sender: TObject);
|
|
{$ENDIF}
|
|
function GetRootItemClass: TTBRootItemClass; dynamic;
|
|
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
|
|
function IsShortCut(var Message: TWMKey): Boolean; override;
|
|
procedure Popup(X, Y: Integer); override;
|
|
function PopupEx(X, Y: Integer; ReturnClickedItemOnly: Boolean = False): TTBCustomItem;
|
|
published
|
|
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 [];
|
|
end;
|
|
|
|
TTBCustomImageList = class(TImageList)
|
|
private
|
|
FCheckedImages: TCustomImageList;
|
|
FCheckedImagesChangeLink: TChangeLink;
|
|
FDisabledImages: TCustomImageList;
|
|
FDisabledImagesChangeLink: TChangeLink;
|
|
FHotImages: TCustomImageList;
|
|
FHotImagesChangeLink: TChangeLink;
|
|
FImagesBitmap: TBitmap;
|
|
FImagesBitmapMaskColor: TColor;
|
|
procedure ChangeImages(var AImageList: TCustomImageList;
|
|
Value: TCustomImageList; AChangeLink: TChangeLink);
|
|
procedure ImageListChanged(Sender: TObject);
|
|
procedure ImagesBitmapChanged(Sender: TObject);
|
|
procedure SetCheckedImages(Value: TCustomImageList);
|
|
procedure SetDisabledImages(Value: TCustomImageList);
|
|
procedure SetHotImages(Value: TCustomImageList);
|
|
procedure SetImagesBitmap(Value: TBitmap);
|
|
procedure SetImagesBitmapMaskColor(Value: TColor);
|
|
{$IFDEF CLR}
|
|
procedure WriteLeft(Writer: TWriter);
|
|
procedure WriteTop(Writer: TWriter);
|
|
{$ENDIF}
|
|
protected
|
|
procedure DefineProperties(Filer: TFiler); override;
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
property CheckedImages: TCustomImageList read FCheckedImages write SetCheckedImages;
|
|
property DisabledImages: TCustomImageList read FDisabledImages write SetDisabledImages;
|
|
property HotImages: TCustomImageList read FHotImages write SetHotImages;
|
|
property ImagesBitmap: TBitmap read FImagesBitmap write SetImagesBitmap;
|
|
property ImagesBitmapMaskColor: TColor read FImagesBitmapMaskColor
|
|
write SetImagesBitmapMaskColor default clFuchsia;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure DrawState(Canvas: TCanvas; X, Y, Index: Integer;
|
|
Enabled, Selected, Checked: Boolean); virtual;
|
|
end;
|
|
|
|
TTBImageList = class(TTBCustomImageList)
|
|
published
|
|
property CheckedImages;
|
|
property DisabledImages;
|
|
property HotImages;
|
|
property ImagesBitmap;
|
|
property ImagesBitmapMaskColor;
|
|
end;
|
|
|
|
const
|
|
{$IFNDEF TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
tbMenuBkColor = clMenu;
|
|
tbMenuTextColor = clMenuText;
|
|
{$ELSE}
|
|
tbMenuBkColor = clBtnFace;
|
|
tbMenuTextColor = clBtnText;
|
|
{$ENDIF}
|
|
|
|
tbMenuVerticalMargin = 4;
|
|
tbMenuImageTextSpace = 1;
|
|
tbMenuLeftTextMargin = 2;
|
|
tbMenuRightTextMargin = 3;
|
|
|
|
tbMenuSeparatorOffset = 12;
|
|
|
|
tbMenuScrollArrowHeight = 19;
|
|
|
|
tbDropdownArrowWidth = 8;
|
|
tbDropdownArrowMargin = 3;
|
|
tbDropdownComboArrowWidth = 11;
|
|
tbDropdownComboMargin = 2;
|
|
|
|
tbLineSpacing = 6;
|
|
tbLineSepOffset = 1;
|
|
tbDockedLineSepOffset = 4;
|
|
|
|
WM_TB2K_CLICKITEM = WM_USER + $100;
|
|
|
|
function TBGetItems(const AObject: TObject): TTBCustomItem;
|
|
procedure TBInitToolbarSystemFont;
|
|
|
|
var
|
|
ToolbarFont: TFont;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFDEF CLR} System.Runtime.InteropServices, System.Text, System.Threading,
|
|
Types, WinUtils, {$ENDIF}
|
|
TB2Consts, TB2Common, IMM, TB2Acc;
|
|
|
|
{$UNDEF ALLOCHWND_CLASSES}
|
|
{$IFNDEF CLR}
|
|
{$IFDEF JR_D6}
|
|
{$DEFINE ALLOCHWND_CLASSES}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
var
|
|
LastPos: TPoint;
|
|
|
|
threadvar
|
|
ClickWndRefCount: Integer;
|
|
ClickWnd: HWND;
|
|
ClickList: TList;
|
|
|
|
type
|
|
TTBModalHandler = class
|
|
private
|
|
FCreatedWnd: Boolean;
|
|
FInited: Boolean;
|
|
FWnd: HWND;
|
|
FRootPopup: TTBPopupWindow;
|
|
FSaveFocusWnd: HWND;
|
|
procedure WndProc(var Msg: TMessage);
|
|
public
|
|
constructor Create(AExistingWnd: HWND);
|
|
destructor Destroy; override;
|
|
procedure Loop(const RootView: TTBView; const AMouseDown, AExecuteSelected,
|
|
AFromMSAA, TrackRightButton: Boolean);
|
|
property RootPopup: TTBPopupWindow read FRootPopup write FRootPopup;
|
|
property Wnd: HWND read FWnd;
|
|
end;
|
|
|
|
TItemChangedNotificationData = class
|
|
private
|
|
Proc: TTBItemChangedProc;
|
|
RefCount: Integer;
|
|
end;
|
|
|
|
{$IFNDEF CLR}
|
|
TComponentAccess = class(TComponent);
|
|
TControlAccess = class(TControl);
|
|
{$ENDIF}
|
|
|
|
const
|
|
ViewTimerBaseID = 9000;
|
|
MaxGroupLevel = 10;
|
|
|
|
|
|
{ Misc. }
|
|
|
|
function TBGetItems(const AObject: TObject): TTBCustomItem;
|
|
{ If AObject is an item, returns AObject, otherwise finds the root item
|
|
associated with AObject. If AObject is not a TTBCustomItem and does not
|
|
implement the ITBItems interface, nil is returned. }
|
|
var
|
|
Intf: ITBItems;
|
|
begin
|
|
if AObject is TTBCustomItem then
|
|
Result := TTBCustomItem(AObject)
|
|
else begin
|
|
{$IFNDEF CLR}
|
|
if AObject.GetInterface(ITBItems, Intf) then
|
|
{$ELSE}
|
|
Intf := ITBItems(AObject);
|
|
if Assigned(Intf) then
|
|
{$ENDIF}
|
|
Result := Intf.GetItems
|
|
else
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure DestroyClickWnd;
|
|
begin
|
|
if ClickWnd <> 0 then begin
|
|
{$IFDEF ALLOCHWND_CLASSES}Classes.{$ENDIF} DeallocateHWnd(ClickWnd);
|
|
ClickWnd := 0;
|
|
end;
|
|
FreeAndNil(ClickList);
|
|
end;
|
|
|
|
procedure ReferenceClickWnd;
|
|
begin
|
|
Inc(ClickWndRefCount);
|
|
end;
|
|
|
|
procedure ReleaseClickWnd;
|
|
begin
|
|
Dec(ClickWndRefCount);
|
|
if ClickWndRefCount = 0 then
|
|
DestroyClickWnd;
|
|
end;
|
|
|
|
procedure QueueClick(const AItem: TObject; const AArg: Integer);
|
|
{ Adds an item to ClickList and posts a message to handle it. AItem must be
|
|
either a TTBCustomItem or TTBItemViewer. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if ClickWnd = 0 then
|
|
ClickWnd := {$IFDEF ALLOCHWND_CLASSES}Classes.{$ENDIF} AllocateHWnd(TTBCustomItem.ClickWndProc);
|
|
if ClickList = nil then
|
|
ClickList := TList.Create;
|
|
|
|
{ Add a new item to ClickList or replace an empty one }
|
|
I := ClickList.IndexOf(nil);
|
|
if I = -1 then
|
|
I := ClickList.Add(AItem)
|
|
else
|
|
ClickList[I] := AItem;
|
|
|
|
PostMessage(ClickWnd, WM_TB2K_CLICKITEM, AArg, I);
|
|
end;
|
|
|
|
procedure RemoveFromClickList(const AItem: TObject);
|
|
{ Any class that potentially calls QueueClick needs to call RemoveFromClickList
|
|
before an instance is destroyed to ensure that any references to the
|
|
instance still in ClickList are removed. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if Assigned(ClickList) and Assigned(AItem) then
|
|
for I := 0 to ClickList.Count-1 do
|
|
if ClickList[I] = AItem then
|
|
ClickList[I] := ClickList;
|
|
{ ^ The special value of ClickList is assigned to the item instead of
|
|
of nil because we want the index to stay reserved until the
|
|
WM_TB2K_CLICKITEM message for the index is processed. We don't want
|
|
the WM_TB2K_CLICKITEM message that's still in the queue to later
|
|
refer to a different item; this would result in queued clicks being
|
|
processed in the wrong order in a case like this:
|
|
A.PostClick; B.PostClick; A.Free; C.PostClick;
|
|
C's click would end up being processed before A's, because C would
|
|
get A's index. }
|
|
end;
|
|
|
|
function ProcessDoneAction(const DoneActionData: TTBDoneActionData;
|
|
const ReturnClickedItemOnly: Boolean): TTBCustomItem;
|
|
begin
|
|
Result := nil;
|
|
case DoneActionData.DoneAction of
|
|
tbdaNone: ;
|
|
tbdaClickItem: begin
|
|
if DoneActionData.Sound and NeedToPlaySound('MenuCommand') then
|
|
PlaySystemSound('MenuCommand');
|
|
Result := DoneActionData.ClickItem;
|
|
if not ReturnClickedItemOnly then
|
|
Result.PostClick;
|
|
end;
|
|
tbdaOpenSystemMenu: begin
|
|
SendMessage(DoneActionData.Wnd, WM_SYSCOMMAND, SC_KEYMENU, DoneActionData.Key);
|
|
end;
|
|
tbdaHelpContext: begin
|
|
{ Based on code in TPopupList.WndProc: }
|
|
if Assigned(Screen.ActiveForm) and
|
|
(biHelp in Screen.ActiveForm.BorderIcons) then
|
|
Application.HelpCommand(HELP_CONTEXTPOPUP, DoneActionData.ContextID)
|
|
else
|
|
Application.HelpContext(DoneActionData.ContextID);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBCustomItemActionLink }
|
|
|
|
procedure TTBCustomItemActionLink.AssignClient(AClient: TObject);
|
|
begin
|
|
FClient := AClient as TTBCustomItem;
|
|
end;
|
|
|
|
{$IFDEF JR_D6}
|
|
function TTBCustomItemActionLink.IsAutoCheckLinked: Boolean;
|
|
begin
|
|
Result := (FClient.AutoCheck = (Action as TCustomAction).AutoCheck);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function TTBCustomItemActionLink.IsCaptionLinked: Boolean;
|
|
begin
|
|
Result := inherited IsCaptionLinked and
|
|
(FClient.Caption = (Action as TCustomAction).Caption);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsCheckedLinked: Boolean;
|
|
begin
|
|
Result := inherited IsCheckedLinked and
|
|
(FClient.Checked = (Action as TCustomAction).Checked);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsEnabledLinked: Boolean;
|
|
begin
|
|
Result := inherited IsEnabledLinked and
|
|
(FClient.Enabled = (Action as TCustomAction).Enabled);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsHelpContextLinked: Boolean;
|
|
begin
|
|
Result := inherited IsHelpContextLinked and
|
|
(FClient.HelpContext = (Action as TCustomAction).HelpContext);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsHintLinked: Boolean;
|
|
begin
|
|
Result := inherited IsHintLinked and
|
|
(FClient.Hint = (Action as TCustomAction).Hint);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsImageIndexLinked: Boolean;
|
|
begin
|
|
Result := inherited IsImageIndexLinked and
|
|
(FClient.ImageIndex = (Action as TCustomAction).ImageIndex);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsShortCutLinked: Boolean;
|
|
begin
|
|
Result := inherited IsShortCutLinked and
|
|
(FClient.ShortCut = (Action as TCustomAction).ShortCut);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsVisibleLinked: Boolean;
|
|
begin
|
|
Result := inherited IsVisibleLinked and
|
|
(FClient.Visible = (Action as TCustomAction).Visible);
|
|
end;
|
|
|
|
function TTBCustomItemActionLink.IsOnExecuteLinked: Boolean;
|
|
begin
|
|
Result := inherited IsOnExecuteLinked and
|
|
{$IFNDEF CLR}
|
|
MethodsEqual(TMethod(FClient.OnClick), TMethod(Action.OnExecute));
|
|
{$ELSE}
|
|
(@FClient.OnClick = @Action.OnExecute);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{$IFDEF JR_D6}
|
|
procedure TTBCustomItemActionLink.SetAutoCheck(Value: Boolean);
|
|
begin
|
|
if IsAutoCheckLinked then FClient.AutoCheck := Value;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TTBCustomItemActionLink.SetCaption(const Value: string);
|
|
begin
|
|
if IsCaptionLinked then FClient.Caption := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetChecked(Value: Boolean);
|
|
begin
|
|
if IsCheckedLinked then FClient.Checked := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetEnabled(Value: Boolean);
|
|
begin
|
|
if IsEnabledLinked then FClient.Enabled := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetHelpContext(Value: THelpContext);
|
|
begin
|
|
if IsHelpContextLinked then FClient.HelpContext := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetHint(const Value: string);
|
|
begin
|
|
if IsHintLinked then FClient.Hint := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetImageIndex(Value: Integer);
|
|
begin
|
|
if IsImageIndexLinked then FClient.ImageIndex := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetShortCut(Value: TShortCut);
|
|
begin
|
|
if IsShortCutLinked then FClient.ShortCut := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetVisible(Value: Boolean);
|
|
begin
|
|
if IsVisibleLinked then FClient.Visible := Value;
|
|
end;
|
|
|
|
procedure TTBCustomItemActionLink.SetOnExecute(Value: TNotifyEvent);
|
|
begin
|
|
if IsOnExecuteLinked then FClient.OnClick := Value;
|
|
end;
|
|
|
|
|
|
{ TTBCustomItem }
|
|
|
|
{}function ItemContainingItems(const AItem: TTBCustomItem): TTBCustomItem;
|
|
begin
|
|
if Assigned(AItem) and Assigned(AItem.FLinkSubitems) then
|
|
Result := AItem.FLinkSubitems
|
|
else
|
|
Result := AItem;
|
|
end;
|
|
|
|
constructor TTBCustomItem.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FEnabled := True;
|
|
FImageIndex := -1;
|
|
FInheritOptions := True;
|
|
FItemStyle := [tbisSelectable, tbisRedrawOnSelChange, tbisRedrawOnMouseOverChange];
|
|
FVisible := True;
|
|
ReferenceClickWnd;
|
|
end;
|
|
|
|
destructor TTBCustomItem.Destroy;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Destroying;
|
|
RemoveFromClickList(Self);
|
|
{ Changed in 0.33. Moved FParent.Remove call *after* the child items are
|
|
deleted. }
|
|
for I := Count-1 downto 0 do
|
|
Items[I].Free;
|
|
if Assigned(FParent) then
|
|
FParent.Remove(Self);
|
|
FreeAndNil(FItems);
|
|
FActionLink.Free;
|
|
FActionLink := nil;
|
|
FreeAndNil(FSubMenuImagesChangeLink);
|
|
FreeAndNil(FImagesChangeLink);
|
|
inherited;
|
|
if Assigned(FNotifyList) then begin
|
|
for I := FNotifyList.Count-1 downto 0 do
|
|
TItemChangedNotificationData(FNotifyList[I]).Free;
|
|
FNotifyList.Free;
|
|
end;
|
|
FLinkParents.Free;
|
|
ReleaseClickWnd;
|
|
end;
|
|
|
|
{$IFDEF JR_D6}
|
|
function TTBCustomItem.IsAutoCheckStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsAutoCheckLinked;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function TTBCustomItem.IsCaptionStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsCaptionLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsCheckedStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsCheckedLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsEnabledStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsEnabledLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsHintStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsHintLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsHelpContextStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsHelpContextLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsImageIndexStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsImageIndexLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsShortCutStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsShortCutLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsVisibleStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsVisibleLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.IsOnClickStored: Boolean;
|
|
begin
|
|
Result := (ActionLink = nil) or not FActionLink.IsOnExecuteLinked;
|
|
end;
|
|
|
|
function TTBCustomItem.GetAction: TBasicAction;
|
|
begin
|
|
if FActionLink <> nil then
|
|
Result := FActionLink.Action
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTBCustomItem.GetActionLinkClass: TTBCustomItemActionLinkClass;
|
|
begin
|
|
Result := TTBCustomItemActionLink;
|
|
end;
|
|
|
|
procedure TTBCustomItem.DoActionChange(Sender: TObject);
|
|
begin
|
|
if Sender = Action then ActionChange(Sender, False);
|
|
end;
|
|
|
|
procedure TTBCustomItem.ActionChange(Sender: TObject; CheckDefaults: Boolean);
|
|
begin
|
|
if Action is TCustomAction then
|
|
with TCustomAction(Sender) do
|
|
begin
|
|
{$IFDEF JR_D6}
|
|
if not CheckDefaults or (Self.AutoCheck = False) then
|
|
Self.AutoCheck := AutoCheck;
|
|
{$ENDIF}
|
|
if not CheckDefaults or (Self.Caption = '') then
|
|
Self.Caption := Caption;
|
|
if not CheckDefaults or (Self.Checked = False) then
|
|
Self.Checked := Checked;
|
|
if not CheckDefaults or (Self.Enabled = True) then
|
|
Self.Enabled := Enabled;
|
|
if not CheckDefaults or (Self.HelpContext = 0) then
|
|
Self.HelpContext := HelpContext;
|
|
if not CheckDefaults or (Self.Hint = '') then
|
|
Self.Hint := Hint;
|
|
if not CheckDefaults or (Self.ImageIndex = -1) then
|
|
Self.ImageIndex := ImageIndex;
|
|
if not CheckDefaults or (Self.ShortCut = scNone) then
|
|
Self.ShortCut := ShortCut;
|
|
if not CheckDefaults or (Self.Visible = True) then
|
|
Self.Visible := Visible;
|
|
if not CheckDefaults or not Assigned(Self.OnClick) then
|
|
Self.OnClick := OnExecute;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetAction(Value: TBasicAction);
|
|
begin
|
|
if Value = nil then begin
|
|
FActionLink.Free;
|
|
FActionLink := nil;
|
|
end
|
|
else begin
|
|
if FActionLink = nil then
|
|
FActionLink := GetActionLinkClass.Create(Self);
|
|
FActionLink.Action := Value;
|
|
FActionLink.OnChange := DoActionChange;
|
|
{ Note: Delphi's Controls.pas and Menus.pas merely check for
|
|
"csLoading in Value.ComponentState" here. But that doesn't help when
|
|
the Action property references an action on another form / data module
|
|
that has already finished loading. So we check two things:
|
|
1. csLoading in Value.ComponentState
|
|
2. csLoading in ComponentState
|
|
In the typical case where the item and action list reside on the same
|
|
form, #1 and #2 are both true.
|
|
Only #1 is true when Action references an action on another form / data
|
|
module that is created *after* the item (e.g. if Form1.TBItem1.Action =
|
|
Form2.Action1, and Form1 is created before Form2).
|
|
Only #2 is true when Action references an action on another form / data
|
|
module that is created *before* the item (e.g. if Form2.TBItem1.Action =
|
|
Form1.Action1, and Form1 is created before Form2). }
|
|
ActionChange(Value, (csLoading in Value.ComponentState) or
|
|
(csLoading in ComponentState));
|
|
Value.FreeNotification(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.InitiateAction;
|
|
begin
|
|
if FActionLink <> nil then FActionLink.Update;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Loaded;
|
|
begin
|
|
inherited;
|
|
if Action <> nil then ActionChange(Action, True);
|
|
end;
|
|
|
|
procedure TTBCustomItem.GetChildren(Proc: TGetChildProc; Root: TComponent);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to Count-1 do
|
|
Proc(Items[I]);
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetChildOrder(Child: TComponent; Order: Integer);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := IndexOf(Child as TTBCustomItem);
|
|
if I <> -1 then
|
|
Move(I, Order);
|
|
end;
|
|
|
|
function TTBCustomItem.HasParent: Boolean;
|
|
begin
|
|
Result := True;
|
|
end;
|
|
|
|
function TTBCustomItem.GetParentComponent: TComponent;
|
|
begin
|
|
if (FParent <> nil) and (FParent.FParentComponent <> nil) then
|
|
Result := FParent.FParentComponent
|
|
else
|
|
Result := FParent;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetName(const NewName: TComponentName);
|
|
begin
|
|
if Name <> NewName then begin
|
|
inherited;
|
|
if Assigned(FParent) then
|
|
FParent.Notify(tbicNameChanged, -1, Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetParentComponent(Value: TComponent);
|
|
var
|
|
RootItem: TTBCustomItem;
|
|
begin
|
|
if FParent <> nil then FParent.Remove(Self);
|
|
if Value <> nil then begin
|
|
RootItem := TBGetItems(Value);
|
|
if Assigned(RootItem) then
|
|
RootItem.Add(Self)
|
|
else
|
|
raise ETBItemError.CreateFmt(STBToolbarItemParentInvalid, [Value.ClassName]);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited;
|
|
if Operation = opRemove then begin
|
|
RemoveFromList(FLinkParents, AComponent);
|
|
if AComponent = Action then Action := nil;
|
|
if AComponent = Images then Images := nil;
|
|
if AComponent = SubMenuImages then SubMenuImages := nil;
|
|
if AComponent = LinkSubitems then LinkSubitems := nil;
|
|
end;
|
|
end;
|
|
|
|
class procedure TTBCustomItem.IndexError;
|
|
begin
|
|
raise ETBItemError.Create(STBToolbarIndexOutOfBounds);
|
|
end;
|
|
|
|
class procedure TTBCustomItem.ClickWndProc(var Message: TMessage);
|
|
var
|
|
List: TList;
|
|
I: Integer;
|
|
Item: TObject;
|
|
begin
|
|
if Message.Msg = WM_TB2K_CLICKITEM then begin
|
|
List := ClickList; { optimization... }
|
|
if Assigned(List) then begin
|
|
I := ClipToLongint(Message.LParam);
|
|
if (I >= 0) and (I < List.Count) then begin
|
|
Item := List[I];
|
|
List[I] := nil;
|
|
{ If the item value is set to ClickList, then it was 'removed' from
|
|
the list by RemoveFromClickList }
|
|
if Item = List then
|
|
Item := nil;
|
|
end
|
|
else
|
|
Item := nil;
|
|
|
|
{ Remove trailing nil items from ClickList. This is not *necessary*, but
|
|
it will make RemoveFromClickList faster if we clean out items that
|
|
aren't used, and may never be used again. }
|
|
for I := List.Count-1 downto 0 do begin
|
|
if List[I] = nil then
|
|
List.Delete(I)
|
|
else
|
|
Break;
|
|
end;
|
|
|
|
if Assigned(Item) then begin
|
|
try
|
|
if Item is TTBCustomItem then
|
|
TTBCustomItem(Item).Click
|
|
else if Item is TTBItemViewer then
|
|
TTBItemViewer(Item).AccSelect(Message.WParam <> 0);
|
|
except
|
|
Application.HandleException(Item);
|
|
end;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
with Message do
|
|
Result := DefWindowProc(ClickWnd, Msg, wParam, lParam);
|
|
end;
|
|
|
|
procedure TTBCustomItem.PostClick;
|
|
{ Posts a message to the message queue that causes the item's Click handler to
|
|
be executed when control is returned to the message loop.
|
|
This should be called instead of Click when a WM_SYSCOMMAND message is
|
|
(possibly) currently being handled, because TApplication.WndProc's
|
|
CM_APPSYSCOMMAND handler disables the VCL's processing of focus messages
|
|
until the Perform(WM_SYSCOMMAND, ...) call returns. (An OnClick handler which
|
|
calls TForm.ShowModal needs focus messages to be enabled or else the form
|
|
will be shown with no initial focus.) }
|
|
begin
|
|
QueueClick(Self, 0);
|
|
end;
|
|
|
|
procedure TTBCustomItem.Click;
|
|
begin
|
|
if Enabled then begin
|
|
{ Following code based on D6's TMenuItem.Click }
|
|
{$IFDEF JR_D6}
|
|
if (not Assigned(ActionLink) and AutoCheck) or
|
|
(Assigned(ActionLink) and not ActionLink.IsAutoCheckLinked and AutoCheck) then
|
|
{$ELSE}
|
|
if AutoCheck then
|
|
{$ENDIF}
|
|
Checked := not Checked;
|
|
{ Following code based on D4's TControl.Click }
|
|
{ Call OnClick if assigned and not equal to associated action's OnExecute.
|
|
If associated action's OnExecute assigned then call it, otherwise, call
|
|
OnClick. }
|
|
if Assigned(FOnClick) and (Action <> nil) and
|
|
{$IFNDEF CLR}
|
|
not MethodsEqual(TMethod(FOnClick), TMethod(Action.OnExecute)) then
|
|
{$ELSE}
|
|
(@FOnClick <> @Action.OnExecute) then
|
|
{$ENDIF}
|
|
FOnClick(Self)
|
|
else
|
|
if not(csDesigning in ComponentState) and (ActionLink <> nil) then
|
|
ActionLink.Execute {$IFDEF JR_D6}(Self){$ENDIF}
|
|
else
|
|
if Assigned(FOnClick) then
|
|
FOnClick(Self);
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.GetCount: Integer;
|
|
begin
|
|
if FItems = nil then
|
|
Result := 0
|
|
else
|
|
Result := FItems.Count;
|
|
end;
|
|
|
|
function TTBCustomItem.GetItem(Index: Integer): TTBCustomItem;
|
|
begin
|
|
if (FItems = nil) or (Index < 0) or (Index >= FItems.Count) then begin
|
|
IndexError;
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
Result := TTBCustomItem(FItems.List[Index]);
|
|
end;
|
|
|
|
procedure TTBCustomItem.Add(AItem: TTBCustomItem);
|
|
begin
|
|
Insert(Count, AItem);
|
|
end;
|
|
|
|
procedure TTBCustomItem.InternalNotify(Ancestor: TTBCustomItem;
|
|
NestingLevel: Integer; Action: TTBItemChangedAction; Index: Integer;
|
|
Item: TTBCustomItem);
|
|
{ Note: Ancestor is Item's parent, or in the case of a group item relayed
|
|
notification, it can also be a group item which *links* to Item's parent
|
|
(i.e. ItemContainingItems(Ancestor) = Item.Parent). }
|
|
|
|
procedure RelayToParentOf(const AItem: TTBCustomItem);
|
|
begin
|
|
if NestingLevel > MaxGroupLevel then
|
|
Exit;
|
|
if (tbisEmbeddedGroup in AItem.ItemStyle) and Assigned(AItem.Parent) then begin
|
|
if Ancestor = Self then
|
|
AItem.Parent.InternalNotify(AItem, NestingLevel + 1, Action, Index, Item)
|
|
else
|
|
{ Don't alter Ancestor on subsequent relays; only on the first. }
|
|
AItem.Parent.InternalNotify(Ancestor, NestingLevel + 1, Action, Index, Item);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
P: TTBCustomItem;
|
|
SaveProc: TTBItemChangedProc;
|
|
begin
|
|
{ If Self is a group item, relay the notification to the parent }
|
|
RelayToParentOf(Self);
|
|
{ If any group items are linked to Self, relay the notification to
|
|
those items' parents }
|
|
if Assigned(FLinkParents) then
|
|
for I := 0 to FLinkParents.Count-1 do begin
|
|
P := TTBCustomItem(FLinkParents[I]);
|
|
if P <> Parent then
|
|
RelayToParentOf(P);
|
|
end;
|
|
if Assigned(FNotifyList) then begin
|
|
I := 0;
|
|
while I < FNotifyList.Count do begin
|
|
with TItemChangedNotificationData(FNotifyList[I]) do begin
|
|
SaveProc := Proc;
|
|
Proc(Ancestor, Ancestor <> Self, Action, Index, Item);
|
|
end;
|
|
{ Is I now out of bounds? }
|
|
if I >= FNotifyList.Count then
|
|
Break;
|
|
{ Only proceed to the next index if the list didn't change }
|
|
{$IFNDEF CLR}
|
|
if MethodsEqual(TMethod(TItemChangedNotificationData(FNotifyList[I]).Proc),
|
|
TMethod(SaveProc)) then
|
|
{$ELSE}
|
|
if @TItemChangedNotificationData(FNotifyList[I]).Proc = @SaveProc then
|
|
{$ENDIF}
|
|
Inc(I);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Notify(Action: TTBItemChangedAction; Index: Integer;
|
|
Item: TTBCustomItem);
|
|
begin
|
|
InternalNotify(Self, 0, Action, Index, Item);
|
|
end;
|
|
|
|
procedure TTBCustomItem.ViewBeginUpdate;
|
|
begin
|
|
Notify(tbicSubitemsBeginUpdate, -1, nil);
|
|
end;
|
|
|
|
procedure TTBCustomItem.ViewEndUpdate;
|
|
begin
|
|
Notify(tbicSubitemsEndUpdate, -1, nil);
|
|
end;
|
|
|
|
procedure TTBCustomItem.Insert(NewIndex: Integer; AItem: TTBCustomItem);
|
|
begin
|
|
if Assigned(AItem.FParent) then
|
|
raise ETBItemError.Create(STBToolbarItemReinserted);
|
|
if (NewIndex < 0) or (NewIndex > Count) then IndexError;
|
|
if FItems = nil then
|
|
FItems := TList.Create;
|
|
FItems.Insert(NewIndex, AItem);
|
|
AItem.FParent := Self;
|
|
ViewBeginUpdate;
|
|
try
|
|
Notify(tbicInserted, NewIndex, AItem);
|
|
AItem.RefreshOptions;
|
|
finally
|
|
ViewEndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Delete(Index: Integer);
|
|
var
|
|
Item: TTBCustomItem;
|
|
begin
|
|
Item := Items[Index]; { will raise exception if out of range }
|
|
Notify(tbicDeleting, Index, Item);
|
|
Item.FParent := nil;
|
|
FItems.Delete(Index);
|
|
end;
|
|
|
|
function TTBCustomItem.IndexOf(AItem: TTBCustomItem): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to Count-1 do
|
|
if FItems.List[I] = AItem then begin
|
|
Result := I;
|
|
Exit;
|
|
end;
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Remove(Item: TTBCustomItem);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
I := IndexOf(Item);
|
|
//if I = -1 then raise ETBItemError.Create(STBToolbarItemNotFound);
|
|
if I <> -1 then
|
|
Delete(I);
|
|
end;
|
|
|
|
procedure TTBCustomItem.Clear;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := Count-1 downto 0 do
|
|
Items[I].Free;
|
|
end;
|
|
|
|
procedure TTBCustomItem.Move(CurIndex, NewIndex: Integer);
|
|
var
|
|
Item: TTBCustomItem;
|
|
begin
|
|
if CurIndex <> NewIndex then begin
|
|
if (NewIndex < 0) or (NewIndex >= Count) then IndexError;
|
|
Item := Items[CurIndex];
|
|
ViewBeginUpdate;
|
|
try
|
|
Delete(CurIndex);
|
|
Insert(NewIndex, Item);
|
|
finally
|
|
ViewEndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.ContainsItem(AItem: TTBCustomItem): Boolean;
|
|
begin
|
|
while Assigned(AItem) and (AItem <> Self) do
|
|
AItem := AItem.Parent;
|
|
Result := Assigned(AItem);
|
|
end;
|
|
|
|
procedure TTBCustomItem.RegisterNotification(ANotify: TTBItemChangedProc);
|
|
var
|
|
I: Integer;
|
|
Data: TItemChangedNotificationData;
|
|
begin
|
|
if FNotifyList = nil then FNotifyList := TList.Create;
|
|
for I := 0 to FNotifyList.Count-1 do
|
|
with TItemChangedNotificationData(FNotifyList[I]) do
|
|
{$IFNDEF CLR}
|
|
if MethodsEqual(TMethod(ANotify), TMethod(Proc)) then begin
|
|
{$ELSE}
|
|
if @ANotify = @Proc then begin
|
|
{$ENDIF}
|
|
Inc(RefCount);
|
|
Exit;
|
|
end;
|
|
FNotifyList.Expand;
|
|
Data := TItemChangedNotificationData.Create;
|
|
Data.Proc := ANotify;
|
|
Data.RefCount := 1;
|
|
FNotifyList.Add(Data);
|
|
end;
|
|
|
|
procedure TTBCustomItem.UnregisterNotification(ANotify: TTBItemChangedProc);
|
|
var
|
|
I: Integer;
|
|
Data: TItemChangedNotificationData;
|
|
begin
|
|
if Assigned(FNotifyList) then
|
|
for I := 0 to FNotifyList.Count-1 do begin
|
|
Data := TItemChangedNotificationData(FNotifyList[I]);
|
|
{$IFNDEF CLR}
|
|
if MethodsEqual(TMethod(Data.Proc), TMethod(ANotify)) then begin
|
|
{$ELSE}
|
|
if @Data.Proc = @ANotify then begin
|
|
{$ENDIF}
|
|
Dec(Data.RefCount);
|
|
if Data.RefCount = 0 then begin
|
|
FNotifyList.Delete(I);
|
|
Data.Free;
|
|
if FNotifyList.Count = 0 then
|
|
FreeAndNil(FNotifyList);
|
|
end;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.GetPopupWindowClass: TTBPopupWindowClass;
|
|
begin
|
|
Result := TTBPopupWindow;
|
|
end;
|
|
|
|
procedure TTBCustomItem.DoPopup(Sender: TTBCustomItem; FromLink: Boolean);
|
|
begin
|
|
if Assigned(FOnPopup) then
|
|
FOnPopup(Sender, FromLink);
|
|
if not(tbisCombo in ItemStyle) then
|
|
Click;
|
|
end;
|
|
|
|
var
|
|
PlayedSound: Boolean = False;
|
|
|
|
function TTBCustomItem.CreatePopup(const ParentView: TTBView;
|
|
const ParentViewer: TTBItemViewer; const PositionAsSubmenu, SelectFirstItem,
|
|
Customizing: Boolean; const APopupPoint: TPoint;
|
|
const Alignment: TTBPopupAlignment): TTBPopupWindow;
|
|
|
|
function CountObscured(X, Y, W, H: Integer): Integer;
|
|
var
|
|
I: Integer;
|
|
P: TPoint;
|
|
V: TTBItemViewer;
|
|
begin
|
|
Result := 0;
|
|
if ParentView = nil then
|
|
Exit;
|
|
P := ParentView.FWindow.ClientToScreen(Point(0, 0));
|
|
Dec(X, P.X);
|
|
Dec(Y, P.Y);
|
|
Inc(W, X);
|
|
Inc(H, Y);
|
|
for I := 0 to ParentView.FViewers.Count-1 do begin
|
|
V := ParentView.Viewers[I];
|
|
if V.Show and (V.BoundsRect.Left >= X) and (V.BoundsRect.Right <= W) and
|
|
(V.BoundsRect.Top >= Y) and (V.BoundsRect.Bottom <= H) then
|
|
Inc(Result);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
EventItem, ParentItem: TTBCustomItem;
|
|
Opposite: Boolean;
|
|
ChevronParentView: TTBView;
|
|
X, X2, Y, Y2, W, H: Integer;
|
|
P: TPoint;
|
|
RepeatCalcX: Boolean;
|
|
ParentItemRect: TRect;
|
|
MonitorRect: TRect;
|
|
AnimDir: TTBAnimationDirection;
|
|
begin
|
|
EventItem := ItemContainingItems(Self);
|
|
if EventItem <> Self then
|
|
EventItem.DoPopup(Self, True);
|
|
DoPopup(Self, False);
|
|
|
|
ChevronParentView := GetChevronParentView;
|
|
if ChevronParentView = nil then
|
|
ParentItem := Self
|
|
else
|
|
ParentItem := ChevronParentView.FParentItem;
|
|
|
|
Opposite := Assigned(ParentView) and (vsOppositePopup in ParentView.FState);
|
|
Result := GetPopupWindowClass.CreatePopupWindow(nil, ParentView, ParentItem,
|
|
Customizing);
|
|
try
|
|
if Assigned(ChevronParentView) then begin
|
|
ChevronParentView.FreeNotification(Result.View);
|
|
Result.View.FChevronParentView := ChevronParentView;
|
|
Result.View.FIsToolbar := True;
|
|
Result.View.Style := Result.View.Style +
|
|
(ChevronParentView.Style * [vsAlwaysShowHints]);
|
|
Result.Color := clBtnFace;
|
|
end;
|
|
|
|
{ Calculate ParentItemRect, and MonitorRect (the rectangle of the monitor
|
|
that the popup window will be confined to) }
|
|
if Assigned(ParentView) then begin
|
|
ParentView.ValidatePositions;
|
|
ParentItemRect := ParentViewer.BoundsRect;
|
|
P := ParentView.FWindow.ClientToScreen(Point(0, 0));
|
|
OffsetRect(ParentItemRect, P.X, P.Y);
|
|
if not IsRectEmpty(ParentView.FMonitorRect) then
|
|
MonitorRect := ParentView.FMonitorRect
|
|
else
|
|
MonitorRect := GetRectOfMonitorContainingRect(ParentItemRect, False);
|
|
end
|
|
else begin
|
|
ParentItemRect.TopLeft := APopupPoint;
|
|
ParentItemRect.BottomRight := APopupPoint;
|
|
MonitorRect := GetRectOfMonitorContainingPoint(APopupPoint, False);
|
|
end;
|
|
Result.View.FMonitorRect := MonitorRect;
|
|
|
|
{ Initialize item positions and size of the popup window }
|
|
if ChevronParentView = nil then
|
|
Result.View.FMaxHeight := (MonitorRect.Bottom - MonitorRect.Top) -
|
|
(PopupMenuWindowNCSize * 2)
|
|
else
|
|
Result.View.WrapOffset := (MonitorRect.Right - MonitorRect.Left) -
|
|
(PopupMenuWindowNCSize * 2);
|
|
if SelectFirstItem then
|
|
Result.View.Selected := Result.View.FirstSelectable;
|
|
Result.View.UpdatePositions;
|
|
W := Result.Width;
|
|
H := Result.Height;
|
|
|
|
{ Calculate initial X,Y position of the popup window }
|
|
if Assigned(ParentView) then begin
|
|
if not PositionAsSubmenu then begin
|
|
if ChevronParentView = nil then begin
|
|
if (ParentView = nil) or (ParentView.FOrientation <> tbvoVertical) then begin
|
|
if GetSystemMetrics(SM_MENUDROPALIGNMENT) = 0 then
|
|
X := ParentItemRect.Left
|
|
else
|
|
X := ParentItemRect.Right - W;
|
|
Y := ParentItemRect.Bottom;
|
|
end
|
|
else begin
|
|
X := ParentItemRect.Left - W;
|
|
Y := ParentItemRect.Top;
|
|
end;
|
|
end
|
|
else begin
|
|
if ChevronParentView.FOrientation <> tbvoVertical then begin
|
|
X := ParentItemRect.Right - W;
|
|
Y := ParentItemRect.Bottom;
|
|
end
|
|
else begin
|
|
X := ParentItemRect.Left - W;
|
|
Y := ParentItemRect.Top;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
X := ParentItemRect.Right - PopupMenuWindowNCSize;
|
|
Y := ParentItemRect.Top - PopupMenuWindowNCSize;
|
|
end;
|
|
end
|
|
else begin
|
|
X := APopupPoint.X;
|
|
Y := APopupPoint.Y;
|
|
case Alignment of
|
|
tbpaRight: Dec(X, W);
|
|
tbpaCenter: Dec(X, W div 2);
|
|
end;
|
|
end;
|
|
|
|
{ Adjust the Y position of the popup window }
|
|
{ If the window is going off the bottom of the monitor, try placing it
|
|
above the parent item }
|
|
if (Y + H > MonitorRect.Bottom) and
|
|
((ParentView = nil) or (ParentView.FOrientation <> tbvoVertical)) then begin
|
|
if not PositionAsSubmenu then
|
|
Y2 := ParentItemRect.Top
|
|
else
|
|
Y2 := ParentItemRect.Bottom + PopupMenuWindowNCSize;
|
|
Dec(Y2, H);
|
|
{ Only place it above the parent item if it isn't going to go off the
|
|
top of the monitor }
|
|
if Y2 >= MonitorRect.Top then
|
|
Y := Y2;
|
|
end;
|
|
{ If it's still going off the bottom (which can be possible if a menu bar
|
|
was off the screen to begin with), clip it to the bottom of the monitor }
|
|
if Y + H > MonitorRect.Bottom then
|
|
Y := MonitorRect.Bottom - H;
|
|
if Y < MonitorRect.Top then
|
|
Y := MonitorRect.Top;
|
|
|
|
{ Other adjustments to the position of the popup window }
|
|
if not PositionAsSubmenu then begin
|
|
if (ParentView = nil) and (Alignment = tbpaRight) and (X < MonitorRect.Left) then
|
|
Inc(X, W);
|
|
if X + W > MonitorRect.Right then begin
|
|
if Assigned(ParentView) or (Alignment <> tbpaLeft) then
|
|
X := MonitorRect.Right;
|
|
Dec(X, W);
|
|
end;
|
|
if X < MonitorRect.Left then
|
|
X := MonitorRect.Left;
|
|
if (ParentView = nil) or (ParentView.FOrientation <> tbvoVertical) then begin
|
|
Y2 := ParentItemRect.Top - H;
|
|
if Y2 >= MonitorRect.Top then begin
|
|
{ Would the popup window obscure less items if it popped out to the
|
|
top instead? }
|
|
if (CountObscured(X, Y2, W, H) < CountObscured(X, Y, W, H)) or
|
|
((Y < ParentItemRect.Bottom) and (Y + H > ParentItemRect.Top) and
|
|
(X < ParentItemRect.Right) and (X + W > ParentItemRect.Left)) then
|
|
Y := Y2;
|
|
end;
|
|
{ Make sure a tall popup window doesn't overlap the parent item }
|
|
if (Y < ParentItemRect.Bottom) and (Y + H > ParentItemRect.Top) and
|
|
(X < ParentItemRect.Right) and (X + W > ParentItemRect.Left) then begin
|
|
if ParentItemRect.Right + W <= MonitorRect.Right then
|
|
X := ParentItemRect.Right
|
|
else
|
|
X := ParentItemRect.Left - W;
|
|
if X < MonitorRect.Top then
|
|
X := MonitorRect.Top;
|
|
end;
|
|
end
|
|
else begin
|
|
X2 := ParentItemRect.Right;
|
|
if X2 + W <= MonitorRect.Right then begin
|
|
{ Would the popup window obscure less items if it popped out to the
|
|
right instead? }
|
|
if (CountObscured(X2, Y, W, H) < CountObscured(X, Y, W, H)) or
|
|
((Y < ParentItemRect.Bottom) and (Y + H > ParentItemRect.Top) and
|
|
(X < ParentItemRect.Right) and (X + W > ParentItemRect.Left)) then
|
|
X := X2;
|
|
end;
|
|
{ Make sure a wide popup window doesn't overlap the parent item }
|
|
if (Y < ParentItemRect.Bottom) and (Y + H > ParentItemRect.Top) and
|
|
(X < ParentItemRect.Right) and (X + W > ParentItemRect.Left) then begin
|
|
if ParentItemRect.Bottom + H <= MonitorRect.Bottom then
|
|
Y := ParentItemRect.Bottom
|
|
else
|
|
Y := ParentItemRect.Top - H;
|
|
if Y < MonitorRect.Top then
|
|
Y := MonitorRect.Top;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
{ Make nested submenus go from left to right on the screen. Each it
|
|
runs out of space on the screen, switch directions }
|
|
repeat
|
|
RepeatCalcX := False;
|
|
X2 := X;
|
|
if Opposite or (X2 + W > MonitorRect.Right) then begin
|
|
if Assigned(ParentView) then
|
|
X2 := ParentItemRect.Left + PopupMenuWindowNCSize;
|
|
Dec(X2, W);
|
|
if not Opposite then
|
|
Include(Result.View.FState, vsOppositePopup)
|
|
else begin
|
|
if X2 < MonitorRect.Left then begin
|
|
Opposite := False;
|
|
RepeatCalcX := True;
|
|
end
|
|
else
|
|
Include(Result.View.FState, vsOppositePopup);
|
|
end;
|
|
end;
|
|
until not RepeatCalcX;
|
|
X := X2;
|
|
if X < MonitorRect.Left then
|
|
X := MonitorRect.Left;
|
|
end;
|
|
|
|
{ Determine animation direction }
|
|
AnimDir := [];
|
|
if not PositionAsSubmenu then begin
|
|
if Y >= ParentItemRect.Bottom then
|
|
Include(AnimDir, tbadDown)
|
|
else if Y + H <= ParentItemRect.Top then
|
|
Include(AnimDir, tbadUp);
|
|
if X >= ParentItemRect.Right then
|
|
Include(AnimDir, tbadRight)
|
|
else if X + W <= ParentItemRect.Left then
|
|
Include(AnimDir, tbadLeft);
|
|
end
|
|
else begin
|
|
if X + W div 2 >= ParentItemRect.Left + (ParentItemRect.Right - ParentItemRect.Left) div 2 then
|
|
Include(AnimDir, tbadRight)
|
|
else
|
|
Include(AnimDir, tbadLeft);
|
|
end;
|
|
Result.FAnimationDirection := AnimDir;
|
|
|
|
Result.SetBounds(X, Y, W, H);
|
|
if Assigned(ParentView) then begin
|
|
Result.FreeNotification(ParentView);
|
|
ParentView.FOpenViewerWindow := Result;
|
|
ParentView.FOpenViewerView := Result.View;
|
|
ParentView.FOpenViewer := ParentViewer;
|
|
if ParentView.FIsToolbar then begin
|
|
Include(ParentView.FState, vsDropDownMenus);
|
|
ParentView.Invalidate(ParentViewer);
|
|
ParentView.FWindow.Update;
|
|
end;
|
|
end;
|
|
Include(Result.View.FState, vsDrawInOrder);
|
|
if not NeedToPlaySound('MenuPopup') then begin
|
|
{ Don't call PlaySound if we don't have to }
|
|
Result.Visible := True;
|
|
end
|
|
else begin
|
|
if not PlayedSound then begin
|
|
{ Work around Windows 2000 "bug" where there's a 1/3 second delay upon the
|
|
first call to PlaySound (or sndPlaySound) by painting the window
|
|
completely first. This way the delay isn't very noticable. }
|
|
PlayedSound := True;
|
|
Result.Visible := True;
|
|
Result.Update;
|
|
PlaySystemSound('MenuPopup');
|
|
end
|
|
else begin
|
|
PlaySystemSound('MenuPopup');
|
|
Result.Visible := True;
|
|
end;
|
|
end;
|
|
CallNotifyWinEvent(EVENT_SYSTEM_MENUPOPUPSTART, Result.View.FWindow.Handle,
|
|
OBJID_CLIENT, CHILDID_SELF);
|
|
{ Call NotifyFocusEvent now that the window is visible }
|
|
if Assigned(Result.View.Selected) then
|
|
Result.View.NotifyFocusEvent;
|
|
except
|
|
Result.Free;
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.OpenPopup(const SelectFirstItem, TrackRightButton: Boolean;
|
|
const PopupPoint: TPoint; const Alignment: TTBPopupAlignment;
|
|
const ReturnClickedItemOnly: Boolean): TTBCustomItem;
|
|
var
|
|
ModalHandler: TTBModalHandler;
|
|
Popup: TTBPopupWindow;
|
|
DoneActionData: TTBDoneActionData;
|
|
begin
|
|
ModalHandler := TTBModalHandler.Create(0);
|
|
try
|
|
Popup := CreatePopup(nil, nil, False, SelectFirstItem, False, PopupPoint,
|
|
Alignment);
|
|
try
|
|
Include(Popup.View.FState, vsIgnoreFirstMouseUp);
|
|
ModalHandler.RootPopup := Popup;
|
|
ModalHandler.Loop(Popup.View, False, False, False, TrackRightButton);
|
|
DoneActionData := Popup.View.FDoneActionData;
|
|
finally
|
|
ModalHandler.RootPopup := nil;
|
|
{ Remove vsModal state from the root view before any TTBView.Destroy
|
|
methods get called, so that NotifyFocusEvent becomes a no-op }
|
|
Exclude(Popup.View.FState, vsModal);
|
|
Popup.Free;
|
|
end;
|
|
finally
|
|
ModalHandler.Free;
|
|
end;
|
|
Result := ProcessDoneAction(DoneActionData, ReturnClickedItemOnly);
|
|
end;
|
|
|
|
function TTBCustomItem.Popup(X, Y: Integer; TrackRightButton: Boolean;
|
|
Alignment: TTBPopupAlignment = tbpaLeft;
|
|
ReturnClickedItemOnly: Boolean = False): TTBCustomItem;
|
|
var
|
|
P: TPoint;
|
|
begin
|
|
P.X := X;
|
|
P.Y := Y;
|
|
Result := OpenPopup(False, TrackRightButton, P, Alignment,
|
|
ReturnClickedItemOnly);
|
|
end;
|
|
|
|
function TTBCustomItem.FindItemWithShortCut(AShortCut: TShortCut;
|
|
var ATopmostParent: TTBCustomItem): TTBCustomItem;
|
|
|
|
function DoItem(AParentItem: TTBCustomItem; LinkDepth: Integer): TTBCustomItem;
|
|
var
|
|
I: Integer;
|
|
NewParentItem, Item: TTBCustomItem;
|
|
begin
|
|
Result := nil;
|
|
NewParentItem := AParentItem;
|
|
if Assigned(NewParentItem.LinkSubitems) then begin
|
|
NewParentItem := NewParentItem.LinkSubitems;
|
|
Inc(LinkDepth);
|
|
if LinkDepth > 25 then
|
|
Exit; { prevent infinite link recursion }
|
|
end;
|
|
for I := 0 to NewParentItem.Count-1 do begin
|
|
Item := NewParentItem.Items[I];
|
|
if Item.ShortCut = AShortCut then begin
|
|
Result := Item;
|
|
Exit;
|
|
end;
|
|
Result := DoItem(Item, LinkDepth);
|
|
if Assigned(Result) then begin
|
|
ATopmostParent := Item;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
ATopmostParent := nil;
|
|
Result := DoItem(Self, 0);
|
|
end;
|
|
|
|
function TTBCustomItem.IsShortCut(var Message: TWMKey): Boolean;
|
|
var
|
|
ShortCut: TShortCut;
|
|
ShiftState: TShiftState;
|
|
ShortCutItem, TopmostItem, Item, EventItem: TTBCustomItem;
|
|
I: Integer;
|
|
label StartOver;
|
|
begin
|
|
Result := False;
|
|
ShiftState := KeyDataToShiftState(ClipToLongint(Message.KeyData));
|
|
ShortCut := Menus.ShortCut(Message.CharCode, ShiftState);
|
|
StartOver:
|
|
ShortCutItem := FindItemWithShortCut(ShortCut, TopmostItem);
|
|
if Assigned(ShortCutItem) then begin
|
|
{ Send OnPopup/OnClick events to ShortCutItem's parents so that they can
|
|
update the Enabled state of ShortCutItem if needed }
|
|
Item := Self;
|
|
repeat
|
|
if not Item.Enabled then
|
|
Exit;
|
|
EventItem := ItemContainingItems(Item);
|
|
if not(csDesigning in ComponentState) then begin
|
|
for I := 0 to EventItem.Count-1 do
|
|
EventItem.Items[I].InitiateAction;
|
|
end;
|
|
if not(tbisEmbeddedGroup in Item.ItemStyle) then begin
|
|
if EventItem <> Item then begin
|
|
try
|
|
EventItem.DoPopup(Item, True);
|
|
except
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
try
|
|
Item.DoPopup(Item, False);
|
|
except
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
ShortCutItem := Item.FindItemWithShortCut(ShortCut, TopmostItem);
|
|
if ShortCutItem = nil then
|
|
{ Can no longer find the shortcut inside TopmostItem. Start over
|
|
because the shortcut might have moved. }
|
|
goto StartOver;
|
|
Item := TopmostItem;
|
|
until Item = nil;
|
|
if ShortCutItem.Enabled then begin
|
|
try
|
|
ShortCutItem.Click;
|
|
except
|
|
Application.HandleException(Self);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.GetChevronParentView: TTBView;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTBCustomItem.GetItemViewerClass(AView: TTBView): TTBItemViewerClass;
|
|
begin
|
|
Result := TTBItemViewer;
|
|
end;
|
|
|
|
function TTBCustomItem.NeedToRecreateViewer(AViewer: TTBItemViewer): Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
function TTBCustomItem.GetItemStyle: TTBItemStyle;
|
|
begin
|
|
{ This public method exists for TB2DsgnItemEditor. It needs access to
|
|
ItemStyle but can't access a protected member across assembly boundaries. }
|
|
Result := FItemStyle;
|
|
end;
|
|
|
|
function TTBCustomItem.GetShortCutText: String;
|
|
var
|
|
P: Integer;
|
|
begin
|
|
P := Pos(#9, Caption);
|
|
if P = 0 then begin
|
|
if ShortCut <> 0 then
|
|
Result := ShortCutToText(ShortCut)
|
|
else
|
|
Result := '';
|
|
end
|
|
else
|
|
Result := Copy(Caption, P+1, Maxint);
|
|
end;
|
|
|
|
procedure TTBCustomItem.Change(NeedResize: Boolean);
|
|
const
|
|
ItemChangedActions: array[Boolean] of TTBItemChangedAction =
|
|
(tbicInvalidate, tbicInvalidateAndResize);
|
|
begin
|
|
if Assigned(FParent) then
|
|
FParent.Notify(ItemChangedActions[NeedResize], -1, Self);
|
|
end;
|
|
|
|
procedure TTBCustomItem.RecreateItemViewers;
|
|
begin
|
|
if Assigned(FParent) then
|
|
FParent.Notify(tbicRecreateItemViewers, -1, Self);
|
|
end;
|
|
|
|
procedure TTBCustomItem.ImageListChangeHandler(Sender: TObject);
|
|
var
|
|
Resize: Boolean;
|
|
begin
|
|
if Sender = FSubMenuImages then begin
|
|
FSubMenuImagesChangeLink.FLastWidth := FSubMenuImages.Width;
|
|
FSubMenuImagesChangeLink.FLastHeight := FSubMenuImages.Height;
|
|
SubMenuImagesChanged;
|
|
end
|
|
else begin
|
|
{ Sender is FImages }
|
|
Resize := False;
|
|
if (FImagesChangeLink.FLastWidth <> FImages.Width) or
|
|
(FImagesChangeLink.FLastHeight <> FImages.Height) then begin
|
|
FImagesChangeLink.FLastWidth := FImages.Width;
|
|
FImagesChangeLink.FLastHeight := FImages.Height;
|
|
Resize := True;
|
|
end;
|
|
Change(Resize);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SubMenuImagesChanged;
|
|
begin
|
|
Notify(tbicSubMenuImagesChanged, -1, nil);
|
|
end;
|
|
|
|
procedure TTBCustomItem.TurnSiblingsOff;
|
|
var
|
|
I: Integer;
|
|
Item: TTBCustomItem;
|
|
begin
|
|
if (GroupIndex <> 0) and Assigned(FParent) then begin
|
|
for I := 0 to FParent.Count-1 do begin
|
|
Item := FParent[I];
|
|
if (Item <> Self) and (Item.GroupIndex = GroupIndex) then
|
|
Item.Checked := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetCaption(Value: String);
|
|
begin
|
|
if FCaption <> Value then begin
|
|
FCaption := Value;
|
|
Change(True);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetChecked(Value: Boolean);
|
|
begin
|
|
if FChecked <> Value then begin
|
|
FChecked := Value;
|
|
Change(False);
|
|
if Value then
|
|
TurnSiblingsOff;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetDisplayMode(Value: TTBItemDisplayMode);
|
|
begin
|
|
if FDisplayMode <> Value then begin
|
|
FDisplayMode := Value;
|
|
Change(True);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.EnabledChanged;
|
|
begin
|
|
Change(False);
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetEnabled(Value: Boolean);
|
|
begin
|
|
if FEnabled <> Value then begin
|
|
FEnabled := Value;
|
|
EnabledChanged;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetGroupIndex(Value: Integer);
|
|
begin
|
|
if FGroupIndex <> Value then begin
|
|
FGroupIndex := Value;
|
|
if Checked then
|
|
TurnSiblingsOff;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetImageIndex(Value: TImageIndex);
|
|
var
|
|
HadNoImage: Boolean;
|
|
begin
|
|
if FImageIndex <> Value then begin
|
|
HadNoImage := FImageIndex = -1;
|
|
FImageIndex := Value;
|
|
Change(HadNoImage xor (Value = -1));
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.ChangeImages(var AImages: TCustomImageList;
|
|
const Value: TCustomImageList; var AChangeLink: TTBImageChangeLink): Boolean;
|
|
{ Returns True if image list was resized }
|
|
var
|
|
LastWidth, LastHeight: Integer;
|
|
begin
|
|
Result := False;
|
|
LastWidth := -1;
|
|
LastHeight := -1;
|
|
if Assigned(AImages) then begin
|
|
LastWidth := AImages.Width;
|
|
LastHeight := AImages.Height;
|
|
AImages.UnregisterChanges(AChangeLink);
|
|
if Value = nil then begin
|
|
AChangeLink.Free;
|
|
AChangeLink := nil;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
AImages := Value;
|
|
if Assigned(Value) then begin
|
|
Result := (Value.Width <> LastWidth) or (Value.Height <> LastHeight);
|
|
if AChangeLink = nil then begin
|
|
AChangeLink := TTBImageChangeLink.Create;
|
|
AChangeLink.FLastWidth := Value.Width;
|
|
AChangeLink.FLastHeight := Value.Height;
|
|
AChangeLink.OnChange := ImageListChangeHandler;
|
|
end;
|
|
Value.RegisterChanges(AChangeLink);
|
|
Value.FreeNotification(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetImages(Value: TCustomImageList);
|
|
begin
|
|
if FImages <> Value then
|
|
Change(ChangeImages(FImages, Value, FImagesChangeLink));
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetSubMenuImages(Value: TCustomImageList);
|
|
begin
|
|
if FSubMenuImages <> Value then begin
|
|
ChangeImages(FSubMenuImages, Value, FSubMenuImagesChangeLink);
|
|
SubMenuImagesChanged;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetInheritOptions(Value: Boolean);
|
|
begin
|
|
if FInheritOptions <> Value then begin
|
|
FInheritOptions := Value;
|
|
RefreshOptions;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetLinkSubitems(Value: TTBCustomItem);
|
|
begin
|
|
if Value = Self then
|
|
Value := nil;
|
|
if FLinkSubitems <> Value then begin
|
|
if Assigned(FLinkSubitems) then
|
|
RemoveFromList(FLinkSubitems.FLinkParents, Self);
|
|
FLinkSubitems := Value;
|
|
if Assigned(Value) then begin
|
|
Value.FreeNotification(Self);
|
|
AddToList(Value.FLinkParents, Self);
|
|
end;
|
|
Notify(tbicSubitemsChanged, -1, nil);
|
|
end;
|
|
end;
|
|
|
|
function TTBCustomItem.FixOptions(const AOptions: TTBItemOptions): TTBItemOptions;
|
|
begin
|
|
Result := AOptions;
|
|
if not(tboToolbarStyle in Result) then
|
|
Exclude(Result, tboToolbarSize);
|
|
end;
|
|
|
|
procedure TTBCustomItem.RefreshOptions;
|
|
const
|
|
NonInheritedOptions = [tboDefault];
|
|
ChangeOptions = [tboDefault, tboDropdownArrow, tboImageAboveCaption,
|
|
tboNoRotation, tboSameWidth, tboToolbarStyle, tboToolbarSize];
|
|
var
|
|
OldOptions, NewOptions: TTBItemOptions;
|
|
I: Integer;
|
|
Item: TTBCustomItem;
|
|
begin
|
|
OldOptions := FEffectiveOptions;
|
|
if FInheritOptions and Assigned(FParent) then
|
|
NewOptions := FParent.FEffectiveOptions - NonInheritedOptions
|
|
else
|
|
NewOptions := [];
|
|
NewOptions := FixOptions(NewOptions - FMaskOptions + FOptions);
|
|
if FEffectiveOptions <> NewOptions then begin
|
|
FEffectiveOptions := NewOptions;
|
|
if (OldOptions * ChangeOptions) <> (NewOptions * ChangeOptions) then
|
|
Change(True);
|
|
for I := 0 to Count-1 do begin
|
|
Item := Items[I];
|
|
if Item.FInheritOptions then
|
|
Item.RefreshOptions;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetMaskOptions(Value: TTBItemOptions);
|
|
begin
|
|
if FMaskOptions <> Value then begin
|
|
FMaskOptions := Value;
|
|
RefreshOptions;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetOptions(Value: TTBItemOptions);
|
|
begin
|
|
Value := FixOptions(Value);
|
|
if FOptions <> Value then begin
|
|
FOptions := Value;
|
|
RefreshOptions;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetRadioItem(Value: Boolean);
|
|
begin
|
|
if FRadioItem <> Value then begin
|
|
FRadioItem := Value;
|
|
Change(False);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomItem.SetVisible(Value: Boolean);
|
|
begin
|
|
if FVisible <> Value then begin
|
|
FVisible := Value;
|
|
Change(True);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBGroupItem }
|
|
|
|
constructor TTBGroupItem.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
ItemStyle := ItemStyle + [tbisEmbeddedGroup, tbisSubitemsEditable];
|
|
end;
|
|
|
|
|
|
{ TTBSubmenuItem }
|
|
|
|
constructor TTBSubmenuItem.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
ItemStyle := ItemStyle + [tbisSubMenu, tbisSubitemsEditable];
|
|
end;
|
|
|
|
function TTBSubmenuItem.GetDropdownCombo: Boolean;
|
|
begin
|
|
Result := tbisCombo in ItemStyle;
|
|
end;
|
|
|
|
procedure TTBSubmenuItem.SetDropdownCombo(Value: Boolean);
|
|
begin
|
|
if (tbisCombo in ItemStyle) <> Value then begin
|
|
if Value then
|
|
ItemStyle := ItemStyle + [tbisCombo]
|
|
else
|
|
ItemStyle := ItemStyle - [tbisCombo];
|
|
Change(True);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBSeparatorItem }
|
|
|
|
constructor TTBSeparatorItem.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
ItemStyle := ItemStyle - [tbisSelectable, tbisRedrawOnSelChange,
|
|
tbisRedrawOnMouseOverChange] + [tbisSeparator, tbisClicksTransparent];
|
|
end;
|
|
|
|
function TTBSeparatorItem.GetItemViewerClass(AView: TTBView): TTBItemViewerClass;
|
|
begin
|
|
Result := TTBSeparatorItemViewer;
|
|
end;
|
|
|
|
procedure TTBSeparatorItem.SetBlank(Value: Boolean);
|
|
begin
|
|
if FBlank <> Value then begin
|
|
FBlank := Value;
|
|
Change(False);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBSeparatorItemViewer }
|
|
|
|
procedure TTBSeparatorItemViewer.CalcSize(const Canvas: TCanvas;
|
|
var AWidth, AHeight: Integer);
|
|
begin
|
|
if not IsToolbarStyle then
|
|
{ Office 2000's menu separators have a hard-coded height of 10 }
|
|
AHeight := 10
|
|
else begin
|
|
AWidth := 6;
|
|
AHeight := 6;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBSeparatorItemViewer.Paint(const Canvas: TCanvas;
|
|
const ClientAreaRect: TRect; IsSelected, IsPushed, UseDisabledShadow: Boolean);
|
|
var
|
|
DC: HDC;
|
|
R: TRect;
|
|
ToolbarStyle, Horiz, LineSep: Boolean;
|
|
begin
|
|
DC := Canvas.Handle;
|
|
if TTBSeparatorItem(Item).FBlank then
|
|
Exit;
|
|
|
|
R := ClientAreaRect;
|
|
ToolbarStyle := IsToolbarStyle;
|
|
Horiz := not ToolbarStyle or (View.FOrientation = tbvoVertical);
|
|
LineSep := tbisLineSep in State;
|
|
if LineSep then
|
|
Horiz := not Horiz;
|
|
if Horiz then begin
|
|
R.Top := R.Bottom div 2 - 1;
|
|
if not ToolbarStyle then
|
|
InflateRect(R, -tbMenuSeparatorOffset, 0)
|
|
else if LineSep then begin
|
|
if View.FOrientation = tbvoFloating then
|
|
InflateRect(R, -tbLineSepOffset, 0)
|
|
else
|
|
InflateRect(R, -tbDockedLineSepOffset, 0);
|
|
end;
|
|
DrawEdge(DC, R, EDGE_ETCHED, BF_TOP);
|
|
end
|
|
else begin
|
|
R.Left := R.Right div 2 - 1;
|
|
if LineSep then
|
|
InflateRect(R, 0, -tbDockedLineSepOffset);
|
|
DrawEdge(DC, R, EDGE_ETCHED, BF_LEFT);
|
|
end;
|
|
end;
|
|
|
|
function TTBSeparatorItemViewer.UsesSameWidth: Boolean;
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
|
|
{ TTBControlItem }
|
|
|
|
constructor TTBControlItem.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
ItemStyle := ItemStyle - [tbisSelectable] + [tbisClicksTransparent];
|
|
end;
|
|
|
|
destructor TTBControlItem.Destroy;
|
|
begin
|
|
inherited;
|
|
{ Free the associated control *after* the item is completely destroyed }
|
|
if not FDontFreeControl and Assigned(FControl) and
|
|
not(csAncestor in FControl.ComponentState) then
|
|
FControl.Free;
|
|
end;
|
|
|
|
procedure TTBControlItem.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited;
|
|
if (Operation = opRemove) and (AComponent = FControl) then
|
|
Control := nil;
|
|
end;
|
|
|
|
procedure TTBControlItem.SetControl(Value: TControl);
|
|
begin
|
|
if FControl <> Value then begin
|
|
FControl := Value;
|
|
if Assigned(Value) then
|
|
Value.FreeNotification(Self);
|
|
Change(True);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBItemViewer }
|
|
|
|
constructor TTBItemViewer.Create(AView: TTBView; AItem: TTBCustomItem;
|
|
AGroupLevel: Integer);
|
|
begin
|
|
inherited Create;
|
|
FItem := AItem;
|
|
FView := AView;
|
|
FGroupLevel := AGroupLevel;
|
|
ReferenceClickWnd;
|
|
end;
|
|
|
|
destructor TTBItemViewer.Destroy;
|
|
begin
|
|
RemoveFromClickList(Self);
|
|
if Assigned(FAccObjectInstance) then begin
|
|
FAccObjectInstance.ClientIsDestroying;
|
|
FAccObjectInstance := nil;
|
|
end;
|
|
inherited;
|
|
ReleaseClickWnd;
|
|
end;
|
|
|
|
function TTBItemViewer.GetAccObject: TTBBaseAccObject;
|
|
begin
|
|
if FAccObjectInstance = nil then begin
|
|
if not InitializeOleAcc then begin
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
FAccObjectInstance := TTBItemViewerAccObject.Create(Self);
|
|
end;
|
|
Result := FAccObjectInstance;
|
|
end;
|
|
|
|
procedure TTBItemViewer.AccSelect(const AExecute: Boolean);
|
|
{ Called by ClickWndProc when an item of type TTBItemViewer is in ClickList }
|
|
var
|
|
Obj: {$IFNDEF CLR} IDispatch {$ELSE} TTBBaseAccObject {$ENDIF};
|
|
begin
|
|
{ Ensure FAccObjectInstance is created by calling GetAccObject. Store the
|
|
reference as an interface so that the object will be destroyed when we
|
|
exit if it's no longer used. }
|
|
Obj := GetAccObject;
|
|
if Assigned(Obj) then
|
|
(FAccObjectInstance as TTBItemViewerAccObject).HandleAccSelect(AExecute);
|
|
end;
|
|
|
|
procedure TTBItemViewer.PostAccSelect(const AExecute: Boolean);
|
|
{ Internally called by TTBItemViewerAccObject. Don't call directly. }
|
|
begin
|
|
QueueClick(Self, Ord(AExecute));
|
|
end;
|
|
|
|
function TTBItemViewer.IsAccessible: Boolean;
|
|
{ Returns True if MSAA clients should know about the viewer, specifically
|
|
if it's either shown, off-edge, or clipped (in other words, not completely
|
|
invisible/inaccessible). }
|
|
begin
|
|
{ Note: Can't simply check Item.Visible because the chevron item's Visible
|
|
property is always True }
|
|
Result := Show or OffEdge or Clipped;
|
|
end;
|
|
|
|
function TTBItemViewer.GetCaptionText: String;
|
|
var
|
|
P: Integer;
|
|
begin
|
|
Result := Item.Caption;
|
|
P := Pos(#9, Result);
|
|
if P <> 0 then
|
|
SetLength(Result, P-1);
|
|
end;
|
|
|
|
function TTBItemViewer.GetHintText: String;
|
|
begin
|
|
Result := GetShortHint(Item.Hint);
|
|
{ If there is no short hint, use the caption for the hint. Like Office,
|
|
strip any trailing colon or ellipsis. }
|
|
if (Result = '') and not(tboNoAutoHint in Item.EffectiveOptions) and
|
|
(not(tbisSubmenu in Item.ItemStyle) or (tbisCombo in Item.ItemStyle) or
|
|
not CaptionShown) then
|
|
Result := StripAccelChars(StripTrailingPunctuation(GetCaptionText));
|
|
{ Call associated action's OnHint event handler to post-process the hint }
|
|
if Assigned(Item.ActionLink) and
|
|
(Item.ActionLink.Action is TCustomAction) then begin
|
|
if not TCustomAction(Item.ActionLink.Action).DoHint(Result) then
|
|
Result := '';
|
|
{ Note: TControlActionLink.DoShowHint actually misinterprets the result
|
|
of DoHint, but we get it right... }
|
|
end;
|
|
{ Add shortcut text }
|
|
if (Result <> '') and Application.HintShortCuts and
|
|
(Item.ShortCut <> scNone) then
|
|
Result := Format('%s (%s)', [Result, ShortCutToText(Item.ShortCut)]);
|
|
end;
|
|
|
|
function TTBItemViewer.CaptionShown: Boolean;
|
|
begin
|
|
Result := (GetCaptionText <> '') and (not IsToolbarSize or
|
|
(Item.ImageIndex < 0) or (Item.DisplayMode in [nbdmTextOnly, nbdmImageAndText])) or
|
|
(tboImageAboveCaption in Item.EffectiveOptions);
|
|
end;
|
|
|
|
function TTBItemViewer.ImageShown: Boolean;
|
|
begin
|
|
{}{should also return false if Images=nil (use UsedImageList?)}
|
|
ImageShown := (Item.ImageIndex >= 0) and
|
|
((Item.DisplayMode in [nbdmDefault, nbdmImageAndText]) or
|
|
(IsToolbarStyle and (Item.DisplayMode = nbdmTextOnlyInMenus)));
|
|
end;
|
|
|
|
function TTBItemViewer.GetImageList: TCustomImageList;
|
|
var
|
|
V: TTBView;
|
|
begin
|
|
Result := Item.Images;
|
|
if Assigned(Result) then
|
|
Exit;
|
|
V := View;
|
|
repeat
|
|
if Assigned(V.FCurParentItem) then begin
|
|
Result := V.FCurParentItem.SubMenuImages;
|
|
if Assigned(Result) then
|
|
Break;
|
|
end;
|
|
if Assigned(V.FParentItem) then begin
|
|
Result := V.FParentItem.SubMenuImages;
|
|
if Assigned(Result) then
|
|
Break;
|
|
end;
|
|
V := V.FParentView;
|
|
until V = nil;
|
|
end;
|
|
|
|
function TTBItemViewer.IsRotated: Boolean;
|
|
{ Returns True if the caption should be drawn with rotated (vertical) text,
|
|
underneath the image }
|
|
begin
|
|
Result := (View.Orientation = tbvoVertical) and
|
|
not (tboNoRotation in Item.EffectiveOptions) and
|
|
not (tboImageAboveCaption in Item.EffectiveOptions);
|
|
end;
|
|
|
|
procedure TTBItemViewer.CalcSize(const Canvas: TCanvas;
|
|
var AWidth, AHeight: Integer);
|
|
var
|
|
ToolbarStyle: Boolean;
|
|
DC: HDC;
|
|
TextMetrics: TTextMetric;
|
|
H, LeftMargin: Integer;
|
|
ImgList: TCustomImageList;
|
|
S: String;
|
|
RotatedFont, SaveFont: HFONT;
|
|
begin
|
|
ToolbarStyle := IsToolbarStyle;
|
|
DC := Canvas.Handle;
|
|
ImgList := GetImageList;
|
|
if ToolbarStyle then begin
|
|
AWidth := 6;
|
|
AHeight := 6;
|
|
end
|
|
else begin
|
|
AWidth := 0;
|
|
AHeight := 0;
|
|
end;
|
|
if not ToolbarStyle or CaptionShown then begin
|
|
if not IsRotated then begin
|
|
GetTextMetrics(DC, TextMetrics);
|
|
Inc(AHeight, TextMetrics.tmHeight);
|
|
Inc(AWidth, GetTextWidth(DC, GetCaptionText, True));
|
|
if ToolbarStyle then
|
|
Inc(AWidth, 6);
|
|
end
|
|
else begin
|
|
{ Vertical text isn't always the same size as horizontal text, so we have
|
|
to select the rotated font into the DC to get an accurate size }
|
|
RotatedFont := CreateRotatedFont(DC);
|
|
SaveFont := SelectObject(DC, RotatedFont);
|
|
GetTextMetrics(DC, TextMetrics);
|
|
Inc(AWidth, TextMetrics.tmHeight);
|
|
Inc(AHeight, GetTextWidth(DC, GetCaptionText, True));
|
|
if ToolbarStyle then
|
|
Inc(AHeight, 6);
|
|
SelectObject(DC, SaveFont);
|
|
DeleteObject(RotatedFont);
|
|
end;
|
|
end;
|
|
if ToolbarStyle and ImageShown and Assigned(ImgList) then begin
|
|
if not IsRotated and not(tboImageAboveCaption in Item.EffectiveOptions) then begin
|
|
Inc(AWidth, ImgList.Width + 1);
|
|
if AHeight < ImgList.Height + 6 then
|
|
AHeight := ImgList.Height + 6;
|
|
end
|
|
else begin
|
|
Inc(AHeight, ImgList.Height);
|
|
if AWidth < ImgList.Width + 7 then
|
|
AWidth := ImgList.Width + 7;
|
|
end;
|
|
end;
|
|
if ToolbarStyle and (tbisSubmenu in Item.ItemStyle) then begin
|
|
if tbisCombo in Item.ItemStyle then
|
|
Inc(AWidth, tbDropdownComboArrowWidth)
|
|
else
|
|
if tboDropdownArrow in Item.EffectiveOptions then begin
|
|
if View.Orientation <> tbvoVertical then
|
|
Inc(AWidth, tbDropdownArrowWidth)
|
|
else
|
|
Inc(AHeight, tbDropdownArrowWidth);
|
|
end;
|
|
end;
|
|
if not ToolbarStyle then begin
|
|
Inc(AHeight, TextMetrics.tmExternalLeading + tbMenuVerticalMargin);
|
|
if Assigned(ImgList) then begin
|
|
H := ImgList.Height + 3;
|
|
if H > AHeight then
|
|
AHeight := H;
|
|
LeftMargin := MulDiv(ImgList.Width + 3, AHeight, H);
|
|
end
|
|
else
|
|
LeftMargin := AHeight;
|
|
Inc(AWidth, LeftMargin + tbMenuImageTextSpace + tbMenuLeftTextMargin +
|
|
tbMenuRightTextMargin);
|
|
S := Item.GetShortCutText;
|
|
if S <> '' then
|
|
Inc(AWidth, (AHeight - 6) + GetTextWidth(DC, S, True));
|
|
Inc(AWidth, AHeight);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.DrawItemCaption(const Canvas: TCanvas; ARect: TRect;
|
|
const ACaption: String; ADrawDisabledShadow: Boolean; AFormat: UINT);
|
|
var
|
|
DC: HDC;
|
|
|
|
procedure Draw;
|
|
begin
|
|
if not IsRotated then
|
|
DrawTextStr(DC, ACaption, ARect, AFormat)
|
|
else
|
|
DrawRotatedText(DC, ACaption, ARect, AFormat);
|
|
end;
|
|
|
|
var
|
|
ShadowColor, HighlightColor, SaveTextColor: DWORD;
|
|
begin
|
|
DC := Canvas.Handle;
|
|
if not ADrawDisabledShadow then
|
|
Draw
|
|
else begin
|
|
ShadowColor := GetSysColor(COLOR_BTNSHADOW);
|
|
HighlightColor := GetSysColor(COLOR_BTNHIGHLIGHT);
|
|
OffsetRect(ARect, 1, 1);
|
|
SaveTextColor := SetTextColor(DC, HighlightColor);
|
|
Draw;
|
|
OffsetRect(ARect, -1, -1);
|
|
SetTextColor(DC, ShadowColor);
|
|
Draw;
|
|
SetTextColor(DC, SaveTextColor);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.Paint(const Canvas: TCanvas;
|
|
const ClientAreaRect: TRect; IsSelected, IsPushed, UseDisabledShadow: Boolean);
|
|
var
|
|
ShowEnabled, HasArrow: Boolean;
|
|
MenuCheckWidth, MenuCheckHeight: Integer;
|
|
|
|
function GetDrawTextFlags: UINT;
|
|
begin
|
|
Result := 0;
|
|
if not AreKeyboardCuesEnabled and (vsUseHiddenAccels in View.FStyle) and
|
|
not(vsShowAccels in View.FState) then
|
|
Result := DT_HIDEPREFIX;
|
|
end;
|
|
|
|
procedure DrawSubmenuArrow;
|
|
var
|
|
BR: TRect;
|
|
Bmp: TBitmap;
|
|
|
|
procedure DrawWithColor(AColor: TColor);
|
|
const
|
|
ROP_DSPDxax = $00E20746;
|
|
var
|
|
DC: HDC;
|
|
SaveTextColor, SaveBkColor: TColorRef;
|
|
begin
|
|
Canvas.Brush.Color := AColor;
|
|
DC := Canvas.Handle;
|
|
SaveTextColor := SetTextColor(DC, clWhite);
|
|
SaveBkColor := SetBkColor(DC, clBlack);
|
|
BitBlt(DC, BR.Left, BR.Top, MenuCheckWidth, MenuCheckHeight,
|
|
Bmp.Canvas.Handle, 0, 0, ROP_DSPDxax);
|
|
SetBkColor(DC, SaveBkColor);
|
|
SetTextColor(DC, SaveTextColor);
|
|
Canvas.Brush.Style := bsClear;
|
|
end;
|
|
|
|
begin
|
|
Bmp := TBitmap.Create;
|
|
try
|
|
Bmp.Monochrome := True;
|
|
Bmp.Width := MenuCheckWidth;
|
|
Bmp.Height := MenuCheckHeight;
|
|
BR := Rect(0, 0, MenuCheckWidth, MenuCheckHeight);
|
|
DrawFrameControl(Bmp.Canvas.Handle, BR, DFC_MENU, DFCS_MENUARROW);
|
|
OffsetRect(BR, ClientAreaRect.Right - MenuCheckWidth,
|
|
ClientAreaRect.Top + ((ClientAreaRect.Bottom - ClientAreaRect.Top) - MenuCheckHeight) div 2);
|
|
if not UseDisabledShadow then begin
|
|
if ShowEnabled and (tbisCombo in Item.ItemStyle) and IsSelected then begin
|
|
OffsetRect(BR, 1, 1);
|
|
DrawWithColor(clBtnText);
|
|
end
|
|
else
|
|
DrawWithColor(Canvas.Font.Color);
|
|
end
|
|
else begin
|
|
OffsetRect(BR, 1, 1);
|
|
DrawWithColor(clBtnHighlight);
|
|
OffsetRect(BR, -1, -1);
|
|
DrawWithColor(clBtnShadow);
|
|
end;
|
|
finally
|
|
Bmp.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure DrawDropdownArrow(R: TRect; Rotated: Boolean);
|
|
|
|
procedure DrawWithColor(AColor: TColor);
|
|
var
|
|
X, Y: Integer;
|
|
P: array[0..2] of TPoint;
|
|
begin
|
|
X := (R.Left + R.Right) div 2;
|
|
Y := (R.Top + R.Bottom) div 2;
|
|
if not Rotated then begin
|
|
Dec(Y);
|
|
P[0].X := X-2;
|
|
P[0].Y := Y;
|
|
P[1].X := X+2;
|
|
P[1].Y := Y;
|
|
P[2].X := X;
|
|
P[2].Y := Y+2;
|
|
end
|
|
else begin
|
|
Dec(X);
|
|
P[0].X := X;
|
|
P[0].Y := Y+2;
|
|
P[1].X := X;
|
|
P[1].Y := Y-2;
|
|
P[2].X := X-2;
|
|
P[2].Y := Y;
|
|
end;
|
|
Canvas.Pen.Color := AColor;
|
|
Canvas.Brush.Color := AColor;
|
|
Canvas.Polygon(P);
|
|
end;
|
|
|
|
begin
|
|
if not UseDisabledShadow then
|
|
DrawWithColor(Canvas.Font.Color)
|
|
else begin
|
|
OffsetRect(R, 1, 1);
|
|
DrawWithColor(clBtnHighlight);
|
|
OffsetRect(R, -1, -1);
|
|
DrawWithColor(clBtnShadow);
|
|
end;
|
|
end;
|
|
|
|
function GetDitherBitmap: TBitmap;
|
|
begin
|
|
Result := AllocPatternBitmap(clBtnFace, clBtnHighlight);
|
|
Result.HandleType := bmDDB; { needed for Win95, or else brush is solid white }
|
|
end;
|
|
|
|
const
|
|
EdgeStyles: array[Boolean] of UINT = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
|
|
BlackCheckMarkPoints: array[0..6] of TPoint = (
|
|
(X: -2; Y: -2), (X: 0; Y: 0), (X: 4; Y: -4),
|
|
(X: 4; Y: -3), (X: 0; Y: 1), (X: -2; Y: -1),
|
|
(X: -2; Y: -2));
|
|
WhiteCheckMarkPoints: array[0..4] of TPoint = (
|
|
(X: -3; Y: -2), (X: -3; Y: -1), (X: 0; Y: 2),
|
|
(X: 5; Y: -3), (X: 5; Y: -5));
|
|
var
|
|
ToolbarStyle, ImageIsShown: Boolean;
|
|
R, RC, RD: TRect;
|
|
S: String;
|
|
ImgList: TCustomImageList;
|
|
I, X, Y: Integer;
|
|
BlackPoints: array[0..6] of TPoint;
|
|
WhitePoints: array[0..4] of TPoint;
|
|
DrawTextFlags: UINT;
|
|
LeftMargin: Integer;
|
|
TextMetrics: TTextMetric;
|
|
begin
|
|
ToolbarStyle := IsToolbarStyle;
|
|
ShowEnabled := Item.Enabled or View.Customizing;
|
|
HasArrow := (tbisSubmenu in Item.ItemStyle) and
|
|
((tbisCombo in Item.ItemStyle) or (tboDropdownArrow in Item.EffectiveOptions));
|
|
MenuCheckWidth := GetSystemMetrics(SM_CXMENUCHECK);
|
|
MenuCheckHeight := GetSystemMetrics(SM_CYMENUCHECK);
|
|
ImgList := GetImageList;
|
|
ImageIsShown := ImageShown and Assigned(ImgList);
|
|
LeftMargin := 0;
|
|
if not ToolbarStyle then begin
|
|
if Assigned(ImgList) then
|
|
LeftMargin := MulDiv(ImgList.Width + 3, ClientAreaRect.Bottom, ImgList.Height + 3)
|
|
else
|
|
LeftMargin := ClientAreaRect.Bottom;
|
|
end;
|
|
|
|
{ Border }
|
|
RC := ClientAreaRect;
|
|
if ToolbarStyle then begin
|
|
if HasArrow then begin
|
|
if tbisCombo in Item.ItemStyle then begin
|
|
Dec(RC.Right, tbDropdownComboMargin);
|
|
RD := RC;
|
|
Dec(RC.Right, tbDropdownComboArrowWidth - tbDropdownComboMargin);
|
|
RD.Left := RC.Right;
|
|
end
|
|
else begin
|
|
if View.Orientation <> tbvoVertical then
|
|
RD := Rect(RC.Right - tbDropdownArrowWidth - tbDropdownArrowMargin, 0,
|
|
RC.Right - tbDropdownArrowMargin, RC.Bottom)
|
|
else
|
|
RD := Rect(0, RC.Bottom - tbDropdownArrowWidth - tbDropdownArrowMargin,
|
|
RC.Right, RC.Bottom - tbDropdownArrowMargin);
|
|
end;
|
|
end
|
|
else
|
|
SetRectEmpty(RD);
|
|
if (IsSelected and ShowEnabled) or Item.Checked or
|
|
(csDesigning in Item.ComponentState) then begin
|
|
if not(tbisCombo in Item.ItemStyle) then
|
|
DrawEdge(Canvas.Handle, RC, EdgeStyles[IsPushed or Item.Checked], BF_RECT)
|
|
else begin
|
|
DrawEdge(Canvas.Handle, RC, EdgeStyles[(IsPushed and View.FCapture) or Item.Checked], BF_RECT);
|
|
if (IsSelected and ShowEnabled) or
|
|
(csDesigning in Item.ComponentState) then
|
|
DrawEdge(Canvas.Handle, RD, EdgeStyles[IsPushed and not View.FCapture], BF_RECT);
|
|
end;
|
|
end;
|
|
if HasArrow then begin
|
|
if not(tbisCombo in Item.ItemStyle) and IsPushed then
|
|
OffsetRect(RD, 1, 1);
|
|
DrawDropdownArrow(RD, not(tbisCombo in Item.ItemStyle) and
|
|
(View.Orientation = tbvoVertical));
|
|
end;
|
|
InflateRect(RC, -1, -1);
|
|
if Item.Checked and not (IsSelected and ShowEnabled) then begin
|
|
Canvas.Brush.Bitmap := GetDitherBitmap;
|
|
Canvas.FillRect(RC);
|
|
Canvas.Brush.Style := bsClear;
|
|
end;
|
|
InflateRect(RC, -1, -1);
|
|
if Item.Checked or
|
|
((IsSelected and IsPushed) and
|
|
(not(tbisCombo in Item.ItemStyle) or View.FCapture)) then
|
|
OffsetRect(RC, 1, 1);
|
|
if HasArrow and not(tbisCombo in Item.ItemStyle) then begin
|
|
if View.Orientation <> tbvoVertical then
|
|
Dec(RC.Right, tbDropdownArrowWidth)
|
|
else
|
|
Dec(RC.Bottom, tbDropdownArrowWidth);
|
|
end;
|
|
end
|
|
else begin
|
|
{ On selected menu items, fill the background with the selected color.
|
|
Note: This assumes the brush color was not changed from the initial
|
|
value. }
|
|
if IsSelected then begin
|
|
R := RC;
|
|
if ImageIsShown or Item.Checked then
|
|
Inc(R.Left, LeftMargin + tbMenuImageTextSpace);
|
|
if (tbisCombo in Item.ItemStyle) and IsSelected and ShowEnabled then
|
|
Dec(R.Right, MenuCheckWidth);
|
|
Canvas.FillRect(R);
|
|
end;
|
|
end;
|
|
|
|
{ Adjust brush & font }
|
|
Canvas.Brush.Style := bsClear;
|
|
if tboDefault in Item.EffectiveOptions then
|
|
with Canvas.Font do Style := Style + [fsBold];
|
|
GetTextMetrics(Canvas.Handle, TextMetrics);
|
|
|
|
{ Caption }
|
|
if CaptionShown then begin
|
|
S := GetCaptionText;
|
|
R := RC;
|
|
DrawTextFlags := GetDrawTextFlags;
|
|
if ToolbarStyle then begin
|
|
if ImageIsShown then begin
|
|
if not IsRotated and not(tboImageAboveCaption in Item.EffectiveOptions) then
|
|
Inc(R.Left, ImgList.Width + 1)
|
|
else
|
|
Inc(R.Top, ImgList.Height + 1);
|
|
end;
|
|
DrawItemCaption(Canvas, R, S, UseDisabledShadow,
|
|
DT_SINGLELINE or DT_CENTER or DT_VCENTER or DrawTextFlags)
|
|
end
|
|
else begin
|
|
Inc(R.Left, LeftMargin + tbMenuImageTextSpace + tbMenuLeftTextMargin);
|
|
{ Like standard menus, shift the text up one pixel if the text height
|
|
is 4 pixels less than the total item height. This is done so underlined
|
|
characters aren't displayed too low. }
|
|
if (R.Bottom - R.Top) - (TextMetrics.tmHeight + TextMetrics.tmExternalLeading) = tbMenuVerticalMargin then
|
|
Dec(R.Bottom);
|
|
Inc(R.Top, TextMetrics.tmExternalLeading);
|
|
DrawItemCaption(Canvas, R, S, UseDisabledShadow,
|
|
DT_SINGLELINE or DT_LEFT or DT_VCENTER or DrawTextFlags);
|
|
end;
|
|
end;
|
|
|
|
{ Shortcut and/or submenu arrow (menus only) }
|
|
if not ToolbarStyle then begin
|
|
S := Item.GetShortCutText;
|
|
if S <> '' then begin
|
|
R := RC;
|
|
R.Left := R.Right - (R.Bottom - R.Top) - GetTextWidth(Canvas.Handle, S, True);
|
|
{ Like standard menus, shift the text up one pixel if the text height
|
|
is 4 pixels less than the total item height. This is done so underlined
|
|
characters aren't displayed too low. }
|
|
if (R.Bottom - R.Top) - (TextMetrics.tmHeight + TextMetrics.tmExternalLeading) = tbMenuVerticalMargin then
|
|
Dec(R.Bottom);
|
|
Inc(R.Top, TextMetrics.tmExternalLeading);
|
|
DrawItemCaption(Canvas, R, S, UseDisabledShadow,
|
|
DT_SINGLELINE or DT_LEFT or DT_VCENTER or DT_NOPREFIX);
|
|
end;
|
|
if tbisSubmenu in Item.ItemStyle then begin
|
|
if tbisCombo in Item.ItemStyle then begin
|
|
R := RC;
|
|
R.Left := R.Right - MenuCheckWidth;
|
|
if IsSelected and ShowEnabled then
|
|
DrawEdge(Canvas.Handle, R, BDR_SUNKENOUTER, BF_RECT or BF_MIDDLE)
|
|
else begin
|
|
Dec(R.Left);
|
|
if not IsSelected then
|
|
DrawEdge(Canvas.Handle, R, EDGE_ETCHED, BF_LEFT)
|
|
else
|
|
DrawEdge(Canvas.Handle, R, BDR_SUNKENOUTER, BF_LEFT);
|
|
end;
|
|
end;
|
|
DrawSubmenuArrow;
|
|
end;
|
|
end;
|
|
|
|
{ Image, or check box }
|
|
if ImageIsShown or (not ToolbarStyle and Item.Checked) then begin
|
|
R := RC;
|
|
if ToolbarStyle then begin
|
|
if not IsRotated and not(tboImageAboveCaption in Item.EffectiveOptions) then
|
|
R.Right := R.Left + ImgList.Width + 2
|
|
else
|
|
R.Bottom := R.Top + ImgList.Height + 2;
|
|
end
|
|
else begin
|
|
R.Right := R.Left + LeftMargin;
|
|
if (IsSelected and ShowEnabled) or Item.Checked then
|
|
DrawEdge(Canvas.Handle, R, EdgeStyles[Item.Checked], BF_RECT or BF_MIDDLE);
|
|
if Item.Checked and not IsSelected then begin
|
|
InflateRect(R, -1, -1);
|
|
Canvas.Brush.Bitmap := GetDitherBitmap;
|
|
Canvas.FillRect(R);
|
|
Canvas.Brush.Style := bsClear;
|
|
InflateRect(R, 1, 1);
|
|
end;
|
|
if Item.Checked then
|
|
OffsetRect(R, 1, 1);
|
|
end;
|
|
if ImageIsShown then begin
|
|
X := R.Left + ((R.Right - R.Left) - ImgList.Width) div 2;
|
|
Y := R.Top + ((R.Bottom - R.Top) - ImgList.Height) div 2;
|
|
if ImgList is TTBCustomImageList then
|
|
TTBCustomImageList(ImgList).DrawState(Canvas, X, Y, Item.ImageIndex,
|
|
ShowEnabled, IsSelected, Item.Checked)
|
|
else
|
|
ImgList.Draw(Canvas, X, Y, Item.ImageIndex, ShowEnabled);
|
|
end
|
|
else
|
|
if not ToolbarStyle and Item.Checked then begin
|
|
{ Draw default check mark or radio button image when user hasn't
|
|
specified their own }
|
|
X := (R.Left + R.Right) div 2;
|
|
Y := (R.Top + R.Bottom) div 2;
|
|
if Item.RadioItem then begin
|
|
Canvas.Pen.Color := clBtnText;
|
|
Canvas.Brush.Color := clBtnText;
|
|
Canvas.RoundRect(X-3, Y-3, X+2, Y+2, 2, 2);
|
|
Canvas.Pen.Color := clBtnHighlight;
|
|
Canvas.Brush.Style := bsClear;
|
|
Canvas.RoundRect(X-4, Y-4, X+3, Y+3, 6, 6);
|
|
end
|
|
else begin
|
|
Dec(X, 2);
|
|
Inc(Y);
|
|
for I := Low(BlackPoints) to High(BlackPoints) do begin
|
|
BlackPoints[I].X := X + BlackCheckMarkPoints[I].X;
|
|
BlackPoints[I].Y := Y + BlackCheckMarkPoints[I].Y;
|
|
end;
|
|
for I := Low(WhitePoints) to High(WhitePoints) do begin
|
|
WhitePoints[I].X := X + WhiteCheckMarkPoints[I].X;
|
|
WhitePoints[I].Y := Y + WhiteCheckMarkPoints[I].Y;
|
|
end;
|
|
Canvas.Pen.Color := clBtnText;
|
|
Polyline(Canvas.Handle, BlackPoints, Length(BlackPoints));
|
|
Canvas.Pen.Color := clBtnHighlight;
|
|
Polyline(Canvas.Handle, WhitePoints, Length(WhitePoints));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.GetCursor(const Pt: TPoint; var ACursor: HCURSOR);
|
|
begin
|
|
end;
|
|
|
|
function TTBItemViewer.GetIndex: Integer;
|
|
begin
|
|
Result := View.IndexOf(Self);
|
|
end;
|
|
|
|
function TTBItemViewer.IsToolbarSize: Boolean;
|
|
begin
|
|
Result := View.FIsToolbar or (tboToolbarSize in Item.FEffectiveOptions);
|
|
end;
|
|
|
|
function TTBItemViewer.IsToolbarStyle: Boolean;
|
|
begin
|
|
Result := View.FIsToolbar or (tboToolbarStyle in Item.FEffectiveOptions);
|
|
end;
|
|
|
|
function TTBItemViewer.IsPtInButtonPart(X, Y: Integer): Boolean;
|
|
var
|
|
W: Integer;
|
|
begin
|
|
Result := not(tbisSubmenu in Item.ItemStyle);
|
|
if tbisCombo in Item.ItemStyle then begin
|
|
if IsToolbarStyle then
|
|
W := tbDropdownComboArrowWidth
|
|
else
|
|
W := GetSystemMetrics(SM_CXMENUCHECK);
|
|
Result := X < (BoundsRect.Right - BoundsRect.Left) - W;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.MouseDown(Shift: TShiftState; X, Y: Integer;
|
|
var MouseDownOnMenu: Boolean);
|
|
|
|
procedure HandleDefaultDoubleClick(const View: TTBView);
|
|
{ Looks for a tboDefault item in View and ends the modal loop if it finds
|
|
one. }
|
|
var
|
|
I: Integer;
|
|
Viewer: TTBItemViewer;
|
|
Item: TTBCustomItem;
|
|
begin
|
|
for I := 0 to View.FViewers.Count-1 do begin
|
|
Viewer := View.Viewers[I];
|
|
Item := Viewer.Item;
|
|
if (Viewer.Show or Viewer.Clipped) and (tboDefault in Item.EffectiveOptions) and
|
|
(tbisSelectable in Item.ItemStyle) and Item.Enabled and Item.Visible then begin
|
|
Viewer.Execute(True);
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
WasAlreadyOpen: Boolean;
|
|
begin
|
|
if not Item.Enabled then begin
|
|
if (View.FParentView = nil) and not View.FIsPopup then
|
|
View.EndModal;
|
|
Exit;
|
|
end;
|
|
if IsPtInButtonPart(X, Y) then begin
|
|
if IsToolbarStyle then begin
|
|
View.CancelChildPopups;
|
|
View.SetCapture;
|
|
View.Invalidate(Self);
|
|
end;
|
|
end
|
|
else begin
|
|
WasAlreadyOpen := (View.FOpenViewer = Self);
|
|
if View.OpenChildPopup(False) then begin
|
|
if WasAlreadyOpen and ((View.FParentView = nil) and not View.FIsPopup) then
|
|
MouseDownOnMenu := True;
|
|
if (ssDouble in Shift) and not(tbisCombo in Item.ItemStyle) then
|
|
HandleDefaultDoubleClick(View.FOpenViewerView);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.MouseMove(X, Y: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TTBItemViewer.MouseUp(X, Y: Integer; MouseWasDownOnMenu: Boolean);
|
|
var
|
|
HadCapture, IsToolbarItem: Boolean;
|
|
begin
|
|
HadCapture := View.FCapture;
|
|
View.CancelCapture;
|
|
IsToolbarItem := (View.FParentView = nil) and not View.FIsPopup;
|
|
if not View.FMouseOverSelected or not Item.Enabled or
|
|
(tbisClicksTransparent in Item.ItemStyle) then begin
|
|
if IsToolbarItem then
|
|
View.EndModal;
|
|
Exit;
|
|
end;
|
|
if (tbisSubmenu in Item.ItemStyle) and not IsPtInButtonPart(X, Y) then begin
|
|
if IsToolbarItem and MouseWasDownOnMenu then
|
|
View.EndModal;
|
|
end
|
|
else begin
|
|
{ it's a 'normal' item }
|
|
if not IsToolbarStyle or HadCapture then
|
|
Execute(True);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBItemViewer.MouseWheel(WheelDelta, X, Y: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TTBItemViewer.LosingCapture;
|
|
begin
|
|
View.Invalidate(Self);
|
|
end;
|
|
|
|
procedure TTBItemViewer.Entering;
|
|
begin
|
|
if Assigned(Item.FOnSelect) then
|
|
Item.FOnSelect(Item, Self, True);
|
|
end;
|
|
|
|
procedure TTBItemViewer.Leaving;
|
|
begin
|
|
if Assigned(Item.FOnSelect) then
|
|
Item.FOnSelect(Item, Self, False);
|
|
end;
|
|
|
|
procedure TTBItemViewer.KeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
end;
|
|
|
|
function TTBItemViewer.ScreenToClient(const P: TPoint): TPoint;
|
|
begin
|
|
Result := View.FWindow.ScreenToClient(P);
|
|
Dec(Result.X, BoundsRect.Left);
|
|
Dec(Result.Y, BoundsRect.Top);
|
|
end;
|
|
|
|
function TTBItemViewer.UsesSameWidth: Boolean;
|
|
{ If UsesSameWidth returns True, the item viewer's width will be expanded to
|
|
match the widest item viewer on the same view whose UsesSameWidth method
|
|
also returns True. }
|
|
begin
|
|
Result := (tboImageAboveCaption in Item.FEffectiveOptions) and
|
|
(tboSameWidth in Item.FEffectiveOptions) and IsToolbarSize;
|
|
end;
|
|
|
|
function TTBItemViewer.DoExecute: Boolean;
|
|
{ Low-level 'execute' handler. Returns True if the caller should call
|
|
GivePriority on the viewer (normally, if the 'execute' operation was a
|
|
success and the modal loop is ending). }
|
|
begin
|
|
View.EndModalWithClick(Self);
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TTBItemViewer.Execute(AGivePriority: Boolean);
|
|
{ Calls DoExecute and, if applicable, View.GivePriority. Note that it is up to
|
|
the caller to check the viewer's visibility and enabled state. }
|
|
begin
|
|
if DoExecute and AGivePriority then
|
|
View.GivePriority(Self);
|
|
end;
|
|
|
|
function TTBItemViewer.GetAccRole: Integer;
|
|
{ Returns the MSAA "role" of the viewer. }
|
|
const
|
|
{ Constants from OleAcc.h }
|
|
ROLE_SYSTEM_CLIENT = $a;
|
|
ROLE_SYSTEM_MENUITEM = $c;
|
|
ROLE_SYSTEM_SEPARATOR = $15;
|
|
ROLE_SYSTEM_PUSHBUTTON = $2b;
|
|
ROLE_SYSTEM_BUTTONMENU = $39;
|
|
begin
|
|
if Item is TTBControlItem then
|
|
Result := ROLE_SYSTEM_CLIENT
|
|
else if tbisSeparator in Item.ItemStyle then
|
|
Result := ROLE_SYSTEM_SEPARATOR
|
|
else if View.IsPopup or (vsMenuBar in View.Style) then
|
|
Result := ROLE_SYSTEM_MENUITEM
|
|
else if tbisSubmenu in Item.ItemStyle then
|
|
Result := ROLE_SYSTEM_BUTTONMENU
|
|
else
|
|
Result := ROLE_SYSTEM_PUSHBUTTON;
|
|
end;
|
|
|
|
function TTBItemViewer.GetAccValue(var Value: WideString): Boolean;
|
|
{ Gets the MSAA "value" text of the viewer. Returns True if something was
|
|
assigned to Value, or False if the viewer does not possess a "value". }
|
|
begin
|
|
Result := False;
|
|
end;
|
|
|
|
|
|
{ TTBView }
|
|
|
|
constructor TTBView.Create(AOwner: TComponent; AParentView: TTBView;
|
|
AParentItem: TTBCustomItem; AWindow: TWinControl;
|
|
AIsToolbar, ACustomizing, AUsePriorityList: Boolean);
|
|
begin
|
|
{$IFDEF CLR}
|
|
{ TB2Acc's IAccessible implementations must be called from the same thread
|
|
that created the view, so verify that the program has [STAThread] }
|
|
CheckThreadingModel(System.Threading.ApartmentState.STA);
|
|
{$ENDIF}
|
|
inherited Create(AOwner);
|
|
FViewers := TList.Create;
|
|
FBackgroundColor := clDefault;
|
|
FCustomizing := ACustomizing;
|
|
FIsPopup := not AIsToolbar;
|
|
FIsToolbar := AIsToolbar;
|
|
FNewViewersGetHighestPriority := True;
|
|
FParentView := AParentView;
|
|
FParentItem := AParentItem;
|
|
if Assigned(FParentItem) then begin
|
|
//FIsToolbar := FIsToolbar or FParentItem.FDisplayAsToolbar;
|
|
FParentItem.RegisterNotification(LinkNotification);
|
|
FParentItem.FreeNotification(Self);
|
|
end;
|
|
FUsePriorityList := AUsePriorityList;
|
|
FWindow := AWindow;
|
|
UpdateCurParentItem;
|
|
end;
|
|
|
|
destructor TTBView.Destroy;
|
|
begin
|
|
CloseChildPopups;
|
|
if Assigned(FAccObjectInstance) then begin
|
|
FAccObjectInstance.ClientIsDestroying;
|
|
{ Get rid of our own reference to FAccObjectInstance. Normally the
|
|
reference count will be now be zero and FAccObjectInstance will be
|
|
freed, unless MSAA still holds a reference. }
|
|
{$IFNDEF CLR}
|
|
FAccObjectInstance._Release;
|
|
{$ENDIF}
|
|
FAccObjectInstance := nil;
|
|
end;
|
|
{ If parent view is a toolbar, invalidate the open item so that it's
|
|
redrawn back in the "up" position }
|
|
if Assigned(ParentView) and ParentView.FIsToolbar then begin
|
|
Include(ParentView.FState, vsNoAnimation);
|
|
if Assigned(ParentView.FOpenViewer) then
|
|
ParentView.Invalidate(ParentView.FOpenViewer);
|
|
end;
|
|
if Assigned(FCurParentItem) then
|
|
FCurParentItem.UnregisterNotification(ItemNotification);
|
|
if Assigned(FParentItem) then
|
|
FParentItem.UnregisterNotification(LinkNotification);
|
|
inherited;
|
|
FPriorityList.Free;
|
|
FreeViewers;
|
|
FreeAndNil(FViewers);
|
|
{ Now that we're destroyed, "focus" the parent view }
|
|
if Assigned(FParentView) then
|
|
FParentView.NotifyFocusEvent;
|
|
end;
|
|
|
|
function TTBView.GetAccObject: TTBBaseAccObject;
|
|
begin
|
|
if FAccObjectInstance = nil then begin
|
|
if not InitializeOleAcc then begin
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
FAccObjectInstance := TTBViewAccObject.Create(Self);
|
|
{ Strictly as an optimization, take a reference for ourself and keep it
|
|
for the lifetime of the view. (Destroy calls _Release.) }
|
|
{$IFNDEF CLR}
|
|
FAccObjectInstance._AddRef;
|
|
{$ENDIF}
|
|
end;
|
|
Result := FAccObjectInstance;
|
|
end;
|
|
|
|
function TTBView.HandleWMGetObject(var Message: TMessage): Boolean;
|
|
begin
|
|
{ Note: In a 64-bit build, object identifiers can come in either
|
|
sign-extended or zero-extended from 32 to 64 bits. Clip to 32 bits here
|
|
to ensure we accept both forms. }
|
|
if (ClipToLongint(Message.LParam) = Longint(OBJID_CLIENT)) and InitializeOleAcc then begin
|
|
Message.Result := LresultFromObjectFunc(
|
|
{$IFNDEF CLR} ITBAccessible {$ELSE} TypeOf(ITBAccessible).GUID {$ENDIF},
|
|
Message.WParam, GetAccObject);
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TTBView.UpdateCurParentItem;
|
|
var
|
|
Value: TTBCustomItem;
|
|
begin
|
|
Value := ItemContainingItems(FParentItem);
|
|
if FCurParentItem <> Value then begin
|
|
CloseChildPopups;
|
|
if Assigned(FCurParentItem) then
|
|
FCurParentItem.UnregisterNotification(ItemNotification);
|
|
FCurParentItem := Value;
|
|
if Assigned(Value) then
|
|
Value.RegisterNotification(ItemNotification);
|
|
RecreateAllViewers;
|
|
if Assigned(Value) and not(csDesigning in Value.ComponentState) then
|
|
InitiateActions;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.InitiateActions;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
{ Use a 'while' instead of a 'for' since an InitiateAction implementation
|
|
may add/delete items }
|
|
I := 0;
|
|
while I < FViewers.Count do begin
|
|
Viewers[I].Item.InitiateAction;
|
|
Inc(I);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited;
|
|
if Operation = opRemove then begin
|
|
if AComponent = FParentItem then begin
|
|
FParentItem := nil;
|
|
UpdateCurParentItem;
|
|
if Assigned(FParentView) then
|
|
FParentView.CloseChildPopups;
|
|
end
|
|
else if AComponent = FOpenViewerWindow then begin
|
|
FOpenViewerWindow := nil;
|
|
FOpenViewerView := nil;
|
|
FOpenViewer := nil;
|
|
end
|
|
else if AComponent = FChevronParentView then
|
|
FChevronParentView := nil;
|
|
end
|
|
end;
|
|
|
|
function TTBView.ContainsView(AView: TTBView): Boolean;
|
|
begin
|
|
while Assigned(AView) and (AView <> Self) do
|
|
AView := AView.FParentView;
|
|
Result := Assigned(AView);
|
|
end;
|
|
|
|
function TTBView.GetRootView: TTBView;
|
|
begin
|
|
Result := Self;
|
|
while Assigned(Result.FParentView) do
|
|
Result := Result.FParentView;
|
|
end;
|
|
|
|
function TTBView.GetParentToolbarView: TTBView;
|
|
begin
|
|
Result := Self;
|
|
while Assigned(Result) and not Result.FIsToolbar do
|
|
Result := Result.FParentView;
|
|
end;
|
|
|
|
function TTBView.GetViewer(Index: Integer): TTBItemViewer;
|
|
begin
|
|
if (Index < 0) or (Index >= FViewers.Count) then begin
|
|
TTBCustomItem.IndexError;
|
|
Result := nil;
|
|
Exit;
|
|
end;
|
|
Result := TTBItemViewer(FViewers.List[Index]);
|
|
end;
|
|
|
|
function TTBView.GetViewerCount: Integer;
|
|
begin
|
|
Result := FViewers.Count;
|
|
end;
|
|
|
|
procedure TTBView.FreeViewers;
|
|
var
|
|
I: Integer;
|
|
Viewer: TTBItemViewer;
|
|
begin
|
|
if Assigned(FViewers) then begin
|
|
for I := FViewers.Count-1 downto 0 do begin
|
|
Viewer := Viewers[I];
|
|
FViewers.Delete(I);
|
|
Viewer.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.InvalidatePositions;
|
|
begin
|
|
if FValidated then begin
|
|
FValidated := False;
|
|
if Assigned(FWindow) and FWindow.HandleAllocated then
|
|
InvalidateRect(FWindow.Handle, nil, True);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.ValidatePositions;
|
|
begin
|
|
if not FValidated then
|
|
UpdatePositions;
|
|
end;
|
|
|
|
procedure TTBView.TryValidatePositions;
|
|
begin
|
|
if (FUpdating = 0) and
|
|
(not Assigned(FParentItem) or not(csLoading in FParentItem.ComponentState)) and
|
|
(not Assigned(FParentItem.Owner) or not(csLoading in FParentItem.Owner.ComponentState)) then
|
|
ValidatePositions;
|
|
end;
|
|
|
|
(*procedure TTBView.TryRevalidatePositions;
|
|
begin
|
|
if FValidated then begin
|
|
if FUpdating = 0 then begin
|
|
FreePositions;
|
|
UpdatePositions;
|
|
end
|
|
else
|
|
InvalidatePositions;
|
|
end;
|
|
end;*)
|
|
|
|
function TTBView.Find(Item: TTBCustomItem): TTBItemViewer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FViewers.Count-1 do
|
|
if Viewers[I].Item = Item then begin
|
|
Result := Viewers[I];
|
|
Exit;
|
|
end;
|
|
raise ETBItemError.Create(STBViewerNotFound);
|
|
end;
|
|
|
|
function TTBView.IndexOf(AViewer: TTBItemViewer): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if Assigned(AViewer) then
|
|
for I := 0 to FViewers.Count-1 do
|
|
if FViewers.List[I] = AViewer then begin
|
|
Result := I;
|
|
Exit;
|
|
end;
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure TTBView.DeletingViewer(Viewer: TTBItemViewer);
|
|
begin
|
|
if FSelected = Viewer then
|
|
FSelected := nil;
|
|
if FOpenViewer = Viewer then
|
|
CloseChildPopups;
|
|
end;
|
|
|
|
procedure TTBView.RecreateItemViewer(const I: Integer);
|
|
var
|
|
OldViewer, NewViewer: TTBItemViewer;
|
|
J: Integer;
|
|
begin
|
|
OldViewer := Viewers[I];
|
|
DeletingViewer(OldViewer);
|
|
NewViewer := OldViewer.Item.GetItemViewerClass(Self).Create(Self,
|
|
OldViewer.Item, OldViewer.FGroupLevel);
|
|
FViewers[I] := NewViewer;
|
|
if Assigned(FPriorityList) then begin
|
|
J := FPriorityList.IndexOf(OldViewer);
|
|
if J <> -1 then
|
|
FPriorityList[J] := NewViewer;
|
|
end;
|
|
OldViewer.Free;
|
|
end;
|
|
|
|
function TTBView.InsertItemViewers(const NewIndex: Integer;
|
|
const AItem: TTBCustomItem; const AGroupLevel: Integer;
|
|
const AddToPriorityList, TopOfPriorityList: Boolean): Integer;
|
|
var
|
|
NewViewer: TTBItemViewer;
|
|
LinkItem: TTBCustomItem;
|
|
I: Integer;
|
|
begin
|
|
if AGroupLevel > MaxGroupLevel then begin
|
|
Result := 0;
|
|
Exit;
|
|
end;
|
|
|
|
FViewers.Expand;
|
|
NewViewer := AItem.GetItemViewerClass(Self).Create(Self, AItem,
|
|
AGroupLevel);
|
|
FViewers.Insert(NewIndex, NewViewer);
|
|
if AddToPriorityList and FUsePriorityList then begin
|
|
if not TopOfPriorityList then
|
|
AddToList(FPriorityList, NewViewer)
|
|
else
|
|
{ When new items are inserted programmatically at run-time, place
|
|
them at the top of FPriorityList }
|
|
AddToFrontOfList(FPriorityList, NewViewer);
|
|
end;
|
|
Result := 1;
|
|
|
|
{ If a new group item is being inserted, insert all its child items too }
|
|
if not FCustomizing and (tbisEmbeddedGroup in AItem.ItemStyle) then begin
|
|
LinkItem := ItemContainingItems(AItem);
|
|
for I := 0 to LinkItem.Count-1 do begin
|
|
Inc(Result, InsertItemViewers(NewIndex + Result, LinkItem.Items[I],
|
|
AGroupLevel + 1, AddToPriorityList, TopOfPriorityList));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.ItemNotification(Ancestor: TTBCustomItem; Relayed: Boolean;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
|
|
procedure ItemInserted;
|
|
var
|
|
NewLevel, Start, InsertPoint, Last: Integer;
|
|
GroupItem, NextItem: TTBCustomItem;
|
|
Found, SearchAgain: Boolean;
|
|
begin
|
|
InvalidatePositions;
|
|
NewLevel := 0;
|
|
Start := 0;
|
|
if Ancestor = FCurParentItem then
|
|
InsertPoint := FViewers.Count
|
|
else begin
|
|
{ Ancestor <> FCurParentItem, so apparently an item has been inserted
|
|
inside a group item }
|
|
repeat
|
|
Found := False;
|
|
while Start < FViewers.Count do begin
|
|
GroupItem := Viewers[Start].Item;
|
|
if (tbisEmbeddedGroup in GroupItem.ItemStyle) and (GroupItem = Ancestor) then begin
|
|
NewLevel := Viewers[Start].FGroupLevel + 1;
|
|
Inc(Start);
|
|
Found := True;
|
|
Break;
|
|
end;
|
|
Inc(Start);
|
|
end;
|
|
if not Found then
|
|
{ Couldn't find Ancestor; it shouldn't get here }
|
|
Exit;
|
|
InsertPoint := Start;
|
|
SearchAgain := False;
|
|
while (InsertPoint < FViewers.Count) and
|
|
(Viewers[InsertPoint].FGroupLevel >= NewLevel) do begin
|
|
if (Viewers[InsertPoint].Item = Item) and
|
|
(Viewers[InsertPoint].FGroupLevel = NewLevel) then begin
|
|
{ If the item we were going to insert already exists, then there
|
|
must be multiple instances of the same group item. This can
|
|
happen when are two group items on the same toolbar each
|
|
linking to the same submenu item, with the submenu item
|
|
containing a group item of its own, and an item is inserted
|
|
inside that. }
|
|
SearchAgain := True;
|
|
Break;
|
|
end;
|
|
Inc(InsertPoint);
|
|
end;
|
|
until not SearchAgain;
|
|
end;
|
|
if InsertPoint = FViewers.Count then begin
|
|
{ Don't add items after the chevron or MDI buttons item }
|
|
Dec(InsertPoint, FInternalViewersAtEnd);
|
|
if InsertPoint < 0 then
|
|
InsertPoint := 0; { just in case? }
|
|
end;
|
|
{ If the new item wasn't placed at the end, adjust InsertPoint accordingly }
|
|
if Index < Item.Parent.Count-1 then begin
|
|
Last := InsertPoint;
|
|
InsertPoint := Start;
|
|
NextItem := Item.Parent.Items[Index+1];
|
|
while (InsertPoint < Last) and
|
|
((Viewers[InsertPoint].Item <> NextItem) or
|
|
(Viewers[InsertPoint].FGroupLevel <> NewLevel)) do
|
|
Inc(InsertPoint);
|
|
end;
|
|
InsertItemViewers(InsertPoint, Item, NewLevel, True,
|
|
not(csLoading in Item.ComponentState) and FNewViewersGetHighestPriority);
|
|
end;
|
|
|
|
procedure ItemDeleting;
|
|
|
|
procedure DeleteItem(DeleteIndex: Integer);
|
|
var
|
|
Viewer: TTBItemViewer;
|
|
begin
|
|
Viewer := Viewers[DeleteIndex];
|
|
DeletingViewer(Viewer);
|
|
RemoveFromList(FPriorityList, Viewer);
|
|
FreeAndNil(Viewer);
|
|
FViewers.Delete(DeleteIndex);
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
DeleteLevel: Integer;
|
|
begin
|
|
InvalidatePositions;
|
|
I := 0;
|
|
DeleteLevel := 0;
|
|
while I < FViewers.Count do begin
|
|
if DeleteLevel > 0 then begin
|
|
if Viewers[I].FGroupLevel >= DeleteLevel then begin
|
|
DeleteItem(I);
|
|
Continue;
|
|
end
|
|
else
|
|
DeleteLevel := 0;
|
|
end;
|
|
if Viewers[I].Item = Item then begin
|
|
{ Delete the item, and any group item children afterward }
|
|
DeleteLevel := Viewers[I].FGroupLevel + 1;
|
|
DeleteItem(I);
|
|
Continue;
|
|
end;
|
|
Inc(I);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
begin
|
|
case Action of
|
|
tbicInserted: ItemInserted;
|
|
tbicDeleting: ItemDeleting;
|
|
tbicSubitemsChanged: begin
|
|
{ If Relayed=True, LinkSubitems must have changed on a child group
|
|
item. Currently there isn't any optimized way of handling this
|
|
situation; just recreate all viewers. }
|
|
if Relayed then
|
|
RecreateAllViewers;
|
|
end;
|
|
tbicSubitemsBeginUpdate: BeginUpdate;
|
|
tbicSubitemsEndUpdate: EndUpdate;
|
|
tbicInvalidate: begin
|
|
for I := 0 to FViewers.Count-1 do
|
|
if Viewers[I].Item = Item then
|
|
Invalidate(Viewers[I]);
|
|
end;
|
|
tbicInvalidateAndResize: InvalidatePositions;
|
|
tbicRecreateItemViewers: begin
|
|
InvalidatePositions;
|
|
for I := 0 to FViewers.Count-1 do
|
|
if Viewers[I].Item = Item then
|
|
RecreateItemViewer(I);
|
|
end;
|
|
tbicSubMenuImagesChanged: ImagesChanged;
|
|
else
|
|
{ Prevent TryValidatePositions from being called below on Actions other than
|
|
those listed above. Currently there are no other Actions, but for forward
|
|
compatibility, we should ignore unknown Actions completely. }
|
|
Exit;
|
|
end;
|
|
TryValidatePositions;
|
|
end;
|
|
|
|
procedure TTBView.LinkNotification(Ancestor: TTBCustomItem; Relayed: Boolean;
|
|
Action: TTBItemChangedAction; Index: Integer; Item: TTBCustomItem);
|
|
{ This notification procedure watches for tbicSubitemsChanged notifications
|
|
from FParentItem }
|
|
begin
|
|
case Action of
|
|
tbicSubitemsChanged: begin
|
|
{ LinkSubitems may have changed on FParentItem, e.g. on the root item
|
|
of a toolbar, so see if FCurParentItem needs updating }
|
|
UpdateCurParentItem;
|
|
end;
|
|
tbicSubMenuImagesChanged: begin
|
|
{ In case the images were inherited from the actual parent instead of
|
|
the linked parent... }
|
|
if FParentItem <> FCurParentItem then
|
|
ImagesChanged;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.ImagesChanged;
|
|
begin
|
|
InvalidatePositions;
|
|
TryValidatePositions;
|
|
if Assigned(FOpenViewerView) then
|
|
FOpenViewerView.ImagesChanged;
|
|
end;
|
|
|
|
procedure TTBView.GivePriority(AViewer: TTBItemViewer);
|
|
{ Move item to top of priority list. Rearranges items if necessary. }
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if Assigned(FChevronParentView) then begin
|
|
I := AViewer.Index + FChevronParentView.FInternalViewersAtFront;
|
|
if I < FChevronParentView.FViewers.Count then { range check just in case }
|
|
FChevronParentView.GivePriority(FChevronParentView.Viewers[I]);
|
|
Exit;
|
|
end;
|
|
if Assigned(FPriorityList) then begin
|
|
I := FPriorityList.IndexOf(AViewer);
|
|
if I <> -1 then begin
|
|
FPriorityList.Move(I, 0);
|
|
if not FValidated or AViewer.OffEdge then
|
|
UpdatePositions;
|
|
end;
|
|
end;
|
|
{ Call GivePriority on parent view, so that if an item on a submenu is
|
|
clicked, the parent item of the submenu gets priority. }
|
|
if Assigned(FParentView) and Assigned(FParentView.FOpenViewer) then
|
|
FParentView.GivePriority(FParentView.FOpenViewer);
|
|
end;
|
|
|
|
function TTBView.HighestPriorityViewer: TTBItemViewer;
|
|
{ Returns index of first visible, non-separator item at top of priority list,
|
|
or -1 if there are no items found }
|
|
var
|
|
I: Integer;
|
|
J: TTBItemViewer;
|
|
begin
|
|
ValidatePositions;
|
|
Result := nil;
|
|
if Assigned(FPriorityList) then begin
|
|
for I := 0 to FPriorityList.Count-1 do begin
|
|
J := TTBItemViewer(FPriorityList[I]);
|
|
if J.Show and not(tbisSeparator in J.Item.ItemStyle) then begin
|
|
Result := J;
|
|
Break;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
J := Viewers[I];
|
|
if J.Show and not(tbisSeparator in J.Item.ItemStyle) then begin
|
|
Result := J;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.StartTimer(const ATimer: TTBViewTimerID;
|
|
const Interval: Integer);
|
|
{ Starts a timer. Stops any previously set timer of the same ID first.
|
|
Note: WM_TIMER messages generated by timers set by the method are handled
|
|
in PopupMessageLoop. }
|
|
begin
|
|
StopTimer(ATimer);
|
|
if (FWindow is TTBPopupWindow) and FWindow.HandleAllocated then begin
|
|
SetTimer(FWindow.Handle, ViewTimerBaseID + Ord(ATimer), Interval, nil);
|
|
Include(FActiveTimers, ATimer);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.StopAllTimers;
|
|
var
|
|
I: TTBViewTimerID;
|
|
begin
|
|
for I := Low(I) to High(I) do
|
|
StopTimer(I);
|
|
end;
|
|
|
|
procedure TTBView.StopTimer(const ATimer: TTBViewTimerID);
|
|
begin
|
|
if ATimer in FActiveTimers then begin
|
|
if (FWindow is TTBPopupWindow) and FWindow.HandleAllocated then
|
|
KillTimer(FWindow.Handle, ViewTimerBaseID + Ord(ATimer));
|
|
Exclude(FActiveTimers, ATimer);
|
|
end;
|
|
end;
|
|
|
|
function TTBView.OpenChildPopup(const SelectFirstItem: Boolean): Boolean;
|
|
var
|
|
Item: TTBCustomItem;
|
|
begin
|
|
StopTimer(tiClose);
|
|
StopTimer(tiOpen);
|
|
if FSelected <> FOpenViewer then begin
|
|
CloseChildPopups;
|
|
if Assigned(FSelected) then begin
|
|
Item := FSelected.Item;
|
|
if Item.Enabled and (tbisSubmenu in Item.ItemStyle) then
|
|
Item.CreatePopup(Self, FSelected, not FIsToolbar, SelectFirstItem,
|
|
False, Point(0, 0), tbpaLeft);
|
|
end;
|
|
end;
|
|
Result := Assigned(FOpenViewer);
|
|
end;
|
|
|
|
procedure TTBView.CloseChildPopups;
|
|
begin
|
|
if Assigned(FOpenViewerView) then
|
|
FOpenViewerView.CloseChildPopups;
|
|
StopTimer(tiClose);
|
|
FOpenViewerWindow.Free;
|
|
FOpenViewerWindow := nil;
|
|
FOpenViewerView := nil;
|
|
FOpenViewer := nil;
|
|
end;
|
|
|
|
procedure TTBView.CancelChildPopups;
|
|
begin
|
|
if FIsToolbar then
|
|
Exclude(FState, vsDropDownMenus);
|
|
CloseChildPopups;
|
|
end;
|
|
|
|
function TTBView.ViewerFromPoint(const P: TPoint): TTBItemViewer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
ValidatePositions;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
if Viewers[I].Show and
|
|
PtInRect(Viewers[I].BoundsRect, P) then begin
|
|
Result := Viewers[I];
|
|
Exit;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TTBView.NotifyFocusEvent;
|
|
{ Notifies Active Accessibility of a change in "focus". Has no effect if the
|
|
view or the root view lacks the vsModal state, or if the modal loop is
|
|
ending (EndModal* was called). }
|
|
var
|
|
I, ChildID, J: Integer;
|
|
begin
|
|
{ Note: We don't notify about windows not yet shown (e.g. a popup menu that
|
|
is still initializing) because that would probably confuse screen readers.
|
|
Also allocating a window handle at this point *might* not be a good idea. }
|
|
if (vsModal in FState) and (vsModal in GetRootView.FState) and
|
|
not IsModalEnding and
|
|
FWindow.HandleAllocated and IsWindowVisible(FWindow.Handle) then begin
|
|
if Assigned(FSelected) and FSelected.IsAccessible then
|
|
I := IndexOf(FSelected)
|
|
else
|
|
I := -1;
|
|
if (I < 0) and Assigned(FParentView) then begin
|
|
{ If we have no selected item, report the the selected item on the parent
|
|
view as having the "focus".
|
|
Note: With standard menus, when you go from having a selection to no
|
|
selection on a submenu, it sends two focus events - first with the
|
|
client window as having the focus, then with the parent item. I
|
|
figure that's probably a bug, so I don't try to emulate that behavior
|
|
here. }
|
|
FParentView.NotifyFocusEvent;
|
|
end
|
|
else begin
|
|
if I >= 0 then begin
|
|
{ Convert viewer index into a one-based child index.
|
|
(TTBViewAccObject.get_accChild does the inverse.) }
|
|
ChildID := 1;
|
|
for J := 0 to I-1 do
|
|
if Viewers[J].IsAccessible then
|
|
Inc(ChildID);
|
|
end
|
|
else begin
|
|
{ If there is no (accessible) selection and no parent view, report
|
|
the client window itself as being "focused". This is what happens
|
|
when a standard context menu has no selection. }
|
|
ChildID := CHILDID_SELF;
|
|
end;
|
|
CallNotifyWinEvent(EVENT_OBJECT_FOCUS, FWindow.Handle, OBJID_CLIENT, ChildID);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.SetSelected(Value: TTBItemViewer);
|
|
begin
|
|
Select(Value, False);
|
|
end;
|
|
|
|
procedure TTBView.Select(Value: TTBItemViewer; ViaMouse: Boolean);
|
|
{ Sets the current selection.
|
|
When the selection is changing it will also, if necessary, open/close child
|
|
popups. How exactly this works depends on the setting of ViaMouse. If
|
|
ViaMouse is True it will delay the opening/closing of popups using timers. }
|
|
var
|
|
OldSelected: TTBItemViewer;
|
|
NewMouseOverSelected: Boolean;
|
|
P: TPoint;
|
|
begin
|
|
OldSelected := FSelected;
|
|
if Value <> OldSelected then begin
|
|
{ If there's a new selection and the parent item on the parent view
|
|
isn't currently selected, select it. Also stop any timer running on
|
|
the parent view. }
|
|
if Assigned(Value) and Assigned(FParentView) and
|
|
Assigned(FParentView.FOpenViewer) and
|
|
(FParentView.FSelected <> FParentView.FOpenViewer) then begin
|
|
FParentView.Selected := FParentView.FOpenViewer;
|
|
FParentView.StopTimer(tiClose);
|
|
FParentView.StopTimer(tiOpen);
|
|
end;
|
|
|
|
{ Handle automatic closing of child popups }
|
|
if vsModal in FState then begin
|
|
{ If the view is a toolbar, or if the new selection didn't come from
|
|
the mouse, close child popups immediately }
|
|
if FIsToolbar or not ViaMouse then begin
|
|
{ Always stop any close timer because CloseChildPopups may not be
|
|
called below }
|
|
StopTimer(tiClose);
|
|
if Value <> FOpenViewer then
|
|
{ ^ But don't close if selection is returning to the open item.
|
|
Needed for the "FParentView.Selected := FParentView.FOpenViewer"
|
|
line above to work. }
|
|
CloseChildPopups;
|
|
end
|
|
else begin
|
|
{ Otherwise, delay-close any child popup }
|
|
if Assigned(FOpenViewerView) and not(tiClose in FActiveTimers) then
|
|
StartTimer(tiClose, GetMenuShowDelay);
|
|
end;
|
|
end;
|
|
|
|
CancelCapture;
|
|
if Assigned(OldSelected) then
|
|
OldSelected.Leaving;
|
|
FSelected := Value;
|
|
FSelectedViaMouse := ViaMouse;
|
|
end;
|
|
|
|
NewMouseOverSelected := False;
|
|
if Assigned(Value) and Assigned(FWindow) then begin
|
|
P := GetMessagePosAsPoint;
|
|
if FindDragTarget(P, True) = FWindow then begin
|
|
P := FWindow.ScreenToClient(P);
|
|
NewMouseOverSelected := (ViewerFromPoint(P) = Value);
|
|
if NewMouseOverSelected and FCapture and
|
|
not Value.IsPtInButtonPart(P.X - Value.BoundsRect.Left,
|
|
P.Y - Value.BoundsRect.Top) then
|
|
NewMouseOverSelected := False;
|
|
end;
|
|
end;
|
|
|
|
if Value <> OldSelected then begin
|
|
FMouseOverSelected := NewMouseOverSelected;
|
|
if Assigned(OldSelected) and (tbisRedrawOnSelChange in OldSelected.Item.ItemStyle) then
|
|
Invalidate(OldSelected);
|
|
if Assigned(Value) then begin
|
|
if tbisRedrawOnSelChange in Value.Item.ItemStyle then
|
|
Invalidate(Value);
|
|
Value.Entering;
|
|
end;
|
|
NotifyFocusEvent;
|
|
|
|
{ Handle automatic opening of a child popup }
|
|
if vsModal in FState then begin
|
|
{ If the view is a toolbar, immediately open any child popup }
|
|
if FIsToolbar then begin
|
|
if Assigned(Value) then begin
|
|
if ViaMouse and Assigned(FParentView) then begin
|
|
{ On chevron popups, always drop down menus when mouse passes
|
|
over them, like Office 2000 }
|
|
Include(FState, vsDropDownMenus);
|
|
end;
|
|
if (vsDropDownMenus in FState) and
|
|
(ViaMouse or not(tbisNoAutoOpen in Value.Item.ItemStyle)) then
|
|
OpenChildPopup(not ViaMouse);
|
|
end;
|
|
end
|
|
else begin
|
|
{ Otherwise, delay-open any child popup if the selection came from
|
|
the mouse }
|
|
StopTimer(tiOpen);
|
|
if ViaMouse and Assigned(Value) and (tbisSubmenu in Value.Item.ItemStyle) then
|
|
StartTimer(tiOpen, GetMenuShowDelay);
|
|
end;
|
|
end;
|
|
end
|
|
else if FMouseOverSelected <> NewMouseOverSelected then begin
|
|
FMouseOverSelected := NewMouseOverSelected;
|
|
if Assigned(Value) and FCapture and (tbisRedrawOnMouseOverChange in Value.Item.ItemStyle) then
|
|
Invalidate(Value);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.UpdateSelection(const P: TPoint; const AllowNewSelection: Boolean);
|
|
{ Called in response to a mouse movement, this method updates the current
|
|
selection, updates the vsMouseInWindow view state, and enables/disables
|
|
scroll timers. }
|
|
|
|
function IsPtInScrollArrow(ADownArrow: Boolean): Boolean;
|
|
var
|
|
P2: TPoint;
|
|
R: TRect;
|
|
begin
|
|
Result := False;
|
|
if (vsModal in FState) and (vsMouseInWindow in FState) and not FCapture and
|
|
(P.X <> Low(Integer)) then begin
|
|
P2 := FWindow.ScreenToClient(P);
|
|
R := FWindow.ClientRect;
|
|
if PtInRect(R, P2) then begin
|
|
if ADownArrow then
|
|
Result := FShowDownArrow and (P2.Y >= R.Bottom - tbMenuScrollArrowHeight)
|
|
else
|
|
Result := FShowUpArrow and (P2.Y < tbMenuScrollArrowHeight);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
NewSelected, ViewerAtPoint: TTBItemViewer;
|
|
P2: TPoint;
|
|
MouseWasInWindow: Boolean;
|
|
begin
|
|
ValidatePositions;
|
|
|
|
if FCapture then begin
|
|
{ If we have the capture, don't allow the selection to change. And always
|
|
set vsMouseInWindow so that if the mouse is released outside the window,
|
|
the "remove the selection" code below will be reached the next time
|
|
UpdateSelection is called. }
|
|
NewSelected := FSelected;
|
|
Include(FState, vsMouseInWindow);
|
|
end
|
|
else begin
|
|
{ If modal, default to keeping the existing selection }
|
|
if vsModal in FState then
|
|
NewSelected := FSelected
|
|
else
|
|
NewSelected := nil;
|
|
|
|
{ Is the mouse inside the window? }
|
|
MouseWasInWindow := vsMouseInWindow in FState;
|
|
if (P.X <> Low(Integer)) and Assigned(FWindow) and (FindDragTarget(P, True) = FWindow) then begin
|
|
{ If we're a popup window and the mouse is inside, default to no selection }
|
|
if FIsPopup then
|
|
NewSelected := nil;
|
|
Include(FState, vsMouseInWindow);
|
|
if AllowNewSelection or Assigned(FSelected) then begin
|
|
P2 := FWindow.ScreenToClient(P);
|
|
ViewerAtPoint := ViewerFromPoint(P2);
|
|
if Assigned(ViewerAtPoint) then
|
|
NewSelected := ViewerAtPoint;
|
|
end;
|
|
end
|
|
else begin
|
|
Exclude(FState, vsMouseInWindow);
|
|
{ If we're a popup window and the mouse just moved outside the window
|
|
while no submenu was open or a non-submenu-displaying item was
|
|
selected, remove the selection }
|
|
if FIsPopup and Assigned(NewSelected) and MouseWasInWindow and
|
|
(not Assigned(FOpenViewerView) or not(tbisSubmenu in NewSelected.Item.ItemStyle)) then
|
|
NewSelected := nil;
|
|
end;
|
|
end;
|
|
|
|
{ Now we set the new Selected value }
|
|
Select(NewSelected, True);
|
|
|
|
{ Update scroll arrow timers }
|
|
if IsPtInScrollArrow(False) then begin
|
|
StopTimer(tiScrollDown);
|
|
if not(tiScrollUp in FActiveTimers) then
|
|
StartTimer(tiScrollUp, 100);
|
|
end
|
|
else if IsPtInScrollArrow(True) then begin
|
|
StopTimer(tiScrollUp);
|
|
if not(tiScrollDown in FActiveTimers) then
|
|
StartTimer(tiScrollDown, 100);
|
|
end
|
|
else begin
|
|
StopTimer(tiScrollUp);
|
|
StopTimer(tiScrollDown);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.RecreateAllViewers;
|
|
var
|
|
Item: TTBCustomItem;
|
|
I: Integer;
|
|
begin
|
|
{ Since the FViewers list is being rebuilt, FOpenViewer and FSelected
|
|
will no longer be valid, so ensure they're set to nil. }
|
|
CloseChildPopups;
|
|
Selected := nil;
|
|
|
|
InvalidatePositions;
|
|
|
|
FreeAndNil(FPriorityList);
|
|
FreeViewers;
|
|
FInternalViewersAtFront := 0;
|
|
FInternalViewersAtEnd := 0;
|
|
|
|
{ MDI system menu item }
|
|
Item := GetMDISystemMenuItem;
|
|
if Assigned(Item) then
|
|
Inc(FInternalViewersAtFront, InsertItemViewers(FViewers.Count, Item, 0,
|
|
False, False));
|
|
|
|
{ Items }
|
|
if Assigned(FCurParentItem) then begin
|
|
for I := 0 to FCurParentItem.Count-1 do
|
|
InsertItemViewers(FViewers.Count, FCurParentItem.Items[I], 0,
|
|
True, False);
|
|
end;
|
|
|
|
{ MDI buttons item }
|
|
Item := GetMDIButtonsItem;
|
|
if Assigned(Item) then begin
|
|
for I := 0 to Item.Count-1 do
|
|
Inc(FInternalViewersAtEnd, InsertItemViewers(FViewers.Count,
|
|
Item.Items[I], 0, False, False));
|
|
end;
|
|
|
|
{ Chevron item }
|
|
Item := GetChevronItem;
|
|
if Assigned(Item) then
|
|
Inc(FInternalViewersAtEnd, InsertItemViewers(FViewers.Count, Item, 0,
|
|
False, False));
|
|
end;
|
|
|
|
function TTBView.CalculatePositions(const CanMoveControls: Boolean;
|
|
const AOrientation: TTBViewOrientation;
|
|
AWrapOffset, AChevronOffset, AChevronSize: Integer;
|
|
var ABaseSize, TotalSize: TPoint;
|
|
var AWrappedLines: Integer): Boolean;
|
|
{ Returns True if the positions have changed }
|
|
type
|
|
TTempPosition = record
|
|
BoundsRect: TRect;
|
|
Show, OffEdge, LineSep, Clipped, SameWidth: Boolean;
|
|
{ Include an Integer field to enforce Integer alignment of the record
|
|
(which we don't get by default due to TRect being wrongly declared as
|
|
'packed'). Needed to avoid alignment fault on Delphi.NET 2007 IA-64. }
|
|
DummyAlignment: Integer;
|
|
end;
|
|
TTempPositionArrayItem = record
|
|
Pos: TTempPosition;
|
|
end;
|
|
var
|
|
DC: HDC;
|
|
LeftX, TopY, CurX, CurY: Integer;
|
|
NewPositions: array of TTempPositionArrayItem;
|
|
GroupSplit, DidWrap: Boolean;
|
|
LineStart, HighestHeightOnLine, HighestWidthOnLine: Integer;
|
|
|
|
function GetSizeOfGroup(const StartingIndex: Integer): Integer;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := 0;
|
|
for I := StartingIndex to FViewers.Count-1 do begin
|
|
with NewPositions[I] do begin
|
|
if not Pos.Show then
|
|
Continue;
|
|
if tbisSeparator in Viewers[I].Item.ItemStyle then
|
|
Break;
|
|
if AOrientation <> tbvoVertical then
|
|
Inc(Result, Pos.BoundsRect.Right)
|
|
else
|
|
Inc(Result, Pos.BoundsRect.Bottom);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure Mirror;
|
|
{ Reverses the horizontal ordering (i.e. first item becomes last) }
|
|
var
|
|
I, NewRight: Integer;
|
|
begin
|
|
for I := 0 to FViewers.Count-1 do
|
|
with NewPositions[I] do
|
|
if Pos.Show then begin
|
|
NewRight := TotalSize.X - Pos.BoundsRect.Left;
|
|
Pos.BoundsRect.Left := TotalSize.X - Pos.BoundsRect.Right;
|
|
Pos.BoundsRect.Right := NewRight;
|
|
end;
|
|
end;
|
|
|
|
procedure HandleMaxHeight;
|
|
{ Decreases, if necessary, the height of the view to FMaxHeight, and adjusts
|
|
the visibility of the scroll arrows }
|
|
var
|
|
MaxOffset, I, MaxTop, MaxBottom: Integer;
|
|
begin
|
|
FShowUpArrow := False;
|
|
FShowDownArrow := False;
|
|
if (FMaxHeight > 0) and (TotalSize.Y > FMaxHeight) then begin
|
|
MaxOffset := TotalSize.Y - FMaxHeight;
|
|
if FScrollOffset > MaxOffset then
|
|
FScrollOffset := MaxOffset;
|
|
if FScrollOffset < 0 then
|
|
FScrollOffset := 0;
|
|
FShowUpArrow := (FScrollOffset > 0);
|
|
FShowDownArrow := (FScrollOffset < MaxOffset);
|
|
MaxTop := 0;
|
|
if FShowUpArrow then
|
|
MaxTop := tbMenuScrollArrowHeight;
|
|
MaxBottom := FMaxHeight;
|
|
if FShowDownArrow then
|
|
Dec(MaxBottom, tbMenuScrollArrowHeight);
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
with NewPositions[I] do begin
|
|
if not IsRectEmpty(Pos.BoundsRect) then begin
|
|
OffsetRect(Pos.BoundsRect, 0, -FScrollOffset);
|
|
if Pos.Show and
|
|
((Pos.BoundsRect.Top < MaxTop) or
|
|
(Pos.BoundsRect.Bottom > MaxBottom)) then begin
|
|
Pos.Show := False;
|
|
Pos.Clipped := True;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
TotalSize.Y := FMaxHeight;
|
|
end
|
|
else
|
|
FScrollOffset := 0;
|
|
end;
|
|
|
|
procedure FinalizeLine(const LineEnd: Integer; const LastLine: Boolean);
|
|
var
|
|
I, RightAlignStart: Integer;
|
|
Item: TTBCustomItem;
|
|
IsButton: Boolean;
|
|
Z: Integer;
|
|
begin
|
|
if LineStart <> -1 then begin
|
|
if DidWrap and (FChevronParentView = nil) then begin
|
|
{ When wrapping on a docked toolbar, extend TotalSize.X/Y to
|
|
AWrapOffset so that the toolbar always fills the whole row }
|
|
if (AOrientation = tbvoHorizontal) and (TotalSize.X < AWrapOffset) then
|
|
TotalSize.X := AWrapOffset
|
|
else if (AOrientation = tbvoVertical) and (TotalSize.Y < AWrapOffset) then
|
|
TotalSize.Y := AWrapOffset;
|
|
end;
|
|
RightAlignStart := -1;
|
|
for I := LineStart to LineEnd do begin
|
|
with NewPositions[I] do begin
|
|
if not Pos.Show then
|
|
Continue;
|
|
Item := Viewers[I].Item;
|
|
if (RightAlignStart < 0) and (tbisRightAlign in Item.ItemStyle) then
|
|
RightAlignStart := I;
|
|
IsButton := FIsToolbar or (tboToolbarSize in Item.FEffectiveOptions);
|
|
if FIsToolbar then begin
|
|
if LastLine and not DidWrap and (AOrientation <> tbvoFloating) then begin
|
|
{ In case the toolbar is docked next to a taller/wider toolbar... }
|
|
HighestWidthOnLine := TotalSize.X;
|
|
HighestHeightOnLine := TotalSize.Y;
|
|
end;
|
|
{ Make separators on toolbars as tall/wide as the tallest/widest item }
|
|
if tbisSeparator in Item.ItemStyle then begin
|
|
if AOrientation <> tbvoVertical then
|
|
Pos.BoundsRect.Bottom := Pos.BoundsRect.Top + HighestHeightOnLine
|
|
else
|
|
Pos.BoundsRect.Right := Pos.BoundsRect.Left + HighestWidthOnLine;
|
|
end
|
|
else begin
|
|
{ Center the item }
|
|
if AOrientation <> tbvoVertical then begin
|
|
Z := (HighestHeightOnLine - (Pos.BoundsRect.Bottom - Pos.BoundsRect.Top)) div 2;
|
|
Inc(Pos.BoundsRect.Top, Z);
|
|
Inc(Pos.BoundsRect.Bottom, Z);
|
|
end
|
|
else begin
|
|
Z := (HighestWidthOnLine - (Pos.BoundsRect.Right - Pos.BoundsRect.Left)) div 2;
|
|
Inc(Pos.BoundsRect.Left, Z);
|
|
Inc(Pos.BoundsRect.Right, Z);
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
{ Make items in a menu as wide as the widest item }
|
|
if not IsButton then begin
|
|
with Pos.BoundsRect do Right := Left + HighestWidthOnLine;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if RightAlignStart >= 0 then begin
|
|
Z := 0;
|
|
for I := LineEnd downto RightAlignStart do begin
|
|
with NewPositions[I] do begin
|
|
if not Pos.Show then
|
|
Continue;
|
|
if AOrientation <> tbvoVertical then
|
|
Z := Min(AWrapOffset, TotalSize.X) - Pos.BoundsRect.Right
|
|
else
|
|
Z := Min(AWrapOffset, TotalSize.Y) - Pos.BoundsRect.Bottom;
|
|
end;
|
|
Break;
|
|
end;
|
|
if Z > 0 then begin
|
|
for I := RightAlignStart to LineEnd do begin
|
|
with NewPositions[I] do begin
|
|
if not Pos.Show then
|
|
Continue;
|
|
if AOrientation <> tbvoVertical then begin
|
|
Inc(Pos.BoundsRect.Left, Z);
|
|
Inc(Pos.BoundsRect.Right, Z);
|
|
end
|
|
else begin
|
|
Inc(Pos.BoundsRect.Top, Z);
|
|
Inc(Pos.BoundsRect.Bottom, Z);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
LineStart := -1;
|
|
HighestHeightOnLine := 0;
|
|
HighestWidthOnLine := 0;
|
|
end;
|
|
|
|
procedure PositionItem(const CurIndex: Integer; var Pos: TTempPosition);
|
|
var
|
|
O, X, Y: Integer;
|
|
IsLineSep, Vert: Boolean;
|
|
begin
|
|
if LineStart = -1 then begin
|
|
LineStart := CurIndex;
|
|
HighestHeightOnLine := 0;
|
|
HighestWidthOnLine := 0;
|
|
end;
|
|
IsLineSep := False;
|
|
Vert := (AOrientation = tbvoVertical);
|
|
if not Vert then
|
|
O := CurX
|
|
else
|
|
O := CurY;
|
|
if (AWrapOffset > 0) and (O > 0) then begin
|
|
if not Vert then
|
|
Inc(O, Pos.BoundsRect.Right)
|
|
else
|
|
Inc(O, Pos.BoundsRect.Bottom);
|
|
if (tbisSeparator in Viewers[CurIndex].Item.ItemStyle) and
|
|
((GroupSplit and not(tbisNoLineBreak in Viewers[CurIndex].Item.ItemStyle))
|
|
or (O + GetSizeOfGroup(CurIndex+1) > AWrapOffset)) then begin
|
|
DidWrap := True;
|
|
Inc(AWrappedLines);
|
|
if not Vert then begin
|
|
CurX := 0;
|
|
Inc(CurY, HighestHeightOnLine);
|
|
end
|
|
else begin
|
|
CurY := 0;
|
|
Inc(CurX, HighestWidthOnLine);
|
|
end;
|
|
FinalizeLine(CurIndex-1, False);
|
|
LineStart := CurIndex+1;
|
|
if not Vert then begin
|
|
Pos.BoundsRect.Right := 0;
|
|
Pos.BoundsRect.Bottom := tbLineSpacing;
|
|
end
|
|
else begin
|
|
Pos.BoundsRect.Right := tbLineSpacing;
|
|
Pos.BoundsRect.Bottom := 0;
|
|
end;
|
|
Pos.LineSep := True;
|
|
IsLineSep := True;
|
|
end
|
|
else if O > AWrapOffset then begin
|
|
{ proceed to next row }
|
|
DidWrap := True;
|
|
Inc(AWrappedLines);
|
|
if not Vert then begin
|
|
CurX := LeftX;
|
|
Inc(CurY, HighestHeightOnLine);
|
|
end
|
|
else begin
|
|
CurY := TopY;
|
|
Inc(CurX, HighestWidthOnLine);
|
|
end;
|
|
GroupSplit := True;
|
|
FinalizeLine(CurIndex-1, False);
|
|
LineStart := CurIndex;
|
|
end;
|
|
end;
|
|
if Pos.BoundsRect.Bottom > HighestHeightOnLine then
|
|
HighestHeightOnLine := Pos.BoundsRect.Bottom;
|
|
if Pos.BoundsRect.Right > HighestWidthOnLine then
|
|
HighestWidthOnLine := Pos.BoundsRect.Right;
|
|
X := CurX;
|
|
Y := CurY;
|
|
if X < 0 then X := 0;
|
|
if Y < 0 then Y := 0;
|
|
OffsetRect(Pos.BoundsRect, X, Y);
|
|
if IsLineSep then begin
|
|
if not Vert then begin
|
|
CurX := LeftX;
|
|
Inc(CurY, tbLineSpacing);
|
|
end
|
|
else begin
|
|
CurY := TopY;
|
|
Inc(CurX, tbLineSpacing);
|
|
end;
|
|
GroupSplit := False;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
SaveOrientation: TTBViewOrientation;
|
|
ChevronItem: TTBCustomItem;
|
|
CalcCanvas: TCanvas;
|
|
LastWasSep, LastWasButton, IsButton, IsControl: Boolean;
|
|
Item: TTBCustomItem;
|
|
Ctl: TControl;
|
|
ChangedBold: Boolean;
|
|
I, HighestSameWidthViewerWidth, Total, J, TotalVisibleItems: Integer;
|
|
IsFirst: Boolean;
|
|
Viewer: TTBItemViewer;
|
|
UseChevron, NonControlsOffEdge, TempViewerCreated: Boolean;
|
|
Margins: TRect;
|
|
label FoundItemToHide;
|
|
begin
|
|
SaveOrientation := FOrientation;
|
|
AWrappedLines := 1;
|
|
ChevronItem := GetChevronItem;
|
|
DC := 0;
|
|
CalcCanvas := nil;
|
|
try
|
|
FOrientation := AOrientation;
|
|
|
|
CalcCanvas := TCanvas.Create;
|
|
DC := GetDC(0);
|
|
CalcCanvas.Handle := DC;
|
|
CalcCanvas.Font.Assign(GetFont);
|
|
|
|
SetLength(NewPositions, FViewers.Count);
|
|
|
|
{ Figure out which items should be shown }
|
|
LastWasSep := True; { set to True initially so it won't show leading seps }
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
IsControl := Item is TTBControlItem;
|
|
with NewPositions[I] do begin
|
|
{ Pos.Show is initially False since SetLength initializes to zero }
|
|
if Item = ChevronItem then
|
|
Continue;
|
|
if Assigned(FChevronParentView) then begin
|
|
if IsControl then
|
|
Continue;
|
|
FChevronParentView.ValidatePositions;
|
|
J := I + FChevronParentView.FInternalViewersAtFront;
|
|
if J < FChevronParentView.FViewers.Count then
|
|
{ range check just in case }
|
|
Viewer := FChevronParentView.Viewers[J]
|
|
else
|
|
Viewer := nil;
|
|
if (Viewer = nil) or (not Viewer.OffEdge and not(tbisSeparator in Item.ItemStyle)) then
|
|
Continue;
|
|
end;
|
|
if not IsControl then begin
|
|
if not(tbisEmbeddedGroup in Item.ItemStyle) or FCustomizing then begin
|
|
Pos.Show := Item.Visible;
|
|
{ Don't display two consecutive separators }
|
|
if Pos.Show then begin
|
|
if (tbisSeparator in Item.ItemStyle) and LastWasSep then
|
|
Pos.Show := False;
|
|
LastWasSep := tbisSeparator in Item.ItemStyle;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
{ Controls can only be rendered on a single Parent, so only
|
|
include the control if its parent is currently equal to
|
|
FWindow }
|
|
Ctl := TTBControlItem(Item).FControl;
|
|
if Assigned(Ctl) and Assigned(FWindow) and (Ctl.Parent = FWindow) and
|
|
(Ctl.Visible or (csDesigning in Ctl.ComponentState)) then begin
|
|
Pos.Show := True;
|
|
LastWasSep := False;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Hide any trailing separators, so that they aren't included in the
|
|
base size }
|
|
for I := FViewers.Count-1 downto 0 do begin
|
|
with NewPositions[I] do
|
|
if Pos.Show then begin
|
|
if not(tbisSeparator in Viewers[I].Item.ItemStyle) then
|
|
Break;
|
|
Pos.Show := False;
|
|
end;
|
|
end;
|
|
|
|
{ Calculate sizes of all the items }
|
|
HighestSameWidthViewerWidth := 0;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
IsControl := Item is TTBControlItem;
|
|
with NewPositions[I] do begin
|
|
{ Pos.BoundsRect is currently empty since SetLength initializes to zero }
|
|
if not Pos.Show then
|
|
Continue;
|
|
if not IsControl then begin
|
|
ChangedBold := False;
|
|
if tboDefault in Item.EffectiveOptions then
|
|
with CalcCanvas.Font do
|
|
if not(fsBold in Style) then begin
|
|
ChangedBold := True;
|
|
Style := Style + [fsBold];
|
|
end;
|
|
Viewer := Viewers[I];
|
|
TempViewerCreated := False;
|
|
if Item.NeedToRecreateViewer(Viewer) then begin
|
|
if CanMoveControls then begin
|
|
RecreateItemViewer(I);
|
|
Viewer := Viewers[I];
|
|
end
|
|
else begin
|
|
Viewer := Item.GetItemViewerClass(Self).Create(Self, Item, 0);
|
|
TempViewerCreated := True;
|
|
end;
|
|
end;
|
|
try
|
|
Viewer.CalcSize(CalcCanvas, Pos.BoundsRect.Right, Pos.BoundsRect.Bottom);
|
|
if Viewer.UsesSameWidth then begin
|
|
Pos.SameWidth := True;
|
|
if (Pos.BoundsRect.Right > HighestSameWidthViewerWidth) then
|
|
HighestSameWidthViewerWidth := Pos.BoundsRect.Right;
|
|
end;
|
|
finally
|
|
if TempViewerCreated then
|
|
Viewer.Free;
|
|
end;
|
|
if ChangedBold then
|
|
with CalcCanvas.Font do
|
|
Style := Style - [fsBold];
|
|
end
|
|
else begin
|
|
Ctl := TTBControlItem(Item).FControl;
|
|
Pos.BoundsRect.Right := Ctl.Width;
|
|
Pos.BoundsRect.Bottom := Ctl.Height;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Increase widths of SameWidth items if necessary. Also calculate
|
|
ABaseSize.X (or Y). }
|
|
ABaseSize.X := 0;
|
|
ABaseSize.Y := 0;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
with NewPositions[I] do begin
|
|
if Pos.SameWidth and (Pos.BoundsRect.Right < HighestSameWidthViewerWidth) then
|
|
Pos.BoundsRect.Right := HighestSameWidthViewerWidth;
|
|
if AOrientation <> tbvoVertical then
|
|
Inc(ABaseSize.X, Pos.BoundsRect.Right)
|
|
else
|
|
Inc(ABaseSize.Y, Pos.BoundsRect.Bottom);
|
|
end;
|
|
end;
|
|
|
|
{ Hide partially visible items, mark them as 'OffEdge' }
|
|
if AOrientation <> tbvoVertical then
|
|
Total := ABaseSize.X
|
|
else
|
|
Total := ABaseSize.Y;
|
|
NonControlsOffEdge := False;
|
|
UseChevron := Assigned(ChevronItem) and (AChevronOffset > 0) and
|
|
(Total > AChevronOffset);
|
|
if UseChevron then begin
|
|
Dec(AChevronOffset, AChevronSize);
|
|
while Total > AChevronOffset do begin
|
|
{ Count number of items. Stop loop if <= 1 }
|
|
TotalVisibleItems := 0;
|
|
for I := FViewers.Count-1 downto 0 do begin
|
|
if NewPositions[I].Pos.Show and not(tbisSeparator in Viewers[I].Item.ItemStyle) then
|
|
Inc(TotalVisibleItems);
|
|
end;
|
|
if TotalVisibleItems <= 1 then
|
|
Break;
|
|
{ Hide any trailing separators }
|
|
for I := FViewers.Count-1 downto 0 do begin
|
|
if NewPositions[I].Pos.Show then begin
|
|
if not(tbisSeparator in Viewers[I].Item.ItemStyle) then
|
|
Break;
|
|
NewPositions[I].Pos.Show := False;
|
|
if AOrientation <> tbvoVertical then
|
|
Dec(Total, NewPositions[I].Pos.BoundsRect.Right)
|
|
else
|
|
Dec(Total, NewPositions[I].Pos.BoundsRect.Bottom);
|
|
goto FoundItemToHide;
|
|
end;
|
|
end;
|
|
{ Find an item to hide }
|
|
if Assigned(FPriorityList) then
|
|
I := FPriorityList.Count-1
|
|
else
|
|
I := FViewers.Count-1;
|
|
while I >= 0 do begin
|
|
if Assigned(FPriorityList) then begin
|
|
Viewer := TTBItemViewer(FPriorityList[I]);
|
|
J := Viewer.Index;
|
|
end
|
|
else begin
|
|
Viewer := Viewers[I];
|
|
J := I;
|
|
end;
|
|
if NewPositions[J].Pos.Show and not(tbisSeparator in Viewer.Item.ItemStyle) then begin
|
|
NewPositions[J].Pos.Show := False;
|
|
NewPositions[J].Pos.OffEdge := True;
|
|
if AOrientation <> tbvoVertical then
|
|
Dec(Total, NewPositions[J].Pos.BoundsRect.Right)
|
|
else
|
|
Dec(Total, NewPositions[J].Pos.BoundsRect.Bottom);
|
|
if not NonControlsOffEdge and not(Viewer.Item is TTBControlItem) then
|
|
NonControlsOffEdge := True;
|
|
goto FoundItemToHide;
|
|
end;
|
|
Dec(I);
|
|
end;
|
|
Break; { prevent endless loop }
|
|
FoundItemToHide:
|
|
{ Don't show two consecutive separators }
|
|
LastWasSep := True; { set to True initially so it won't show leading seps }
|
|
for J := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[J].Item;
|
|
with NewPositions[J] do begin
|
|
if Pos.Show then begin
|
|
if (tbisSeparator in Item.ItemStyle) and LastWasSep then begin
|
|
Pos.Show := False;
|
|
if AOrientation <> tbvoVertical then
|
|
Dec(Total, Pos.BoundsRect.Right)
|
|
else
|
|
Dec(Total, Pos.BoundsRect.Bottom);
|
|
end;
|
|
LastWasSep := tbisSeparator in Item.ItemStyle;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Hide any trailing separators after items were hidden }
|
|
for I := FViewers.Count-1 downto 0 do begin
|
|
with NewPositions[I] do
|
|
if Pos.Show then begin
|
|
if not(tbisSeparator in Viewers[I].Item.ItemStyle) then
|
|
Break;
|
|
Pos.Show := False;
|
|
end;
|
|
end;
|
|
|
|
{ Set the ABaseSize.Y (or X) *after* items were hidden }
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
with NewPositions[I] do
|
|
if Pos.Show then begin
|
|
if AOrientation <> tbvoVertical then begin
|
|
if Pos.BoundsRect.Bottom > ABaseSize.Y then
|
|
ABaseSize.Y := Pos.BoundsRect.Bottom;
|
|
end
|
|
else begin
|
|
if Pos.BoundsRect.Right > ABaseSize.X then
|
|
ABaseSize.X := Pos.BoundsRect.Right;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ On menus, set all non-separator items to be as tall as the tallest item }
|
|
{if not FIsToolbar then begin
|
|
J := 0;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
with NewPositions[I] do
|
|
if Pos.Show and not(tbisSeparator in Item.ItemStyle) and
|
|
not(tboToolbarSize in Item.FEffectiveOptions) and
|
|
(Pos.BoundsRect.Bottom - Pos.BoundsRect.Top > J) then
|
|
J := Pos.BoundsRect.Bottom - Pos.BoundsRect.Top;
|
|
end;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
with NewPositions[I] do
|
|
if Pos.Show and not(tbisSeparator in Item.ItemStyle) and
|
|
not(tboToolbarSize in Item.FEffectiveOptions) then
|
|
Pos.BoundsRect.Bottom := Pos.BoundsRect.Top + J;
|
|
end;
|
|
end;}
|
|
|
|
{ Calculate the position of the items }
|
|
GetMargins(AOrientation, Margins);
|
|
LeftX := Margins.Left;
|
|
TopY := Margins.Top;
|
|
if AWrapOffset > 0 then begin
|
|
Dec(AWrapOffset, Margins.Right);
|
|
if AWrapOffset < 1 then AWrapOffset := 1;
|
|
end;
|
|
CurX := LeftX;
|
|
CurY := TopY;
|
|
GroupSplit := False;
|
|
DidWrap := False;
|
|
LastWasButton := FIsToolbar;
|
|
LineStart := -1;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
with NewPositions[I] do begin
|
|
if not Pos.Show then
|
|
Continue;
|
|
IsButton := FIsToolbar or (tboToolbarSize in Item.FEffectiveOptions);
|
|
if LastWasButton and not IsButton then begin
|
|
{ On a menu, if last item was a button and the current item isn't,
|
|
proceed to next row }
|
|
CurX := LeftX;
|
|
CurY := TotalSize.Y;
|
|
end;
|
|
LastWasButton := IsButton;
|
|
PositionItem(I, NewPositions[I].Pos);
|
|
if IsButton and (AOrientation <> tbvoVertical) then
|
|
Inc(CurX, Pos.BoundsRect.Right - Pos.BoundsRect.Left)
|
|
else
|
|
Inc(CurY, Pos.BoundsRect.Bottom - Pos.BoundsRect.Top);
|
|
if Pos.BoundsRect.Right > TotalSize.X then
|
|
TotalSize.X := Pos.BoundsRect.Right;
|
|
if Pos.BoundsRect.Bottom > TotalSize.Y then
|
|
TotalSize.Y := Pos.BoundsRect.Bottom;
|
|
end;
|
|
end;
|
|
if FViewers.Count <> 0 then
|
|
FinalizeLine(FViewers.Count-1, True);
|
|
Inc(TotalSize.X, Margins.Right);
|
|
Inc(TotalSize.Y, Margins.Bottom);
|
|
if AOrientation = tbvoVertical then
|
|
Mirror;
|
|
HandleMaxHeight;
|
|
if CanMoveControls then begin
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
if Item is TTBControlItem then begin
|
|
if NewPositions[I].Pos.Show then begin
|
|
Ctl := TTBControlItem(Item).FControl;
|
|
if not EqualRect(NewPositions[I].Pos.BoundsRect, Ctl.BoundsRect) then
|
|
Ctl.BoundsRect := NewPositions[I].Pos.BoundsRect;
|
|
end
|
|
else if NewPositions[I].Pos.OffEdge or NewPositions[I].Pos.Clipped then begin
|
|
{ Simulate hiding of OddEdge controls by literally moving them
|
|
off the edge. Do the same for Clipped controls. }
|
|
Ctl := TTBControlItem(Item).FControl;
|
|
Ctl.SetBounds(FWindow.ClientWidth, FWindow.ClientHeight,
|
|
Ctl.Width, Ctl.Height);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{ Set size of line separators }
|
|
if FIsToolbar then
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
with NewPositions[I] do
|
|
if Pos.Show and (tbisSeparator in Item.ItemStyle) and
|
|
Pos.LineSep then begin
|
|
if AOrientation <> tbvoVertical then
|
|
Pos.BoundsRect.Right := TotalSize.X
|
|
else
|
|
Pos.BoundsRect.Bottom := TotalSize.Y;
|
|
end;
|
|
end;
|
|
|
|
{ Position the chevron item }
|
|
if UseChevron then begin
|
|
if CanMoveControls then
|
|
ChevronItem.Enabled := NonControlsOffEdge;
|
|
NewPositions[FViewers.Count-1].Pos.Show := True;
|
|
I := AChevronOffset;
|
|
if AOrientation <> tbvoVertical then begin
|
|
if I < TotalSize.X then
|
|
I := TotalSize.X;
|
|
NewPositions[FViewers.Count-1].Pos.BoundsRect := Bounds(I, 0,
|
|
AChevronSize, TotalSize.Y);
|
|
end
|
|
else begin
|
|
if I < TotalSize.Y then
|
|
I := TotalSize.Y;
|
|
NewPositions[FViewers.Count-1].Pos.BoundsRect := Bounds(0, I,
|
|
TotalSize.X, AChevronSize);
|
|
end;
|
|
end;
|
|
|
|
{ Commit changes }
|
|
Result := False;
|
|
if CanMoveControls then begin
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Viewer := Viewers[I];
|
|
with NewPositions[I] do begin
|
|
if not Result and
|
|
(not EqualRect(Viewer.BoundsRect, Pos.BoundsRect) or
|
|
(Viewer.Show <> Pos.Show) or
|
|
((tbisLineSep in Viewer.State) <> Pos.LineSep)) then
|
|
Result := True;
|
|
Viewer.FBoundsRect := Pos.BoundsRect;
|
|
Viewer.FShow := Pos.Show;
|
|
Viewer.FOffEdge := Pos.OffEdge;
|
|
Viewer.FClipped := Pos.Clipped;
|
|
if Pos.LineSep then
|
|
Include(Viewer.State, tbisLineSep)
|
|
else
|
|
Exclude(Viewer.State, tbisLineSep);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
FOrientation := SaveOrientation;
|
|
if Assigned(CalcCanvas) then
|
|
CalcCanvas.Handle := 0;
|
|
if DC <> 0 then ReleaseDC(0, DC);
|
|
CalcCanvas.Free;
|
|
end;
|
|
if (ABaseSize.X = 0) or (ABaseSize.Y = 0) then begin
|
|
{ If there are no visible items... }
|
|
{}{scale this?}
|
|
ABaseSize.X := 23;
|
|
ABaseSize.Y := 22;
|
|
if TotalSize.X < 23 then TotalSize.X := 23;
|
|
if TotalSize.Y < 22 then TotalSize.Y := 22;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.DoUpdatePositions(var ASize: TPoint);
|
|
{ This is called by UpdatePositions }
|
|
var
|
|
WrappedLines: Integer;
|
|
begin
|
|
{ Don't call InvalidatePositions before CalculatePositions so that
|
|
endless recursion doesn't happen if an item's CalcSize uses a method that
|
|
calls ValidatePositions }
|
|
CalculatePositions(True, FOrientation, FWrapOffset, FChevronOffset,
|
|
FChevronSize, FBaseSize, ASize, WrappedLines);
|
|
FValidated := True;
|
|
{ Need to call ValidateRect before AutoSize, otherwise Windows will
|
|
erase the client area during a resize }
|
|
if FWindow.HandleAllocated then
|
|
ValidateRect(FWindow.Handle, nil);
|
|
AutoSize(ASize.X, ASize.Y);
|
|
if FWindow.HandleAllocated then
|
|
DoubleBufferedRepaint(FWindow.Handle);
|
|
end;
|
|
|
|
function TTBView.UpdatePositions: TPoint;
|
|
{ Called whenever the size or orientation of a view changes. When items are
|
|
added or removed from the view, InvalidatePositions must be called instead,
|
|
otherwise the view may not be redrawn properly. }
|
|
begin
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
DoUpdatePositions(Result);
|
|
end;
|
|
|
|
procedure TTBView.AutoSize(AWidth, AHeight: Integer);
|
|
begin
|
|
end;
|
|
|
|
function TTBView.GetChevronItem: TTBCustomItem;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TTBView.GetMargins(AOrientation: TTBViewOrientation;
|
|
var Margins: TRect);
|
|
begin
|
|
if AOrientation = tbvoFloating then begin
|
|
Margins.Left := 4;
|
|
Margins.Top := 2;
|
|
Margins.Right := 4;
|
|
Margins.Bottom := 1;
|
|
end
|
|
else begin
|
|
Margins.Left := 0;
|
|
Margins.Top := 0;
|
|
Margins.Right := 0;
|
|
Margins.Bottom := 0;
|
|
end;
|
|
end;
|
|
|
|
function TTBView.GetMDIButtonsItem: TTBCustomItem;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTBView.GetMDISystemMenuItem: TTBCustomItem;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTBView.GetFont: TFont;
|
|
begin
|
|
if Assigned(ToolbarFont) then
|
|
Result := ToolbarFont
|
|
else begin
|
|
{ ToolbarFont is destroyed during unit finalization, but in rare cases
|
|
this method may end up being called from ValidatePositions *after*
|
|
unit finalization if Application.Run is never called; see the
|
|
"EConvertError" newsgroup thread. We can't return nil because that would
|
|
cause an exception in the calling function, so just return the window
|
|
font. It's not the *right* font, but it shouldn't matter since the app
|
|
is exiting anyway. }
|
|
Result := {$IFNDEF CLR}TControlAccess{$ENDIF}(FWindow).Font;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.DrawItem(Viewer: TTBItemViewer; DrawTo: TCanvas;
|
|
Offscreen: Boolean);
|
|
const
|
|
COLOR_MENUHILIGHT = 29;
|
|
clMenuHighlight = TColor(COLOR_MENUHILIGHT or $80000000);
|
|
var
|
|
Bmp: TBitmap;
|
|
DrawToDC, BmpDC: HDC;
|
|
DrawCanvas: TCanvas;
|
|
R1, R2, R3: TRect;
|
|
IsOpen, IsSelected, IsPushed: Boolean;
|
|
ToolbarStyle: Boolean;
|
|
UseDisabledShadow: Boolean;
|
|
SaveIndex, SaveIndex2: Integer;
|
|
WindowOrg: TPoint;
|
|
BkColor: TColor;
|
|
begin
|
|
ValidatePositions;
|
|
|
|
if tbisInvalidated in Viewer.State then begin
|
|
Offscreen := True;
|
|
Exclude(Viewer.State, tbisInvalidated);
|
|
end;
|
|
|
|
R1 := Viewer.BoundsRect;
|
|
if not Viewer.Show or IsRectEmpty(R1) or (Viewer.Item is TTBControlItem) then
|
|
Exit;
|
|
R2 := R1;
|
|
OffsetRect(R2, -R2.Left, -R2.Top);
|
|
|
|
IsOpen := FOpenViewer = Viewer;
|
|
IsSelected := (FSelected = Viewer);
|
|
IsPushed := IsSelected and (IsOpen or (FMouseOverSelected and FCapture));
|
|
ToolbarStyle := Viewer.IsToolbarStyle;
|
|
|
|
DrawToDC := DrawTo.Handle;
|
|
Bmp := nil;
|
|
{ Must deselect any currently selected handles before calling SaveDC, because
|
|
if they are left selected and DeleteObject gets called on them after the
|
|
SaveDC call, it will fail on Win9x/Me, and thus leak GDI resources. }
|
|
DrawTo.Refresh;
|
|
SaveIndex := SaveDC(DrawToDC);
|
|
try
|
|
IntersectClipRect(DrawToDC, R1.Left, R1.Top, R1.Right, R1.Bottom);
|
|
GetClipBox(DrawToDC, R3);
|
|
if IsRectEmpty(R3) then
|
|
Exit;
|
|
|
|
if not Offscreen then begin
|
|
MoveWindowOrg(DrawToDC, R1.Left, R1.Top);
|
|
{ Tweak the brush origin so that the checked background drawn behind
|
|
checked items always looks the same regardless of whether the item
|
|
is positioned on an even or odd Left or Top coordinate. }
|
|
if GetWindowOrgEx(DrawToDC, WindowOrg) then
|
|
SetBrushOrgEx(DrawToDC, -WindowOrg.X, -WindowOrg.Y, nil);
|
|
DrawCanvas := DrawTo;
|
|
end
|
|
else begin
|
|
Bmp := TBitmap.Create;
|
|
Bmp.Width := R2.Right;
|
|
Bmp.Height := R2.Bottom;
|
|
DrawCanvas := Bmp.Canvas;
|
|
BmpDC := DrawCanvas.Handle;
|
|
SaveIndex2 := SaveDC(BmpDC);
|
|
SetWindowOrgEx(BmpDC, R1.Left, R1.Top, nil);
|
|
FWindow.Perform(WM_ERASEBKGND, WPARAM(BmpDC), 0);
|
|
RestoreDC(BmpDC, SaveIndex2);
|
|
end;
|
|
|
|
{ Initialize brush }
|
|
if not ToolbarStyle and IsSelected then begin
|
|
{$IFNDEF TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
if AreFlatMenusEnabled then
|
|
{ Windows XP uses a different fill color for selected menu items when
|
|
flat menus are enabled }
|
|
DrawCanvas.Brush.Color := clMenuHighlight
|
|
else
|
|
{$ENDIF}
|
|
DrawCanvas.Brush.Color := clHighlight;
|
|
end
|
|
else
|
|
DrawCanvas.Brush.Style := bsClear;
|
|
|
|
{ Initialize font }
|
|
DrawCanvas.Font.Assign(GetFont);
|
|
if Viewer.Item.Enabled then begin
|
|
if not ToolbarStyle and IsSelected then
|
|
DrawCanvas.Font.Color := clHighlightText
|
|
else begin
|
|
if ToolbarStyle then
|
|
DrawCanvas.Font.Color := clBtnText
|
|
else
|
|
DrawCanvas.Font.Color := tbMenuTextColor;
|
|
end;
|
|
UseDisabledShadow := False;
|
|
end
|
|
else begin
|
|
DrawCanvas.Font.Color := clGrayText;
|
|
{ Use the disabled shadow if either:
|
|
1. The item is a toolbar-style item.
|
|
2. The item is not selected, and the background color equals the
|
|
button-face color.
|
|
3. The gray-text color is the same as the background color.
|
|
Note: Windows actually uses dithered text in this case. }
|
|
BkColor := ColorToRGB({$IFNDEF CLR}TControlAccess{$ENDIF}(FWindow).Color);
|
|
UseDisabledShadow := ToolbarStyle or
|
|
(not IsSelected and (BkColor = ColorToRGB(clBtnFace))) or
|
|
(ColorToRGB(clGrayText) = BkColor);
|
|
end;
|
|
|
|
Viewer.Paint(DrawCanvas, R2, IsSelected, IsPushed, UseDisabledShadow);
|
|
|
|
if Offscreen then
|
|
BitBlt(DrawToDC, R1.Left, R1.Top, Bmp.Width, Bmp.Height, DrawCanvas.Handle,
|
|
0, 0, SRCCOPY);
|
|
finally
|
|
DrawTo.Refresh; { must do this before a RestoreDC }
|
|
RestoreDC(DrawToDC, SaveIndex);
|
|
Bmp.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.DrawSubitems(ACanvas: TCanvas);
|
|
var
|
|
ClipRect: TRect;
|
|
|
|
procedure DoDraw(const AViewer: TTBItemViewer);
|
|
var
|
|
Temp: TRect;
|
|
begin
|
|
{ Speed optimization: Only call DrawItem on viewers that intersect the
|
|
canvas's clipping rectangle. Without this check, moving the mouse across
|
|
a toolbar with thousands of visible items uses 100% of the CPU. }
|
|
if AViewer.Show and IntersectRect(Temp, ClipRect, AViewer.BoundsRect) then
|
|
DrawItem(AViewer, ACanvas, False)
|
|
else begin
|
|
{ Not going to draw the item. Go ahead and clear the tbisInvalidated
|
|
flag if it's set so it won't needlessly double-buffer next time. }
|
|
Exclude(AViewer.State, tbisInvalidated);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
begin
|
|
ValidatePositions;
|
|
ClipRect := ACanvas.ClipRect;
|
|
|
|
{ Draw non-selected items before drawing the selected item, so that when the
|
|
selection is changing there's no brief window in which two items appear
|
|
to be selected }
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
if (vsDrawInOrder in FState) or (Viewers[I] <> FSelected) then
|
|
DoDraw(Viewers[I]);
|
|
end;
|
|
if not(vsDrawInOrder in FState) and Assigned(FSelected) then
|
|
DoDraw(FSelected);
|
|
|
|
Exclude(FState, vsDrawInOrder);
|
|
end;
|
|
|
|
procedure TTBView.Invalidate(AViewer: TTBItemViewer);
|
|
begin
|
|
if not FValidated or not Assigned(FWindow) or not FWindow.HandleAllocated then
|
|
Exit;
|
|
if AViewer.Show and not IsRectEmpty(AViewer.BoundsRect) and
|
|
not(AViewer.Item is TTBControlItem) then begin
|
|
Include(AViewer.State, tbisInvalidated);
|
|
InvalidateRect(FWindow.Handle, {$IFNDEF CLR}@{$ENDIF} AViewer.BoundsRect, False);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.SetAccelsVisibility(AShowAccels: Boolean);
|
|
var
|
|
I: Integer;
|
|
Viewer: TTBItemViewer;
|
|
begin
|
|
{ Always show accels when keyboard cues are enabled }
|
|
AShowAccels := AShowAccels or not(vsUseHiddenAccels in FStyle) or
|
|
AreKeyboardCuesEnabled;
|
|
if AShowAccels <> (vsShowAccels in FState) then begin
|
|
if AShowAccels then
|
|
Include(FState, vsShowAccels)
|
|
else
|
|
Exclude(FState, vsShowAccels);
|
|
if Assigned(FWindow) and FWindow.HandleAllocated and
|
|
IsWindowVisible(FWindow.Handle) then
|
|
{ ^ the visibility check is just an optimization }
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Viewer := Viewers[I];
|
|
if Viewer.CaptionShown and
|
|
(FindAccelChar(Viewer.GetCaptionText) <> #0) then
|
|
Invalidate(Viewer);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTBView.FirstSelectable: TTBItemViewer;
|
|
var
|
|
FirstViewer: TTBItemViewer;
|
|
begin
|
|
Result := NextSelectable(nil, True);
|
|
if Assigned(Result) then begin
|
|
FirstViewer := Result;
|
|
while tbisDontSelectFirst in Result.Item.ItemStyle do begin
|
|
Result := NextSelectable(Result, True);
|
|
if Result = FirstViewer then
|
|
{ don't loop endlessly if all items have the tbisDontSelectFirst style }
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTBView.NextSelectable(CurViewer: TTBItemViewer;
|
|
GoForward: Boolean): TTBItemViewer;
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
ValidatePositions;
|
|
Result := nil;
|
|
if FViewers.Count = 0 then Exit;
|
|
J := -1;
|
|
I := IndexOf(CurViewer);
|
|
while True do begin
|
|
if GoForward then begin
|
|
Inc(I);
|
|
if I >= FViewers.Count then I := 0;
|
|
end
|
|
else begin
|
|
Dec(I);
|
|
if I < 0 then I := FViewers.Count-1;
|
|
end;
|
|
if J = -1 then
|
|
J := I
|
|
else
|
|
if I = J then
|
|
Exit;
|
|
if (Viewers[I].Show or Viewers[I].Clipped) and Viewers[I].Item.Visible and
|
|
(tbisSelectable in Viewers[I].Item.ItemStyle) then
|
|
Break;
|
|
end;
|
|
Result := Viewers[I];
|
|
end;
|
|
|
|
function TTBView.NextSelectableWithAccel(CurViewer: TTBItemViewer;
|
|
Key: Char; RequirePrimaryAccel: Boolean; var IsOnlyItemWithAccel: Boolean): TTBItemViewer;
|
|
|
|
function IsAccelItem(const Index: Integer;
|
|
const Primary, EnabledItems: Boolean): Boolean;
|
|
var
|
|
S: String;
|
|
LastAccel: Char;
|
|
Viewer: TTBItemViewer;
|
|
Item: TTBCustomItem;
|
|
begin
|
|
Result := False;
|
|
Viewer := Viewers[Index];
|
|
Item := Viewer.Item;
|
|
if (Viewer.Show or Viewer.Clipped) and (tbisSelectable in Item.ItemStyle) and
|
|
(Item.Enabled = EnabledItems) and
|
|
Item.Visible and Viewer.CaptionShown then begin
|
|
S := Viewer.GetCaptionText;
|
|
if S <> '' then begin
|
|
LastAccel := FindAccelChar(S);
|
|
if Primary then begin
|
|
if LastAccel <> #0 then
|
|
Result := (CharToLower(LastAccel) = CharToLower(Key));
|
|
end
|
|
else
|
|
if (LastAccel = #0) and (Key <> ' ') then
|
|
Result := (CharToLower(S[1]) = CharToLower(Key));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function FindAccel(I: Integer;
|
|
const Primary, EnabledItems: Boolean): Integer;
|
|
var
|
|
J: Integer;
|
|
begin
|
|
Result := -1;
|
|
J := -1;
|
|
while True do begin
|
|
Inc(I);
|
|
if I >= FViewers.Count then I := 0;
|
|
if J = -1 then
|
|
J := I
|
|
else
|
|
if I = J then
|
|
Break;
|
|
if IsAccelItem(I, Primary, EnabledItems) then begin
|
|
Result := I;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Start, I: Integer;
|
|
Primary, EnabledItems: Boolean;
|
|
begin
|
|
ValidatePositions;
|
|
Result := nil;
|
|
IsOnlyItemWithAccel := False;
|
|
if FViewers.Count = 0 then Exit;
|
|
|
|
Start := IndexOf(CurViewer);
|
|
for Primary := True downto False do
|
|
if not RequirePrimaryAccel or Primary then
|
|
for EnabledItems := True downto False do begin
|
|
I := FindAccel(Start, Primary, EnabledItems);
|
|
if I <> -1 then begin
|
|
Result := Viewers[I];
|
|
IsOnlyItemWithAccel := not EnabledItems or
|
|
(FindAccel(I, Primary, EnabledItems) = I);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.EnterToolbarLoop(Options: TTBEnterToolbarLoopOptions);
|
|
var
|
|
ModalHandler: TTBModalHandler;
|
|
begin
|
|
if vsModal in FState then Exit;
|
|
ModalHandler := TTBModalHandler.Create(FWindow.Handle);
|
|
try
|
|
{ remove all states except... }
|
|
FState := FState * [vsShowAccels];
|
|
try
|
|
Include(FState, vsModal);
|
|
{ Must ensure that DoneAction is reset to tbdaNone *before* calling
|
|
NotifyFocusEvent so that the IsModalEnding call it makes won't return
|
|
True }
|
|
FDoneActionData.DoneAction := tbdaNone;
|
|
{ Now that the vsModal state has been added, send an MSAA focus event }
|
|
if Assigned(Selected) then
|
|
NotifyFocusEvent;
|
|
ModalHandler.Loop(Self, tbetMouseDown in Options,
|
|
tbetExecuteSelected in Options, tbetFromMSAA in Options, False);
|
|
finally
|
|
{ Remove vsModal state from the root view before any TTBView.Destroy
|
|
methods get called (as a result of the CloseChildPopups call below),
|
|
so that NotifyFocusEvent becomes a no-op }
|
|
Exclude(FState, vsModal);
|
|
StopAllTimers;
|
|
CloseChildPopups;
|
|
UpdateSelection(Point(Low(Integer), Low(Integer)), True);
|
|
end;
|
|
finally
|
|
ModalHandler.Free;
|
|
end;
|
|
SetAccelsVisibility(False);
|
|
ProcessDoneAction(FDoneActionData, False);
|
|
end;
|
|
|
|
procedure TTBView.SetCustomizing(Value: Boolean);
|
|
begin
|
|
if FCustomizing <> Value then begin
|
|
FCustomizing := Value;
|
|
RecreateAllViewers;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.BeginUpdate;
|
|
begin
|
|
Inc(FUpdating);
|
|
end;
|
|
|
|
procedure TTBView.EndUpdate;
|
|
begin
|
|
Dec(FUpdating);
|
|
if FUpdating = 0 then
|
|
TryValidatePositions;
|
|
end;
|
|
|
|
procedure TTBView.GetOffEdgeControlList(const List: TList);
|
|
var
|
|
I: Integer;
|
|
Item: TTBCustomItem;
|
|
begin
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
Item := Viewers[I].Item;
|
|
if (Item is TTBControlItem) and Viewers[I].OffEdge and
|
|
(TTBControlItem(Item).FControl is TWinControl) then
|
|
List.Add(TTBControlItem(Item).FControl);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.SetCapture;
|
|
begin
|
|
FCapture := True;
|
|
end;
|
|
|
|
procedure TTBView.CancelCapture;
|
|
begin
|
|
if FCapture then begin
|
|
FCapture := False;
|
|
LastPos.X := Low(LastPos.X);
|
|
if Assigned(FSelected) then
|
|
FSelected.LosingCapture;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.KeyDown(var Key: Word; Shift: TShiftState);
|
|
|
|
procedure SelNextItem(const ParentView: TTBView; const GoForward: Boolean);
|
|
begin
|
|
ParentView.Selected := ParentView.NextSelectable(ParentView.FSelected,
|
|
GoForward);
|
|
ParentView.ScrollSelectedIntoView;
|
|
end;
|
|
|
|
procedure HelpKey;
|
|
var
|
|
V: TTBView;
|
|
ContextID: Integer;
|
|
begin
|
|
ContextID := 0;
|
|
V := Self;
|
|
while Assigned(V) do begin
|
|
if Assigned(V.FSelected) then begin
|
|
ContextID := V.FSelected.Item.HelpContext;
|
|
if ContextID <> 0 then Break;
|
|
end;
|
|
V := V.FParentView;
|
|
end;
|
|
if ContextID <> 0 then
|
|
EndModalWithHelp(ContextID);
|
|
end;
|
|
|
|
var
|
|
ParentTBView: TTBView;
|
|
begin
|
|
ParentTBView := GetParentToolbarView;
|
|
case Key of
|
|
VK_TAB: begin
|
|
SelNextItem(Self, GetKeyState(VK_SHIFT) >= 0);
|
|
end;
|
|
VK_RETURN: begin
|
|
ExecuteSelected(True);
|
|
end;
|
|
VK_MENU, VK_F10: begin
|
|
EndModal;
|
|
end;
|
|
VK_ESCAPE: begin
|
|
Key := 0;
|
|
if FParentView = nil then
|
|
EndModal
|
|
else
|
|
FParentView.CancelChildPopups;
|
|
end;
|
|
VK_LEFT, VK_RIGHT: begin
|
|
if (Self = ParentTBView) and (Orientation = tbvoVertical) then
|
|
OpenChildPopup(True)
|
|
else if Key = VK_LEFT then begin
|
|
if Assigned(ParentTBView) and (ParentTBView.Orientation <> tbvoVertical) then begin
|
|
if (Self = ParentTBView) or
|
|
(FParentView = ParentTBView) then
|
|
SelNextItem(ParentTBView, False)
|
|
else
|
|
FParentView.CloseChildPopups;
|
|
end
|
|
else begin
|
|
if Assigned(FParentView) then
|
|
FParentView.CancelChildPopups;
|
|
end;
|
|
end
|
|
else begin
|
|
if ((Self = ParentTBView) or not OpenChildPopup(True)) and
|
|
(Assigned(ParentTBView) and (ParentTBView.Orientation <> tbvoVertical)) then begin
|
|
{ If we're on ParentTBView, or if the selected item can't display
|
|
a submenu, proceed to next item on ParentTBView }
|
|
SelNextItem(ParentTBView, True);
|
|
end;
|
|
end;
|
|
end;
|
|
VK_UP, VK_DOWN: begin
|
|
if (Self = ParentTBView) and (Orientation <> tbvoVertical) then
|
|
OpenChildPopup(True)
|
|
else
|
|
SelNextItem(Self, Key = VK_DOWN);
|
|
end;
|
|
VK_HOME, VK_END: begin
|
|
Selected := NextSelectable(nil, Key = VK_HOME);
|
|
ScrollSelectedIntoView;
|
|
end;
|
|
VK_F1: HelpKey;
|
|
else
|
|
Exit; { don't set Key to 0 for unprocessed keys }
|
|
end;
|
|
Key := 0;
|
|
end;
|
|
|
|
function TTBView.IsModalEnding: Boolean;
|
|
begin
|
|
Result := (GetRootView.FDoneActionData.DoneAction <> tbdaNone);
|
|
end;
|
|
|
|
procedure TTBView.EndModal;
|
|
var
|
|
RootView: TTBView;
|
|
begin
|
|
RootView := GetRootView;
|
|
RootView.FDoneActionData.DoneAction := tbdaCancel;
|
|
end;
|
|
|
|
procedure TTBView.EndModalWithClick(AViewer: TTBItemViewer);
|
|
var
|
|
RootView: TTBView;
|
|
begin
|
|
RootView := GetRootView;
|
|
RootView.FDoneActionData.ClickItem := AViewer.Item;
|
|
RootView.FDoneActionData.Sound := AViewer.FView.FIsPopup;
|
|
RootView.FDoneActionData.DoneAction := tbdaClickItem;
|
|
end;
|
|
|
|
procedure TTBView.EndModalWithHelp(AContextID: Integer);
|
|
var
|
|
RootView: TTBView;
|
|
begin
|
|
RootView := GetRootView;
|
|
RootView.FDoneActionData.ContextID := AContextID;
|
|
RootView.FDoneActionData.DoneAction := tbdaHelpContext;
|
|
end;
|
|
|
|
procedure TTBView.EndModalWithSystemMenu(AWnd: HWND; AKey: Word);
|
|
var
|
|
RootView: TTBView;
|
|
begin
|
|
RootView := GetRootView;
|
|
RootView.FDoneActionData.Wnd := AWnd;
|
|
RootView.FDoneActionData.Key := AKey;
|
|
RootView.FDoneActionData.DoneAction := tbdaOpenSystemMenu;
|
|
end;
|
|
|
|
procedure TTBView.ExecuteSelected(AGivePriority: Boolean);
|
|
{ Normally called after an Enter or accelerator key press on the view, this
|
|
method 'executes' or opens the selected item. It ends the modal loop, except
|
|
when a submenu is opened. }
|
|
var
|
|
Item: TTBCustomItem;
|
|
begin
|
|
if Assigned(FSelected) and FSelected.Item.Enabled then begin
|
|
Item := FSelected.Item;
|
|
if (tbisCombo in Item.ItemStyle) or not OpenChildPopup(True) then begin
|
|
if tbisSelectable in Item.ItemStyle then
|
|
FSelected.Execute(AGivePriority)
|
|
else
|
|
EndModal;
|
|
end
|
|
end
|
|
else
|
|
EndModal;
|
|
{$IFNDEF CLR}
|
|
Exit; asm db 0,'Toolbar2000 (C) 1998-2008 Jordan Russell',0 end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TTBView.Scroll(ADown: Boolean);
|
|
var
|
|
CurPos, NewPos, I: Integer;
|
|
begin
|
|
ValidatePositions;
|
|
if ADown then begin
|
|
NewPos := High(NewPos);
|
|
CurPos := FMaxHeight - tbMenuScrollArrowHeight;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
with Viewers[I] do
|
|
if Clipped and not(tbisSeparator in Item.ItemStyle) and
|
|
(BoundsRect.Bottom < NewPos) and (BoundsRect.Bottom > CurPos) then
|
|
NewPos := BoundsRect.Bottom;
|
|
end;
|
|
if NewPos = High(NewPos) then
|
|
Exit;
|
|
Dec(NewPos, FMaxHeight - tbMenuScrollArrowHeight);
|
|
end
|
|
else begin
|
|
NewPos := Low(NewPos);
|
|
CurPos := tbMenuScrollArrowHeight;
|
|
for I := 0 to FViewers.Count-1 do begin
|
|
with Viewers[I] do
|
|
if Clipped and not(tbisSeparator in Item.ItemStyle) and
|
|
(BoundsRect.Top > NewPos) and (BoundsRect.Top < CurPos) then
|
|
NewPos := BoundsRect.Top;
|
|
end;
|
|
if NewPos = Low(NewPos) then
|
|
Exit;
|
|
Dec(NewPos, tbMenuScrollArrowHeight);
|
|
end;
|
|
Inc(FScrollOffset, NewPos);
|
|
UpdatePositions;
|
|
end;
|
|
|
|
procedure TTBView.ScrollSelectedIntoView;
|
|
begin
|
|
ValidatePositions;
|
|
if (FSelected = nil) or not FSelected.Clipped then
|
|
Exit;
|
|
|
|
if FSelected.BoundsRect.Top < tbMenuScrollArrowHeight then begin
|
|
Dec(FScrollOffset, tbMenuScrollArrowHeight - FSelected.BoundsRect.Top);
|
|
UpdatePositions;
|
|
end
|
|
else if FSelected.BoundsRect.Bottom > FMaxHeight - tbMenuScrollArrowHeight then begin
|
|
Dec(FScrollOffset, (FMaxHeight - tbMenuScrollArrowHeight) -
|
|
FSelected.BoundsRect.Bottom);
|
|
UpdatePositions;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBView.SetUsePriorityList(Value: Boolean);
|
|
begin
|
|
if FUsePriorityList <> Value then begin
|
|
FUsePriorityList := Value;
|
|
RecreateAllViewers;
|
|
end;
|
|
end;
|
|
|
|
function TTBView.GetCaptureWnd: HWND;
|
|
begin
|
|
Result := GetRootView.FCaptureWnd;
|
|
end;
|
|
|
|
procedure TTBView.CancelMode;
|
|
var
|
|
View: TTBView;
|
|
begin
|
|
EndModal;
|
|
|
|
{ Hide all parent/child popup windows. Can't actually destroy them using
|
|
CloseChildPopups because this method may be called while inside
|
|
TTBEditItemViewer's message loop, and it could result in the active
|
|
TTBEditItemViewer instance being destroyed (leading to an AV). }
|
|
View := Self;
|
|
while Assigned(View.FOpenViewerView) do
|
|
View := View.FOpenViewerView;
|
|
repeat
|
|
View.StopAllTimers;
|
|
if View.FWindow is TTBPopupWindow then
|
|
View.FWindow.Visible := False;
|
|
View := View.FParentView;
|
|
until View = nil;
|
|
|
|
{ Note: This doesn't remove the selection from a top-level toolbar item.
|
|
Unfortunately, we can't do 'Selected := nil' because it would destroy
|
|
child popups and that must'nt happen for the reason stated above. }
|
|
end;
|
|
|
|
procedure TTBView.HandleHintShowMessage(var Message: TCMHintShow);
|
|
|
|
procedure UpdateInfo(var Info: {$IFDEF JR_D12}Controls.{$ENDIF} THintInfo);
|
|
var
|
|
V: TTBItemViewer;
|
|
begin
|
|
Info.HintStr := '';
|
|
V := ViewerFromPoint(Info.CursorPos);
|
|
if Assigned(V) then begin
|
|
Info.CursorRect := V.BoundsRect;
|
|
Info.HintStr := V.GetHintText;
|
|
Info.HintData := V;
|
|
end;
|
|
end;
|
|
|
|
{$IFNDEF CLR}
|
|
begin
|
|
UpdateInfo(Message.HintInfo^);
|
|
end;
|
|
{$ELSE}
|
|
var
|
|
Info: THintInfo;
|
|
begin
|
|
Info := Message.HintInfo;
|
|
UpdateInfo(Info);
|
|
Message.HintInfo := Info;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
|
|
{ TTBModalHandler }
|
|
|
|
constructor TTBModalHandler.Create(AExistingWnd: HWND);
|
|
|
|
procedure RemoveFocusIfOnOtherThread;
|
|
{ This ensures that the message loop will receive key messages when an Adobe
|
|
Reader (8.1.2) control embedded in a TWebBrowser is currently focused.
|
|
The Reader control is actually hosted in a separate thread (in a separate
|
|
process, AcroRd32.exe). When Alt/Alt+[letter] is pressed, Reader calls
|
|
GetAncestor(..., GA_ROOT) and forwards the WM_SYSCOMMAND/WM_SYSCHAR
|
|
message to that window using SendMessage (not PostMessage, for some
|
|
reason). The focus, however, is left on the Reader control. Consequently,
|
|
any keystrokes will generate key messages in the Reader thread's queue
|
|
instead of ours. To avoid that, call SetFocus(0) to remove the focus if
|
|
it's currently on another thread's window. When no window has the focus,
|
|
key messages will be posted to the active window, which *should* be a
|
|
form owned by the same thread as us. }
|
|
var
|
|
FocusWnd: HWND;
|
|
begin
|
|
FocusWnd := GetFocus;
|
|
if (FocusWnd <> 0) and
|
|
(GetWindowThreadProcessId(FocusWnd, nil) <> GetCurrentThreadId) then begin
|
|
FSaveFocusWnd := FocusWnd;
|
|
SetFocus(0);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
inherited Create;
|
|
LastPos := GetMessagePosAsPoint;
|
|
if AExistingWnd <> 0 then
|
|
FWnd := AExistingWnd
|
|
else begin
|
|
FWnd := {$IFDEF ALLOCHWND_CLASSES}Classes.{$ENDIF} AllocateHWnd(WndProc);
|
|
FCreatedWnd := True;
|
|
end;
|
|
RemoveFocusIfOnOtherThread;
|
|
{ Like standard menus, don't allow other apps to steal the focus during
|
|
our modal loop. This also prevents us from losing activation when
|
|
"active window tracking" is enabled and the user moves the mouse over
|
|
another application's window. }
|
|
CallLockSetForegroundWindow(True);
|
|
SetCapture(FWnd);
|
|
SetCursor(LoadCursor(0, IDC_ARROW));
|
|
CallNotifyWinEvent(EVENT_SYSTEM_MENUSTART, FWnd, OBJID_CLIENT, CHILDID_SELF);
|
|
FInited := True;
|
|
end;
|
|
|
|
destructor TTBModalHandler.Destroy;
|
|
begin
|
|
CallLockSetForegroundWindow(False);
|
|
if FWnd <> 0 then begin
|
|
if GetCapture = FWnd then
|
|
ReleaseCapture;
|
|
if FInited then
|
|
CallNotifyWinEvent(EVENT_SYSTEM_MENUEND, FWnd, OBJID_CLIENT, CHILDID_SELF);
|
|
if FCreatedWnd then
|
|
{$IFDEF ALLOCHWND_CLASSES}Classes.{$ENDIF} DeallocateHWnd(FWnd);
|
|
end;
|
|
if (FSaveFocusWnd <> 0) and (GetFocus = 0) then
|
|
SetFocus(FSaveFocusWnd);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBModalHandler.WndProc(var Msg: TMessage);
|
|
begin
|
|
Msg.Result := DefWindowProc(FWnd, Msg.Msg, Msg.WParam, Msg.LParam);
|
|
if (Msg.Msg = WM_CANCELMODE) and Assigned(FRootPopup) then begin
|
|
try
|
|
{ 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 TTBCustomToolbar.WMCancelMode. }
|
|
FRootPopup.View.CancelMode;
|
|
except
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBModalHandler.Loop(const RootView: TTBView;
|
|
const AMouseDown, AExecuteSelected, AFromMSAA, TrackRightButton: Boolean);
|
|
var
|
|
OriginalActiveWindow: HWND;
|
|
|
|
function GetActiveView: TTBView;
|
|
begin
|
|
Result := RootView;
|
|
while Assigned(Result.FOpenViewerView) do
|
|
Result := Result.FOpenViewerView;
|
|
end;
|
|
|
|
function GetCaptureView: TTBView;
|
|
begin
|
|
Result := RootView;
|
|
while Assigned(Result) and not Result.FCapture do
|
|
Result := Result.FOpenViewerView;
|
|
end;
|
|
|
|
procedure UpdateAllSelections(const P: TPoint; const AllowNewSelection: Boolean);
|
|
var
|
|
View, CapView: TTBView;
|
|
begin
|
|
View := GetActiveView;
|
|
CapView := GetCaptureView;
|
|
while Assigned(View) do begin
|
|
if (CapView = nil) or (View = CapView) then
|
|
View.UpdateSelection(P, AllowNewSelection);
|
|
View := View.FParentView;
|
|
end;
|
|
end;
|
|
|
|
function GetSelectedViewer(var AView: TTBView; var AViewer: TTBItemViewer): Boolean;
|
|
{ Returns True if AViewer <> nil. }
|
|
var
|
|
View: TTBView;
|
|
begin
|
|
{ Look for a capture item first }
|
|
AView := GetCaptureView;
|
|
if Assigned(AView) then
|
|
AViewer := AView.FSelected
|
|
else begin
|
|
AView := nil;
|
|
AViewer := nil;
|
|
View := RootView;
|
|
repeat
|
|
if Assigned(View.FSelected) and View.FMouseOverSelected then begin
|
|
AView := View;
|
|
AViewer := View.FSelected;
|
|
Break;
|
|
end;
|
|
if vsMouseInWindow in View.FState then begin
|
|
{ ...there is no current selection, but the mouse is still in the
|
|
window. This can happen if the mouse is over the non-client area
|
|
of the toolbar or popup window, or in an area not containing an
|
|
item. }
|
|
AView := View;
|
|
Break;
|
|
end;
|
|
View := View.FOpenViewerView;
|
|
until View = nil;
|
|
end;
|
|
Result := Assigned(AViewer);
|
|
end;
|
|
|
|
function ContinueLoop: Boolean;
|
|
begin
|
|
{ Don't continue if the mouse capture is lost, if a (modeless) top-level
|
|
window is shown causing the active window to change, or if EndModal* was
|
|
called. }
|
|
Result := (GetCapture = FWnd) and (GetActiveWindow = OriginalActiveWindow)
|
|
and not RootView.IsModalEnding;
|
|
end;
|
|
|
|
function SendKeyEvent(const View: TTBView; var Key: Word;
|
|
const Shift: TShiftState): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(View.FSelected) then begin
|
|
View.FSelected.KeyDown(Key, Shift);
|
|
if RootView.IsModalEnding then
|
|
Exit;
|
|
end;
|
|
if Key <> 0 then begin
|
|
View.KeyDown(Key, Shift);
|
|
if RootView.IsModalEnding then
|
|
Exit;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
procedure DoHintMouseMessage(const Ctl: TControl; const P: TPoint);
|
|
var
|
|
M: TMessage;
|
|
begin
|
|
{$IFDEF CLR}
|
|
M := TMessage.Create;
|
|
{$ENDIF}
|
|
M.Msg := WM_MOUSEMOVE;
|
|
M.WParam := 0;
|
|
M.LParam := MAKELPARAM(Word(P.X), Word(P.Y));
|
|
M.Result := 0;
|
|
Application.HintMouseMessage(Ctl, M);
|
|
end;
|
|
|
|
procedure MouseMoved;
|
|
var
|
|
Cursor: HCURSOR;
|
|
View: TTBView;
|
|
Viewer: TTBItemViewer;
|
|
P: TPoint;
|
|
begin
|
|
UpdateAllSelections(LastPos, True);
|
|
Cursor := 0;
|
|
if GetSelectedViewer(View, Viewer) then begin
|
|
P := View.FWindow.ScreenToClient(LastPos);
|
|
if ((vsAlwaysShowHints in View.FStyle) or
|
|
(tboShowHint in Viewer.Item.FEffectiveOptions)) and not View.FCapture then begin
|
|
{ Display popup hint for the item. Update is called
|
|
first to minimize flicker caused by the hiding &
|
|
showing of the hint window. }
|
|
View.FWindow.Update;
|
|
DoHintMouseMessage(View.FWindow, P);
|
|
end
|
|
else
|
|
Application.CancelHint;
|
|
Dec(P.X, Viewer.BoundsRect.Left);
|
|
Dec(P.Y, Viewer.BoundsRect.Top);
|
|
Viewer.GetCursor(P, Cursor);
|
|
end
|
|
else
|
|
Application.CancelHint;
|
|
if Cursor = 0 then
|
|
Cursor := LoadCursor(0, IDC_ARROW);
|
|
SetCursor(Cursor);
|
|
if Assigned(Viewer) then
|
|
Viewer.MouseMove(P.X, P.Y);
|
|
end;
|
|
|
|
procedure UpdateAppHint;
|
|
var
|
|
View: TTBView;
|
|
begin
|
|
View := RootView;
|
|
while Assigned(View.FOpenViewerView) and Assigned(View.FOpenViewerView.FSelected) do
|
|
View := View.FOpenViewerView;
|
|
if Assigned(View.FSelected) then
|
|
Application.Hint := GetLongHint(View.FSelected.Item.Hint)
|
|
else
|
|
Application.Hint := '';
|
|
end;
|
|
|
|
procedure HandleTimer(const View: TTBView; const ID: TTBViewTimerID);
|
|
begin
|
|
case ID of
|
|
tiOpen: begin
|
|
{ Similar to standard menus, always close child popups, even if
|
|
Selected = OpenViewer.
|
|
Note: CloseChildPopups and OpenChildPopup will stop the tiClose
|
|
and tiOpen timers respectively. }
|
|
View.CloseChildPopups;
|
|
View.OpenChildPopup(False);
|
|
end;
|
|
tiClose: begin
|
|
{ Note: CloseChildPopups stops the tiClose timer. }
|
|
View.CloseChildPopups;
|
|
end;
|
|
tiScrollUp: begin
|
|
if View.FShowUpArrow then
|
|
View.Scroll(False)
|
|
else
|
|
View.StopTimer(tiScrollUp);
|
|
end;
|
|
tiScrollDown: begin
|
|
if View.FShowDownArrow then
|
|
View.Scroll(True)
|
|
else
|
|
View.StopTimer(tiScrollDown);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
MouseDownOnMenu: Boolean;
|
|
Msg: TMsg;
|
|
P: TPoint;
|
|
Ctl: TControl;
|
|
View: TTBView;
|
|
ConvertedKey: Char;
|
|
IsOnlyItemWithAccel: Boolean;
|
|
MouseIsDown: Boolean;
|
|
Key: Word;
|
|
Shift: TShiftState;
|
|
Viewer: TTBItemViewer;
|
|
begin
|
|
RootView.FDoneActionData.DoneAction := tbdaNone;
|
|
RootView.ValidatePositions;
|
|
try
|
|
try
|
|
RootView.FCaptureWnd := FWnd;
|
|
MouseDownOnMenu := False;
|
|
if AMouseDown then begin
|
|
P := RootView.FSelected.ScreenToClient(GetMessagePosAsPoint);
|
|
RootView.FSelected.MouseDown([], P.X, P.Y, MouseDownOnMenu);
|
|
if RootView.IsModalEnding then
|
|
Exit;
|
|
MouseDownOnMenu := False; { never set MouseDownOnMenu to True on first click }
|
|
end
|
|
else if AExecuteSelected then begin
|
|
RootView.ExecuteSelected(not AFromMSAA);
|
|
if RootView.IsModalEnding then
|
|
Exit;
|
|
end;
|
|
OriginalActiveWindow := GetActiveWindow;
|
|
while ContinueLoop do begin
|
|
TBUpdateAnimation;
|
|
{ Examine the next message before popping it out of the queue }
|
|
if not PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then begin
|
|
{ No message available; wait for one to arrive }
|
|
if TBIsAnimationInProgress then
|
|
{ While animating, if no message arrives within 1 ms, loop back and
|
|
call TBUpdateAnimation again to see if it's ready for a new frame.
|
|
Note: We don't use a timer to call TBUpdateAnimation because on
|
|
Windows 98, timers only have a resolution of 55 ms in my tests,
|
|
too poor for smooth animation. (timeBeginPeriod does not help.)
|
|
Sleep and MsgWaitForMultipleObjects, on the other hand, appear to
|
|
have a resolution of 5 ms by default. (Better resolution is
|
|
possible with a call to timeBeginPeriod, but we don't need it.)
|
|
Note: On 2000/XP, timers and Sleep both have a resolution of 10-15
|
|
ms by default. }
|
|
MsgWaitForMultipleObjects(0, {$IFNDEF CLR} THandle(nil^) {$ELSE} [] {$ENDIF},
|
|
False, 1, QS_ALLINPUT)
|
|
else
|
|
WaitMessage;
|
|
Continue;
|
|
end;
|
|
case Msg.message of
|
|
WM_LBUTTONDOWN, WM_RBUTTONDOWN: begin
|
|
P := Msg.pt;
|
|
Ctl := FindDragTarget(P, True);
|
|
{ Was the mouse not clicked on a popup, or was it clicked on a
|
|
popup that is not a child of RootView?
|
|
(The latter can happen when in customization mode, for example,
|
|
if the user right-clicks a popup menu being customized and
|
|
the context menu is displayed.) }
|
|
if not(Ctl is TTBPopupWindow) or
|
|
not RootView.ContainsView(TTBPopupWindow(Ctl).View) then begin
|
|
{ If the root view is a popup, or if the root view is a toolbar
|
|
and the user clicked outside the toolbar or in its non-client
|
|
area (e.g. on its drag handle), exit }
|
|
if RootView.FIsPopup or (Ctl <> RootView.FWindow) or
|
|
not PtInRect(RootView.FWindow.ClientRect, RootView.FWindow.ScreenToClient(P)) then
|
|
Exit
|
|
else
|
|
if Msg.message = WM_LBUTTONDOWN then begin
|
|
{ If the user clicked inside a toolbar on anything but an
|
|
item, exit }
|
|
UpdateAllSelections(P, True);
|
|
if (RootView.FSelected = nil) or not RootView.FMouseOverSelected or
|
|
(tbisClicksTransparent in RootView.FSelected.Item.ItemStyle) then
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{ Now pop the message out of the queue }
|
|
if not PeekMessage(Msg, 0, Msg.message, Msg.message, PM_REMOVE or PM_NOYIELD) then
|
|
Continue;
|
|
case Msg.message of
|
|
$4D:
|
|
{ This undocumented message is sent to the focused window when
|
|
F1 is pressed. Windows handles it by sending a WM_HELP message
|
|
to the same window. We don't want this to happen while a menu
|
|
is up, so swallow the message. }
|
|
;
|
|
WM_CONTEXTMENU:
|
|
{ Windows still sends WM_CONTEXTMENU messages for "context menu"
|
|
keystrokes even if WM_KEYUP messages are never dispatched,
|
|
so it must specifically ignore this message }
|
|
;
|
|
WM_KEYFIRST..WM_KEYLAST: begin
|
|
Application.CancelHint;
|
|
MouseIsDown := (GetKeyState(VK_LBUTTON) < 0) or
|
|
(TrackRightButton and (GetKeyState(VK_RBUTTON) < 0));
|
|
case Msg.message of
|
|
WM_KEYDOWN, WM_SYSKEYDOWN:
|
|
begin
|
|
if Msg.wParam = VK_PROCESSKEY then
|
|
{ Don't let IME process the key }
|
|
Msg.wParam := WPARAM(ImmGetVirtualKey(Msg.hwnd));
|
|
Key := Word(Msg.wParam);
|
|
if not MouseIsDown or (Key = VK_F1) then begin
|
|
if SendKeyEvent(GetActiveView, Key,
|
|
KeyDataToShiftState(ClipToLongint(Msg.lParam))) then
|
|
Exit;
|
|
{ If it's not handled by a KeyDown method, translate
|
|
it into a WM_*CHAR message }
|
|
if Key <> 0 then
|
|
TranslateMessage(Msg);
|
|
end;
|
|
end;
|
|
WM_CHAR, WM_SYSCHAR:
|
|
if not MouseIsDown then begin
|
|
Key := Word(Msg.wParam);
|
|
View := GetActiveView;
|
|
{$IFDEF CLR}
|
|
{ On .NET, under Windows 9x/Me we must convert the character
|
|
code from ANSI->Unicode. (We shouldn't get any double-byte
|
|
characters due to our VK_PROCESSKEY handling above.) }
|
|
if Marshal.SystemDefaultCharSize = 1 then
|
|
ConvertedKey := Encoding.GetEncoding(GetInputLocaleCodePage).
|
|
GetChars([Byte(Key)])[0]
|
|
else
|
|
{$ENDIF}
|
|
ConvertedKey := Chr(Key);
|
|
Viewer := View.NextSelectableWithAccel(View.FSelected,
|
|
ConvertedKey, False, IsOnlyItemWithAccel);
|
|
if Viewer = nil then begin
|
|
if (Key in [VK_SPACE, Ord('-')]) and
|
|
not RootView.FIsPopup and (View = RootView) and
|
|
(GetActiveWindow <> 0) then begin
|
|
RootView.EndModalWithSystemMenu(GetActiveWindow, Key);
|
|
Exit;
|
|
end
|
|
else
|
|
MessageBeep(0);
|
|
end
|
|
else begin
|
|
View.Selected := Viewer;
|
|
View.ScrollSelectedIntoView;
|
|
if IsOnlyItemWithAccel then
|
|
View.ExecuteSelected(True);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
WM_TIMER:
|
|
begin
|
|
Ctl := FindControl(Msg.hwnd);
|
|
if Assigned(Ctl) and (Ctl is TTBPopupWindow) and
|
|
(Msg.wParam >= ViewTimerBaseID + Ord(Low(TTBViewTimerID))) and
|
|
(Msg.wParam <= ViewTimerBaseID + Ord(High(TTBViewTimerID))) then begin
|
|
if Assigned(TTBPopupWindow(Ctl).FView) then
|
|
HandleTimer(TTBPopupWindow(Ctl).FView,
|
|
TTBViewTimerID(Msg.wParam - ViewTimerBaseID));
|
|
end
|
|
else
|
|
DispatchMessage(Msg);
|
|
end;
|
|
$118: ;
|
|
{ ^ Like standard menus, don't dispatch WM_SYSTIMER messages
|
|
(the internal Windows message used for things like caret
|
|
blink and list box scrolling). }
|
|
WM_MOUSEFIRST..WM_MOUSELAST:
|
|
case Msg.message of
|
|
WM_MOUSEMOVE: begin
|
|
if (Msg.pt.X <> LastPos.X) or (Msg.pt.Y <> LastPos.Y) then begin
|
|
LastPos := Msg.pt;
|
|
MouseMoved;
|
|
end;
|
|
end;
|
|
WM_MOUSEWHEEL:
|
|
if GetSelectedViewer(View, Viewer) then begin
|
|
P := Viewer.ScreenToClient(Msg.pt);
|
|
Viewer.MouseWheel(Smallint(Msg.wParam shr 16), P.X, P.Y);
|
|
end;
|
|
WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, WM_RBUTTONDOWN:
|
|
if (Msg.message <> WM_RBUTTONDOWN) or TrackRightButton then begin
|
|
Application.CancelHint;
|
|
MouseDownOnMenu := False;
|
|
Exclude(RootView.FState, vsIgnoreFirstMouseUp);
|
|
UpdateAllSelections(Msg.pt, True);
|
|
if GetSelectedViewer(View, Viewer) then begin
|
|
if Msg.message <> WM_LBUTTONDBLCLK then
|
|
Shift := []
|
|
else
|
|
Shift := [ssDouble];
|
|
P := Viewer.ScreenToClient(Msg.pt);
|
|
Viewer.MouseDown(Shift, P.X, P.Y, MouseDownOnMenu);
|
|
LastPos := GetMessagePosAsPoint;
|
|
end;
|
|
end;
|
|
WM_LBUTTONUP, WM_RBUTTONUP:
|
|
if (Msg.message = WM_LBUTTONUP) or TrackRightButton then begin
|
|
UpdateAllSelections(Msg.pt, False);
|
|
{ ^ False is used so that when a popup menu is
|
|
displayed with the cursor currently inside it, the item
|
|
under the cursor won't be accidentally selected when the
|
|
user releases the button. The user must move the mouse at
|
|
at least one pixel (generating a WM_MOUSEMOVE message),
|
|
and then release the button. }
|
|
if not GetSelectedViewer(View, Viewer) then begin
|
|
{ Mouse was not released over any item. Cancel out of the
|
|
loop if it's outside all views, or is inside unused
|
|
space on a topmost toolbar }
|
|
if not Assigned(View) or
|
|
((View = RootView) and RootView.FIsToolbar) then begin
|
|
if not(vsIgnoreFirstMouseUp in RootView.FState) then
|
|
Exit
|
|
else
|
|
Exclude(RootView.FState, vsIgnoreFirstMouseUp);
|
|
end;
|
|
end
|
|
else begin
|
|
P := Viewer.ScreenToClient(Msg.pt);
|
|
Viewer.MouseUp(P.X, P.Y, MouseDownOnMenu);
|
|
end;
|
|
end;
|
|
end;
|
|
else
|
|
DispatchMessage(Msg);
|
|
end;
|
|
if not ContinueLoop then
|
|
Exit;
|
|
if LastPos.X = Low(LastPos.X) then begin
|
|
{ The capture was released; generate a fake mouse movement to update
|
|
the selection }
|
|
LastPos := GetMessagePosAsPoint;
|
|
MouseMoved;
|
|
end;
|
|
UpdateAppHint;
|
|
end;
|
|
finally
|
|
RootView.CancelCapture;
|
|
end;
|
|
finally
|
|
RootView.FCaptureWnd := 0;
|
|
Application.Hint := '';
|
|
{ Make sure there are no outstanding WM_*CHAR messages }
|
|
RemoveMessages(WM_CHAR, WM_DEADCHAR);
|
|
RemoveMessages(WM_SYSCHAR, WM_SYSDEADCHAR);
|
|
{ Nor any outstanding 'send WM_HELP' messages caused by an earlier press
|
|
of the F1 key }
|
|
RemoveMessages($4D, $4D);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBPopupView }
|
|
|
|
procedure TTBPopupView.AutoSize(AWidth, AHeight: Integer);
|
|
begin
|
|
with FWindow do
|
|
SetBounds(Left, Top, AWidth + (PopupMenuWindowNCSize * 2),
|
|
AHeight + (PopupMenuWindowNCSize * 2));
|
|
end;
|
|
|
|
function TTBPopupView.GetFont: TFont;
|
|
begin
|
|
Result := (Owner as TTBPopupWindow).Font;
|
|
end;
|
|
|
|
|
|
{ TTBPopupWindow }
|
|
|
|
constructor TTBPopupWindow.CreatePopupWindow(AOwner: TComponent;
|
|
const AParentView: TTBView; const AItem: TTBCustomItem;
|
|
const ACustomizing: Boolean);
|
|
begin
|
|
inherited Create(AOwner);
|
|
Visible := False;
|
|
SetBounds(0, 0, 320, 240);
|
|
ControlStyle := ControlStyle - [csCaptureMouse];
|
|
ShowHint := True;
|
|
Color := tbMenuBkColor;
|
|
FView := GetViewClass.Create(Self, AParentView, AItem, Self, False,
|
|
ACustomizing, False);
|
|
Include(FView.FState, vsModal);
|
|
|
|
{ Inherit the font from the parent view, or use the system menu font if
|
|
there is no parent view }
|
|
if Assigned(AParentView) then
|
|
Font.Assign(AParentView.GetFont)
|
|
else
|
|
Font.Assign(ToolbarFont);
|
|
|
|
{ Inherit the accelerator visibility state from the parent view. If there
|
|
is no parent view (i.e. it's a standalone popup menu), then default to
|
|
hiding accelerator keys, but change this in CreateWnd if the last input
|
|
came from the keyboard. }
|
|
if Assigned(AParentView) then begin
|
|
if vsUseHiddenAccels in AParentView.FStyle then
|
|
Include(FView.FStyle, vsUseHiddenAccels);
|
|
if vsShowAccels in AParentView.FState then
|
|
Include(FView.FState, vsShowAccels);
|
|
end
|
|
else
|
|
Include(FView.FStyle, vsUseHiddenAccels);
|
|
|
|
if Application.Handle <> 0 then
|
|
{ Use Application.Handle if possible so that the taskbar button for the app
|
|
doesn't pop up when a TTBEditItem on a popup menu is focused }
|
|
ParentWindow := Application.Handle
|
|
else
|
|
{ When Application.Handle is zero, use GetDesktopWindow() as the parent
|
|
window, not zero, otherwise UpdateControlState won't show the window }
|
|
ParentWindow := GetDesktopWindow;
|
|
end;
|
|
|
|
destructor TTBPopupWindow.Destroy;
|
|
begin
|
|
Destroying;
|
|
{ Before destroying the window handle we need to close any child popups so
|
|
that pixels behind the popups are properly restored without generating a
|
|
WM_PAINT message. }
|
|
if Assigned(FView) then
|
|
FView.CloseChildPopups;
|
|
{ Ensure window handle is destroyed *before* FView is freed, since
|
|
DestroyWindowHandle calls CallNotifyWinEvent which may result in
|
|
FView.HandleWMObject being called }
|
|
if HandleAllocated then
|
|
DestroyWindowHandle;
|
|
FreeAndNil(FView);
|
|
inherited;
|
|
end;
|
|
|
|
function TTBPopupWindow.GetViewClass: TTBViewClass;
|
|
begin
|
|
Result := TTBPopupView;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.CreateParams(var Params: TCreateParams);
|
|
const
|
|
CS_DROPSHADOW = $00020000;
|
|
begin
|
|
inherited;
|
|
with Params do begin
|
|
Style := (Style and not (WS_CHILD or WS_GROUP or WS_TABSTOP)) or WS_POPUP;
|
|
ExStyle := ExStyle or WS_EX_TOPMOST or WS_EX_TOOLWINDOW;
|
|
WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
|
|
{ Enable drop shadow effect on Windows XP and later }
|
|
if IsWindowsXP then
|
|
WindowClass.Style := WindowClass.Style or CS_DROPSHADOW;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.CreateWnd;
|
|
const
|
|
WM_CHANGEUISTATE = $0127;
|
|
WM_QUERYUISTATE = $0129;
|
|
UIS_INITIALIZE = 3;
|
|
UISF_HIDEACCEL = $2;
|
|
var
|
|
B: Boolean;
|
|
begin
|
|
inherited;
|
|
{ On a top-level popup window, send WM_CHANGEUISTATE & WM_QUERYUISTATE
|
|
messages to the window to see if the last input came from the keyboard
|
|
and if the accelerator keys should be shown }
|
|
if (FView.ParentView = nil) and not FAccelsVisibilitySet then begin
|
|
FAccelsVisibilitySet := True;
|
|
SendMessage(Handle, WM_CHANGEUISTATE, UIS_INITIALIZE, 0);
|
|
B := (SendMessage(Handle, WM_QUERYUISTATE, 0, 0) and UISF_HIDEACCEL = 0);
|
|
FView.SetAccelsVisibility(B);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.DestroyWindowHandle;
|
|
begin
|
|
{ Before destroying the window handle, we must stop any animation, otherwise
|
|
the animation thread will use an invalid handle }
|
|
TBEndAnimation(WindowHandle);
|
|
{ Cleanly destroy any timers before the window handle is destroyed }
|
|
if Assigned(FView) then
|
|
FView.StopAllTimers;
|
|
CallNotifyWinEvent(EVENT_SYSTEM_MENUPOPUPEND, WindowHandle, OBJID_CLIENT,
|
|
CHILDID_SELF);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMGetObject(var Message: TMessage);
|
|
begin
|
|
if not FView.HandleWMGetObject(Message) then
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.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);
|
|
SPI_GETMENUFADE = $1012;
|
|
var
|
|
Blend: Boolean;
|
|
begin
|
|
{ Must override TCustomForm/TForm's CM_SHOWINGCHANGED handler so that the
|
|
form doesn't get activated when Visible is set to True. }
|
|
|
|
{ Handle animation. NOTE: I do not recommend trying to enable animation on
|
|
Windows 95 and NT 4.0 because there's a difference in the way the
|
|
SetWindowPos works on those versions. See the comment in the
|
|
TBStartAnimation function of TB2Anim.pas. }
|
|
{$IFNDEF TB2K_NO_ANIMATION}
|
|
if ((FView.ParentView = nil) or not(vsNoAnimation in FView.FParentView.FState)) and
|
|
Showing and (FView.Selected = nil) and not IsWindowVisible(WindowHandle) and
|
|
GetSystemParametersInfoBool(SPI_GETMENUANIMATION, False) then begin
|
|
Blend := GetSystemParametersInfoBool(SPI_GETMENUFADE, False);
|
|
if Blend or (FAnimationDirection <> []) then begin
|
|
TBStartAnimation(WindowHandle, Blend, FAnimationDirection);
|
|
Exit;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{ No animation... }
|
|
if not Showing then begin
|
|
{ Call TBEndAnimation to ensure WS_EX_LAYERED style is removed before
|
|
hiding, otherwise windows under the popup window aren't repainted
|
|
properly. }
|
|
TBEndAnimation(WindowHandle);
|
|
end;
|
|
SetWindowPos(WindowHandle, 0, 0, 0, 0, 0, ShowFlags[Showing]);
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMEraseBkgnd(var Message: TWMEraseBkgnd);
|
|
begin
|
|
{ May be necessary in some cases... }
|
|
TBEndAnimation(WindowHandle);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMPaint(var Message: TWMPaint);
|
|
begin
|
|
{ Must abort animation when a WM_PAINT message is received }
|
|
TBEndAnimation(WindowHandle);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.Paint;
|
|
begin
|
|
FView.DrawSubitems(Canvas);
|
|
PaintScrollArrows;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.PaintScrollArrows;
|
|
|
|
procedure DrawArrow(const R: TRect; ADown: Boolean);
|
|
var
|
|
X, Y: Integer;
|
|
P: array[0..2] of TPoint;
|
|
begin
|
|
X := (R.Left + R.Right) div 2;
|
|
Y := (R.Top + R.Bottom) div 2;
|
|
Dec(Y);
|
|
P[0].X := X-3;
|
|
P[0].Y := Y;
|
|
P[1].X := X+3;
|
|
P[1].Y := Y;
|
|
P[2].X := X;
|
|
P[2].Y := Y;
|
|
if ADown then
|
|
Inc(P[2].Y, 3)
|
|
else begin
|
|
Inc(P[0].Y, 3);
|
|
Inc(P[1].Y, 3);
|
|
end;
|
|
Canvas.Pen.Color := tbMenuTextColor;
|
|
Canvas.Brush.Color := tbMenuTextColor;
|
|
Canvas.Polygon(P);
|
|
end;
|
|
|
|
begin
|
|
if FView.FShowUpArrow then
|
|
DrawArrow(Rect(0, 0, ClientWidth, tbMenuScrollArrowHeight), False);
|
|
if FView.FShowDownArrow then
|
|
DrawArrow(Bounds(0, ClientHeight - tbMenuScrollArrowHeight,
|
|
ClientWidth, tbMenuScrollArrowHeight), True);
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMClose(var Message: TWMClose);
|
|
begin
|
|
{ do nothing -- ignore Alt+F4 keypresses }
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMNCCalcSize(var Message: TWMNCCalcSize);
|
|
|
|
procedure ApplyToRect(var R: TRect);
|
|
begin
|
|
InflateRect(R, -PopupMenuWindowNCSize, -PopupMenuWindowNCSize);
|
|
end;
|
|
|
|
{$IFDEF CLR}
|
|
var
|
|
Params: TNCCalcSizeParams;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFNDEF CLR}
|
|
ApplyToRect(Message.CalcSize_Params.rgrc[0]);
|
|
{$ELSE}
|
|
Params := Message.CalcSize_Params;
|
|
ApplyToRect(Params.rgrc0);
|
|
Message.CalcSize_Params := Params;
|
|
{$ENDIF}
|
|
inherited;
|
|
end;
|
|
|
|
procedure PopupWindowNCPaintProc(Wnd: HWND; DC: HDC; AppData: TObject);
|
|
var
|
|
R: TRect;
|
|
{$IFNDEF TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
Brush: HBRUSH;
|
|
{$ENDIF}
|
|
begin
|
|
GetWindowRect(Wnd, R); OffsetRect(R, -R.Left, -R.Top);
|
|
{$IFNDEF TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
if not AreFlatMenusEnabled then begin
|
|
{$ENDIF}
|
|
DrawEdge(DC, R, EDGE_RAISED, BF_RECT or BF_ADJUST);
|
|
FrameRect(DC, R, GetSysColorBrush(COLOR_BTNFACE));
|
|
{$IFNDEF TB2K_USE_STRICT_O2K_MENU_STYLE}
|
|
end
|
|
else begin
|
|
FrameRect(DC, R, GetSysColorBrush(COLOR_BTNSHADOW));
|
|
Brush := CreateSolidBrush(ColorToRGB(TTBPopupWindow(AppData).Color));
|
|
InflateRect(R, -1, -1);
|
|
FrameRect(DC, R, Brush);
|
|
InflateRect(R, -1, -1);
|
|
FrameRect(DC, R, Brush);
|
|
DeleteObject(Brush);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMNCPaint(var Message: TMessage);
|
|
var
|
|
DC: HDC;
|
|
begin
|
|
DC := GetWindowDC(Handle);
|
|
try
|
|
SelectNCUpdateRgn(Handle, DC, HRGN(Message.WParam));
|
|
PopupWindowNCPaintProc(Handle, DC, Self);
|
|
finally
|
|
ReleaseDC(Handle, DC);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMPrint(var Message: TMessage);
|
|
begin
|
|
HandleWMPrint(Handle, Message, PopupWindowNCPaintProc, Self);
|
|
end;
|
|
|
|
procedure TTBPopupWindow.WMPrintClient(var Message:
|
|
{$IFNDEF CLR} TMessage {$ELSE} TWMPrintClient {$ENDIF});
|
|
begin
|
|
HandleWMPrintClient(PaintHandler, Message);
|
|
end;
|
|
|
|
procedure TTBPopupWindow.CMHintShow(var Message: TCMHintShow);
|
|
begin
|
|
FView.HandleHintShowMessage(Message);
|
|
end;
|
|
|
|
|
|
{ TTBItemContainer }
|
|
|
|
constructor TTBItemContainer.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FItem := TTBRootItem.Create(Self);
|
|
FItem.ParentComponent := Self;
|
|
end;
|
|
|
|
destructor TTBItemContainer.Destroy;
|
|
begin
|
|
FItem.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TTBItemContainer.GetItems: TTBCustomItem;
|
|
begin
|
|
Result := FItem;
|
|
end;
|
|
|
|
procedure TTBItemContainer.GetChildren(Proc: TGetChildProc; Root: TComponent);
|
|
begin
|
|
FItem.GetChildren(Proc, Root);
|
|
end;
|
|
|
|
function TTBItemContainer.GetImages: TCustomImageList;
|
|
begin
|
|
Result := FItem.SubMenuImages;
|
|
end;
|
|
|
|
procedure TTBItemContainer.SetImages(Value: TCustomImageList);
|
|
begin
|
|
FItem.SubMenuImages := Value;
|
|
end;
|
|
|
|
|
|
{ TTBPopupMenu }
|
|
|
|
constructor TTBPopupMenu.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FItem := GetRootItemClass.Create(Self);
|
|
FItem.ParentComponent := Self;
|
|
FItem.OnClick := RootItemClick;
|
|
end;
|
|
|
|
destructor TTBPopupMenu.Destroy;
|
|
begin
|
|
FItem.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TTBPopupMenu.GetItems: TTBCustomItem;
|
|
begin
|
|
Result := FItem;
|
|
end;
|
|
|
|
procedure TTBPopupMenu.GetChildren(Proc: TGetChildProc; Root: TComponent);
|
|
begin
|
|
FItem.GetChildren(Proc, Root);
|
|
end;
|
|
|
|
procedure TTBPopupMenu.SetChildOrder(Child: TComponent; Order: Integer);
|
|
begin
|
|
FItem.SetChildOrder(Child, Order);
|
|
end;
|
|
|
|
function TTBPopupMenu.GetRootItemClass: TTBRootItemClass;
|
|
begin
|
|
Result := TTBRootItem;
|
|
end;
|
|
|
|
function TTBPopupMenu.GetImages: TCustomImageList;
|
|
begin
|
|
Result := FItem.SubMenuImages;
|
|
end;
|
|
|
|
function TTBPopupMenu.GetLinkSubitems: TTBCustomItem;
|
|
begin
|
|
Result := FItem.LinkSubitems;
|
|
end;
|
|
|
|
function TTBPopupMenu.GetOptions: TTBItemOptions;
|
|
begin
|
|
Result := FItem.Options;
|
|
end;
|
|
|
|
procedure TTBPopupMenu.SetImages(Value: TCustomImageList);
|
|
begin
|
|
FItem.SubMenuImages := Value;
|
|
end;
|
|
|
|
procedure TTBPopupMenu.SetLinkSubitems(Value: TTBCustomItem);
|
|
begin
|
|
FItem.LinkSubitems := Value;
|
|
end;
|
|
|
|
procedure TTBPopupMenu.SetOptions(Value: TTBItemOptions);
|
|
begin
|
|
FItem.Options := Value;
|
|
end;
|
|
|
|
procedure TTBPopupMenu.RootItemClick(Sender: TObject);
|
|
begin
|
|
if Sender = FItem then
|
|
Sender := Self;
|
|
DoPopup(Sender);
|
|
end;
|
|
|
|
{$IFNDEF JR_D5}
|
|
procedure TTBPopupMenu.DoPopup(Sender: TObject);
|
|
begin
|
|
if Assigned(OnPopup) then OnPopup(Sender);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TTBPopupMenu.Popup(X, Y: Integer);
|
|
begin
|
|
PopupEx(X, Y, False);
|
|
end;
|
|
|
|
function TTBPopupMenu.PopupEx(X, Y: Integer;
|
|
ReturnClickedItemOnly: Boolean = False): TTBCustomItem;
|
|
begin
|
|
{$IFDEF JR_D5}
|
|
{$IFDEF JR_D9}
|
|
SetPopupPoint(Point(X, Y));
|
|
{$ELSE}
|
|
PPoint(@PopupPoint)^ := Point(X, Y);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
Result := FItem.Popup(X, Y, TrackButton = tbRightButton,
|
|
TTBPopupAlignment(Alignment), ReturnClickedItemOnly);
|
|
end;
|
|
|
|
function TTBPopupMenu.IsShortCut(var Message: TWMKey): Boolean;
|
|
begin
|
|
Result := FItem.IsShortCut(Message);
|
|
end;
|
|
|
|
|
|
{ TTBImageList }
|
|
|
|
constructor TTBCustomImageList.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FCheckedImagesChangeLink := TChangeLink.Create;
|
|
FCheckedImagesChangeLink.OnChange := ImageListChanged;
|
|
FDisabledImagesChangeLink := TChangeLink.Create;
|
|
FDisabledImagesChangeLink.OnChange := ImageListChanged;
|
|
FHotImagesChangeLink := TChangeLink.Create;
|
|
FHotImagesChangeLink.OnChange := ImageListChanged;
|
|
FImagesBitmap := TBitmap.Create;
|
|
FImagesBitmap.OnChange := ImagesBitmapChanged;
|
|
FImagesBitmapMaskColor := clFuchsia;
|
|
end;
|
|
|
|
destructor TTBCustomImageList.Destroy;
|
|
begin
|
|
FreeAndNil(FImagesBitmap);
|
|
FreeAndNil(FHotImagesChangeLink);
|
|
FreeAndNil(FDisabledImagesChangeLink);
|
|
FreeAndNil(FCheckedImagesChangeLink);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TTBCustomImageList.ImagesBitmapChanged(Sender: TObject);
|
|
begin
|
|
if not ImagesBitmap.Empty then begin
|
|
Clear;
|
|
AddMasked(ImagesBitmap, FImagesBitmapMaskColor);
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomImageList.ImageListChanged(Sender: TObject);
|
|
begin
|
|
Change;
|
|
end;
|
|
|
|
{$IFDEF CLR}
|
|
procedure TTBCustomImageList.WriteLeft(Writer: TWriter);
|
|
begin
|
|
Writer.WriteInteger(DesignInfo shr 16);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.WriteTop(Writer: TWriter);
|
|
begin
|
|
Writer.WriteInteger(DesignInfo and $FFFF);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TTBCustomImageList.DefineProperties(Filer: TFiler);
|
|
{$IFNDEF CLR}
|
|
type
|
|
TProc = procedure(ASelf: TObject; Filer: TFiler);
|
|
{$ELSE}
|
|
var
|
|
Ancestor: TComponent;
|
|
AncestorInfo: Longint;
|
|
DesignInfo: Longint;
|
|
{$ENDIF}
|
|
begin
|
|
if (Filer is TReader) or FImagesBitmap.Empty then
|
|
inherited
|
|
else begin
|
|
{$IFNDEF CLR}
|
|
{ Bypass TCustomImageList.DefineProperties when we've got an ImageBitmap }
|
|
TProc(@TComponentAccess.DefineProperties)(Self, Filer);
|
|
{$ELSE}
|
|
{ On .NET I'm not aware of any way to bypass an inherited method, so we
|
|
have to handle DefineProperties all by ourself. The following code is
|
|
copied from TComponentHelper.DefineProperties, with references to
|
|
private fields changed and the Read* methods removed. }
|
|
AncestorInfo := 0;
|
|
DesignInfo := Self.DesignInfo;
|
|
Ancestor := TComponent(Filer.Ancestor);
|
|
if Ancestor <> nil then
|
|
AncestorInfo := Ancestor.DesignInfo;
|
|
Filer.DefineProperty('Left', nil, WriteLeft, (DesignInfo and $FFFF) <>
|
|
(AncestorInfo and $FFFF));
|
|
Filer.DefineProperty('Top', nil, WriteTop, (DesignInfo shr 16) <>
|
|
(AncestorInfo shr 16));
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomImageList.DrawState(Canvas: TCanvas; X, Y, Index: Integer;
|
|
Enabled, Selected, Checked: Boolean);
|
|
begin
|
|
if not Enabled and Assigned(DisabledImages) then
|
|
DisabledImages.Draw(Canvas, X, Y, Index)
|
|
else if Checked and Assigned(CheckedImages) then
|
|
CheckedImages.Draw(Canvas, X, Y, Index, Enabled)
|
|
else if Selected and Assigned(HotImages) then
|
|
HotImages.Draw(Canvas, X, Y, Index, Enabled)
|
|
else
|
|
Draw(Canvas, X, Y, Index, Enabled);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited;
|
|
if Operation = opRemove then begin
|
|
if AComponent = CheckedImages then CheckedImages := nil;
|
|
if AComponent = DisabledImages then DisabledImages := nil;
|
|
if AComponent = HotImages then HotImages := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomImageList.ChangeImages(var AImageList: TCustomImageList;
|
|
Value: TCustomImageList; AChangeLink: TChangeLink);
|
|
begin
|
|
if Value = Self then
|
|
Value := nil;
|
|
if AImageList <> Value then begin
|
|
if Assigned(AImageList) then
|
|
AImageList.UnregisterChanges(AChangeLink);
|
|
AImageList := Value;
|
|
if Assigned(Value) then begin
|
|
Value.RegisterChanges(AChangeLink);
|
|
Value.FreeNotification(Self);
|
|
end;
|
|
{ Don't call Change while loading because it causes the Delphi IDE to
|
|
think the form has been modified (?). Also, don't call Change while
|
|
destroying since there's no reason to. }
|
|
if not(csLoading in ComponentState) and
|
|
not(csDestroying in ComponentState) then
|
|
Change;
|
|
end;
|
|
end;
|
|
|
|
procedure TTBCustomImageList.SetCheckedImages(Value: TCustomImageList);
|
|
begin
|
|
ChangeImages(FCheckedImages, Value, FCheckedImagesChangeLink);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.SetDisabledImages(Value: TCustomImageList);
|
|
begin
|
|
ChangeImages(FDisabledImages, Value, FDisabledImagesChangeLink);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.SetHotImages(Value: TCustomImageList);
|
|
begin
|
|
ChangeImages(FHotImages, Value, FHotImagesChangeLink);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.SetImagesBitmap(Value: TBitmap);
|
|
begin
|
|
FImagesBitmap.Assign(Value);
|
|
end;
|
|
|
|
procedure TTBCustomImageList.SetImagesBitmapMaskColor(Value: TColor);
|
|
begin
|
|
if FImagesBitmapMaskColor <> Value then begin
|
|
FImagesBitmapMaskColor := Value;
|
|
ImagesBitmapChanged(nil);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTBBaseAccObject }
|
|
|
|
{ According to the MSAA docs:
|
|
"With Active Accessibility 2.0, servers can return E_NOTIMPL from IDispatch
|
|
methods and Active Accessibility will implement the IAccessible interface
|
|
for them."
|
|
And there was much rejoicing. }
|
|
|
|
{$IFNDEF CLR}
|
|
function TTBBaseAccObject.GetIDsOfNames(const IID: TGUID; Names: Pointer;
|
|
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
|
|
begin
|
|
Result := E_NOTIMPL;
|
|
end;
|
|
|
|
function TTBBaseAccObject.GetTypeInfo(Index, LocaleID: Integer;
|
|
out TypeInfo): HResult;
|
|
begin
|
|
Result := E_NOTIMPL;
|
|
end;
|
|
|
|
function TTBBaseAccObject.GetTypeInfoCount(out Count: Integer): HResult;
|
|
begin
|
|
Result := E_NOTIMPL;
|
|
end;
|
|
|
|
function TTBBaseAccObject.Invoke(DispID: Integer; const IID: TGUID;
|
|
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
|
|
ArgErr: Pointer): HResult;
|
|
begin
|
|
Result := E_NOTIMPL;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
|
|
{ Initialization & finalization }
|
|
|
|
procedure TBInitToolbarSystemFont;
|
|
var
|
|
NonClientMetrics: TNonClientMetrics;
|
|
begin
|
|
if GetSystemNonClientMetrics(NonClientMetrics) then
|
|
ToolbarFont.Handle := CreateFontIndirect(NonClientMetrics.lfMenuFont);
|
|
end;
|
|
|
|
initialization
|
|
ToolbarFont := TFont.Create;
|
|
TBInitToolbarSystemFont;
|
|
finalization
|
|
DestroyClickWnd;
|
|
FreeAndNil(ToolbarFont);
|
|
end.
|