home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Delphi Anthology
/
aDELPHI.iso
/
Runimage
/
Delphi50
/
Source
/
Vcl
/
menus.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1999-08-11
|
106KB
|
3,547 lines
{*******************************************************}
{ }
{ Borland Delphi Visual Component Library }
{ }
{ Copyright (c) 1995,99 Inprise Corporation }
{ }
{*******************************************************}
unit Menus;
{$S-,W-,R-,T-,H+,X+}
{$C PRELOAD}
interface
uses Windows, SysUtils, Classes, Contnrs, Messages, Graphics, ImgList, ActnList;
type
TMenuItem = class;
EMenuError = class(Exception);
TMenu = class;
TMenuBreak = (mbNone, mbBreak, mbBarBreak);
TMenuChangeEvent = procedure (Sender: TObject; Source: TMenuItem; Rebuild: Boolean) of object;
TMenuDrawItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean) of object;
TAdvancedMenuDrawItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState) of object;
TMenuMeasureItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
var Width, Height: Integer) of object;
TMenuItemAutoFlag = (maAutomatic, maManual, maParent);
TMenuAutoFlag = maAutomatic..maManual;
{ TMenuActionLink }
TMenuActionLink = class(TActionLink)
protected
FClient: TMenuItem;
procedure AssignClient(AClient: TObject); override;
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;
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;
TMenuActionLinkClass = class of TMenuActionLink;
{ TMenuItem }
TMenuItem = class(TComponent)
private
FCaption: string;
FHandle: HMENU;
FChecked: Boolean;
FEnabled: Boolean;
FDefault: Boolean;
FAutoHotkeys: TMenuItemAutoFlag;
FAutoLineReduction: TMenuItemAutoFlag;
FRadioItem: Boolean;
FVisible: Boolean;
FGroupIndex: Byte;
FImageIndex: TImageIndex;
FActionLink: TMenuActionLink;
FBreak: TMenuBreak;
FBitmap: TBitmap;
FCommand: Word;
FHelpContext: THelpContext;
FHint: string;
FItems: TList;
FShortCut: TShortCut;
FParent: TMenuItem;
FMerged: TMenuItem;
FMergedWith: TMenuItem;
FMenu: TMenu;
FStreamedRebuild: Boolean;
FImageChangeLink: TChangeLink;
FSubMenuImages: TCustomImageList;
FOnChange: TMenuChangeEvent;
FOnClick: TNotifyEvent;
FOnDrawItem: TMenuDrawItemEvent;
FOnAdvancedDrawItem: TAdvancedMenuDrawItemEvent;
FOnMeasureItem: TMenuMeasureItemEvent;
procedure AppendTo(Menu: HMENU; ARightToLeft: Boolean);
procedure DoActionChange(Sender: TObject);
procedure ReadShortCutText(Reader: TReader);
procedure MergeWith(Menu: TMenuItem);
procedure RebuildHandle;
procedure PopulateMenu;
procedure SubItemChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
procedure TurnSiblingsOff;
procedure VerifyGroupIndex(Position: Integer; Value: Byte);
function GetAction: TBasicAction;
function GetBitmap: TBitmap;
procedure SetAction(Value: TBasicAction);
procedure SetBitmap(Value: TBitmap);
procedure SetSubMenuImages(Value: TCustomImageList);
procedure ImageListChange(Sender: TObject);
procedure InitiateActions;
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;
function InternalRethinkHotkeys(ForceRethink: Boolean): Boolean;
procedure SetAutoHotkeys(const Value: TMenuItemAutoFlag);
function InternalRethinkLines(ForceRethink: Boolean): Boolean;
procedure SetAutoLineReduction(const Value: TMenuItemAutoFlag);
protected
procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); dynamic;
procedure AdvancedDrawItem(ACanvas: TCanvas; ARect: TRect;
State: TOwnerDrawState; TopLevel: Boolean); virtual;
procedure AssignTo(Dest: TPersistent); override;
procedure DefineProperties(Filer: TFiler); override;
procedure DoDrawText(ACanvas: TCanvas; const ACaption: string;
var Rect: TRect; Selected: Boolean; Flags: Longint);
procedure DrawItem(ACanvas: TCanvas; ARect: TRect; Selected: Boolean); virtual;
function GetActionLinkClass: TMenuActionLinkClass; dynamic;
function GetHandle: HMENU;
function GetCount: Integer;
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
function GetItem(Index: Integer): TMenuItem;
function GetMenuIndex: Integer;
function GetAutoHotkeys: Boolean;
function GetAutoLineReduction: Boolean;
function InsertNewLine(ABefore: Boolean; AItem: TMenuItem): Integer;
procedure MeasureItem(ACanvas: TCanvas; var Width, Height: Integer);
procedure MenuChanged(Rebuild: Boolean); virtual;
procedure Loaded; override;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
procedure SetBreak(Value: TMenuBreak);
procedure SetCaption(const Value: string);
procedure SetChecked(Value: Boolean);
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
procedure SetDefault(Value: Boolean);
procedure SetEnabled(Value: Boolean);
procedure SetGroupIndex(Value: Byte);
procedure SetImageIndex(Value: TImageIndex);
procedure SetMenuIndex(Value: Integer);
procedure SetParentComponent(Value: TComponent); override;
procedure SetRadioItem(Value: Boolean);
procedure SetShortCut(Value: TShortCut);
procedure SetVisible(Value: Boolean);
procedure UpdateItems;
property ActionLink: TMenuActionLink read FActionLink write FActionLink;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure InitiateAction; virtual;
procedure Insert(Index: Integer; Item: TMenuItem);
procedure Delete(Index: Integer);
procedure Clear;
procedure Click; virtual;
function Find(ACaption: string): TMenuItem;
function IndexOf(Item: TMenuItem): Integer;
function IsLine: Boolean;
function GetImageList: TCustomImageList;
function GetParentComponent: TComponent; override;
function GetParentMenu: TMenu;
function HasParent: Boolean; override;
function NewTopLine: Integer;
function NewBottomLine: Integer;
function InsertNewLineBefore(AItem: TMenuItem): Integer;
function InsertNewLineAfter(AItem: TMenuItem): Integer;
procedure Add(Item: TMenuItem); overload;
procedure Add(const AItems: array of TMenuItem); overload;
procedure Remove(Item: TMenuItem);
function RethinkHotkeys: Boolean;
function RethinkLines: Boolean;
property Command: Word read FCommand;
property Handle: HMENU read GetHandle;
property Count: Integer read GetCount;
property Items[Index: Integer]: TMenuItem read GetItem; default;
property MenuIndex: Integer read GetMenuIndex write SetMenuIndex;
property Parent: TMenuItem read FParent;
published
property Action: TBasicAction read GetAction write SetAction;
property AutoHotkeys: TMenuItemAutoFlag read FAutoHotkeys write SetAutoHotkeys default maParent;
property AutoLineReduction: TMenuItemAutoFlag read FAutoLineReduction write SetAutoLineReduction default maParent;
property Bitmap: TBitmap read GetBitmap write SetBitmap;
property Break: TMenuBreak read FBreak write SetBreak default mbNone;
property Caption: string read FCaption write SetCaption stored IsCaptionStored;
property Checked: Boolean read FChecked write SetChecked stored IsCheckedStored default False;
property SubMenuImages: TCustomImageList read FSubMenuImages write SetSubMenuImages;
property Default: Boolean read FDefault write SetDefault default False;
property Enabled: Boolean read FEnabled write SetEnabled stored IsEnabledStored default True;
property GroupIndex: Byte 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 RadioItem: Boolean read FRadioItem write SetRadioItem default False;
property ShortCut: TShortCut read FShortCut write SetShortCut stored IsShortCutStored default 0;
property Visible: Boolean read FVisible write SetVisible stored IsVisibleStored default True;
property OnClick: TNotifyEvent read FOnClick write FOnClick stored IsOnClickStored;
property OnDrawItem: TMenuDrawItemEvent read FOnDrawItem write FOnDrawItem;
property OnAdvancedDrawItem: TAdvancedMenuDrawItemEvent read FOnAdvancedDrawItem write FOnAdvancedDrawItem;
property OnMeasureItem: TMenuMeasureItemEvent read FOnMeasureItem write FOnMeasureItem;
end;
TFindItemKind = (fkCommand, fkHandle, fkShortCut);
TMenu = class(TComponent)
private
FBiDiMode: TBiDiMode;
FItems: TMenuItem;
FWindowHandle: HWND;
FMenuImage: string;
FOwnerDraw: Boolean;
FParentBiDiMode: Boolean;
FImageChangeLink: TChangeLink;
FImages: TCustomImageList;
FOnChange: TMenuChangeEvent;
procedure SetBiDiMode(Value: TBiDiMode);
procedure SetOwnerDraw(Value: Boolean);
procedure SetImages(Value: TCustomImageList);
procedure SetParentBiDiMode(Value: Boolean);
procedure SetWindowHandle(Value: HWND);
procedure ImageListChange(Sender: TObject);
function IsBiDiModeStored: Boolean;
function UpdateImage: Boolean;
function GetAutoHotkeys: TMenuAutoFlag;
procedure SetAutoHotkeys(const Value: TMenuAutoFlag);
function GetAutoLineReduction: TMenuAutoFlag;
procedure SetAutoLineReduction(const Value: TMenuAutoFlag);
protected
procedure AdjustBiDiBehavior;
procedure DoChange(Source: TMenuItem; Rebuild: Boolean); virtual;
procedure DoBiDiModeChanged;
function DoGetMenuString(Menu: HMENU; ItemID: UINT; Str: PChar;
MaxCount: Integer; Flag: UINT): Integer;
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
function GetHandle: HMENU; virtual;
function IsOwnerDraw: Boolean;
procedure Loaded; override;
procedure MenuChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean); virtual;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
procedure UpdateItems;
property OnChange: TMenuChangeEvent read FOnChange write FOnChange;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function DispatchCommand(ACommand: Word): Boolean;
function DispatchPopup(AHandle: HMENU): Boolean;
function FindItem(Value: Integer; Kind: TFindItemKind): TMenuItem;
function GetHelpContext(Value: Integer; ByCommand: Boolean): THelpContext;
property Images: TCustomImageList read FImages write SetImages;
function IsRightToLeft: Boolean;
function IsShortCut(var Message: TWMKey): Boolean; dynamic;
procedure ParentBiDiModeChanged; overload;
procedure ParentBiDiModeChanged(AControl: TObject); overload;
procedure ProcessMenuChar(var Message: TWMMenuChar);
property AutoHotkeys: TMenuAutoFlag read GetAutoHotkeys write SetAutoHotkeys default maAutomatic;
property AutoLineReduction: TMenuAutoFlag read GetAutoLineReduction write SetAutoLineReduction default maAutomatic;
property BiDiMode: TBiDiMode read FBiDiMode write SetBiDiMode stored IsBiDiModeStored;
property Handle: HMENU read GetHandle;
property OwnerDraw: Boolean read FOwnerDraw write SetOwnerDraw default False;
property ParentBiDiMode: Boolean read FParentBiDiMode write SetParentBiDiMode default True;
property WindowHandle: HWND read FWindowHandle write SetWindowHandle;
published
property Items: TMenuItem read FItems;
end;
TMainMenu = class(TMenu)
private
FOle2Menu: HMENU;
FAutoMerge: Boolean;
procedure ItemChanged;
procedure SetAutoMerge(Value: Boolean);
protected
procedure MenuChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean); override;
function GetHandle: HMENU; override;
public
procedure Merge(Menu: TMainMenu);
procedure Unmerge(Menu: TMainMenu);
procedure PopulateOle2Menu(SharedMenu: HMenu; Groups: array of Integer;
var Widths: array of Longint);
procedure GetOle2AcceleratorTable(var AccelTable: HAccel;
var AccelCount: Integer; Groups: array of Integer);
procedure SetOle2MenuHandle(Handle: HMENU);
published
property AutoHotkeys;
property AutoLineReduction;
property AutoMerge: Boolean read FAutoMerge write SetAutoMerge default False;
property BiDiMode;
property Images;
property OwnerDraw;
property ParentBiDiMode;
property OnChange;
end;
TPopupAlignment = (paLeft, paRight, paCenter);
TTrackButton = (tbRightButton, tbLeftButton);
TMenuAnimations = (maLeftToRight, maRightToLeft, maTopToBottom, maBottomToTop, maNone);
TMenuAnimation = set of TMenuAnimations;
TPopupMenu = class(TMenu)
private
FPopupPoint: TPoint;
FAlignment: TPopupAlignment;
FAutoPopup: Boolean;
FPopupComponent: TComponent;
FTrackButton: TTrackButton;
FMenuAnimation: TMenuAnimation;
FOnPopup: TNotifyEvent;
function GetHelpContext: THelpContext;
procedure SetHelpContext(Value: THelpContext);
procedure SetBiDiModeFromPopupControl;
protected
function UseRightToLeftAlignment: Boolean;
procedure DoPopup(Sender: TObject); virtual;
property PopupPoint: TPoint read FPopupPoint;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Popup(X, Y: Integer); virtual;
property PopupComponent: TComponent read FPopupComponent write FPopupComponent;
published
property Alignment: TPopupAlignment read FAlignment write FAlignment default paLeft;
property AutoHotkeys;
property AutoLineReduction;
property AutoPopup: Boolean read FAutoPopup write FAutoPopup default True;
property BiDiMode;
property HelpContext: THelpContext read GetHelpContext write SetHelpContext default 0;
property Images;
property MenuAnimation: TMenuAnimation read FMenuAnimation write FMenuAnimation default [];
property OwnerDraw;
property ParentBiDiMode;
property TrackButton: TTrackButton read FTrackButton write FTrackButton default tbRightButton;
property OnChange;
property OnPopup: TNotifyEvent read FOnPopup write FOnPopup;
end;
TPopupList = class(TList)
protected
FWindow: HWND;
procedure MainWndProc(var Message: TMessage);
procedure WndProc(var Message: TMessage); virtual;
public
property Window: HWND read FWindow;
procedure Add(Popup: TPopupMenu);
procedure Remove(Popup: TPopupMenu);
end;
PMenuItem = ^TMenuItem;
TMenuItemStack = class(TStack)
public
procedure ClearItem(AItem: TMenuItem);
end;
var
PopupList: TPopupList;
ShortCutItems: TMenuItemStack;
function ShortCut(Key: Word; Shift: TShiftState): TShortCut;
procedure ShortCutToKey(ShortCut: TShortCut; var Key: Word; var Shift: TShiftState);
function ShortCutToText(ShortCut: TShortCut): string;
function TextToShortCut(Text: string): TShortCut;
function NewMenu(Owner: TComponent; const AName: string; Items: array of TMenuItem): TMainMenu;
function NewPopupMenu(Owner: TComponent; const AName: string;
Alignment: TPopupAlignment; AutoPopup: Boolean; Items: array of TMenuitem): TPopupMenu;
function NewSubMenu(const ACaption: string; hCtx: Word;
const AName: string; Items: array of TMenuItem; AEnabled: Boolean = True): TMenuItem;
function NewItem(const ACaption: string; AShortCut: TShortCut;
AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent; hCtx: Word;
const AName: string): TMenuItem;
function NewLine: TMenuItem;
procedure DrawMenuItem(MenuItem: TMenuItem; ACanvas: TCanvas; ARect: TRect;
State: TOwnerDrawState);
var
{ These are the hotkeys that the auto-hotkey system will pick from.
Change this global variable at runtime if you want to add or remove
characters from the available characters. Notice that by default we
do not do international characters. }
ValidMenuHotkeys: string = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'; { do not localize }
const
cHotkeyPrefix = '&';
cLineCaption = '-';
cDialogSuffix = '...';
{ StripHotkey removes the & escape char that marks the hotkey character(s) in
the string. When the current locale is a Far East locale, this function also
looks for and removes parens around the hotkey, common in Far East locales. }
function StripHotkey(const Text: string): string;
{ GetHotkey will return the last hotkey that StripHotkey would strip. }
function GetHotkey(const Text: string): string;
{ Similar to AnsiSameText but strips hotkeys before comparing }
function AnsiSameCaption(const Text1, Text2: string): Boolean;
implementation
uses Controls, Forms, Consts;
const
RightToLeftMenuFlag = MFT_RIGHTORDER or MFT_RIGHTJUSTIFY;
cMenuAutoFlagToItem: array [TMenuAutoFlag] of TMenuItemAutoFlag = (maAutomatic, maManual);
cItemAutoFlagToMenu: array [TMenuItemAutoFlag] of TMenuAutoFlag = (maAutomatic, maManual, maAutomatic);
cBooleanToItemAutoFlag: array [Boolean] of TMenuItemAutoFlag = (maManual, maAutomatic);
cItemAutoFlagToBoolean: array [TMenuItemAutoFlag] of Boolean = (True, False, True);
function FindPopupControl(const Pos: TPoint): TControl;
var
Window: TWinControl;
begin
Result := nil;
Window := FindVCLWindow(Pos);
if Window <> nil then
begin
Result := Window.ControlAtPos(Pos, False);
if Result = nil then Result := Window;
end;
end;
procedure Error(ResStr: PResStringRec);
function ReturnAddr: Pointer;
asm
MOV EAX,[EBP+4]
end;
begin
raise EMenuError.CreateRes(ResStr) at ReturnAddr;
end;
{ TShortCut processing routines }
function ShortCut(Key: Word; Shift: TShiftState): TShortCut;
begin
Result := 0;
if WordRec(Key).Hi <> 0 then Exit;
Result := Key;
if ssShift in Shift then Inc(Result, scShift);
if ssCtrl in Shift then Inc(Result, scCtrl);
if ssAlt in Shift then Inc(Result, scAlt);
end;
procedure ShortCutToKey(ShortCut: TShortCut; var Key: Word; var Shift: TShiftState);
begin
Key := ShortCut and not (scShift + scCtrl + scAlt);
Shift := [];
if ShortCut and scShift <> 0 then Include(Shift, ssShift);
if ShortCut and scCtrl <> 0 then Include(Shift, ssCtrl);
if ShortCut and scAlt <> 0 then Include(Shift, ssAlt);
end;
type
TMenuKeyCap = (mkcBkSp, mkcTab, mkcEsc, mkcEnter, mkcSpace, mkcPgUp,
mkcPgDn, mkcEnd, mkcHome, mkcLeft, mkcUp, mkcRight, mkcDown, mkcIns,
mkcDel, mkcShift, mkcCtrl, mkcAlt);
var
MenuKeyCaps: array[TMenuKeyCap] of string = (
SmkcBkSp, SmkcTab, SmkcEsc, SmkcEnter, SmkcSpace, SmkcPgUp,
SmkcPgDn, SmkcEnd, SmkcHome, SmkcLeft, SmkcUp, SmkcRight,
SmkcDown, SmkcIns, SmkcDel, SmkcShift, SmkcCtrl, SmkcAlt);
function GetSpecialName(ShortCut: TShortCut): string;
var
ScanCode: Integer;
KeyName: array[0..255] of Char;
begin
Result := '';
ScanCode := MapVirtualKey(WordRec(ShortCut).Lo, 0) shl 16;
if ScanCode <> 0 then
begin
GetKeyNameText(ScanCode, KeyName, SizeOf(KeyName));
GetSpecialName := KeyName;
end;
end;
function ShortCutToText(ShortCut: TShortCut): string;
var
Name: string;
begin
case WordRec(ShortCut).Lo of
$08, $09:
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcBkSp) + WordRec(ShortCut).Lo - $08)];
$0D: Name := MenuKeyCaps[mkcEnter];
$1B: Name := MenuKeyCaps[mkcEsc];
$20..$28:
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcSpace) + WordRec(ShortCut).Lo - $20)];
$2D..$2E:
Name := MenuKeyCaps[TMenuKeyCap(Ord(mkcIns) + WordRec(ShortCut).Lo - $2D)];
$30..$39: Name := Chr(WordRec(ShortCut).Lo - $30 + Ord('0'));
$41..$5A: Name := Chr(WordRec(ShortCut).Lo - $41 + Ord('A'));
$60..$69: Name := Chr(WordRec(ShortCut).Lo - $60 + Ord('0'));
$70..$87: Name := 'F' + IntToStr(WordRec(ShortCut).Lo - $6F);
else
Name := GetSpecialName(ShortCut);
end;
if Name <> '' then
begin
Result := '';
if ShortCut and scShift <> 0 then Result := Result + MenuKeyCaps[mkcShift];
if ShortCut and scCtrl <> 0 then Result := Result + MenuKeyCaps[mkcCtrl];
if ShortCut and scAlt <> 0 then Result := Result + MenuKeyCaps[mkcAlt];
Result := Result + Name;
end
else Result := '';
end;
{ This function is *very* slow. Use sparingly. Return 0 if no VK code was
found for the text }
function TextToShortCut(Text: string): TShortCut;
{ If the front of Text is equal to Front then remove the matching piece
from Text and return True, otherwise return False }
function CompareFront(var Text: string; const Front: string): Boolean;
begin
Result := False;
if (Length(Text) >= Length(Front)) and
(AnsiStrLIComp(PChar(Text), PChar(Front), Length(Front)) = 0) then
begin
Result := True;
Delete(Text, 1, Length(Front));
end;
end;
var
Key: TShortCut;
Shift: TShortCut;
begin
Result := 0;
Shift := 0;
while True do
begin
if CompareFront(Text, MenuKeyCaps[mkcShift]) then Shift := Shift or scShift
else if CompareFront(Text, '^') then Shift := Shift or scCtrl
else if CompareFront(Text, MenuKeyCaps[mkcCtrl]) then Shift := Shift or scCtrl
else if CompareFront(Text, MenuKeyCaps[mkcAlt]) then Shift := Shift or scAlt
else Break;
end;
if Text = '' then Exit;
for Key := $08 to $255 do { Copy range from table in ShortCutToText }
if AnsiCompareText(Text, ShortCutToText(Key)) = 0 then
begin
Result := Key or Shift;
Exit;
end;
end;
{ Menu command managment }
var
CommandPool: TBits;
function UniqueCommand: Word;
begin
Result := CommandPool.OpenBit;
CommandPool[Result] := True;
end;
{ Used to populate or merge menus }
procedure IterateMenus(Func: Pointer; Menu1, Menu2: TMenuItem);
var
I, J: Integer;
IIndex, JIndex: Byte;
Menu1Size, Menu2Size: Integer;
Done: Boolean;
function Iterate(var I: Integer; MenuItem: TMenuItem; AFunc: Pointer): Boolean;
var
Item: TMenuItem;
begin
if MenuItem = nil then Exit;
Result := False;
while not Result and (I < MenuItem.Count) do
begin
Item := MenuItem[I];
if Item.GroupIndex > IIndex then Break;
asm
MOV EAX,Item
MOV EDX,[EBP+8]
PUSH DWORD PTR [EDX]
CALL DWORD PTR AFunc
ADD ESP,4
MOV Result,AL
end;
Inc(I);
end;
end;
begin
I := 0;
J := 0;
Menu1Size := 0;
Menu2Size := 0;
if Menu1 <> nil then Menu1Size := Menu1.Count;
if Menu2 <> nil then Menu2Size := Menu2.Count;
Done := False;
while not Done and ((I < Menu1Size) or (J < Menu2Size)) do
begin
IIndex := High(Byte);
JIndex := High(Byte);
if (I < Menu1Size) then IIndex := Menu1[I].GroupIndex;
if (J < Menu2Size) then JIndex := Menu2[J].GroupIndex;
if IIndex <= JIndex then Done := Iterate(I, Menu1, Func)
else
begin
IIndex := JIndex;
Done := Iterate(J, Menu2, Func);
end;
while (I < Menu1Size) and (Menu1[I].GroupIndex <= IIndex) do Inc(I);
while (J < Menu2Size) and (Menu2[J].GroupIndex <= IIndex) do Inc(J);
end;
end;
{ TMenuActionLink }
procedure TMenuActionLink.AssignClient(AClient: TObject);
begin
FClient := AClient as TMenuItem;
end;
function TMenuActionLink.IsCaptionLinked: Boolean;
begin
Result := inherited IsCaptionLinked and
AnsiSameCaption(FClient.Caption, (Action as TCustomAction).Caption);
end;
function TMenuActionLink.IsCheckedLinked: Boolean;
begin
Result := inherited IsCheckedLinked and
(FClient.Checked = (Action as TCustomAction).Checked);
end;
function TMenuActionLink.IsEnabledLinked: Boolean;
begin
Result := inherited IsEnabledLinked and
(FClient.Enabled = (Action as TCustomAction).Enabled);
end;
function TMenuActionLink.IsHelpContextLinked: Boolean;
begin
Result := inherited IsHelpContextLinked and
(FClient.HelpContext = (Action as TCustomAction).HelpContext);
end;
function TMenuActionLink.IsHintLinked: Boolean;
begin
Result := inherited IsHintLinked and
(FClient.Hint = (Action as TCustomAction).Hint);
end;
function TMenuActionLink.IsImageIndexLinked: Boolean;
begin
Result := inherited IsImageIndexLinked and
(FClient.ImageIndex = (Action as TCustomAction).ImageIndex);
end;
function TMenuActionLink.IsShortCutLinked: Boolean;
begin
Result := inherited IsShortCutLinked and
(FClient.ShortCut = (Action as TCustomAction).ShortCut);
end;
function TMenuActionLink.IsVisibleLinked: Boolean;
begin
Result := inherited IsVisibleLinked and
(FClient.Visible = (Action as TCustomAction).Visible);
end;
function TMenuActionLink.IsOnExecuteLinked: Boolean;
begin
Result := inherited IsOnExecuteLinked and
(@FClient.OnClick = @Action.OnExecute);
end;
procedure TMenuActionLink.SetCaption(const Value: string);
begin
if IsCaptionLinked then FClient.Caption := Value;
end;
procedure TMenuActionLink.SetChecked(Value: Boolean);
begin
if IsCheckedLinked then FClient.Checked := Value;
end;
procedure TMenuActionLink.SetEnabled(Value: Boolean);
begin
if IsEnabledLinked then FClient.Enabled := Value;
end;
procedure TMenuActionLink.SetHelpContext(Value: THelpContext);
begin
if IsHelpContextLinked then FClient.HelpContext := Value;
end;
procedure TMenuActionLink.SetHint(const Value: string);
begin
if IsHintLinked then FClient.Hint := Value;
end;
procedure TMenuActionLink.SetImageIndex(Value: Integer);
begin
if IsImageIndexLinked then FClient.ImageIndex := Value;
end;
procedure TMenuActionLink.SetShortCut(Value: TShortCut);
begin
if IsShortCutLinked then FClient.ShortCut := Value;
end;
procedure TMenuActionLink.SetVisible(Value: Boolean);
begin
if IsVisibleLinked then FClient.Visible := Value;
end;
procedure TMenuActionLink.SetOnExecute(Value: TNotifyEvent);
begin
if IsOnExecuteLinked then FClient.OnClick := Value;
end;
{ TMenuItem }
constructor TMenuItem.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FVisible := True;
FEnabled := True;
FAutoHotkeys := maParent;
FAutoLineReduction := maParent;
FCommand := UniqueCommand;
FImageIndex := -1;
FImageChangeLink := TChangeLink.Create;
FImageChangeLink.OnChange := ImageListChange;
end;
destructor TMenuItem.Destroy;
begin
ShortCutItems.ClearItem(Self);
if FParent <> nil then
begin
FParent.Remove(Self);
FParent := nil;
end;
while Count > 0 do Items[0].Free;
if FHandle <> 0 then
begin
MergeWith(nil);
DestroyMenu(FHandle);
end;
FItems.Free;
FreeAndNil(FActionLink);
FreeAndNil(FImageChangeLink);
if FCommand <> 0 then CommandPool[FCommand] := False;
if Assigned(FBitmap) then FBitmap.Free;
inherited Destroy;
end;
const
Checks: array[Boolean] of DWORD = (MF_UNCHECKED, MF_CHECKED);
Enables: array[Boolean] of DWORD = (MF_DISABLED or MF_GRAYED, MF_ENABLED);
Breaks: array[TMenuBreak] of DWORD = (0, MF_MENUBREAK, MF_MENUBARBREAK);
Separators: array[Boolean] of DWORD = (MF_STRING, MF_SEPARATOR);
procedure TMenuItem.AppendTo(Menu: HMENU; ARightToLeft: Boolean);
const
IBreaks: array[TMenuBreak] of DWORD = (MFT_STRING, MFT_MENUBREAK, MFT_MENUBARBREAK);
IChecks: array[Boolean] of DWORD = (MFS_UNCHECKED, MFS_CHECKED);
IDefaults: array[Boolean] of DWORD = (0, MFS_DEFAULT);
IEnables: array[Boolean] of DWORD = (MFS_DISABLED or MFS_GRAYED, MFS_ENABLED);
IRadios: array[Boolean] of DWORD = (MFT_STRING, MFT_RADIOCHECK);
ISeparators: array[Boolean] of DWORD = (MFT_STRING, MFT_SEPARATOR);
IRTL: array[Boolean] of DWORD = (0, RightToLeftMenuFlag);
IOwnerDraw: array[Boolean] of DWORD = (MFT_STRING, MFT_OWNERDRAW);
var
MenuItemInfo: TMenuItemInfo;
Caption: string;
NewFlags: Integer;
IsOwnerDraw: Boolean;
ParentMenu: TMenu;
begin
if FVisible then
begin
Caption := FCaption;
if GetCount > 0 then MenuItemInfo.hSubMenu := GetHandle
else if (FShortCut <> scNone) and ((Parent = nil) or
(Parent.Parent <> nil) or not (Parent.Owner is TMainMenu)) then
Caption := Caption + #9 + ShortCutToText(FShortCut);
if Lo(GetVersion) >= 4 then
begin
MenuItemInfo.cbSize := 44; // Required for Windows 95
MenuItemInfo.fMask := MIIM_CHECKMARKS or MIIM_DATA or MIIM_ID or
MIIM_STATE or MIIM_SUBMENU or MIIM_TYPE;
ParentMenu := GetParentMenu;
// IsOwnerDraw := Assigned(ParentMenu) and ParentMenu.IsOwnerDraw or
IsOwnerDraw := Assigned(ParentMenu) and
(ParentMenu.OwnerDraw or (GetImageList <> nil)) or
Assigned(FBitmap) and not FBitmap.Empty;
MenuItemInfo.fType := IRadios[FRadioItem] or IBreaks[FBreak] or
ISeparators[FCaption = cLineCaption] or IRTL[ARightToLeft] or
IOwnerDraw[IsOwnerDraw];
MenuItemInfo.fState := IChecks[FChecked] or IEnables[FEnabled]
or IDefaults[FDefault];
MenuItemInfo.wID := Command;
MenuItemInfo.hSubMenu := 0;
MenuItemInfo.hbmpChecked := 0;
MenuItemInfo.hbmpUnchecked := 0;
MenuItemInfo.dwTypeData := PChar(Caption);
if GetCount > 0 then MenuItemInfo.hSubMenu := GetHandle;
InsertMenuItem(Menu, DWORD(-1), True, MenuItemInfo);
end
else
begin
NewFlags := Breaks[FBreak] or Checks[FChecked] or Enables[FEnabled] or
Separators[FCaption = cLineCaption] or MF_BYPOSITION;
if GetCount > 0 then
InsertMenu(Menu, DWORD(-1), MF_POPUP or NewFlags, GetHandle,
PChar(FCaption))
else
InsertMenu(Menu, DWORD(-1), NewFlags, Command, PChar(Caption));
end;
end;
end;
procedure TMenuItem.PopulateMenu;
var
MenuRightToLeft: Boolean;
function AddIn(MenuItem: TMenuItem): Boolean;
begin
MenuItem.AppendTo(FHandle, MenuRightToLeft);
Result := False;
end;
begin
if (FMenu <> nil) and
(FMenu is TMainMenu) then
begin
InternalRethinkHotkeys(False);
InternalRethinkLines(False);
end;
// all menu items use BiDiMode of their root menu
MenuRightToLeft := (FMenu <> nil) and FMenu.IsRightToLeft;
IterateMenus(@AddIn, FMerged, Self);
end;
procedure TMenuItem.ReadShortCutText(Reader: TReader);
begin
ShortCut := TextToShortCut(Reader.ReadString);
end;
procedure TMenuItem.MergeWith(Menu: TMenuItem);
begin
if FMerged <> Menu then
begin
if FMerged <> nil then FMerged.FMergedWith := nil;
FMerged := Menu;
if FMerged <> nil then FMerged.FMergedWith := Self;
RebuildHandle;
end;
end;
procedure TMenuItem.Loaded;
begin
inherited Loaded;
if Action <> nil then ActionChange(Action, True);
if FStreamedRebuild then RebuildHandle;
end;
procedure TMenuItem.RebuildHandle;
begin
if csDestroying in ComponentState then Exit;
if csReading in ComponentState then
FStreamedRebuild := True
else
begin
if FMergedWith <> nil then
FMergedWith.RebuildHandle
else
begin
while GetMenuItemCount(Handle) > 0 do
RemoveMenu(Handle, 0, MF_BYPOSITION);
if (FParent = nil) and (FMenu is TMainMenu) then
//if (Owner = nil) or (Owner is TMainMenu) then
begin
DestroyMenu(FHandle);
FHandle := 0;
end
else
PopulateMenu;
MenuChanged(False);
end;
end;
end;
procedure TMenuItem.VerifyGroupIndex(Position: Integer; Value: Byte);
var
I: Integer;
begin
for I := 0 to GetCount - 1 do
if I < Position then
begin
if Items[I].GroupIndex > Value then Error(@SGroupIndexTooLow)
end
else
{ Ripple change to menu items at Position and after }
if Items[I].GroupIndex < Value then Items[I].FGroupIndex := Value;
end;
function TMenuItem.GetHandle: HMENU;
begin
if FHandle = 0 then
begin
if Owner is TPopupMenu then
FHandle := CreatePopupMenu
else
FHandle := CreateMenu;
if FHandle = 0 then Error(@SOutOfResources);
PopulateMenu;
end;
Result := FHandle;
end;
procedure TMenuItem.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('ShortCutText', ReadShortCutText, nil, False);
end;
procedure TMenuItem.DoDrawText(ACanvas: TCanvas; const ACaption: string;
var Rect: TRect; Selected: Boolean; Flags: Longint);
var
Text: string;
R: TRect;
ParentMenu: TMenu;
begin
ParentMenu := GetParentMenu;
if (ParentMenu <> nil) and (ParentMenu.IsRightToLeft) then
begin
if Flags and DT_LEFT = DT_LEFT then
Flags := Flags and (not DT_LEFT) or DT_RIGHT
else if Flags and DT_RIGHT = DT_RIGHT then
Flags := Flags and (not DT_RIGHT) or DT_LEFT;
Flags := Flags or DT_RTLREADING;
end;
Text := ACaption;
if (Flags and DT_CALCRECT <> 0) and ((Text = '') or
(Text[1] = cHotkeyPrefix) and (Text[2] = #0)) then Text := Text + ' ';
with ACanvas do
begin
if Text = cLineCaption then
begin
if Flags and DT_CALCRECT = 0 then
begin
R := Rect;
Inc(R.Top, 4);
DrawEdge(Handle, R, EDGE_ETCHED, BF_TOP);
end;
end
else
begin
Brush.Style := bsClear;
if Default then
Font.Style := Font.Style + [fsBold];
if not Enabled then
begin
if not Selected then
begin
OffsetRect(Rect, 1, 1);
Font.Color := clBtnHighlight;
DrawText(Handle, PChar(Text), Length(Text), Rect, Flags);
OffsetRect(Rect, -1, -1);
end;
if Selected and (ColorToRGB(clHighlight) = ColorToRGB(clBtnShadow)) then
Font.Color := clBtnHighlight else
Font.Color := clBtnShadow;
end;
DrawText(Handle, PChar(Text), Length(Text), Rect, Flags);
end;
end;
end;
procedure TMenuItem.DrawItem(ACanvas: TCanvas; ARect: TRect; Selected: Boolean);
begin
if Assigned(FOnDrawItem) then
FOnDrawItem(Self, ACanvas, ARect, Selected);
end;
procedure TMenuItem.AdvancedDrawItem(ACanvas: TCanvas; ARect: TRect;
State: TOwnerDrawState; TopLevel: Boolean);
const
Alignments: array[TPopupAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER);
EdgeStyle: array[Boolean] of Longint = (BDR_RAISEDINNER, BDR_SUNKENOUTER);
var
ImageList: TCustomImageList;
ParentMenu: TMenu;
Alignment: TPopupAlignment;
DrawImage, DrawGlyph: Boolean;
GlyphRect, SaveRect: TRect;
DrawStyle: Longint;
Glyph: TBitmap;
OldBrushColor: TColor;
Selected: Boolean;
Win98Plus: Boolean;
Win2K: Boolean;
procedure NormalDraw;
begin
with ACanvas do
begin
//ImageList := GetImageList;
if not Selected then FillRect(ARect);
if ParentMenu is TMenu then
Alignment := paLeft
else if ParentMenu is TPopupMenu then
Alignment := TPopupMenu(ParentMenu).Alignment
else
Alignment := paLeft;
GlyphRect.Left := ARect.Left + 1;
GlyphRect.Top := ARect.Top + 1;
if Caption = cLineCaption then
begin
FillRect(ARect);
GlyphRect.Left := 0;
GlyphRect.Right := -4;
DrawGlyph := False;
end
else
begin
DrawImage := (ImageList <> nil) and ((ImageIndex > -1) and
(ImageIndex < ImageList.Count) or Checked and ((FBitmap = nil) or
FBitmap.Empty));
if DrawImage or Assigned(FBitmap) and not FBitmap.Empty then
begin
DrawGlyph := True;
if DrawImage then
begin
GlyphRect.Right := GlyphRect.Left + ImageList.Width;
GlyphRect.Bottom := GlyphRect.Top + ImageList.Height;
end
else
begin
{ Need to add BitmapWidth/Height properties for TMenuItem if we're to
support them. Right now let's hardcode them to 16x16. }
GlyphRect.Right := GlyphRect.Left + 16;
GlyphRect.Bottom := GlyphRect.Top + 16;
end;
{ Draw background pattern brush if selected }
if Checked then
begin
Inc(GlyphRect.Right);
Inc(GlyphRect.Bottom);
OldBrushColor := Brush.Color;
if not (odSelected in State) then
begin
OldBrushColor := Brush.Color;
Brush.Bitmap := AllocPatternBitmap(clBtnFace, clBtnHighlight);
FillRect(GlyphRect);
end
else
begin
Brush.Color := clBtnFace;
FillRect(GlyphRect);
end;
Brush.Color := OldBrushColor;
Inc(GlyphRect.Left);
Inc(GlyphRect.Top);
end;
if DrawImage then
begin
if (ImageIndex > -1) and (ImageIndex < ImageList.Count) then
ImageList.Draw(ACanvas, GlyphRect.Left, GlyphRect.Top, ImageIndex,
Enabled)
else
begin
{ Draw a menu check }
Glyph := TBitmap.Create;
try
Glyph.Transparent := True;
Glyph.Handle := LoadBitmap(0, PChar(OBM_CHECK));
OldBrushColor := Font.Color;
Font.Color := clBtnText;
Draw(GlyphRect.Left + (GlyphRect.Right - GlyphRect.Left - Glyph.Width) div 2 + 1,
GlyphRect.Top + (GlyphRect.Bottom - GlyphRect.Top - Glyph.Height) div 2 + 1, Glyph);
Font.Color := OldBrushColor;
finally
Glyph.Free;
end;
end;
end
else
begin
SaveRect := GlyphRect;
{ Make sure image is within glyph bounds }
if FBitmap.Width < GlyphRect.Right - GlyphRect.Left then
with GlyphRect do
begin
Left := Left + ((Right - Left) - FBitmap.Width) div 2 + 1;
Right := Left + FBitmap.Width;
end;
if FBitmap.Height < GlyphRect.Bottom - GlyphRect.Top then
with GlyphRect do
begin
Top := Top + ((Bottom - Top) - FBitmap.Height) div 2 + 1;
Bottom := Top + FBitmap.Height;
end;
StretchDraw(GlyphRect, FBitmap);
GlyphRect := SaveRect;
end;
if Checked then
begin
Dec(GlyphRect.Right);
Dec(GlyphRect.Bottom);
end;
end
else
begin
if (ImageList <> nil) and not TopLevel then
begin
GlyphRect.Right := GlyphRect.Left + ImageList.Width;
GlyphRect.Bottom := GlyphRect.Top + ImageList.Height;
end
else
begin
GlyphRect.Right := GlyphRect.Left;
GlyphRect.Bottom := GlyphRect.Top;
end;
DrawGlyph := False;
end;
end;
with GlyphRect do
begin
Dec(Left);
Dec(Top);
Inc(Right, 2);
Inc(Bottom, 2);
end;
if Checked or Selected and DrawGlyph then
DrawEdge(Handle, GlyphRect, EdgeStyle[Checked], BF_RECT);
if Selected then
begin
if DrawGlyph then ARect.Left := GlyphRect.Right + 1;
if not (Win98Plus and TopLevel) then
Brush.Color := clHighlight;
FillRect(ARect);
end;
if TopLevel and Win98Plus then
begin
if Selected then
DrawEdge(Handle, ARect, BDR_SUNKENOUTER, BF_RECT)
else if odHotLight in State then
DrawEdge(Handle, ARect, BDR_RAISEDINNER, BF_RECT);
if not Selected then
OffsetRect(ARect, 0, -1);
end;
if not (Selected and DrawGlyph) then
ARect.Left := GlyphRect.Right + 1;
Inc(ARect.Left, 2);
Dec(ARect.Right, 1);
DrawStyle := DT_EXPANDTABS or DT_SINGLELINE or Alignments[Alignment];
if Win2K and (odNoAccel in State) then
DrawStyle := DrawStyle or DT_HIDEPREFIX;
{ Calculate vertical layout }
SaveRect := ARect;
if odDefault in State then
Font.Style := [fsBold];
DoDrawText(ACanvas, Caption, ARect, Selected, DrawStyle or DT_CALCRECT or DT_NOCLIP);
OffsetRect(ARect, 0, ((SaveRect.Bottom - SaveRect.Top) - (ARect.Bottom - ARect.Top)) div 2);
if TopLevel and Selected and Win98Plus then
OffsetRect(ARect, 1, 0);
DoDrawText(ACanvas, Caption, ARect, Selected, DrawStyle);
if (ShortCut <> 0) and not TopLevel then
begin
ARect.Left := ARect.Right;
ARect.Right := SaveRect.Right - 10;
DoDrawText(ACanvas, ShortCutToText(ShortCut), ARect, Selected, DT_RIGHT);
end;
end;
end;
procedure BiDiDraw;
var
S: string;
begin
with ACanvas do
begin
//ImageList := GetImageList;
if not Selected then FillRect(ARect);
if ParentMenu is TMenu then
Alignment := paLeft
else if ParentMenu is TPopupMenu then
Alignment := TPopupMenu(ParentMenu). Alignment
else
Alignment := paLeft;
GlyphRect.Right := ARect.Right - 1;
GlyphRect.Top := ARect.Top + 1;
if Caption = cLineCaption then
begin
FillRect(ARect);
GlyphRect.Left := GlyphRect.Right + 2;
GlyphRect.Right := 0;
DrawGlyph := False;
end
else
begin
DrawImage := (ImageList <> nil) and ((ImageIndex > -1) and
(ImageIndex < ImageList. Count) or Checked and ((FBitmap = nil) or
FBitmap. Empty));
if DrawImage or Assigned(FBitmap) and not FBitmap. Empty then
begin
DrawGlyph := True;
if DrawImage then
begin
GlyphRect.Left := GlyphRect.Right - ImageList.Width;
GlyphRect.Bottom := GlyphRect.Top + ImageList.Height;
end
else
begin
{ Need to add BitmapWidth/Height properties for TMenuItem if we're to
support them. Right now let's hardcode them to 16x16. }
GlyphRect.Left := GlyphRect.Right - 16;
GlyphRect.Bottom := GlyphRect.Top + 16;
end;
{ Draw background pattern brush if selected }
if Checked then
begin
Dec(GlyphRect.Left);
Inc(GlyphRect.Bottom);
OldBrushColor := Brush.Color;
if not Selected then
begin
OldBrushColor := Brush.Color;
Brush.Bitmap := AllocPatternBitmap(clBtnFace, clBtnHighlight);
FillRect(GlyphRect);
end
else
begin
Brush.Color := clBtnFace;
FillRect(GlyphRect);
end;
Brush.Color := OldBrushColor;
Dec(GlyphRect.Right);
Inc(GlyphRect.Top);
end;
if DrawImage then
begin
if (ImageIndex > -1) and (ImageIndex < ImageList.Count) then
ImageList.Draw(ACanvas, GlyphRect.Left, GlyphRect.Top, ImageIndex,
Enabled)
else
begin
{ Draw a menu check }
Glyph := TBitmap.Create;
try
Glyph.Transparent := True;
Glyph.Handle := LoadBitmap(0, PChar(OBM_CHECK));
OldBrushColor := Font.Color;
Font.Color := clBtnText;
Draw(GlyphRect.Left + (GlyphRect.Right - GlyphRect.Left - Glyph.Width) div 2 + 1,
GlyphRect.Top + (GlyphRect.Bottom - GlyphRect.Top - Glyph.Height) div 2 + 1, Glyph);
Font.Color := OldBrushColor;
finally
Glyph.Free;
end;
end;
end
else
begin
SaveRect := GlyphRect;
{ Make sure image is within glyph bounds }
if FBitmap.Width < GlyphRect.Right - GlyphRect.Left then
with GlyphRect do
begin
Right := Right - ((Right - Left) - FBitmap.Width) div 2 + 1;
Left := Right - FBitmap.Width;
end;
if FBitmap.Height < GlyphRect.Bottom - GlyphRect.Top then
with GlyphRect do
begin
Top := Top + ((Bottom - Top) - FBitmap.Height) div 2 + 1;
Bottom := Top + FBitmap.Height;
end;
StretchDraw(GlyphRect, FBitmap);
GlyphRect := SaveRect;
end;
if Checked then
begin
Dec(GlyphRect.Right);
Dec(GlyphRect.Bottom);
end;
end
else
begin
if (ImageList <> nil) and not TopLevel then
begin
GlyphRect.Left := GlyphRect.Right - ImageList.Width;
GlyphRect.Bottom := GlyphRect.Top + ImageList.Height;
end
else
begin
GlyphRect.Left := GlyphRect.Right;
GlyphRect.Bottom := GlyphRect.Top;
end;
DrawGlyph := False;
end;
end;
with GlyphRect do
begin
Dec(Left);
Dec(Top);
Inc(Right, 2);
Inc(Bottom, 2);
end;
if Checked or Selected and DrawGlyph then
DrawEdge(Handle, GlyphRect, EdgeStyle[Checked], BF_RECT);
if Selected then
begin
if DrawGlyph then ARect.Right := GlyphRect.Left - 1;
if not (Win98Plus and TopLevel) then
Brush.Color := clHighlight;
FillRect(ARect);
end;
if TopLevel and Win98Plus then
begin
if Selected then
DrawEdge(Handle, ARect, BDR_SUNKENOUTER, BF_RECT)
else if odHotLight in State then
DrawEdge(Handle, ARect, BDR_RAISEDINNER, BF_RECT);
if not Selected then
OffsetRect(ARect, 0, -1);
end;
if not (Selected and DrawGlyph) then
ARect.Right := GlyphRect.Left - 1;
Inc(ARect.Left, 2);
Dec(ARect.Right, 1);
DrawStyle := DT_EXPANDTABS or DT_SINGLELINE or Alignments[Alignment];
if Win2K and (odNoAccel in State) then
DrawStyle := DrawStyle or DT_HIDEPREFIX;
{ Calculate vertical layout }
SaveRect := ARect;
if odDefault in State then
Font.Style := [fsBold];
DoDrawText(ACanvas, Caption, ARect, Selected, DrawStyle or DT_CALCRECT or DT_NOCLIP);
{ the DT_CALCRECT does not take into account alignment }
ARect.Left := SaveRect.Left;
ARect.Right := SaveRect.Right;
OffsetRect(ARect, 0, ((SaveRect.Bottom - SaveRect.Top) - (ARect.Bottom - ARect.Top)) div 2);
if TopLevel and Selected and Win98Plus then
OffsetRect(ARect, 1, 0);
DoDrawText(ACanvas, Caption, ARect, Selected, DrawStyle);
if (ShortCut <> 0) and not TopLevel then
begin
S := ShortCutToText(ShortCut);
ARect.Left := 10;
ARect.Right := ARect.Left + ACanvas.TextWidth(S);
DoDrawText(ACanvas, S, ARect, Selected, DT_RIGHT);
end;
end;
end;
begin
ParentMenu := GetParentMenu;
ImageList := GetImageList;
Selected := odSelected in State;
Win98Plus := (Win32MajorVersion > 4) or
((Win32MajorVersion = 4) and (Win32MinorVersion > 0));
Win2K := (Win32MajorVersion > 4) and (Win32Platform = VER_PLATFORM_WIN32_NT);
if (ParentMenu <> nil) and (ParentMenu.OwnerDraw or (ImageList <> nil)) and
(Assigned(FOnAdvancedDrawItem) or Assigned(FOnDrawItem)) then
begin
DrawItem(ACanvas, ARect, Selected);
if Assigned(FOnAdvancedDrawItem) then
FOnAdvancedDrawItem(Self, ACanvas, ARect, State);
end else
if (ParentMenu <> nil) and (not ParentMenu.IsRightToLeft) then
NormalDraw
else
BiDiDraw;
end;
function TMenuItem.GetImageList: TCustomImageList;
var
vItem: TMenuItem;
vMenu: TMenu;
begin
Result := nil;
vItem := Parent;
while (vItem <> nil) and (vItem.SubMenuImages = nil) do
vItem := vItem.Parent;
if vItem <> nil then
Result := vItem.SubMenuImages
else
begin
vMenu := GetParentMenu;
if vMenu <> nil then
Result := vMenu.Images;
end;
end;
procedure TMenuItem.MeasureItem(ACanvas: TCanvas; var Width, Height: Integer);
const
Alignments: array[TPopupAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER);
var
Alignment: TPopupAlignment;
ImageList: TCustomImageList;
ParentMenu: TMenu;
DrawGlyph: Boolean;
TopLevel: Boolean;
DrawStyle: Integer;
Text: string;
R: TRect;
procedure GetMenuSize;
var
NonClientMetrics: TNonClientMetrics;
begin
NonClientMetrics.cbSize := sizeof(NonClientMetrics);
if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0) then
begin
Width := NonClientMetrics.iMenuWidth;
Height := NonClientMetrics.iMenuHeight;
end;
end;
begin
if GetParentComponent is TMainMenu then
begin
TopLevel := True;
GetMenuSize;
end
else TopLevel := False;
ParentMenu := GetParentMenu;
ImageList := GetImageList;
if Caption = cLineCaption then
begin
Height := 5;
Width := -2;
DrawGlyph := False;
end
else if Assigned(ImageList) and ((ImageIndex > -1) or not TopLevel) then
begin
Width := ImageList.Width;
if not TopLevel then
Height := ImageList.Height;
DrawGlyph := True;
end
else if Assigned(FBitmap) and not FBitmap.Empty then
begin
Width := 16;
if not TopLevel then
Height := 16;
DrawGlyph := True;
end
else
begin
Width := -7;
DrawGlyph := False;
end;
if DrawGlyph and not TopLevel then
Inc(Width, 15);
if not TopLevel then
Inc(Height, 3);
FillChar(R, SizeOf(R), 0);
if ParentMenu is TMenu then
Alignment := paLeft
else if ParentMenu is TPopupMenu then
Alignment := TPopupMenu(ParentMenu).Alignment
else
Alignment := paLeft;
if ShortCut <> 0 then
Text := Concat(Caption, ShortCutToText(ShortCut)) else
Text := Caption;
DrawStyle := Alignments[Alignment] or DT_EXPANDTABS or DT_SINGLELINE or
DT_NOCLIP or DT_CALCRECT;
DoDrawText(ACanvas, Text, R, False, DrawStyle);
Inc(Width, R.Right - R.Left + 7);
if Assigned(FOnMeasureItem) then FOnMeasureItem(Self, ACanvas, Width, Height);
end;
function TMenuItem.HasParent: Boolean;
begin
Result := True;
end;
procedure TMenuItem.SetBreak(Value: TMenuBreak);
begin
if FBreak <> Value then
begin
FBreak := Value;
MenuChanged(True);
end;
end;
procedure TMenuItem.SetCaption(const Value: string);
begin
if FCaption <> Value then
begin
FCaption := Value;
MenuChanged(True);
end;
end;
procedure TMenuItem.TurnSiblingsOff;
var
I: Integer;
Item: TMenuItem;
begin
if FParent <> nil then
for I := 0 to FParent.Count - 1 do
begin
Item := FParent[I];
if (Item <> Self) and Item.FRadioItem and (Item.GroupIndex = GroupIndex) then
Item.SetChecked(False);
end;
end;
procedure TMenuItem.SetChecked(Value: Boolean);
begin
if FChecked <> Value then
begin
FChecked := Value;
if (FParent <> nil) and not (csReading in ComponentState) then
CheckMenuItem(FParent.Handle, FCommand, MF_BYCOMMAND or Checks[Value]);
if Value and FRadioItem then
TurnSiblingsOff;
end;
end;
procedure TMenuItem.SetEnabled(Value: Boolean);
begin
if FEnabled <> Value then
begin
FEnabled := Value;
if ((Win32Platform = VER_PLATFORM_WIN32_NT) and (Count <> 0)) or
((Parent <> nil) and Assigned(Parent.FMergedWith)) then
MenuChanged(True)
else
begin
if (FParent <> nil) and not (csReading in ComponentState) then
EnableMenuItem(FParent.Handle, FCommand, MF_BYCOMMAND or Enables[Value]);
MenuChanged(False);
end;
end;
end;
procedure TMenuItem.SetGroupIndex(Value: Byte);
begin
if FGroupIndex <> Value then
begin
if Parent <> nil then Parent.VerifyGroupIndex(Parent.IndexOf(Self), Value);
FGroupIndex := Value;
if FChecked and FRadioItem then
TurnSiblingsOff;
end;
end;
function TMenuItem.GetAction: TBasicAction;
begin
if FActionLink <> nil then
Result := FActionLink.Action else
Result := nil;
end;
function TMenuItem.GetActionLinkClass: TMenuActionLinkClass;
begin
Result := TMenuActionLink;
end;
function TMenuItem.GetCount: Integer;
begin
if FItems = nil then Result := 0
else Result := FItems.Count;
end;
function TMenuItem.GetItem(Index: Integer): TMenuItem;
begin
if FItems = nil then Error(@SMenuIndexError);
Result := FItems[Index];
end;
procedure TMenuItem.SetShortCut(Value: TShortCut);
begin
if FShortCut <> Value then
begin
FShortCut := Value;
MenuChanged(True);
end;
end;
procedure TMenuItem.SetVisible(Value: Boolean);
begin
if Value <> FVisible then
begin
FVisible := Value;
MenuChanged(True);
end;
end;
procedure TMenuItem.SetImageIndex(Value: TImageIndex);
begin
if Value <> FImageIndex then
begin
FImageIndex := Value;
MenuChanged(True);
end;
end;
function TMenuItem.GetMenuIndex: Integer;
begin
Result := -1;
if FParent <> nil then Result := FParent.IndexOf(Self);
end;
procedure TMenuItem.SetMenuIndex(Value: Integer);
var
Parent: TMenuItem;
Count: Integer;
begin
if FParent <> nil then
begin
Count := FParent.Count;
if Value < 0 then Value := 0;
if Value >= Count then Value := Count - 1;
if Value <> MenuIndex then
begin
Parent := FParent;
Parent.Remove(Self);
Parent.Insert(Value, Self);
end;
end;
end;
procedure TMenuItem.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
I: Integer;
begin
for I := 0 to Count - 1 do Proc(Items[I]);
end;
procedure TMenuItem.SetChildOrder(Child: TComponent; Order: Integer);
begin
(Child as TMenuItem).MenuIndex := Order;
end;
procedure TMenuItem.SetDefault(Value: Boolean);
var
I: Integer;
begin
if FDefault <> Value then
begin
if Value and (FParent <> nil) then
for I := 0 to FParent.Count - 1 do
if FParent[I].Default then FParent[I].FDefault := False;
FDefault := Value;
MenuChanged(True);
end;
end;
procedure TMenuItem.InitiateAction;
begin
if FActionLink <> nil then FActionLink.Update;
end;
procedure TMenuItem.Insert(Index: Integer; Item: TMenuItem);
begin
if Item.FParent <> nil then Error(@SMenuReinserted);
if FItems = nil then FItems := TList.Create;
if (Index - 1 >= 0) and (Index - 1 < FItems.Count) then
if Item.GroupIndex < TMenuItem(FItems[Index - 1]).GroupIndex then
Item.GroupIndex := TMenuItem(FItems[Index - 1]).GroupIndex;
VerifyGroupIndex(Index, Item.GroupIndex);
FItems.Insert(Index, Item);
Item.FParent := Self;
Item.FOnChange := SubItemChanged;
if FHandle <> 0 then RebuildHandle;
MenuChanged(Count = 1);
end;
procedure TMenuItem.Delete(Index: Integer);
var
Cur: TMenuItem;
begin
if (Index < 0) or (FItems = nil) or (Index >= GetCount) then Error(@SMenuIndexError);
Cur := FItems[Index];
FItems.Delete(Index);
Cur.FParent := nil;
Cur.FOnChange := nil;
if FHandle <> 0 then RebuildHandle;
MenuChanged(Count = 0);
end;
procedure TMenuItem.Click;
begin
if Enabled then
begin
{ 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 (@FOnClick <> @Action.OnExecute) then
FOnClick(Self)
else if not (csDesigning in ComponentState) and (ActionLink <> nil) then
FActionLink.Execute
else if Assigned(FOnClick) then
FOnClick(Self);
end;
end;
function TMenuItem.IndexOf(Item: TMenuItem): Integer;
begin
Result := -1;
if FItems <> nil then Result := FItems.IndexOf(Item);
end;
procedure TMenuItem.Add(Item: TMenuItem);
begin
Insert(GetCount, Item);
end;
procedure TMenuItem.Remove(Item: TMenuItem);
var
I: Integer;
begin
I := IndexOf(Item);
if I = -1 then Error(@SMenuNotFound);
Delete(I);
end;
procedure TMenuItem.MenuChanged(Rebuild: Boolean);
var
Source: TMenuItem;
begin
if (Parent = nil) and (Owner is TMenu) then
Source := nil else
Source := Self;
if Assigned(FOnChange) then FOnChange(Self, Source, Rebuild);
end;
procedure TMenuItem.SubItemChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
begin
if Rebuild and ((FHandle <> 0) or Assigned(FMergedWith)) then RebuildHandle;
if Parent <> nil then Parent.SubItemChanged(Self, Source, False)
else if Owner is TMainMenu then TMainMenu(Owner).ItemChanged;
end;
function TMenuItem.GetBitmap: TBitmap;
begin
if FBitmap = nil then FBitmap := TBitmap.Create;
FBitmap.Transparent := True;
Result := FBitmap;
end;
procedure TMenuItem.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;
ActionChange(Value, csLoading in Value.ComponentState);
Value.FreeNotification(Self);
end;
end;
procedure TMenuItem.SetBitmap(Value: TBitmap);
begin
if FBitmap = nil then FBitmap := TBitmap.Create;
FBitmap.Assign(Value);
MenuChanged(True);
end;
procedure TMenuItem.InitiateActions;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Items[I].InitiateAction;
end;
function TMenuItem.GetParentComponent: TComponent;
begin
if (FParent <> nil) and (FParent.FMenu <> nil) then
Result := FParent.FMenu else
Result := FParent;
end;
procedure TMenuItem.SetParentComponent(Value: TComponent);
begin
if FParent <> nil then FParent.Remove(Self);
if Value <> nil then
if Value is TMenu then
TMenu(Value).Items.Add(Self)
else if Value is TMenuItem then
TMenuItem(Value).Add(Self);
end;
function TMenuItem.GetParentMenu: TMenu;
var
MenuItem: TMenuItem;
begin
MenuItem := Self;
while Assigned(MenuItem.FParent) do MenuItem := MenuItem.FParent;
Result := MenuItem.FMenu
end;
procedure TMenuItem.SetRadioItem(Value: Boolean);
begin
if FRadioItem <> Value then
begin
FRadioItem := Value;
if FChecked and FRadioItem then
TurnSiblingsOff;
MenuChanged(True);
end;
end;
procedure TMenuItem.ActionChange(Sender: TObject; CheckDefaults: Boolean);
begin
if Action is TCustomAction then
with TCustomAction(Sender) do
begin
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 TMenuItem.DoActionChange(Sender: TObject);
begin
if Sender = Action then ActionChange(Sender, False);
end;
function TMenuItem.IsCaptionStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsCaptionLinked;
end;
function TMenuItem.IsCheckedStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsCheckedLinked;
end;
function TMenuItem.IsEnabledStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsEnabledLinked;
end;
function TMenuItem.IsHintStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsHintLinked;
end;
function TMenuItem.IsHelpContextStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsHelpContextLinked;
end;
function TMenuItem.IsImageIndexStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsImageIndexLinked;
end;
function TMenuItem.IsShortCutStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsShortCutLinked;
end;
function TMenuItem.IsVisibleStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsVisibleLinked;
end;
function TMenuItem.IsOnClickStored: Boolean;
begin
Result := (ActionLink = nil) or not FActionLink.IsOnExecuteLinked;
end;
procedure TMenuItem.AssignTo(Dest: TPersistent);
begin
if Dest is TCustomAction then
with TCustomAction(Dest) do
begin
Enabled := Self.Enabled;
HelpContext := Self.HelpContext;
Hint := Self.Hint;
ImageIndex := Self.ImageIndex;
Caption := Self.Caption;
Visible := Self.Visible;
OnExecute := Self.OnClick;
end
else inherited AssignTo(Dest);
end;
procedure TMenuItem.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = Action) then Action := nil;
end;
procedure TMenuItem.SetSubMenuImages(Value: TCustomImageList);
begin
if FSubMenuImages <> nil then FSubMenuImages.UnRegisterChanges(FImageChangeLink);
FSubMenuImages := Value;
if FSubMenuImages <> nil then
begin
FSubMenuImages.RegisterChanges(FImageChangeLink);
FSubMenuImages.FreeNotification(Self);
end;
UpdateItems;
end;
procedure TMenuItem.ImageListChange(Sender: TObject);
begin
if Sender = SubMenuImages then UpdateItems;
end;
procedure TMenuItem.UpdateItems;
function UpdateItem(MenuItem: TMenuItem): Boolean;
begin
Result := False;
IterateMenus(@UpdateItem, MenuItem.FMerged, MenuItem);
MenuItem.SubItemChanged(MenuItem, MenuItem, True);
end;
begin
IterateMenus(@UpdateItem, FMerged, Self);
end;
procedure TMenuItem.Add(const AItems: array of TMenuItem);
var
I: Integer;
begin
for I := Low(AItems) to High(AItems) do
Add(AItems[I]);
end;
procedure TMenuItem.Clear;
var
I: Integer;
begin
for I := Count - 1 downto 0 do
Items[I].Free;
end;
function TMenuItem.InternalRethinkHotkeys(ForceRethink: Boolean): Boolean;
var
vDid, vDoing, vToDo, vBest: TStringList;
I, vIteration, vColumn, vAt, vBestCount: Integer;
vChar, vCaption, vOrigAvailable, vAvailable, vBestAvailable: string;
function IfHotkeyAvailable(const AHotkey: string): Boolean;
var
At: Integer;
begin
At := AnsiPos(AHotkey, vAvailable);
Result := At <> 0;
if Result then
System.Delete(vAvailable, At, 1);
end;
procedure CopyToBest;
var
I: Integer;
begin
vBest.Assign(vDid);
vBestCount := vDid.Count;
for I := 0 to vDoing.Count - 1 do
vBest.AddObject(TMenuItem(vDoing.Objects[I]).FCaption, vDoing.Objects[I]);
vBestAvailable := vAvailable;
end;
procedure InsertHotkeyFarEastFormat(var ACaption: string; const AHotKey: string; AColumn: Integer);
var
I: Integer;
vMBCSFlag: Boolean;
begin
vMBCSFlag := False;
for I := 1 to Length(ACaption) do
if ACaption[I] in LeadBytes then
begin
vMBCSFlag := True;
System.Break;
end;
if vMBCSFlag then
begin
if Copy(ACaption, (Length(ACaption) - Length(cDialogSuffix)) + 1, Length(cDialogSuffix)) = cDialogSuffix then
ACaption := Copy(ACaption, 1, Length(ACaption) - Length(cDialogSuffix)) +
'(' + cHotkeyPrefix + AHotKey + ')' + cDialogSuffix
else
ACaption := ACaption + '(' + cHotkeyPrefix + AHotKey + ')';
end
else if AColumn <> 0 then
System.Insert(cHotkeyPrefix, ACaption, AColumn);
end;
begin
Result := False;
if ForceRethink or
(not (csDesigning in ComponentState) and GetAutoHotkeys) then
begin
vAvailable := ValidMenuHotkeys;
vDid := TStringList.Create;
vDoing := TStringList.Create;
vToDo := TStringList.Create;
vBest := TStringList.Create;
vBestCount := 0;
try
for I := 0 to Count - 1 do
if Items[I].Visible and
(Items[I].FCaption <> cLineCaption) and
(Items[I].FCaption <> '') then
begin
vChar := Uppercase(GetHotkey(Items[I].FCaption));
if vChar = '' then
vToDo.InsertObject(0, Items[I].FCaption, Items[I])
else if (AnsiPos(vChar, ValidMenuHotkeys) <> 0) and
not IfHotkeyAvailable(vChar) then
begin
Items[I].FCaption := StripHotkey(Items[I].FCaption);
vToDo.InsertObject(0, Items[I].FCaption, Items[I]);
end;
end;
vOrigAvailable := vAvailable;
for vIteration := 0 to vToDo.Count - 1 do
begin
vAvailable := vOrigAvailable;
vDoing.Assign(vToDo);
vDid.Clear;
for I := vDoing.Count - 1 downto 0 do
begin
vCaption := vDoing[I];
vColumn := 1;
while vColumn <= Length(vCaption) do
begin
if vCaption[vColumn] in LeadBytes then
Inc(vColumn)
else
begin
vChar := Uppercase(Copy(vCaption, vColumn, 1));
if IfHotkeyAvailable(vChar) then
begin
if SysLocale.FarEast then
InsertHotkeyFarEastFormat(vCaption, vChar, vColumn)
else
System.Insert(cHotkeyPrefix, vCaption, vColumn);
vDid.AddObject(vCaption, vDoing.Objects[I]);
vDoing.Delete(I);
System.Break;
end;
end;
Inc(vColumn);
end;
end;
if vDid.Count > vBestCount then
CopyToBest;
if vDoing.Count > 0 then
for I := 0 to vDoing.Count - 1 do
begin
vAt := vToDo.IndexOfObject(vDoing.Objects[I]);
vToDo.Move(vAt, vToDo.Count - 1);
end
else
System.Break;
end;
if vBestCount = 0 then
CopyToBest;
Result := vBest.Count > 0;
for I := 0 to vBest.Count - 1 do
begin
vCaption := vBest[I];
if SysLocale.FarEast and (AnsiPos(cHotkeyPrefix, vCaption) = 0)
and (vBestAvailable <> '') then
begin
if AnsiPos(cHotkeyPrefix, vCaption) = 0 then
begin
InsertHotkeyFarEastFormat(vCaption, Copy(vBestAvailable, Length(vBestAvailable), 1), 0);
System.Delete(vBestAvailable, length(vBestAvailable), 1);
end;
end;
TMenuItem(vBest.Objects[I]).FCaption := vCaption;
end;
finally
vBest.Free;
vToDo.Free;
vDoing.Free;
vDid.Free;
end;
end;
end;
function TMenuItem.RethinkHotkeys: Boolean;
begin
Result := InternalRethinkHotkeys(True);
if Result then
MenuChanged(True);
end;
procedure TMenuItem.SetAutoHotkeys(const Value: TMenuItemAutoFlag);
begin
if Value <> FAutoHotkeys then
begin
FAutoHotkeys := Value;
MenuChanged(True);
end;
end;
function TMenuItem.IsLine: Boolean;
begin
Result := FCaption = cLineCaption;
end;
function TMenuItem.Find(ACaption: string): TMenuItem;
var
I: Integer;
begin
Result := nil;
ACaption := StripHotkey(ACaption);
for I := 0 to Count - 1 do
if AnsiSameText(ACaption, StripHotkey(Items[I].Caption)) then
begin
Result := Items[I];
System.Break;
end;
end;
function TMenuItem.InsertNewLine(ABefore: Boolean; AItem: TMenuItem): Integer;
begin
if AItem.Parent <> Self then
Error(@SMenuNotFound);
if ABefore then
begin
if (AItem.MenuIndex > 0) and
Items[AItem.MenuIndex - 1].IsLine then
begin
Result := AItem.MenuIndex - 1;
Items[AItem.MenuIndex - 1].Visible := True;
end
else
begin
Result := AItem.MenuIndex;
Insert(AItem.MenuIndex, NewLine);
end;
end
else
begin
if (AItem.MenuIndex < Count - 1) and
Items[AItem.MenuIndex + 1].IsLine then
begin
Result := AItem.MenuIndex + 2;
Items[AItem.MenuIndex + 1].Visible := True;
end
else
begin
Result := AItem.MenuIndex + 2;
Insert(AItem.MenuIndex + 1, NewLine);
end;
end;
end;
function TMenuItem.InsertNewLineAfter(AItem: TMenuItem): Integer;
begin
Result := InsertNewLine(False, AItem);
end;
function TMenuItem.InsertNewLineBefore(AItem: TMenuItem): Integer;
begin
Result := InsertNewLine(True, AItem);
end;
function TMenuItem.NewBottomLine: Integer;
begin
Result := 0;
if Count = 0 then
Add(NewLine)
else
Result := InsertNewLine(False, Items[Count - 1]);
end;
function TMenuItem.NewTopLine: Integer;
begin
Result := 0;
if Count = 0 then
Add(NewLine)
else
Result := InsertNewLine(True, Items[0]);
end;
function TMenuItem.InternalRethinkLines(ForceRethink: Boolean): Boolean;
var
I, vLastAt: Integer;
vLastBar: TMenuItem;
begin
Result := False;
if ForceRethink or
(not (csDesigning in ComponentState) and GetAutoLineReduction) then
begin
vLastAt := 0;
vLastBar := nil;
for I := vLastAt to Count - 1 do
if Items[I].FVisible then
if Items[I].IsLine then
begin
Items[I].FVisible := False;
Result := True;
end
else
begin
vLastAt := I;
System.Break;
end;
for I := vLastAt to Count - 1 do
if Items[I].IsLine then
begin
if vLastBar <> nil then
begin
vLastBar.FVisible := False;
Result := True;
end;
vLastBar := Items[I];
end
else if Items[I].FVisible then
begin
if vLastBar <> nil then
begin
vLastBar.FVisible := True;
Result := True;
end;
vLastBar := nil;
vLastAt := I;
end;
for I := Count - 1 downto vLastAt do
if Items[I].FVisible then
if Items[I].IsLine then
begin
Items[I].FVisible := False;
Result := True;
end
else
System.Break;
end;
end;
procedure TMenuItem.SetAutoLineReduction(const Value: TMenuItemAutoFlag);
begin
if Value <> FAutoLineReduction then
begin
FAutoLineReduction := Value;
MenuChanged(True);
end;
end;
function TMenuItem.RethinkLines: Boolean;
begin
Result := InternalRethinkLines(True);
if Result then
MenuChanged(True);
end;
function TMenuItem.GetAutoHotkeys: Boolean;
var
vAuto: TMenuItemAutoFlag;
begin
vAuto := FAutoHotkeys;
if (vAuto = maParent) and
(Parent <> nil) then
vAuto := cBooleanToItemAutoFlag[Parent.GetAutoHotkeys];
Result := cItemAutoFlagToBoolean[vAuto];
end;
function TMenuItem.GetAutoLineReduction: Boolean;
var
vAuto: TMenuItemAutoFlag;
begin
vAuto := FAutoLineReduction;
if (vAuto = maParent) and
(Parent <> nil) then
vAuto := cBooleanToItemAutoFlag[Parent.GetAutoLineReduction];
Result := cItemAutoFlagToBoolean[vAuto];
end;
{ TMenu }
constructor TMenu.Create(AOwner: TComponent);
begin
FItems := TMenuItem.Create(Self);
FItems.FOnChange := MenuChanged;
FItems.FMenu := Self;
FImageChangeLink := TChangeLink.Create;
FImageChangeLink.OnChange := ImageListChange;
FParentBiDiMode := True;
inherited Create(AOwner);
FItems.FAutoHotkeys := maAutomatic;
FItems.FAutoLineReduction := maAutomatic;
ParentBiDiModeChanged;
end;
destructor TMenu.Destroy;
begin
FItems.Free;
FImageChangeLink.Free;
inherited Destroy;
end;
procedure TMenu.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
FItems.GetChildren(Proc, Root);
end;
function TMenu.GetHandle: HMENU;
begin
Result := FItems.GetHandle;
end;
procedure TMenu.SetChildOrder(Child: TComponent; Order: Integer);
begin
FItems.SetChildOrder(Child, Order);
end;
procedure TMenu.UpdateItems;
function UpdateItem(MenuItem: TMenuItem): Boolean;
begin
Result := False;
IterateMenus(@UpdateItem, MenuItem.FMerged, MenuItem);
MenuItem.SubItemChanged(MenuItem, MenuItem, True);
end;
begin
IterateMenus(@UpdateItem, Items.FMerged, Items);
end;
function TMenu.FindItem(Value: Integer; Kind: TFindItemKind): TMenuItem;
var
FoundItem: TMenuItem;
function Find(Item: TMenuItem): Boolean;
var
I: Integer;
begin
Result := False;
if ((Kind = fkCommand) and (Value = Item.Command)) or
((Kind = fkHandle) and (Value = Integer(Item.FHandle))) or
((Kind = fkShortCut) and (Value = Item.ShortCut)) then
begin
FoundItem := Item;
Result := True;
Exit;
end
else
for I := 0 to Item.GetCount - 1 do
if Find(Item[I]) then
begin
Result := True;
Exit;
end;
end;
begin
FoundItem := nil;
IterateMenus(@Find, Items.FMerged, Items);
Result := FoundItem;
end;
function TMenu.GetHelpContext(Value: Integer; ByCommand: Boolean): THelpContext;
var
Item: TMenuItem;
Kind: TFindItemKind;
begin
Result := 0;
Kind := fkHandle;
if ByCommand then Kind := fkCommand;
if (Kind = fkHandle) and (Self is TPopupMenu) and
(Integer(TPopupMenu(Self).Handle) = Value) then
Result := TPopupMenu(Self).HelpContext
else
begin
Item := FindItem(Value, Kind);
while (Item <> nil) and (Item.FHelpContext = 0) do
Item := Item.FParent;
if Item <> nil then Result := Item.FHelpContext;
end;
end;
function TMenu.DispatchCommand(ACommand: Word): Boolean;
var
Item: TMenuItem;
begin
Result := False;
Item := FindItem(ACommand, fkCommand);
if Item <> nil then
begin
Item.Click;
Result := True;
end;
end;
function TMenu.DispatchPopup(AHandle: HMENU): Boolean;
var
Item: TMenuItem;
begin
Result := False;
Item := FindItem(AHandle, fkHandle);
if Item <> nil then
begin
if not (csDesigning in Item.ComponentState) then Item.InitiateActions;
Item.Click;
if Item.InternalRethinkHotkeys(False) or
Item.InternalRethinkLines(False) then
Item.RebuildHandle;
Result := True;
end
else if not (csDesigning in ComponentState) and (Self is TPopupMenu) then
Items.InitiateActions;
end;
function TMenu.IsOwnerDraw: Boolean;
begin
Result := OwnerDraw or (Images <> nil);
end;
function TMenu.IsShortCut(var Message: TWMKey): Boolean;
type
TClickResult = (crDisabled, crClicked, crShortCutMoved, crShortCutFreed);
const
AltMask = $20000000;
var
ShortCut: TShortCut;
ClickResult: TClickResult;
ShortCutItem: TMenuItem;
function NthParentOf(Item: TMenuItem; N: Integer): TMenuItem;
begin
Result := Item;
while (N > 0) and (Result <> nil) do
begin
Result := Result.Parent;
Dec(N);
end;
end;
function DoClick(var Item: TMenuItem; Level: Integer): TClickResult;
var
ItemParent: TMenuItem;
begin
Result := crClicked;
ItemParent := Item.Parent;
// Assert(Item = NthParentOf(ShortCutItem, Level));
if ItemParent <> nil then Result := DoClick(ItemParent, Level + 1);
if Result in [crDisabled, crShortCutFreed] then Exit;
if Result = crShortCutMoved then
begin
// Shortcut moved, we need to refind the shortcut and restore Item
// to point to the parent at the right level, if possible
if (ShortCutItem = nil) or (ShortCutItem.ShortCut <> ShortCut) then
begin
ShortCutItem := FindItem(ShortCut, fkShortCut);
if ShortCutItem = nil then
begin
Result := crShortCutFreed;
Exit; // Shortcut item could not be found
end;
end;
Item := NthParentOf(ShortCutItem, Level);
if (Item = nil) or (Item.Parent <> ItemParent) then
Exit; // Shortcut moved in structure, level not correct
if Level = 0 then Result := crClicked;
end;
if Item.Enabled then
try
if not (csDesigning in ComponentState) then Item.InitiateActions;
Item.Click;
if (ShortCutItem = nil) or
((Item <> ShortCutItem) and (ShortCutItem.ShortCut <> ShortCut)) then
Result := crShortCutMoved;
except
Application.HandleException(Self);
end
else Result := crDisabled;
end;
begin
//! Moved checking FWindowHandle to TWinControl and TForm. This way we can
//! call this method on menus which aren't necessarily allocated. More
//! specifically, we can make toolbar menus much more dynamic. [rbr]
//! Result := False;
//! if FWindowHandle <> 0 then
begin
ShortCut := Byte(Message.CharCode);
if GetKeyState(VK_SHIFT) < 0 then Inc(ShortCut, scShift);
if GetKeyState(VK_CONTROL) < 0 then Inc(ShortCut, scCtrl);
if Message.KeyData and AltMask <> 0 then Inc(ShortCut, scAlt);
repeat
ClickResult := crDisabled;
ShortCutItem := FindItem(ShortCut, fkShortCut);
if ShortCutItem <> nil then
begin
ShortCutItems.Push(@ShortCutItem);
try
ClickResult := DoClick(ShortCutItem, 0);
finally
ShortCutItems.Pop;
end;
end;
until ClickResult <> crShortCutMoved;
Result := ShortCutItem <> nil;
end;
end;
function TMenu.IsBiDiModeStored: Boolean;
begin
Result := not FParentBiDiMode;
end;
procedure TMenu.DoBiDiModeChanged;
var
Menu: HMENU;
MenuItemInfo: TMenuItemInfo;
Buffer: array[0..79] of Char;
begin
if (not SysLocale.MiddleEast) or (WindowHandle = 0) then Exit;
Menu := GetHandle;
MenuItemInfo.cbSize := 44; // Required for Windows 95
MenuItemInfo.fMask := MIIM_TYPE;
MenuItemInfo.dwTypeData := Buffer;
MenuItemInfo.cch := SizeOf(Buffer);
if GetMenuItemInfo(Menu, 0, True, MenuItemInfo) then
begin
if LongBool(MenuItemInfo.fType and RightToLeftMenuFlag) = IsRightToLeft then
Exit; // Nothing to do
// clear and set the flag
MenuItemInfo.fType := (MenuItemInfo.fType and (not RightToLeftMenuFlag))
or (RightToLeftMenuFlag * DWORD(IsRightToLeft));
MenuItemInfo.fMask := MIIM_TYPE;
if SetMenuItemInfo(Menu, 0, True, MenuItemInfo) then
DrawMenuBar(WindowHandle);
end;
end;
function TMenu.UpdateImage: Boolean;
var
Image: array[0..511] of Char;
procedure BuildImage(Menu: HMENU);
var
P, ImageEnd: PChar;
I, C: Integer;
State: Word;
begin
C := GetMenuItemCount(Menu);
P := Image;
ImageEnd := @Image[SizeOf(Image) - 5];
I := 0;
while (I < C) and (P < ImageEnd) do
begin
DoGetMenuString(Menu, I, P, ImageEnd - P, MF_BYPOSITION);
P := StrEnd(P);
State := GetMenuState(Menu, I, MF_BYPOSITION);
if State and MF_DISABLED <> 0 then
begin
P^ := '$';
Inc(P);
P^ := #0;
end;
if State and MF_MENUBREAK <> 0 then
begin
P^ := '@';
Inc(P);
P^ := #0;
end;
if State and MF_GRAYED <> 0 then
begin
P^ := '#';
Inc(P);
P^ := #0;
end;
P^ := ';';
Inc(P);
P^ := #0;
Inc(I);
end;
end;
begin
Result := False;
Image[0] := #0;
if FWindowHandle <> 0 then BuildImage(Handle);
if (FMenuImage = '') or (StrComp(PChar(FMenuImage), Image) <> 0) then
begin
Result := True;
FMenuImage := Image;
end;
end;
procedure TMenu.SetOwnerDraw(Value: Boolean);
begin
if Value <> FOwnerDraw then
begin
FOwnerDraw := Value;
UpdateItems;
end;
end;
procedure TMenu.AdjustBiDiBehavior;
var
SaveBiDi: TBiDiMode;
SaveParentBiDi: Boolean;
begin
if not SysLocale.MiddleEast then Exit;
SaveBiDi := FBiDiMode;
SaveParentBiDi := FParentBidiMode;
try
if BiDiMode = bdLeftToRight then
BiDiMode := bdRightToLeft { Do not use FBiDiMode }
else
BiDiMode := bdLeftToRight; { Do not use FBiDiMode }
finally
BiDiMode := SaveBiDi; { Do not use FBiDiMode }
FParentBidiMode := SaveParentBiDi;
end;
end;
procedure TMenu.SetWindowHandle(Value: HWND);
begin
FWindowHandle := Value;
UpdateImage;
{ When menus are created, if BiDiMode does not follow the parent,
main menu headers are displayed in reversed order. Changing BiDiMode
twice fixes this. }
if (SysLocale.MiddleEast) and (Value <> 0) then
if FParentBiDiMode then
ParentBiDiModeChanged
else
AdjustBiDiBehavior;
end;
procedure TMenu.DoChange(Source: TMenuItem; Rebuild: Boolean);
begin
if Assigned(FOnChange) then FOnChange(Self, Source, Rebuild);
end;
procedure TMenu.Loaded;
begin
inherited Loaded;
DoChange(nil, False);
end;
procedure TMenu.MenuChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
begin
if ComponentState * [csLoading, csDestroying] = [] then DoChange(Source, Rebuild);
end;
procedure TMenu.ImageListChange(Sender: TObject);
begin
if Sender = Images then UpdateItems;
end;
procedure TMenu.SetImages(Value: TCustomImageList);
begin
if FImages <> nil then FImages.UnRegisterChanges(FImageChangeLink);
FImages := Value;
if FImages <> nil then
begin
FImages.RegisterChanges(FImageChangeLink);
FImages.FreeNotification(Self);
end;
UpdateItems;
end;
procedure TMenu.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (AComponent = Images) and (Operation = opRemove) then Images := nil;
end;
function TMenu.IsRightToLeft: Boolean;
begin
Result := SysLocale.MiddleEast and (BiDiMode <> bdLeftToRight);
end;
procedure TMenu.ProcessMenuChar(var Message: TWMMenuChar);
var
C, I, First, Hilite, Next: Integer;
State: Word;
function IsAccelChar(Menu: HMENU; State: Word; I: Integer; C: Char): Boolean;
var
Item: TMenuItem;
Id: UINT;
begin
Item := nil;
if State and MF_POPUP <> 0 then
begin
Menu := GetSubMenu(Menu, I);
Item := FindItem(Menu, fkHandle);
end
else
begin
Id := GetMenuItemID(Menu, I);
if Id <> $FFFFFFFF then
Item := FindItem(Id, fkCommand);
end;
if Item <> nil then
Result := IsAccel(Ord(C), Item.Caption) else
Result := False;
end;
function IsInitialChar(Menu: HMENU; State: Word; I: Integer; C: Char): Boolean;
var
Item: TMenuItem;
begin
if State and MF_POPUP <> 0 then
begin
Menu := GetSubMenu(Menu, I);
Item := FindItem(Menu, fkHandle);
end
else
begin
Item := FindItem(Menu, fkHandle);
if (Item <> nil) and (I < Item.Count) then
Item := Item.Items[I];
end;
// First char is a valid accelerator only if the caption does not
// contain an explicit accelerator
if (Item <> nil) and (Item.Caption <> '') then
Result := (AnsiCompareText(Item.Caption[1], C) = 0) and
(GetHotkey(Item.Caption) = '')
else
Result := False;
end;
begin
with Message do
begin
Result := MNC_IGNORE; { No item found: beep }
First := -1;
Hilite := -1;
Next := -1;
C := GetMenuItemCount(Menu);
for I := 0 to C - 1 do
begin
State := GetMenuState(Menu, I, MF_BYPOSITION);
if IsAccelChar(Menu, State, I, User) then
begin
if State and MF_DISABLED <> 0 then
begin
{ Close the menu if this is the only disabled item to choose from.
Otherwise, ignore the item. }
if First < 0 then First := -2;
Continue;
end;
if First < 0 then
begin
First := I;
Result := MNC_EXECUTE;
end
else
Result := MNC_SELECT;
if State and MF_HILITE <> 0 then
Hilite := I
else if Hilite >= 0 then
Next := I;
end;
end;
{ We found a single disabled item. End the selection. }
if First < -1 then
begin
Result := MNC_CLOSE shl 16;
Exit;
end;
{ If we can't find accelerators, then look for initial letters }
if First < 0 then
for I := 0 to C - 1 do
begin
State := GetMenuState(Menu, I, MF_BYPOSITION);
if IsInitialChar(Menu, State, I, User) then
begin
if State and MF_DISABLED <> 0 then
begin
Result := MNC_CLOSE shl 16;
Exit;
end;
if First < 0 then
begin
First := I;
Result := MNC_EXECUTE;
end
else
Result := MNC_SELECT;
if State and MF_HILITE <> 0 then
Hilite := I
else if Hilite >= 0 then
Next := I;
end;
end;
if (Result = MNC_EXECUTE) then
Result := Result shl 16 or First
else if Result = MNC_SELECT then
begin
if Next < 0 then
Next := First;
Result := Result shl 16 or Next;
end;
end;
end;
{ Returns the proper caption for a menu item when the menu is owner-drawn. }
function TMenu.DoGetMenuString(Menu: HMENU; ItemID: UINT; Str: PChar;
MaxCount: Integer; Flag: UINT): Integer;
var
Item: TMenuItem;
State: Word;
begin
if IsOwnerDraw then
begin
Item := nil;
State := GetMenuState(Menu, ItemID, Flag);
if State and MF_POPUP <> 0 then
begin
Menu := GetSubMenu(Menu, ItemID);
Item := FindItem(Menu, fkHandle);
end
else
begin
ItemID := GetMenuItemID(Menu, ItemID);
if ItemID <> $FFFFFFFF then
Item := FindItem(ItemID, fkCommand);
end;
if Item <> nil then
begin
Str[0] := #0;
StrPLCopy(Str, Item.Caption, MaxCount);
Result := StrLen(Str);
end
else
Result := 0;
end
else
Result := GetMenuString(Menu, ItemID, Str, MaxCount, Flag);
end;
procedure TMenu.SetBiDiMode(Value: TBiDiMode);
begin
if FBiDiMode <> Value then
begin
FBiDiMode := Value;
FParentBiDiMode := False;
DoBiDiModeChanged;
end;
end;
procedure TMenu.SetParentBiDiMode(Value: Boolean);
begin
if Value <> FParentBiDiMode then
begin
FParentBiDiMode := Value;
ParentBiDiModeChanged;
end;
end;
procedure TMenu.ParentBiDiModeChanged;
var
AForm: TWinControl;
begin
if FParentBiDiMode then
begin
AForm := FindControl(WindowHandle);
if AForm <> nil then
begin
BiDiMode := AForm.BiDiMode;
FParentBiDiMode := True;
end;
end;
end;
procedure TMenu.ParentBiDiModeChanged(AControl: TObject);
begin
if FParentBiDiMode then
begin
BiDiMode := (AControl as TControl).BiDiMode;
FParentBiDiMode := True;
end;
end;
function TMenu.GetAutoHotkeys: TMenuAutoFlag;
begin
Result := cItemAutoFlagToMenu[Items.FAutoHotkeys];
end;
procedure TMenu.SetAutoHotkeys(const Value: TMenuAutoFlag);
begin
Items.FAutoHotkeys := cMenuAutoFlagToItem[Value];
end;
function TMenu.GetAutoLineReduction: TMenuAutoFlag;
begin
Result := cItemAutoFlagToMenu[Items.FAutoLineReduction];
end;
procedure TMenu.SetAutoLineReduction(const Value: TMenuAutoFlag);
begin
Items.FAutoLineReduction := cMenuAutoFlagToItem[Value];
end;
{ TMainMenu }
procedure TMainMenu.SetAutoMerge(Value: Boolean);
begin
if FAutoMerge <> Value then
begin
FAutoMerge := Value;
if FWindowHandle <> 0 then
SendMessage(FWindowHandle, CM_MENUCHANGED, 0, 0);
end;
end;
procedure TMainMenu.MenuChanged(Sender: TObject; Source: TMenuItem; Rebuild: Boolean);
var
NeedUpdate: Boolean;
begin
if (FWindowHandle <> 0) then
begin
NeedUpdate := UpdateImage; // check for changes before CM_MENUCHANGED does
if Source = nil then SendMessage(FWindowHandle, CM_MENUCHANGED, 0, 0);
if NeedUpdate then DrawMenuBar(FWindowHandle);
end;
inherited MenuChanged(Sender, Source, Rebuild);
end;
procedure TMainMenu.Merge(Menu: TMainMenu);
begin
if Menu <> nil then
FItems.MergeWith(Menu.FItems) else
FItems.MergeWith(nil);
end;
procedure TMainMenu.Unmerge(Menu: TMainMenu);
begin
if (Menu <> nil) and (FItems.FMerged = Menu.FItems) then
FItems.MergeWith(nil);
end;
procedure TMainMenu.ItemChanged;
begin
MenuChanged(nil, nil, False);
if FWindowHandle <> 0 then
SendMessage(FWindowHandle, CM_MENUCHANGED, 0, 0);
end;
function TMainMenu.GetHandle: HMENU;
begin
if FOle2Menu <> 0 then
Result := FOle2Menu else
Result := inherited GetHandle;
end;
procedure TMainMenu.GetOle2AcceleratorTable(var AccelTable: HAccel;
var AccelCount: Integer; Groups: array of Integer);
var
NumAccels: Integer;
AccelList, AccelPtr: PAccel;
procedure ProcessAccels(Item: TMenuItem);
var
I: Integer;
Virt: Byte;
begin
if Item.ShortCut <> 0 then
if AccelPtr <> nil then
begin
Virt := FNOINVERT or FVIRTKEY;
if Item.ShortCut and scCtrl <> 0 then Virt := Virt or FCONTROL;
if Item.ShortCut and scAlt <> 0 then Virt := Virt or FALT;
if Item.ShortCut and scShift <> 0 then Virt := Virt or FSHIFT;
AccelPtr^.fVirt := Virt;
AccelPtr^.key := Item.ShortCut and $FF;
AccelPtr^.cmd := Item.Command;
Inc(AccelPtr);
end else
Inc(NumAccels)
else
for I := 0 to Item.GetCount - 1 do ProcessAccels(Item[I]);
end;
function ProcessAccelItems(Item: TMenuItem): Boolean;
var
I: Integer;
begin
for I := 0 to High(Groups) do
if Item.GroupIndex = Groups[I] then
begin
ProcessAccels(Item);
Break;
end;
Result := False;
end;
begin
NumAccels := 0;
AccelPtr := nil;
IterateMenus(@ProcessAccelItems, Items.FMerged, Items);
AccelTable := 0;
if NumAccels <> 0 then
begin
GetMem(AccelList, NumAccels * SizeOf(TAccel));
AccelPtr := AccelList;
IterateMenus(@ProcessAccelItems, Items.FMerged, Items);
AccelTable := CreateAcceleratorTable(AccelList^, NumAccels);
FreeMem(AccelList);
end;
AccelCount := NumAccels;
end;
{ Similar to regular TMenuItem.PopulateMenus except that it only adds
the specified groups to the menu handle }
procedure TMainMenu.PopulateOle2Menu(SharedMenu: HMenu;
Groups: array of Integer; var Widths: array of Longint);
var
NumGroups: Integer;
J: Integer;
MenuRightToLeft: Boolean;
function AddOle2(Item: TMenuItem): Boolean;
var
I: Integer;
begin
for I := 0 to NumGroups do
begin
if Item.GroupIndex = Groups[I] then
begin
Inc(Widths[Item.GroupIndex]);
Item.AppendTo(SharedMenu, MenuRightToLeft);
end;
end;
Result := False;
end;
begin
MenuRightToLeft := IsRightToLeft;
NumGroups := High(Groups);
for J := 0 to High(Widths) do Widths[J] := 0;
IterateMenus(@AddOle2, Items.FMerged, Items);
end;
procedure TMainMenu.SetOle2MenuHandle(Handle: HMENU);
begin
FOle2Menu := Handle;
ItemChanged;
end;
{ TPopupList }
procedure TPopupList.MainWndProc(var Message: TMessage);
begin
try
WndProc(Message);
except
Application.HandleException(Self);
end;
end;
procedure TPopupList.WndProc(var Message: TMessage);
var
I, Item: Integer;
MenuItem: TMenuItem;
FindKind: TFindItemKind;
ContextID: Integer;
Canvas: TCanvas;
SaveIndex: Integer;
DC: HDC;
begin
case Message.Msg of
WM_COMMAND:
for I := 0 to Count - 1 do
if TPopupMenu(Items[I]).DispatchCommand(Message.wParam) then Exit;
WM_INITMENUPOPUP:
for I := 0 to Count - 1 do
with TWMInitMenuPopup(Message) do
if TPopupMenu(Items[I]).DispatchPopup(MenuPopup) then Exit;
WM_MENUSELECT:
with TWMMenuSelect(Message) do
begin
FindKind := fkCommand;
if MenuFlag and MF_POPUP <> 0 then FindKind := fkHandle;
for I := 0 to Count - 1 do
begin
if FindKind = fkHandle then
begin
if Menu <> 0 then
Item := GetSubMenu(Menu, IDItem) else
Item := -1;
end
else
Item := IDItem;
MenuItem := TPopupMenu(Items[I]).FindItem(Item, FindKind);
if MenuItem <> nil then
begin
Application.Hint := GetLongHint(MenuItem.Hint);
Exit;
end;
end;
Application.Hint := '';
end;
WM_HELP:
with PHelpInfo(Message.LParam)^ do
begin
for I := 0 to Count - 1 do
begin
if hItemHandle = TMenu(Items[I]).Handle then
MenuItem := TMenu(Items[I]).Items
else
MenuItem := TPopupMenu(Items[I]).FindItem(hItemHandle, fkHandle);
if MenuItem <> nil then
begin
ContextID := TMenu(Items[I]).GetHelpContext(iCtrlID, True);
if ContextID = 0 then
ContextID := TMenu(Items[I]).GetHelpContext(hItemHandle, False);
if Screen.ActiveForm = nil then Exit;
if (ContextID = 0) then
ContextID := Screen.ActiveForm.HelpContext;
if (biHelp in Screen.ActiveForm.BorderIcons) then
Application.HelpCommand(HELP_CONTEXTPOPUP, ContextID)
else
Application.HelpContext(ContextID);
Exit;
end;
end;
end;
WM_DRAWITEM:
with PDrawItemStruct(Message.LParam)^ do
begin
for I := 0 to Count - 1 do
begin
MenuItem := TPopupMenu(Items[I]).FindItem(itemID, fkCommand);
if MenuItem <> nil then
begin
Canvas := TControlCanvas.Create;
with Canvas do
try
SaveIndex := SaveDC(hDC);
try
Handle := hDC;
Font := Screen.MenuFont;
DrawMenuItem(MenuItem, Canvas, rcItem, TOwnerDrawState(LongRec(itemState).Lo));
finally
Handle := 0;
RestoreDC(hDC, SaveIndex);
end;
finally
Canvas.Free;
end;
Exit;
end;
end;
end;
WM_MEASUREITEM:
with PMeasureItemStruct(Message.LParam)^ do
begin
for I := 0 to Count - 1 do
begin
MenuItem := TPopupMenu(Items[I]).FindItem(itemID, fkCommand);
if MenuItem <> nil then
begin
DC := GetWindowDC(Window);
try
Canvas := TControlCanvas.Create;
with Canvas do
try
SaveIndex := SaveDC(DC);
try
Handle := DC;
Font := Screen.MenuFont;
MenuItem.MeasureItem(Canvas, Integer(itemWidth),
Integer(itemHeight));
finally
Handle := 0;
RestoreDC(DC, SaveIndex);
end;
finally
Canvas.Free;
end;
finally
ReleaseDC(Window, DC);
end;
Exit;
end;
end;
end;
WM_MENUCHAR:
for I := 0 to Count - 1 do
with TPopupMenu(Items[I]) do
if (Handle = HMENU(Message.LParam)) or
(FindItem(Message.LParam, fkHandle) <> nil) then
begin
ProcessMenuChar(TWMMenuChar(Message));
Exit;
end;
end;
with Message do Result := DefWindowProc(Window, Msg, wParam, lParam);
end;
procedure TPopupList.Add(Popup: TPopupMenu);
begin
if Count = 0 then FWindow := AllocateHWnd(MainWndProc);
inherited Add(Popup);
end;
procedure TPopupList.Remove(Popup: TPopupMenu);
begin
inherited Remove(Popup);
if Count = 0 then DeallocateHWnd(FWindow);
end;
{ TPopupMenu }
constructor TPopupMenu.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FPopupPoint.X := -1;
FPopupPoint.Y := -1;
FItems.OnClick := DoPopup;
FWindowHandle := Application.Handle;
FAutoPopup := True;
PopupList.Add(Self);
end;
destructor TPopupMenu.Destroy;
begin
PopupList.Remove(Self);
inherited Destroy;
end;
procedure TPopupMenu.DoPopup(Sender: TObject);
begin
if Assigned(FOnPopup) then FOnPopup(Sender);
end;
function TPopupMenu.GetHelpContext: THelpContext;
begin
Result := FItems.HelpContext;
end;
procedure TPopupMenu.SetHelpContext(Value: THelpContext);
begin
FItems.HelpContext := Value;
end;
procedure TPopupMenu.SetBiDiModeFromPopupControl;
var
AControl: TControl;
begin
if not SysLocale.MiddleEast then Exit;
if FParentBiDiMode then
begin
{ Use the setting from the control that activated the popup.
If there is no control, then use Application }
AControl := FindPopupControl(FPopupPoint);
if AControl <> nil then
begin
BiDiMode := AControl.BiDiMode;
FParentBiDiMode := True;
end
else
begin
BiDiMode := Application.BiDiMode;
FParentBiDiMode := True;
end;
end;
end;
function TPopupMenu.UseRightToLeftAlignment: Boolean;
var
AControl: TControl;
begin
Result := False;
if not SysLocale.MiddleEast then Exit;
if FParentBiDiMode then
begin
{ Use the setting from the control that activated the popup.
If there is no control, then use Application }
AControl := FindPopupControl(FPopupPoint);
if AControl <> nil then
Result := AControl.UseRightToLeftAlignment
else
Result := Application.UseRightToLeftAlignment;
end
else
Result := (FBiDiMode = bdRightToLeft);
end;
procedure TPopupMenu.Popup(X, Y: Integer);
const
Flags: array[Boolean, TPopupAlignment] of Word =
((TPM_LEFTALIGN, TPM_RIGHTALIGN, TPM_CENTERALIGN),
(TPM_RIGHTALIGN, TPM_LEFTALIGN, TPM_CENTERALIGN));
Buttons: array[TTrackButton] of Word = (TPM_RIGHTBUTTON, TPM_LEFTBUTTON);
var
AFlags: Integer;
begin
FPopupPoint := Point(X, Y);
SetBiDiModeFromPopupControl;
DoPopup(Self);
FItems.InternalRethinkHotkeys(False);
FItems.InternalRethinkLines(False);
FItems.RebuildHandle;
AdjustBiDiBehavior;
AFlags := Flags[UseRightToLeftAlignment, FAlignment] or Buttons[FTrackButton] or
(Byte(FMenuAnimation) shl 10);
TrackPopupMenu(FItems.Handle, AFlags, X, Y, 0 { reserved }, PopupList.Window, nil);
end;
{ TMenuItemStack }
procedure TMenuItemStack.ClearItem(AItem: TMenuItem);
var
I: Integer;
begin
for I := 0 to List.Count - 1 do
if PMenuItem(List[I])^ = AItem then
PMenuItem(List[I])^ := nil;
end;
{ Menu building functions }
procedure InitMenuItems(AMenu: TMenu; Items: array of TMenuItem);
var
I: Integer;
procedure SetOwner(Item: TMenuItem);
var
I: Integer;
begin
if Item.Owner = nil then AMenu.Owner.InsertComponent(Item);
for I := 0 to Item.Count - 1 do
SetOwner(Item[I]);
end;
begin
for I := Low(Items) to High(Items) do
begin
SetOwner(Items[I]);
AMenu.FItems.Add(Items[I]);
end;
end;
function NewMenu(Owner: TComponent; const AName: string; Items: array of TMenuItem): TMainMenu;
begin
Result := TMainMenu.Create(Owner);
Result.Name := AName;
InitMenuItems(Result, Items);
end;
function NewPopupMenu(Owner: TComponent; const AName: string;
Alignment: TPopupAlignment; AutoPopup: Boolean; Items: array of TMenuItem): TPopupMenu;
begin
Result := TPopupMenu.Create(Owner);
Result.Name := AName;
Result.AutoPopup := AutoPopup;
Result.Alignment := Alignment;
InitMenuItems(Result, Items);
end;
function NewSubMenu(const ACaption: string; hCtx: Word;
const AName: string; Items: array of TMenuItem; AEnabled: Boolean): TMenuItem;
var
I: Integer;
begin
Result := TMenuItem.Create(nil);
for I := Low(Items) to High(Items) do
Result.Add(Items[I]);
Result.Caption := ACaption;
Result.HelpContext := hCtx;
Result.Name := AName;
Result.Enabled := AEnabled;
end;
function NewItem(const ACaption: string; AShortCut: TShortCut;
AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent; hCtx: Word;
const AName: string): TMenuItem;
begin
Result := TMenuItem.Create(nil);
with Result do
begin
Caption := ACaption;
ShortCut := AShortCut;
OnClick := AOnClick;
HelpContext := hCtx;
Checked := AChecked;
Enabled := AEnabled;
Name := AName;
end;
end;
function NewLine: TMenuItem;
begin
Result := TMenuItem.Create(nil);
Result.Caption := cLineCaption;
end;
procedure DrawMenuItem(MenuItem: TMenuItem; ACanvas: TCanvas; ARect: TRect;
State: TOwnerDrawState);
var
TopLevel: Boolean;
Win98Plus: Boolean;
begin
with ACanvas do
begin
Win98Plus := (Win32MajorVersion > 4) or
((Win32MajorVersion = 4) and (Win32MinorVersion > 0));
TopLevel := MenuItem.GetParentComponent is TMainMenu;
if (odSelected in State) and (not TopLevel or (TopLevel and not Win98Plus)) then
begin
Brush.Color := clHighlight;
Font.Color := clHighlightText;
end
else if Win98Plus and (odInactive in State) then
begin
Brush.Color := clMenu;
Font.Color := clGrayText;
end
else
begin
Brush.Color := clMenu;
Font.Color := clMenuText;
end;
MenuItem.AdvancedDrawItem(ACanvas, ARect, State, TopLevel);
end;
end;
function StripHotkey(const Text: string): string;
var
I: Integer;
begin
Result := Text;
I := 1;
while I <= Length(Result) do
begin
if Result[I] in LeadBytes then
Inc(I)
else if Result[I] = cHotkeyPrefix then
if SysLocale.FarEast and
((I > 1) and (Length(Result)-I >= 2) and
(Result[I-1] = '(') and (Result[I+2] = ')')) then
Delete(Result, I-1, 4)
else
Delete(Result, I, 1);
Inc(I);
end;
end;
function GetHotkey(const Text: string): string;
var
I, L: Integer;
begin
Result := '';
I := 1;
L := Length(Text);
while I <= L do
begin
if Text[I] in LeadBytes then
Inc(I)
else if (Text[I] = cHotkeyPrefix) and
(L - I >= 1) then
begin
Inc(I);
if Text[I] <> cHotkeyPrefix then
Result := Text[I]; // keep going there may be another one
end;
Inc(I);
end;
end;
function AnsiSameCaption(const Text1, Text2: string): Boolean;
begin
Result := AnsiSameText(StripHotkey(Text1), StripHotkey(Text2));
end;
initialization
RegisterClasses([TMenuItem]);
CommandPool := TBits.Create;
PopupList := TPopupList.Create;
ShortCutItems := TMenuItemStack.Create;
finalization
ShortCutItems.Free;
PopupList.Free;
CommandPool.Free;
end.