38
38
////////////////////////////////////////////////////
39
39
WSMenus, WSLCLClasses, WSProc,
40
40
Windows, Controls, Classes, SysUtils, Win32Int, Win32Proc, Win32WSImgList,
41
InterfaceBase, LCLProc, Themes, Win32UxTheme, TmSchema, Win32Themes, Win32Extra,
41
InterfaceBase, LCLProc, Themes, UxTheme, Win32Themes, Win32Extra,
52
52
class procedure DestroyHandle(const AMenuItem: TMenuItem); override;
53
53
class procedure SetCaption(const AMenuItem: TMenuItem; const ACaption: string); override;
54
54
class function SetCheck(const AMenuItem: TMenuItem; const Checked: boolean): boolean; override;
55
class procedure SetShortCut(const AMenuItem: TMenuItem; const OldShortCut, NewShortCut: TShortCut); override;
55
class procedure SetShortCut(const AMenuItem: TMenuItem; const ShortCutK1, ShortCutK2: TShortCut); override;
56
56
class function SetEnable(const AMenuItem: TMenuItem; const Enabled: boolean): boolean; override;
57
57
class function SetRightJustify(const AMenuItem: TMenuItem; const Justified: boolean): boolean; override;
58
58
class procedure UpdateMenuIcon(const AMenuItem: TMenuItem; const HasIcon: Boolean; const AIcon: Graphics.TBitmap); override;
162
163
Result := IntToStr(GetLastError) + ' : ' + UTF8ToConsole(AnsiToUtf8(GetLastErrorText(GetLastError)));
165
(* Returns index of the character in the menu item caption that is displayed
166
as underlined and is therefore the hot key of the menu item.
167
If the caption does not contain any underlined character, 0 is returned.
168
If there are more "underscored" characters in the caption, the last one is returned.
169
Does some Windows API function exists which can do the same?
170
AnUnderlinedChar - character which tells that tne following character should be underlined
171
ACaption - menu item caption which is parsed *)
172
function SearchMenuItemHotKeyIndex(const AnUnderlinedChar: char; ACaption: string): integer;
176
position := pos(AnUnderlinedChar, ACaption);
178
// if aChar is on the last position then there is nothing to underscore, ignore this character
179
while (position > 0) and (position < length(ACaption)) do
181
// two 'AnUnderlinedChar' characters together are not valid hot key, they are replaced by one
182
if ACaption[position + 1] <> AnUnderlinedChar then
183
Result := position + 1;
184
position := posEx(AnUnderlinedChar, ACaption, position + 2);
188
function FindMenuItemAccelerator(const ACharCode: char; const AMenuHandle: HMENU): integer;
166
function FindMenuItemAccelerator(const ACharCode: word; const AMenuHandle: HMENU): integer;
190
168
MenuItemIndex: integer;
191
169
ItemInfo: MENUITEMINFO;
192
170
FirstMenuItem: TMenuItem;
193
171
SiblingMenuItem: TMenuItem;
194
HotKeyIndex: integer;
197
174
Result := MakeLResult(0, MNC_IGNORE);
238
214
Result := CreateFontIndirect(@lf);
217
(* Get the menu item shortcut text *)
218
function MenuItemShortCut(const AMenuItem: TMenuItem): string;
220
Result := ShortCutToText(AMenuItem.ShortCut);
221
if AMenuItem.ShortCutKey2 <> scNone then
222
Result := Result + ', ' + ShortCutToText(AMenuItem.ShortCutKey2);
241
225
(* Get the menu item caption including shortcut *)
242
226
function CompleteMenuItemCaption(const AMenuItem: TMenuItem; Spacing: String): string;
244
228
Result := AMenuItem.Caption;
245
229
if AMenuItem.ShortCut <> scNone then
246
Result := Result + Spacing + ShortCutToText(AMenuItem.ShortCut);
230
Result := Result + Spacing + MenuItemShortCut(AMenuItem);
249
233
(* Get the maximum length of the given string in pixels *)
250
function StringSize(const aCaption: String; const aHDC: HDC; const aDecoration:TCaptionFlagsSet): TSize;
234
function StringSize(const aCaption: String; const aHDC: HDC): TSize;
254
236
tmpRect: Windows.RECT;
255
237
{$ifdef WindowsUnicodeSupport}
256
238
AnsiBuffer: ansistring;
275
255
DrawText(aHDC, pChar(aCaption), length(aCaption), @TmpRect, DT_CALCRECT);
277
SelectObject(aHDC, oldFont);
278
DeleteObject(newFont);
279
257
Result.cx := TmpRect.right - TmpRect.left;
280
258
Result.cy := TmpRect.Bottom - TmpRect.Top;
283
function CheckSpace(AMenuItem: TMenuItem): integer;
261
function GetAverageCharSize(AHDC: HDC): TSize;
263
alph: AnsiString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
288
if AMenuItem.IsInMenuBar then
290
if AMenuItem.Checked then
291
Result := GetSystemMetrics(SM_CXMENUCHECK);
295
for i := 0 to AMenuItem.Parent.Count - 1 do
297
if AMenuItem.Parent.Items[i].Checked then
299
Result := GetSystemMetrics(SM_CXMENUCHECK);
268
if GetTextMetrics(AHDC, @tm) = False then
271
Result.cy := WORD(tm.tmHeight);
273
if GetTextExtentPoint(AHDC, @alph[1], 52, @sz) = False then
276
Result.cx := (sz.cx div 26 + 1) div 2;
306
279
function MenuIconWidth(const AMenuItem: TMenuItem): integer;
332
function LeftCaptionPosition(const AMenuItem: TMenuItem): integer;
305
procedure GetNonTextSpace(const AMenuItem: TMenuItem;
306
AvgCharWidth: Integer;
307
out LeftSpace, RightSpace: Integer);
309
Space: Integer = SpaceNextToCheckMark;
310
CheckMarkWidth: Integer;
336
// If we have Check and Icon then we use only width of Icon
337
// we draw our MenuItem so: space Image space Caption
338
ImageWidth := MenuIconWidth(AMenuItem);
339
if ImageWidth = 0 then
340
ImageWidth := CheckSpace(aMenuItem);
342
Result := SpaceBetweenIcons;
344
inc(Result, ImageWidth);
346
if not aMenuItem.IsInMenuBar or (ImageWidth <> 0) then
347
inc(Result, SpaceBetweenIcons);
312
// If we have Check and Icon then we use only width of Icon.
313
// We draw our MenuItem so: space Image space Caption.
314
// Items not in menu bar always have enough space for a check mark.
316
CheckMarkWidth := GetSystemMetrics(SM_CXMENUCHECK);
317
LeftSpace := MenuIconWidth(AMenuItem);
319
if LeftSpace > 0 then
321
if not AMenuItem.IsInMenuBar then
323
if LeftSpace < CheckMarkWidth then
324
LeftSpace := CheckMarkWidth
326
if LeftSpace > CheckMarkWidth then
327
Space := SpaceNextToIcon;
332
if not AMenuItem.IsInMenuBar or AMenuItem.Checked then
333
LeftSpace := CheckMarkWidth;
336
if LeftSpace > 0 then
338
// Space to the left of the icon or check.
339
if not AMenuItem.IsInMenuBar then
340
Inc(LeftSpace, Space);
341
// Space between icon or check and caption.
342
if AMenuItem.Caption <> '' then
343
Inc(LeftSpace, Space);
346
if AMenuItem.IsInMenuBar then
349
RightSpace := CheckMarkWidth + AvgCharWidth;
351
if AMenuItem.Caption <> '' then
353
if AMenuItem.IsInMenuBar then
355
Inc(LeftSpace, AvgCharWidth);
356
Inc(RightSpace, AvgCharWidth);
360
// Space on the right side of the text.
361
Inc(RightSpace, SpaceNextToCheckMark);
350
366
function TopPosition(const aMenuItemHeight: integer; const anElementHeight: integer): integer;
401
417
if AMenuItem.ShortCut <> scNone then
403
W := UTF8ToUTF16(ShortCutToText(AMenuItem.ShortCut));
419
W := UTF8ToUTF16(MenuItemShortCut(AMenuItem));
404
420
GetThemeTextExtent(Theme, DC, MENU_POPUPITEM, 0, PWideChar(W), Length(W),
405
421
DT_SINGLELINE or DT_LEFT, nil, TextRect);
406
422
Result.ShortCustSize.cx := TextRect.Right - TextRect.Left;
761
777
TextRect.Left := TextRect.Right - Metrics.ShortCustSize.cx;
762
778
TextFlags := TextFlags xor DT_LEFT or DT_RIGHT;
765
ThemeDrawText(AHDC, Details, ShortCutToText(AMenuItem.ShortCut), TextRect, TextFlags, 0);
780
ThemeDrawText(AHDC, Details, MenuItemShortCut(AMenuItem), TextRect, TextFlags, 0);
767
782
// exlude menu item rectangle to prevent drawing by windows after us
768
783
if AMenuItem.Count > 0 then
789
806
if AMenuItem.Default then
790
decoration := [cfBold]
794
Result := StringSize(CompleteMenuItemCaption(AMenuItem, ' '), AHDC, decoration);
795
inc(Result.cx, LeftCaptionPosition(AMenuItem));
797
if not AMenuItem.IsInMenuBar then
798
inc(Result.cx, SpaceBetweenIcons)
800
dec(Result.cx, SpaceBetweenIcons);
802
if (AMenuItem.ShortCut <> scNone) then
803
Inc(Result.cx, SpaceBetweenIcons);
805
minimumHeight := GetSystemMetrics(SM_CYMENU);
806
if not AMenuItem.IsInMenuBar then
807
Dec(minimumHeight, 2);
807
newFont := GetMenuItemFont([cfBold])
809
newFont := GetMenuItemFont([]);
810
oldFont := SelectObject(aHDC, newFont);
811
AvgCharSize := GetAverageCharSize(AHDC);
813
Result := StringSize(CompleteMenuItemCaption(AMenuItem, EmptyStr), AHDC);
815
// Space between text and shortcut.
816
if AMenuItem.ShortCut <> scNone then
817
inc(Result.cx, AvgCharSize.cx);
819
GetNonTextSpace(AMenuItem, AvgCharSize.cx, LeftSpace, RightSpace);
820
inc(Result.cx, LeftSpace + RightSpace);
822
// Windows adds additional space to value returned from WM_MEASUREITEM
823
// for owner drawn menus. This is to negate that.
824
Dec(Result.cx, AvgCharSize.cx * 2);
826
// As for height of items in menu bar, regardless of what is set here,
827
// Windows seems to always use SM_CYMENUSIZE (space for a border is included).
808
829
if AMenuItem.IsLine then
809
Result.cy := 10 // it is a separator
830
Result.cy := GetSystemMetrics(SM_CYMENUSIZE) div 2 // it is a separator
812
if AMenuItem.hasIcon then
813
Result.cy := Max(Result.cy, aMenuItem.GetIconSize.y);
815
if Result.cy < minimumHeight then
816
Result.cy := minimumHeight;
833
if AMenuItem.IsInMenuBar then
835
Result.cy := Max(Result.cy, GetSystemMetrics(SM_CYMENUSIZE));
836
if AMenuItem.hasIcon then
837
Result.cy := Max(Result.cy, aMenuItem.GetIconSize.y);
841
Result.cy := Max(Result.cy + 2, AvgCharSize.cy + 4);
842
if AMenuItem.hasIcon then
843
Result.cy := Max(Result.cy, aMenuItem.GetIconSize.y + 2);
847
SelectObject(aHDC, oldFont);
848
DeleteObject(newFont);
820
851
function IsFlatMenus: Boolean; inline;
875
906
procedure DrawSeparator(const AHDC: HDC; const ARect: Windows.RECT);
877
908
separatorRect: Windows.RECT;
879
separatorRect.left := ARect.left;
880
separatorRect.right := ARect.right;
881
separatorRect.top := (ARect.top + ARect.bottom ) div 2 - 1;
882
separatorRect.bottom := separatorRect.top + 2;
883
DrawEdge(aHDC, separatorRect, BDR_SUNKENOUTER, BF_RECT);
916
separatorRect.Left := ARect.Left + space;
917
separatorRect.Right := ARect.Right - space;
918
separatorRect.Top := ARect.Top + GetSystemMetrics(SM_CYMENUSIZE) div 4 - 1;
919
DrawEdge(AHDC, separatorRect, EDGE_ETCHED, BF_TOP);
886
procedure DrawMenuItemCheckMark(const aMenuItem: TMenuItem; const aHDC: HDC; const aRect: Windows.RECT; const aSelected: boolean);
922
procedure DrawMenuItemCheckMark(const aMenuItem: TMenuItem; const aHDC: HDC;
923
const aRect: Windows.RECT; const aSelected: boolean; AvgCharWidth: Integer);
888
925
checkMarkWidth: integer;
889
926
checkMarkHeight: integer;
909
947
checkMarkShape := DFCS_MENUCHECK;
910
948
DrawFrameControl(hdcMem, @checkMarkRect, DFC_MENU, checkMarkShape);
949
if aMenuItem.IsInMenuBar then
950
space := AvgCharWidth
952
space := SpaceNextToCheckMark;
911
953
if aMenuItem.GetIsRightToLeft then
912
x := aRect.Right - checkMarkWidth - spaceBetweenIcons
954
x := aRect.Right - checkMarkWidth - space
914
x := aRect.left + spaceBetweenIcons;
956
x := aRect.left + space;
915
957
BitBlt(aHDC, x, aRect.top + topPosition(aRect.bottom - aRect.top, checkMarkRect.bottom - checkMarkRect.top), checkMarkWidth, checkMarkHeight, hdcMem, 0, 0, SRCCOPY);
916
958
SelectObject(hdcMem, oldBitmap);
917
959
DeleteObject(monoBitmap);
936
975
AnsiBuffer: ansistring;
937
976
WideBuffer: widestring;
938
977
{$endif WindowsUnicodeSupport}
978
LeftSpace, RightSpace: Integer;
940
980
crText := TextColorMenu(ItemState, AMenuItem.IsInMenuBar, AMenuItem.Enabled);
941
981
crBkgnd := BackgroundColorMenu(ItemState, AMenuItem.IsInMenuBar);
942
982
SetTextColor(AHDC, crText);
943
983
SetBkColor(AHDC, crBkgnd);
945
if AMenuItem.Default then
946
decoration := [cfBold]
950
newFont := GetMenuItemFont(decoration);
951
oldFont := SelectObject(AHDC, newFont);
952
985
IsRightToLeft := AMenuItem.GetIsRightToLeft;
954
986
etoFlags := ETO_OPAQUE;
955
dtFlags := DT_EXPANDTABS;
987
// DT_LEFT is default because its value is 0
988
dtFlags := DT_EXPANDTABS or DT_VCENTER or DT_SINGLELINE;
957
990
dtFlags := dtFlags or DT_HIDEPREFIX;
958
991
if IsRightToLeft then
967
1000
if AMenuItem.IsInMenuBar and not IsFlatMenus then
969
1002
if (ItemState and ODS_SELECTED) <> 0 then
970
DrawEdge(AHDC, ARect, BDR_SUNKENOUTER, BF_RECT or BF_ADJUST)
1004
DrawEdge(AHDC, ARect, BDR_SUNKENOUTER, BF_RECT);
1006
// Adjust caption position when menu is open.
1007
OffsetRect(ARect, 1, 1);
972
1010
if (ItemState and ODS_HOTLIGHT) <> 0 then
973
1011
DrawEdge(AHDC, ARect, BDR_RAISEDINNER, BF_RECT);
976
TmpHeight := ARect.Bottom - ARect.Top;
1014
GetNonTextSpace(AMenuItem, AvgCharWidth, LeftSpace, RightSpace);
1016
if IsRightToLeft then
1018
Dec(ARect.Right, LeftSpace);
1019
Inc(ARect.Left, RightSpace);
1023
Inc(ARect.Left, LeftSpace);
1024
Dec(ARect.Right, RightSpace);
1027
// Move text up by 1 pixel otherwise it is too low.
1029
Dec(ARect.Bottom, 1);
1031
oldBkMode := SetBkMode(AHDC, TRANSPARENT);
978
1033
{$ifdef WindowsUnicodeSupport}
979
1034
if UnicodeEnabledOS then
981
1036
WideBuffer := UTF8ToUTF16(AMenuItem.Caption);
982
DrawTextW(AHDC, PWideChar(WideBuffer), length(WideBuffer), @WorkRect, DT_CALCRECT);
1037
DrawTextW(AHDC, PWideChar(WideBuffer), Length(WideBuffer), @ARect, dtFlags);
986
1041
AnsiBuffer := Utf8ToAnsi(AMenuItem.Caption);
987
DrawText(AHDC, PChar(AnsiBuffer), length(AnsiBuffer), @WorkRect, DT_CALCRECT);
990
DrawText(AHDC, PChar(AMenuItem.Caption), length(AMenuItem.Caption), @WorkRect, DT_CALCRECT);
993
if IsRightToLeft then
994
Dec(ARect.Right, LeftCaptionPosition(AMenuItem))
996
Inc(ARect.Left, LeftCaptionPosition(AMenuItem));
997
Inc(ARect.Top, TopPosition(TmpHeight, WorkRect.Bottom - WorkRect.Top));
999
{$ifdef WindowsUnicodeSupport}
1000
if UnicodeEnabledOS then
1001
DrawTextW(AHDC, PWideChar(WideBuffer), Length(WideBuffer), @ARect, dtFlags)
1003
1042
DrawText(AHDC, PChar(AnsiBuffer), Length(AnsiBuffer), @ARect, dtFlags);
1005
1045
DrawText(AHDC, PChar(AMenuItem.Caption), Length(AMenuItem.Caption), @ARect, dtFlags);
1008
1048
if AMenuItem.ShortCut <> scNone then
1010
shortCutText := ShortCutToText(AMenuItem.ShortCut);
1050
dtFlags := DT_VCENTER or DT_SINGLELINE;
1051
shortCutText := MenuItemShortCut(AMenuItem);
1011
1052
if IsRightToLeft then
1013
Inc(ARect.Left, GetSystemMetrics(SM_CXMENUCHECK));
1053
dtFlags := dtFlags or DT_LEFT
1018
Dec(ARect.Right, GetSystemMetrics(SM_CXMENUCHECK));
1019
dtFlags := DT_RIGHT;
1055
dtFlags := dtFlags or DT_RIGHT;
1022
1057
{$ifdef WindowsUnicodeSupport}
1023
1058
if UnicodeEnabledOS then
1081
1116
const ARect: TRect; const ASelected, AChecked: boolean);
1119
Space: Integer = SpaceNextToCheckMark;
1084
1120
ImageRect: TRect;
1085
1121
IconSize: TPoint;
1122
checkMarkWidth: integer;
1087
1124
IconSize := AMenuItem.GetIconSize;
1125
checkMarkWidth := GetSystemMetrics(SM_CXMENUCHECK);
1126
if not AMenuItem.IsInMenuBar then
1128
if IconSize.x < checkMarkWidth then
1130
// Center the icon horizontally inside check mark space.
1131
Inc(Space, TopPosition(checkMarkWidth, IconSize.x));
1134
if IconSize.x > checkMarkWidth then
1136
Space := SpaceNextToIcon;
1088
1140
if AMenuItem.GetIsRightToLeft then
1089
x := ARect.Right - IconSize.x - spaceBetweenIcons
1141
x := ARect.Right - IconSize.x - Space
1091
x := ARect.Left + spaceBetweenIcons;
1143
x := ARect.Left + Space;
1093
1145
ImageRect := Rect(x, ARect.top + TopPosition(ARect.Bottom - ARect.Top, IconSize.y),
1094
1146
IconSize.x, IconSize.y);
1103
1155
DrawMenuItemIcon(AMenuItem, AHDC, ImageRect, ASelected);
1158
procedure DrawClassicMenuItem(const AMenuItem: TMenuItem; const AHDC: HDC;
1159
const ARect: Windows.RECT; const ASelected, ANoAccel: boolean; ItemState: UINT);
1163
AvgCharWidth: Integer;
1165
if AMenuItem.IsLine then
1166
DrawSeparator(AHDC, ARect)
1169
if AMenuItem.Default then
1170
newFont := GetMenuItemFont([cfBold])
1172
newFont := GetMenuItemFont([]);
1173
oldFont := SelectObject(AHDC, newFont);
1174
AvgCharWidth := GetAverageCharSize(AHDC).cx;
1176
DrawMenuItemText(AMenuItem, AHDC, ARect, ASelected, ANoAccel, ItemState, AvgCharWidth);
1177
if aMenuItem.HasIcon then
1178
DrawClassicMenuItemIcon(AMenuItem, AHDC, ARect, ASelected, AMenuItem.Checked)
1180
if AMenuItem.Checked then
1181
DrawMenuItemCheckMark(AMenuItem, AHDC, ARect, ASelected, AvgCharWidth);
1183
SelectObject(AHDC, oldFont);
1184
DeleteObject(newFont);
1106
1188
procedure DrawMenuItem(const AMenuItem: TMenuItem; const AHDC: HDC; const ARect: Windows.RECT; const ItemAction, ItemState: UINT);
1108
1190
ASelected, ANoAccel: Boolean;
1122
1204
DrawVistaMenuBar(AMenuItem, AHDC, ARect, ASelected, ANoAccel, ItemAction, ItemState)
1124
1206
DrawVistaPopupMenu(AMenuItem, AHDC, ARect, ASelected, ANoAccel);
1128
if aMenuItem.IsLine then
1129
DrawSeparator(AHDC, ARect)
1132
DrawMenuItemText(AMenuItem, AHDC, ARect, ASelected, ANoAccel, ItemState);
1133
if aMenuItem.HasIcon then
1134
DrawClassicMenuItemIcon(AMenuItem, AHDC, ARect, ASelected, AMenuItem.Checked)
1136
if AMenuItem.Checked then
1137
DrawMenuItemCheckMark(AMenuItem, AHDC, ARect, ASelected);
1209
DrawClassicMenuItem(AMenuItem, AHDC, ARect, ASelected, ANoAccel, ItemState);
1141
1212
procedure TriggerFormUpdate(const AMenuItem: TMenuItem);
1368
1439
UpdateCaption(AMenuItem, aCaption);
1371
class function TWin32WSMenuItem.SetCheck(const AMenuItem: TMenuItem;
1372
const Checked: boolean): boolean;
1442
class function TWin32WSMenuItem.SetCheck(const AMenuItem: TMenuItem; const Checked: boolean): boolean;
1374
1444
UpdateCaption(AMenuItem, aMenuItem.Caption);
1375
1445
Result := Checked;
1378
class procedure TWin32WSMenuItem.SetShortCut(const AMenuItem: TMenuItem;
1379
const OldShortCut, NewShortCut: TShortCut);
1448
class procedure TWin32WSMenuItem.SetShortCut(const AMenuItem: TMenuItem; const ShortCutK1, ShortCutK2: TShortCut);
1381
1450
UpdateCaption(AMenuItem, aMenuItem.Caption);