home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1997 April
/
Chip_1997-04_cd.bin
/
prezent
/
cb
/
data.z
/
MENUS.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1997-01-16
|
38KB
|
1,358 lines
{*******************************************************}
{ }
{ Delphi Visual Component Library }
{ }
{ Copyright (c) 1995,96 Borland International }
{ }
{*******************************************************}
unit Menus;
{$S-,W-,R-}
{$C PRELOAD}
interface
uses Windows, SysUtils, Classes, Messages;
const
scShift = $2000;
scCtrl = $4000;
scAlt = $8000;
scNone = 0;
type
EMenuError = class(Exception);
TMenu = class;
TMenuBreak = (mbNone, mbBreak, mbBarBreak);
TShortCut = Low(Word)..High(Word);
TMenuChangeEvent = procedure (Sender: TObject; Rebuild: Boolean) of object;
TMenuItem = class(TComponent)
private
FCaption: string;
FHandle: HMENU;
FChecked: Boolean;
FEnabled: Boolean;
FDefault: Boolean;
FRadioItem: Boolean;
FVisible: Boolean;
FGroupIndex: Byte;
FBreak: TMenuBreak;
FCommand: Word;
FHelpContext: THelpContext;
FHint: string;
FItems: TList;
FShortCut: TShortCut;
FParent: TMenuItem;
FMerged: TMenuItem;
FMenu: TMenu;
FOnChange: TMenuChangeEvent;
FOnClick: TNotifyEvent;
procedure AppendTo(Menu: HMENU);
procedure ClearHandles;
procedure ReadShortCutText(Reader: TReader);
procedure MergeWith(Menu: TMenuItem);
procedure RebuildHandle;
procedure PopulateMenu;
procedure SubItemChanged(Sender: TObject; Rebuild: Boolean);
procedure WriteShortCutText(Writer: TWriter);
procedure VerifyGroupIndex(Position: Integer; Value: Byte);
protected
procedure DefineProperties(Filer: TFiler); override;
function GetHandle: HMENU;
function GetCount: Integer;
procedure GetChildren(Proc: TGetChildProc); override;
function GetItem(Index: Integer): TMenuItem;
function GetMenuIndex: Integer;
function GetParentComponent: TComponent; override;
procedure MenuChanged(Rebuild: Boolean); virtual;
function HasParent: Boolean; 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 SetMenuIndex(Value: Integer);
procedure SetParentComponent(Value: TComponent); override;
procedure SetRadioItem(Value: Boolean);
procedure SetShortCut(Value: TShortCut);
procedure SetVisible(Value: Boolean);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Insert(Index: Integer; Item: TMenuItem);
procedure Delete(Index: Integer);
procedure Click; virtual;
function IndexOf(Item: TMenuItem): Integer;
procedure Add(Item: TMenuItem);
procedure Remove(Item: TMenuItem);
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 Break: TMenuBreak read FBreak write SetBreak default mbNone;
property Caption: string read FCaption write SetCaption;
property Checked: Boolean read FChecked write SetChecked default False;
property Default: Boolean read FDefault write SetDefault default False;
property Enabled: Boolean read FEnabled write SetEnabled default True;
property GroupIndex: Byte read FGroupIndex write SetGroupIndex default 0;
property HelpContext: THelpContext read FHelpContext write FHelpContext default 0;
property Hint: string read FHint write FHint;
property RadioItem: Boolean read FRadioItem write SetRadioItem default False;
property ShortCut: TShortCut read FShortCut write SetShortCut;
property Visible: Boolean read FVisible write SetVisible default True;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;
TFindItemKind = (fkCommand, fkHandle, fkShortCut);
TMenu = class(TComponent)
private
FItems: TMenuItem;
FWindowHandle: HWND;
FMenuImage: string;
procedure MenuChanged(Sender: TObject; Rebuild: Boolean); virtual;
procedure SetWindowHandle(Value: HWND);
function UpdateImage: Boolean;
protected
procedure GetChildren(Proc: TGetChildProc); override;
function GetHandle: HMENU; virtual;
procedure SetChildOrder(Child: TComponent; Order: Integer); override;
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: Word; ByCommand: Boolean): THelpContext;
function IsShortCut(var Message: TWMKey): Boolean;
property Handle: HMENU read GetHandle;
property WindowHandle: HWND read FWindowHandle write SetWindowHandle;
published
property Items: TMenuItem read FItems;
end;
TMainMenu = class(TMenu)
private
MergedMenu: TMenuItem;
FOle2Menu: HMENU;
FAutoMerge: Boolean;
FReserved: Byte;
procedure ItemChanged;
procedure MenuChanged(Sender: TObject; Rebuild: Boolean); override;
procedure SetAutoMerge(Value: Boolean);
protected
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 AutoMerge: Boolean read FAutoMerge write SetAutoMerge default False;
end;
TPopupAlignment = (paLeft, paRight, paCenter);
TPopupMenu = class(TMenu)
private
FAlignment: TPopupAlignment;
FAutoPopup: Boolean;
FPopupComponent: TComponent;
FOnPopup: TNotifyEvent;
procedure DoPopup(Item: TObject);
function GetHelpContext: THelpContext;
procedure SetHelpContext(Value: THelpContext);
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 AutoPopup: Boolean read FAutoPopup write FAutoPopup default True;
property HelpContext: THelpContext read GetHelpContext write SetHelpContext default 0;
property OnPopup: TNotifyEvent read FOnPopup write FOnPopup;
end;
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): TMenuItem;
function NewItem(const ACaption: string; AShortCut: TShortCut;
AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent; hCtx: Word;
const AName: string): TMenuItem;
function NewLine: TMenuItem;
implementation
uses Controls, Forms, Consts;
procedure Error(const S: string);
begin
raise EMenuError.Create(S);
end;
procedure IndexError;
begin
Error(LoadStr(SMenuIndexError));
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);
const
MenuKeyCapIDs: array[TMenuKeyCap] of Word = (
SmkcBkSp, SmkcTab, SmkcEsc, SmkcEnter, SmkcSpace, SmkcPgUp,
SmkcPgDn, SmkcEnd, SmkcHome, SmkcLeft, SmkcUp, SmkcRight,
SmkcDown, SmkcIns, SmkcDel, SmkcShift, SmkcCtrl, SmkcAlt);
var
MenuKeyCaps: array[TMenuKeyCap] of string;
procedure LoadStrings;
var
I: TMenuKeyCap;
begin
for I := Low(TMenuKeyCap) to High(TMenuKeyCap) do
MenuKeyCaps[I] := LoadStr(MenuKeyCapIDs[I]);
end;
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));
if (KeyName[1] = #0) and (KeyName[0] <> #0) then
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 AnsiCompareText(Copy(Text, 1, Length(Front)), 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;
{ TMenuItem }
constructor TMenuItem.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FVisible := True;
FEnabled := True;
FCommand := UniqueCommand;
end;
destructor TMenuItem.Destroy;
begin
if FParent <> nil then
begin
FParent.Remove(Self);
FParent := nil;
end;
if FHandle <> 0 then
begin
MergeWith(nil);
DestroyMenu(FHandle);
ClearHandles;
end;
while Count > 0 do Items[0].Free;
FItems.Free;
if FCommand <> 0 then CommandPool[FCommand] := False;
inherited Destroy;
end;
procedure TMenuItem.ClearHandles;
procedure Clear(Item: TMenuItem);
var
I: Integer;
begin
with Item do
begin
FHandle := 0;
for I := 0 to GetCount - 1 do Clear(FItems[I]);
end;
end;
begin
Clear(Self);
end;
const
Checks: array[Boolean] of LongInt = (MF_UNCHECKED, MF_CHECKED);
Enables: array[Boolean] of LongInt = (MF_DISABLED or MF_GRAYED, MF_ENABLED);
Breaks: array[TMenuBreak] of Longint = (0, MF_MENUBREAK, MF_MENUBARBREAK);
Separators: array[Boolean] of LongInt = (MF_STRING, MF_SEPARATOR);
procedure TMenuItem.AppendTo(Menu: HMENU);
const
IBreaks: array[TMenuBreak] of Longint = (MFT_STRING, MFT_MENUBREAK, MFT_MENUBARBREAK);
IChecks: array[Boolean] of Longint = (MFS_UNCHECKED, MFS_CHECKED);
IDefaults: array[Boolean] of Longint = (0, MFS_DEFAULT);
IEnables: array[Boolean] of Longint = (MFS_DISABLED or MFS_GRAYED, MFS_ENABLED);
IRadios: array[Boolean] of Longint = (MFT_STRING, MFT_RADIOCHECK);
ISeparators: array[Boolean] of Longint = (MFT_STRING, MFT_SEPARATOR);
var
MenuItemInfo: TMenuItemInfo;
Caption: string;
NewFlags: Integer;
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 := SizeOf(TMenuItemInfo);
MenuItemInfo.fMask := MIIM_CHECKMARKS or MIIM_DATA or MIIM_ID or
MIIM_STATE or MIIM_SUBMENU or MIIM_TYPE;
MenuItemInfo.fType := IRadios[FRadioItem] or IBreaks[FBreak] or
ISeparators[FCaption = '-'];
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, -1, True, MenuItemInfo);
end
else
begin
NewFlags := Breaks[FBreak] or Checks[FChecked] or Enables[FEnabled] or
Separators[FCaption = '-'] or MF_BYPOSITION;
if GetCount > 0 then
InsertMenu(Menu, -1, MF_POPUP or NewFlags, GetHandle,
PChar(FCaption))
else
InsertMenu(Menu, -1, NewFlags, Command, PChar(Caption));
end;
end;
end;
procedure TMenuItem.PopulateMenu;
function AddIn(MenuItem: TMenuItem): Boolean;
begin
MenuItem.AppendTo(FHandle);
Result := False;
end;
begin
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
FMerged := Menu;
RebuildHandle;
end;
end;
procedure TMenuItem.RebuildHandle;
begin
while GetMenuItemCount(Handle) > 0 do RemoveMenu(Handle, 0, MF_BYPOSITION);
PopulateMenu;
MenuChanged(False);
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(LoadStr(SGroupIndexTooLow))
end
else
{ Ripple change to menu items at Position and after }
if Items[I].GroupIndex < Value then Items[I].FGroupIndex := Value;
end;
procedure TMenuItem.WriteShortCutText(Writer: TWriter);
begin
{Writer.WriteString(ShortCutToText(ShortCut));}
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 raise EMenuError.CreateRes(SOutOfResources);
PopulateMenu;
end;
Result := FHandle;
end;
procedure TMenuItem.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
Filer.DefineProperty('ShortCutText', ReadShortCutText, WriteShortCutText, False);
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.SetChecked(Value: Boolean);
var
I: Integer;
Item: TMenuItem;
begin
if FChecked <> Value then
begin
if FRadioItem and (GroupIndex <> 0) and (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.Checked := False;
end;
FChecked := Value;
if FParent <> nil then
CheckMenuItem(FParent.Handle, FCommand, MF_BYCOMMAND or Checks[Value]);
end;
end;
procedure TMenuItem.SetEnabled(Value: Boolean);
begin
if FEnabled <> Value then
begin
FEnabled := Value;
if (Win32Platform = VER_PLATFORM_WIN32_NT) and (Count <> 0) then
MenuChanged(True)
else
begin
if FParent <> nil 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;
end;
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 IndexError;
Result := FItems[Index];
end;
procedure TMenuItem.SetShortCut(Value: TShortCut);
begin
FShortCut := Value;
MenuChanged(True);
end;
procedure TMenuItem.SetVisible(Value: Boolean);
begin
FVisible := Value;
MenuChanged(True);
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);
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.Insert(Index: Integer; Item: TMenuItem);
begin
if Item.FParent <> nil then
raise EMenuError.CreateRes(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(True);
end;
procedure TMenuItem.Delete(Index: Integer);
var
Cur: TMenuItem;
begin
if (Index < 0) or (FItems = nil) or (Index >= GetCount) then IndexError;
Cur := FItems[Index];
FItems.Delete(Index);
Cur.FParent := nil;
Cur.FOnChange := nil;
if FHandle <> 0 then RebuildHandle;
MenuChanged(True);
end;
procedure TMenuItem.Click;
begin
if FEnabled and Assigned(FOnClick) then FOnClick(Self);
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 raise EMenuError.CreateRes(SMenuNotFound);
Delete(I);
end;
procedure TMenuItem.MenuChanged(Rebuild: Boolean);
begin
if Assigned(FOnChange) then FOnChange(Self, Rebuild);
end;
procedure TMenuItem.SubItemChanged(Sender: TObject; Rebuild: Boolean);
begin
if Rebuild and (FHandle <> 0) then RebuildHandle;
if Parent <> nil then Parent.SubItemChanged(Self, False)
else if Owner is TMainMenu then TMainMenu(Owner).ItemChanged;
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;
procedure TMenuItem.SetRadioItem(Value: Boolean);
begin
if FRadioItem <> Value then
begin
FRadioItem := Value;
MenuChanged(True);
end;
end;
{ TMenu }
constructor TMenu.Create(AOwner: TComponent);
begin
FItems := TMenuItem.Create(Self);
FItems.FOnChange := MenuChanged;
FItems.FMenu := Self;
inherited Create(AOwner);
end;
destructor TMenu.Destroy;
begin
FItems.Free;
inherited Destroy;
end;
procedure TMenu.GetChildren(Proc: TGetChildProc);
begin
FItems.GetChildren(Proc);
end;
function TMenu.GetHandle: HMENU;
begin
Result := FItems.GetHandle;
end;
procedure TMenu.SetChildOrder(Child: TComponent; Order: Integer);
begin
FItems.SetChildOrder(Child, Order);
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 = 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: Word; ByCommand: Boolean): THelpContext;
var
Item: TMenuItem;
Kind: TFindItemKind;
begin
Result := 0;
Kind := fkHandle;
if ByCommand then Kind := fkCommand;
Item := FindItem(Value, Kind);
while (Item <> nil) and (Item.FHelpContext = 0) do
Item := Item.FParent;
if Item <> nil then Result := Item.FHelpContext;
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
Item.Click;
Result := True;
end;
end;
function TMenu.IsShortCut(var Message: TWMKey): Boolean;
type
TClickResult = (crDisabled, crClicked, crShortCutMoved);
const
AltMask = $20000000;
var
ShortCut: TShortCut;
ShortCutItem: TMenuItem;
ClickResult: TClickResult;
function DoClick(Item: TMenuItem): TClickResult;
begin
Result := crClicked;
if Item.Parent <> nil then Result := DoClick(Item.Parent);
if Result = crClicked then
if Item.Enabled then
try
Item.Click;
if ShortCutItem.ShortCut <> ShortCut then
Result := crShortCutMoved;
except
Application.HandleException(Self);
end
else Result := crDisabled;
end;
begin
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 ClickResult := DoClick(ShortCutItem);
until ClickResult <> crShortCutMoved;
Result := ShortCutItem <> nil;
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
GetMenuString(Menu, I, P, ImageEnd - P, MF_BYPOSITION);
P := StrEnd(P);
State := GetMenuState(Menu, I, MF_BYPOSITION);
if State and MF_DISABLED <> 0 then P := StrECopy(P, '$');
if State and MF_MENUBREAK <> 0 then P := StrECopy(P, '@');
if State and MF_GRAYED <> 0 then P := StrECopy(P, '#');
P := StrECopy(P, ';');
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.SetWindowHandle(Value: HWND);
begin
FWindowHandle := Value;
UpdateImage;
end;
procedure TMenu.MenuChanged(Sender: TObject; Rebuild: Boolean);
begin
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; Rebuild: Boolean);
begin
if (FWindowHandle <> 0) and UpdateImage then DrawMenuBar(FWindowHandle);
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, 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;
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);
end;
end;
Result := False;
end;
begin
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;
{ TPopupMenu }
type
TPopupList = class(TList)
private
procedure WndProc(var Message: TMessage);
public
Window: HWND;
procedure Add(Popup: TPopupMenu);
procedure Remove(Popup: TPopupMenu);
end;
var
PopupList: TPopupList;
procedure TPopupList.WndProc(var Message: TMessage);
var
I: Integer;
MenuItem: TMenuItem;
FindKind: TFindItemKind;
ContextID: Integer;
begin
try
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
MenuItem := TPopupMenu(Items[I]).FindItem(IDItem, FindKind);
if MenuItem <> nil then
begin
Application.Hint := MenuItem.Hint;
Exit;
end;
end;
Application.Hint := '';
end;
WM_HELP:
with PHelpInfo(Message.LParam)^ do
begin
for I := 0 to Count - 1 do
if TPopupMenu(Items[I]).Handle = hItemHandle 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 (biHelp in Screen.ActiveForm.BorderIcons) then
Application.HelpCommand(HELP_CONTEXTPOPUP, ContextID)
else
Application.HelpContext(ContextID);
Exit;
end;
end;
end;
with Message do Result := DefWindowProc(Window, Msg, wParam, lParam);
except
Application.HandleException(Self);
end;
end;
procedure TPopupList.Add(Popup: TPopupMenu);
begin
if Count = 0 then Window := AllocateHWnd(WndProc);
inherited Add(Popup);
end;
procedure TPopupList.Remove(Popup: TPopupMenu);
begin
inherited Remove(Popup);
if Count = 0 then DeallocateHWnd(Window);
end;
constructor TPopupMenu.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
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(Item: TObject);
begin
if Assigned(FOnPopup) then FOnPopup(Item);
end;
function TPopupMenu.GetHelpContext: THelpContext;
begin
Result := FItems.HelpContext;
end;
procedure TPopupMenu.SetHelpContext(Value: THelpContext);
begin
FItems.HelpContext := Value;
end;
procedure TPopupMenu.Popup(X, Y: Integer);
const
Flags: array[TPopupAlignment] of Word = (TPM_LEFTALIGN, TPM_RIGHTALIGN,
TPM_CENTERALIGN);
begin
DoPopup(Self);
TrackPopupMenu(FItems.Handle, Flags[FAlignment] or TPM_RIGHTBUTTON, X, Y,
0 { reserved}, PopupList.Window, 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): 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;
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 := '-';
end;
begin
RegisterClasses([TMenuItem]);
LoadStrings;
CommandPool := TBits.Create;
PopupList := TPopupList.Create;
end.