1unit MenuEditor;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8  // FCL
9  Classes, SysUtils, Types, typinfo, strutils,
10  // LCL
11  ActnList, Controls, Dialogs, StdCtrls, ExtCtrls, Menus,
12  Forms, Graphics, ImgList, Themes, LCLType, LCLIntf, LCLProc,
13  // LazUtils
14  LazLogger, LazTracer,
15  // IdeIntf
16  FormEditingIntf, IDEWindowIntf, IDEImagesIntf, ComponentEditors, IDEDialogs,
17  PropEdits,
18  // IDE
19  LazarusIDEStrConsts, LazIDEIntf, MenuDesignerBase, MenuEditorForm, MenuShortcutDisplay,
20  MenuTemplates, MenuResolveConflicts;
21
22type
23
24  TShadowMenu = class;
25  TShadowBox = class;
26
27  { TFake }
28
29  TFake = class(TCustomControl)
30  private
31    FShadowMenu: TShadowMenu;
32    FMinWidth: integer;
33  protected
34    function GetShouldBeVisible: boolean; virtual; abstract;
35    procedure SetVisibilitySizeAndPosition; virtual; abstract;
36    procedure TextChanged; override;
37    procedure Paint; override;
38    class function GetControlClassDefaultSize: TSize; override;
39  public
40    constructor Create(anOwner: TShadowMenu); reintroduce;
41    procedure Refresh;
42    property ShouldBeVisible: boolean read GetShouldBeVisible;
43  end;
44
45  TAddSiblingFake = class(TFake)
46  protected
47    function GetShouldBeVisible: boolean; override;
48    procedure SetVisibilitySizeAndPosition; override;
49  end;
50
51  TAddSubmenuFake = class(TFake)
52  protected
53    function GetShouldBeVisible: boolean; override;
54    procedure SetVisibilitySizeAndPosition; override;
55  end;
56
57  TAddFirstFake = class(TFake)
58  protected
59    function GetShouldBeVisible: boolean; override;
60    procedure SetVisibilitySizeAndPosition; override;
61  end;
62
63  TMenuDesigner = class;
64
65  { TShadowItem }
66
67  TShadowItem = class(TShadowItemBase)
68  strict private
69    FBottomFake: TFake;
70    FParentBox: TShadowBox;
71    FRightFake: TFake;
72    FShadowMenu: TShadowMenu;
73    FShowingBottomFake: boolean;
74    FShowingRightFake: boolean;
75    function GetBitmapLeftTop: TPoint;
76    function GetBottomFake: TFake;
77    function GetIconTopLeft: TPoint;
78    function GetIsInMenuBar: boolean;
79    function GetIsMainMenu: boolean;
80    function GetLevel: integer;
81    function GetRightFake: TFake;
82    function GetShortcutWidth: integer;
83    function GetShowingBottomFake: boolean;
84    function GetShowingRightFake: boolean;
85    function GetSubImagesIconTopLeft: TPoint;
86    procedure RecursiveHideChildren(aMI: TMenuItem);
87  private
88    function HasChildBox(out aChildBox: TShadowBoxBase): boolean;
89    procedure HideChainFromRoot;
90    procedure HideChildren;
91    procedure ShowChainToRoot;
92    procedure ShowChildBox;
93  protected
94    procedure DblClick; override;
95    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
96    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
97    procedure Paint; override;
98  public
99    constructor CreateWithBoxAndItem(aSMenu: TShadowMenu; aParentBox: TShadowBox;
100      aRealItem: TMenuItem);
101    function GetWidth: integer; override;
102    procedure Invalidate; override;
103  public
104    property BottomFake: TFake read GetBottomFake write FBottomFake;
105    property IsInMenuBar: boolean read GetIsInMenuBar;
106    property IsMainMenu: boolean read GetIsMainMenu;
107    property Level: integer read GetLevel;
108    property ParentBox: TShadowBox read FParentBox;
109    property RightFake: TFake read GetRightFake write FRightFake;
110    property ShowingBottomFake: boolean read GetShowingBottomFake write FShowingBottomFake;
111    property ShowingRightFake: boolean read GetShowingRightFake write FShowingRightFake;
112  end;
113
114  { TShadowBox }
115
116  TShadowBox = class(TShadowBoxBase)
117  strict private
118    FShadowMenu: TShadowMenu;
119    FUpdating: boolean;
120    procedure BeginUpdate;
121    procedure EndUpdate;
122    procedure ShowAllUnSelected;
123  private
124    procedure AddItemAndShadow(existingSI: TShadowItem; addBefore: boolean;
125      isSeparator: boolean=False);
126    procedure LocateShadows;
127    procedure RemoveAllSeparators;
128    procedure SelectPrevious(aSI: TShadowItem);
129    procedure SelectSuccessor(aSI: TShadowItem);
130    property Updating: boolean read FUpdating;
131  protected
132    function GetIsMainMenu: boolean; override;
133    function GetIsMenuBar: boolean; override;
134    procedure Paint; override;
135  public
136    constructor CreateWithParentBox(aSMenu: TShadowMenu; aParentBox: TShadowBox;
137      aParentItem: TMenuItem);
138    procedure SetUnCheckedAllExcept(aMI: TMenuItem);
139  end;
140
141  TPopEnum = {%region}
142    (popItemMoveBefore, popItemMoveAfter,
143     popSeparators_,
144       popAddSeparatorBefore, popAddSeparatorAfter, popRemoveAllSeparators,
145     popItemDelete, popItemAddBefore, popItemAddAfter, popItemAddSubMenu,
146     popItemSep,
147     popAddImgListIcon, popItemAddOnClick, popItemEditCaption,
148     popItemOISep,
149     popShortcuts_,
150       popListShortcuts, popListShortcutsAccelerators, popResolveShortcutConflicts,
151     popTemplates_,
152       popSaveAsTemplate, popAddFromTemplate, popDeleteTemplate);{%endregion}
153
154  { TShadowMenu }
155
156  TShadowMenu = class(TShadowMenuBase)
157  strict private
158    FActionList: TActionList;
159    FAddImgListIconAction: TAction;
160    FAddItemFake: TFake;
161    FAddFirstItemFake: TFake;
162    FAddSubmenuFake: TFake;
163    FInitialising: boolean;
164    FInitialSelectedMenuItem: TMenuItem;
165    FItemsPopupMenu: TPopupMenu;
166    FRootBox: TShadowBox;
167    FInPlaceEditor: TEdit;
168    FEditedMenuItem: TMenuItem;
169    procedure DeleteBox(aMI: TMenuItem);
170    procedure DeleteItm(anItem: TMenuItem);
171    function GetActionForEnum(anEnum: TPopEnum): TAction;
172    function GetMaxVisibleBoxDims(aSB: TShadowBox): TPoint;
173    function GetMaxVisibleFakeDims: TPoint;
174    function GetMenuBarCumWidthForItemIndex(anIndex: integer): integer;
175    function GetParentItemHeightInBox(aParentItem: TMenuItem): integer;
176    function GetSelectedShadowBox: TShadowBox;
177    function GetSelectedShadowItem: TShadowItem;
178    procedure AddManyItems(aPrimaries, aDepth: integer);
179    procedure AddSubMenuTo(anExistingSI: TShadowItem);
180    procedure ConnectSpeedButtonOnClickMethods;
181    procedure CreateShadowBoxesAndItems;
182    procedure DeleteChildlessShadowAndItem(anExistingSI: TShadowItem);
183    procedure DeleteShadowAndItemAndChildren(anExistingSI: TShadowItem);
184    procedure InPlaceEditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
185    procedure OnDesignerModified(Sender: TObject);
186    procedure OnObjectPropertyChanged(Sender: TObject; NewObject: TPersistent);
187    procedure OnDesignerRefreshPropertyValues;
188    procedure RecursiveCreateShadows(aParentBox: TShadowBox; aMI: TMenuItem);
189    procedure SetupPopupMenu;
190    procedure StopEditingCaption;
191    procedure UpdateButtonGlyphs(isInBar: boolean);
192    // user actions
193    procedure AddFromTemplate(Sender: TObject);
194    procedure AddImageListIcon(Sender: TObject);
195    procedure AddItemAfter(Sender: TObject);
196    procedure AddItemBefore(Sender: TObject);
197    procedure AddSeparatorAbove(Sender: TObject);
198    procedure AddSeparatorBelow(Sender: TObject);
199    procedure AddSubMenu(Sender: TObject);
200    procedure AddFirstMenu(Sender: TObject);
201    procedure DeleteTemplate(Sender: TObject);
202    procedure EditCaption(Sender: TObject);
203    procedure ListShortcuts(Sender: TObject);
204    procedure ListShortcutsAndAccelerators(Sender: TObject);
205    procedure MoveItemAfter(Sender: TObject);
206    procedure MoveItemBefore(Sender: TObject);
207    procedure RemoveAllSeparators(Sender: TObject);
208    procedure ResolveShortcutConflicts(Sender: TObject);
209    procedure SaveAsTemplate(Sender: TObject);
210  private
211    FDesigner: TMenuDesigner;
212    function GetMenuBarIconWidth(aMI: TMenuItem): integer;
213    function OnClickIsAssigned(aMI: TMenuItem): boolean;
214    procedure AddOnClick(Sender: TObject);
215    procedure DeleteItem(Sender: TObject);
216    function GetBoxWithParentItem(aParentMI: TMenuItem): TShadowBoxBase;
217    procedure HideFakes;
218    procedure RemoveEmptyBox(aSB: TShadowBox);
219    procedure SetSelectedShadow(const prevSelectedItem, curSelectedItem: TMenuItem; viaDesigner: boolean);
220    procedure UpdateActionsEnabledness;
221  private
222    property AddItemFake: TFake read FAddItemFake;
223    property AddSubmenuFake: TFake read FAddSubmenuFake;
224    property ItemsPopupMenu: TPopupMenu read FItemsPopupMenu;
225    property RootBox: TShadowBox read FRootBox;
226  protected
227    procedure Paint; override;
228    procedure SetParent(NewParent: TWinControl); override;
229  public
230    constructor Create(aDesigner: TMenuDesigner; aForm: TForm; aMenu: TMenu;
231      aSelect: TMenuItem; aWidth, aHeight: integer); reintroduce;
232    destructor Destroy; override;
233    procedure HideBoxesAboveLevel(aLevel: integer);
234    procedure RefreshFakes; override;
235    procedure SetSelectedMenuItem(aMI: TMenuItem;
236      viaDesigner, prevWasDeleted: boolean); override;
237    procedure UpdateBoxLocationsAndSizes; override;
238    procedure UpdateSelectedItemInfo;
239  public
240    property SelectedShadowBox: TShadowBox read GetSelectedShadowBox;
241    property SelectedShadowItem: TShadowItem read GetSelectedShadowItem;
242  end;
243
244  { TMenuDesigner }
245
246  TMenuDesigner = class(TMenuDesignerBase)
247  private
248    FGui: TMenuDesignerForm;
249  public
250    constructor Create;
251    destructor Destroy; override;
252    procedure CreateShadowMenu(aMenu: TMenu; aSelect: TMenuItem;
253      aWidth, aHeight: integer); override;
254  end;
255
256  { TMenuComponentEditor - the default component editor for TMenu }
257
258  TMainMenuComponentEditor = class(TComponentEditor)
259  public
260    procedure Edit; override;
261    function GetVerbCount: Integer; override;
262    function GetVerb(Index: Integer): string; override;
263    procedure ExecuteVerb(Index: Integer); override;
264  end;
265
266procedure ShowMenuEditor(aMenu: TMenu);
267function MenuDesigner: TMenuDesigner;
268
269
270implementation
271
272const
273  Shortcut_Offset = 23;
274  Popup_Origin: TPoint = (x:15; y:15);
275
276var
277  ShadowItemID: integer = 0;
278  ShadowBoxID: integer = 0;
279
280  MenuDesignerSingleton: TMenuDesigner = nil;
281
282procedure ShowMenuEditor(aMenu: TMenu);
283begin
284  if (aMenu = nil) then
285    RaiseGDBException(lisMenuEditorShowMenuEditorTMenuParameterIsNil);
286  MenuDesigner.FGui.SetMenu(aMenu, nil);
287  SetPopupModeParentForPropertyEditor(MenuDesigner.FGui);
288  MenuDesigner.FGui.ShowOnTop;
289end;
290
291function MenuDesigner: TMenuDesigner; // refer always to a single instance
292begin
293  if (MenuDesignerSingleton = nil) then
294    MenuDesignerSingleton:=TMenuDesigner.Create;
295  Result:=MenuDesignerSingleton;
296end;
297
298// utility functions
299{
300function ItemStateToStr(aState: TShadowItemDisplayState): string;
301begin
302  Result:=GetEnumName(TypeInfo(TShadowItemDisplayState), Ord(aState));
303end;
304}
305function GetPreviousNonSepItem(aMI: TMenuItem): TMenuItem;
306var
307  idx: integer;
308begin
309  Result:=nil;
310  idx:=aMI.MenuIndex;
311  if (idx = 0) then
312    Exit
313  else repeat
314    idx:=Pred(idx);
315    Result:=aMI.Parent.Items[idx];
316  until not Result.IsLine or (idx = 0);
317  if Result.IsLine then
318    Result:=nil;
319end;
320
321function GetPreviousItem(aMI: TMenuItem): TMenuItem;
322var
323  idx: integer;
324begin
325  idx:=aMI.MenuIndex;
326  if (idx = 0) then
327    Exit(nil)
328  else
329    Result:=aMI.Parent.Items[Pred(idx)];
330end;
331
332function GetNextItem(aMI: TMenuItem): TMenuItem;
333var
334  idx: integer;
335begin
336  idx:=aMI.MenuIndex;
337  if (idx = Pred(aMI.Parent.Count)) then
338    Exit(nil)
339  else
340    Result:=aMI.Parent.Items[Succ(idx)];
341end;
342
343function GetNextNonSepItem(aMI: TMenuItem): TMenuItem;
344var
345  idx, maxIdx: integer;
346begin
347  Result:=nil;
348  idx:=aMI.MenuIndex;
349  maxIdx:=Pred(aMI.Parent.Count);
350  if (idx = maxIdx) then
351    Exit
352  else repeat
353    idx:=Succ(idx);
354    Result:=aMI.Parent.Items[idx];
355  until not Result.IsLine or (idx = maxIdx);
356  if Result.IsLine then
357    Result:=nil;
358end;
359
360function PreviousItemIsSeparator(aMI: TMenuItem): boolean;
361var
362  idx: integer;
363begin
364  if (aMI = nil) then
365    Exit(False);
366  idx:=aMI.MenuIndex;
367  Result:=(idx > 0) and aMI.Parent.Items[Pred(idx)].IsLine;
368end;
369
370function NextItemIsSeparator(aMI: TMenuItem): boolean;
371var
372  idx: integer;
373begin
374  if (aMI = nil) then
375    Exit(False);
376  idx:=aMI.MenuIndex;
377  Result:=(idx < Pred(aMI.Parent.Count)) and aMI.Parent.Items[Succ(idx)].IsLine;
378end;
379
380function GetChildSeparatorCount(aMI: TMenuItem): integer;
381var
382  i: integer;
383begin
384  Result:=0;
385  for i:=0 to aMI.Count-1 do
386    if aMI.Items[i].IsLine then
387      Inc(Result);
388end;
389
390function AIsDescendantOfB(miA, miB: TMenuItem): boolean;
391var
392  tmp: TMenuItem;
393begin
394  if (miA = nil) or (miB = nil) then
395    Exit(False);
396  tmp:=miA.Parent;
397  repeat
398    if (tmp = miB) then
399      Exit(True);
400    tmp:=tmp.Parent;
401  until (tmp = nil);
402  Result:=False;
403end;
404
405function LevelZeroAndNoGrandchildren(aMI: TMenuItem): boolean;
406var
407  i: integer;
408begin
409  Result:=(aMI.Parent <> nil) and (aMI.Parent.Parent = nil);
410  if Result then
411    for i:=0 to aMI.Count-1 do
412      if (aMI.Items[i].Count > 0) then
413        Exit(False);
414end;
415
416function SortByItemMenuIndex(const Item1, Item2: TShadowItemBase): Integer;
417var
418  i1, i2: integer;
419begin
420  i1:=Item1.RealItem.MenuIndex;
421  i2:=Item2.RealItem.MenuIndex;
422  if (i1 > i2) then
423    Result:=1
424  else if (i2 > i1) then
425    Result:= -1
426  else
427    Result:=0;
428end;
429
430function SortByBoxLevel(const Item1, Item2: TShadowBoxBase): Integer;
431var
432  lvl1, lvl2: integer;
433begin
434  lvl1:=Item1.Level;
435  lvl2:=Item2.Level;
436  if (lvl1 > lvl2) then
437    Result:=1
438  else if (lvl1 < lvl2) then
439    Result:= -1
440  else
441    Result:=0;
442end;
443
444{ TAddFirstFake }
445
446function TAddFirstFake.GetShouldBeVisible: boolean;
447begin
448  Result:=(FShadowMenu.FMenu<>nil) and (FShadowMenu.FMenu.Items.Count=0);
449end;
450
451procedure TAddFirstFake.SetVisibilitySizeAndPosition;
452begin
453  if ShouldBeVisible then begin
454    SetBounds(Left, Top, FMinWidth, DropDown_Height);
455    Show;
456  end
457  else begin
458    Hide;
459  end;
460end;
461
462{ TAddSubmenuFake }
463
464function TAddSubmenuFake.GetShouldBeVisible: boolean;
465var
466  item: TMenuItem;
467begin
468  item:=FShadowMenu.SelectedMenuItem;
469  if (item = nil) then
470    Exit(False)
471  else
472    Result:=not item.IsLine and (item.Count = 0);
473end;
474
475procedure TAddSubmenuFake.SetVisibilitySizeAndPosition;
476var
477  selShadow: TShadowItem;
478  selMI: TMenuItem;
479  w: integer;
480begin
481  selMI:=FShadowMenu.SelectedMenuItem;
482  if (selMI=nil) then
483    Exit;
484  selShadow:=TShadowItem(FShadowMenu.GetShadowForMenuItem(selMI));
485  if selShadow=nil then Exit;
486  if not ShouldBeVisible then begin
487    if selMI.IsInMenuBar then
488      selShadow.BottomFake:=nil
489    else
490      selShadow.RightFake:=nil;
491    Hide;
492  end
493  else begin
494    w:=FMinWidth;
495    if selMI.IsInMenuBar then begin
496      if (selShadow.Width > w) then
497        w:=selShadow.Width;
498      SetBounds(selShadow.Left, MenuBar_Height + 1, w, DropDown_Height);
499      selShadow.ShowingBottomFake:=True;
500      selShadow.BottomFake:=Self;
501      selShadow.ShowingRightFake:=False;
502    end
503    else begin
504      SetBounds(selShadow.ParentBox.Left + selShadow.BoundsRect.Right + 1,
505                selShadow.ParentBox.Top + selShadow.Top, w, DropDown_Height);
506      selShadow.ShowingRightFake:=True;
507      selShadow.RightFake:=Self;
508      selShadow.ShowingBottomFake:=False;
509    end;
510    Show;
511  end;
512end;
513
514{ TAddSiblingFake }
515
516function TAddSiblingFake.GetShouldBeVisible: boolean;
517var
518  item: TMenuItem;
519begin
520  item:=FShadowMenu.SelectedMenuItem;
521  if (item = nil) then
522    Exit(False)
523  else
524    Result:=(item.MenuIndex = Pred(item.Parent.Count));
525end;
526
527procedure TAddSiblingFake.SetVisibilitySizeAndPosition;
528var
529  selShadow: TShadowItem;
530  selMI: TMenuItem;
531  w: integer;
532begin
533  selMI:=FShadowMenu.SelectedMenuItem;
534  if (selMI=nil) then
535    Exit;
536  selShadow:=TShadowItem(FShadowMenu.GetShadowForMenuItem(selMI));
537  if selShadow=nil then Exit;
538  if not ShouldBeVisible then begin
539    if selMI.IsInMenuBar then
540      selShadow.RightFake:=nil
541    else
542      selShadow.BottomFake:=nil;
543    Hide;
544  end
545  else begin
546    if selMI.IsInMenuBar then begin
547      SetBounds(selShadow.Left + selShadow.Width + 1, 0, FMinWidth, MenuBar_Height);
548      selShadow.ShowingRightFake:=True;
549      selShadow.RightFake:=Self;
550      selShadow.ShowingBottomFake:=False;
551    end
552    else begin
553      w:=selShadow.ParentBox.Width - Gutter_X - 1;
554      if (FMinWidth > w) then
555        w:=FMinWidth;
556      SetBounds(selShadow.ParentBox.Left + selShadow.Left + Gutter_X,
557                selShadow.ParentBox.Top + selShadow.ParentBox.Height + 1,
558                w, DropDown_Height);
559      selShadow.ShowingBottomFake:=True;
560      selShadow.BottomFake:=Self;
561      selShadow.ShowingRightFake:=False;
562    end;
563    Invalidate;
564    Show;
565  end;
566end;
567
568{ TFake }
569
570constructor TFake.Create(anOwner: TShadowMenu);
571begin
572  inherited Create(anOwner);
573  FShadowMenu:=anOwner;
574  with GetControlClassDefaultSize do
575    SetInitialBounds(0, 0, cx, cy);
576  BorderStyle:=bsNone;
577  Visible:=False;
578  Canvas.Pen.Color:=clBtnText;
579  Canvas.Pen.Style:=psDot;
580  Canvas.Font.Color:=clBtnText;
581  Canvas.Brush.Color:=clBtnFace;
582  Parent:=anOwner;
583end;
584
585class function TFake.GetControlClassDefaultSize: TSize;
586begin
587  Result.cx:=100;
588  Result.cy:=DropDown_Height;
589end;
590
591procedure TFake.Paint;
592var
593  r: TRect;
594  TextSize: TSize;
595  TextPoint, AddBmpPoint: TPoint;
596  AddBmp: TImageIndex;
597  IL: TLCLGlyphs;
598  Res: TScaledImageListResolution;
599begin
600  r:=ClientRect;
601  Canvas.FillRect(r);
602  Canvas.RoundRect(r, 3, 3);
603  IL:=IDEImages.Images_16;
604  AddBmp:=IL.GetImageIndex('laz_add');
605  Res:=IL.ResolutionForControl[0, Self];
606  TextSize:=Canvas.TextExtent(Caption);
607  TextPoint.y:=(r.Bottom - r.Top - TextSize.cy) div 2;
608  if (TextPoint.y < 1) then
609    TextPoint.y:=1;
610  TextPoint.x:=(r.Right - r.Left - TextSize.cx + Res.Width) div 2;
611  Canvas.TextRect(r, TextPoint.x, TextPoint.y, Caption);
612
613  AddBmpPoint.x:=(TextPoint.x - Res.Width) div 2;
614  AddBmpPoint.y:=(r.Bottom - r.Top - Res.Height) div 2;
615  Res.Draw(Canvas, AddBmpPoint.x, AddBmpPoint.y, AddBmp);
616end;
617
618procedure TFake.Refresh;
619begin
620  SetVisibilitySizeAndPosition;
621end;
622
623procedure TFake.TextChanged;
624begin
625  inherited TextChanged;
626  FMinWidth:=FShadowMenu.GetStringWidth(Caption, False) +
627             Double_MenuBar_Text_Offset +
628             Add_Icon_Width;
629end;
630
631{ TShadowMenu }
632
633procedure TShadowMenu.AddItemAfter(Sender: TObject);
634var
635  si: TShadowItem;
636begin
637  si:=SelectedShadowItem;
638  if (si <> nil) then
639    si.ParentBox.AddItemAndShadow(si, False);
640end;
641
642procedure TShadowMenu.AddItemBefore(Sender: TObject);
643var
644  si: TShadowItem;
645begin
646  si:=SelectedShadowItem;
647  if (si <> nil) then
648    si.ParentBox.AddItemAndShadow(si, True);
649end;
650
651procedure TShadowMenu.AddOnClick(Sender: TObject);
652var
653  CompEditor: TDefaultComponentEditor;
654begin
655  if (FSelectedMenuItem <> nil) then begin
656    FDesigner.FGui.BeginUpdate;
657    CompEditor:=nil;
658    try
659      CompEditor:=TDefaultComponentEditor.Create(FSelectedMenuItem, FEditorDesigner);
660      CompEditor.Edit;
661      UpdateSelectedItemInfo;
662    finally
663      CompEditor.Free;
664      FDesigner.FGui.EndUpdate;
665    end;
666  end;
667end;
668
669procedure TShadowMenu.AddSubMenu(Sender: TObject);
670var
671  si: TShadowItem;
672begin
673  si:=SelectedShadowItem;
674  if (si <> nil) then begin
675    HideFakes;
676    AddSubMenuTo(si);
677  end;
678end;
679
680procedure TShadowMenu.DeleteItem(Sender: TObject);
681var
682  si: TShadowItem;
683begin
684  if (FDesigner.TotalMenuItemsCount > 0) then
685  begin
686    if (Sender is TShadowItem) then
687      DeleteChildlessShadowAndItem(TShadowItem(Sender))
688    else begin
689      si:=SelectedShadowItem;
690      if (si <> nil) then
691        DeleteChildlessShadowAndItem(si);
692    end;
693  end;
694end;
695
696procedure TShadowMenu.EditCaption(Sender: TObject);
697var
698  SelShadow: TShadowItem;
699begin
700  SelShadow := SelectedShadowItem;
701  if (SelShadow <> nil) then begin
702    HideFakes;
703    FEditedMenuItem := FSelectedMenuItem;
704    FInPlaceEditor.Parent := SelShadow;
705    // ToDo: Calculate Left and Width properly.
706    FInPlaceEditor.Left := 24;
707    FInPlaceEditor.Width := SelShadow.Width - 24;
708    FInPlaceEditor.Text := FEditedMenuItem.Caption;
709    FInPlaceEditor.Visible := True;
710    FInPlaceEditor.SetFocus;
711  end;
712end;
713
714procedure TShadowMenu.StopEditingCaption;
715var
716  EditedShadow: TShadowItem;
717  s: TCaption;
718begin
719  if not FInPlaceEditor.Visible then Exit;
720  Assert(Assigned(FEditedMenuItem), 'TShadowMenu.StopEditingCaption: FEditedMenuItem = Nil');
721  EditedShadow := TShadowItem(GetShadowForMenuItem(FEditedMenuItem));
722  s := FInPlaceEditor.Text;
723  if s <> '' then
724  begin
725    FEditedMenuItem.Caption:=s;
726    if (s = cLineCaption) and AnsiStartsStr('MenuItem', FEditedMenuItem.Name) then
727      FEditedMenuItem.Name:=FEditorDesigner.CreateUniqueComponentName('N');
728    GlobalDesignHook.RefreshPropertyValues;
729    GlobalDesignHook.Modified(FEditedMenuItem);
730    EditedShadow.Invalidate;
731  end;
732  EditedShadow.SetFocus;
733  FInPlaceEditor.Text := '';
734  FInPlaceEditor.Visible := False;
735  FInPlaceEditor.Parent := Nil;
736  FEditedMenuItem := Nil;
737  RefreshFakes;
738end;
739
740procedure TShadowMenu.InPlaceEditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
741begin
742  case Key of
743    VK_ESCAPE: begin Key:=0; FInPlaceEditor.Text := ''; StopEditingCaption; end;
744    VK_RETURN: begin Key:=0; StopEditingCaption; end;
745    else inherited KeyDown(Key, Shift);
746  end;
747end;
748
749procedure TShadowMenu.AddSeparatorAbove(Sender: TObject);
750var
751  selected: TShadowItem;
752begin
753  if (FSelectedMenuItem <> nil) then begin
754    selected:=SelectedShadowItem;
755    selected.ParentBox.AddItemAndShadow(selected, True, True);
756  end;
757end;
758
759procedure TShadowMenu.AddSeparatorBelow(Sender: TObject);
760var
761  selected: TShadowItem;
762begin
763  if (FSelectedMenuItem <> nil) then begin
764    selected:=SelectedShadowItem;
765    selected.ParentBox.AddItemAndShadow(selected, False, True);
766  end;
767end;
768
769procedure TShadowMenu.MoveItemAfter(Sender: TObject);
770var
771  nextI, parentI: TMenuItem;
772  currIdx: integer;
773  selected: TShadowItem;
774begin
775  if (FSelectedMenuItem <> nil) then begin
776    nextI:=GetNextItem(FSelectedMenuItem);
777    parentI:=FSelectedMenuItem.Parent;
778    selected:=SelectedShadowItem;
779    if (nextI <> nil) and (parentI <> nil) then
780      begin
781        HideFakes;
782        HideBoxesAboveLevel(selected.Level);
783        currIdx:=FSelectedMenuItem.MenuIndex;
784        parentI.Remove(nextI);
785        parentI.Remove(FSelectedMenuItem);
786        parentI.Insert(currIdx, nextI);
787        parentI.Insert(Succ(currIdx), FSelectedMenuItem);
788        FEditorDesigner.PropertyEditorHook.RefreshPropertyValues;
789        FEditorDesigner.PropertyEditorHook.Modified(FMenu);
790        selected.ParentBox.LocateShadows;
791        UpdateBoxLocationsAndSizes;
792        selected.ShowChildBox;
793        RefreshFakes;
794        UpdateActionsEnabledness;
795      end;
796  end;
797end;
798
799procedure TShadowMenu.MoveItemBefore(Sender: TObject);
800var
801  previousI, parentI: TMenuItem;
802  currIdx: integer;
803  selected: TShadowItem;
804begin
805  if (FSelectedMenuItem <> nil) then begin
806    previousI:=GetPreviousItem(FSelectedMenuItem);
807    parentI:=FSelectedMenuItem.Parent;
808    selected:=SelectedShadowItem;
809    if (previousI <> nil) and (parentI <> nil) then
810      begin
811        HideFakes;
812        HideBoxesAboveLevel(selected.Level);
813        currIdx:=FSelectedMenuItem.MenuIndex;
814        parentI.Remove(previousI);
815        parentI.Remove(FSelectedMenuItem);
816        parentI.Insert(Pred(currIdx), FSelectedMenuItem);
817        parentI.Insert(currIdx, previousI);
818        FEditorDesigner.PropertyEditorHook.RefreshPropertyValues;
819        FEditorDesigner.PropertyEditorHook.Modified(FMenu);
820        selected.ParentBox.LocateShadows;
821        UpdateBoxLocationsAndSizes;
822        selected.ShowChildBox;
823        RefreshFakes;
824        UpdateActionsEnabledness;
825      end;
826  end;
827end;
828
829procedure TShadowMenu.RemoveAllSeparators(Sender: TObject);
830begin
831  if (FSelectedMenuItem <> nil) then
832    SelectedShadowItem.ParentBox.RemoveAllSeparators;
833end;
834
835procedure TShadowMenu.ConnectSpeedButtonOnClickMethods;
836begin
837  with FDesigner.FGui do begin
838    AddSeparatorAboveButton.OnClick:=@AddSeparatorAbove;
839    AddSeparatorBelowButton.OnClick:=@AddSeparatorBelow;
840    MoveItemUpButton.OnClick:=@MoveItemBefore;
841    MoveItemDownButton.OnClick:=@MoveItemAfter;
842    DeleteItemButton.OnClick:=@DeleteItem;
843    AddItemAboveButton.OnClick:=@AddItemBefore;
844    AddItemBelowButton.OnClick:=@AddItemAfter;
845    AddSubMenuButton.OnClick:=@AddSubMenu;
846  end;
847end;
848
849procedure TShadowMenu.RecursiveCreateShadows(aParentBox: TShadowBox; aMI: TMenuItem);
850var
851  j: integer;
852  sb: TShadowBox;
853begin
854  TShadowItem.CreateWithBoxAndItem(Self, aParentBox, aMI);
855  if (aMI.Count > 0) then
856  begin
857    sb:=TShadowBox.CreateWithParentBox(Self, aParentBox, aMI);
858    for j:=0 to aMI.Count-1 do
859      RecursiveCreateShadows(sb, aMI.Items[j]);
860  end;
861end;
862
863procedure TShadowMenu.CreateShadowBoxesAndItems;
864var
865  i: integer;
866begin
867  if (FMenu.Items.Count > 0) then
868  begin
869    FRootBox:=TShadowBox.CreateWithParentBox(Self, nil, FMenu.Items);
870    for i:=0 to FMenu.Items.Count-1 do begin
871      if IsMainMenu and FMenu.Items[i].IsLine then
872        RaiseGDBException(lisMenuEditorSomeWidgetsetsDoNotAllowSeparatorsInTheMainMenubar);
873      RecursiveCreateShadows(FRootBox, FMenu.Items[i]);
874    end;
875  end;
876end;
877
878procedure TShadowMenu.DeleteChildlessShadowAndItem(anExistingSI: TShadowItem);
879var
880  nearestMI, mi: TMenuItem;
881  box: TShadowBox;
882begin
883  StopEditingCaption;
884  FDesigner.FGui.BeginUpdate;
885  try
886    mi:=anExistingSI.RealItem;
887    if (mi.Count > 0) then
888      DeleteShadowAndItemAndChildren(anExistingSI)
889    else begin
890      HideFakes;
891      if (mi = FSelectedMenuItem) then
892        FSelectedMenuItem:=nil;
893      nearestMI:=GetNextNonSepItem(mi);
894      if (nearestMI = nil) then
895        nearestMI:=GetPreviousNonSepItem(mi);
896      if (nearestMI = nil) then
897      begin
898        if mi.Parent<>FMenu.Items then
899          nearestMI:=mi.Parent;
900      end;
901      box:=anExistingSI.ParentBox;
902      box.ParentMenuItem.Remove(mi);
903      anExistingSI.RealItem:=nil;
904      box.ShadowList.Remove(anExistingSI);
905      anExistingSI.Parent:=nil;
906      Application.ReleaseComponent(anExistingSI);
907      FEditorDesigner.PropertyEditorHook.Modified(mi);
908      FEditorDesigner.PropertyEditorHook.DeletePersistent(TPersistent(mi));
909      FEditorDesigner.Modified;
910
911      if (box.ShadowList.Count = 0) then
912      begin
913        FBoxList.Remove(box);
914        box.Parent:=nil;
915        if box=FRootBox then
916          FRootBox:=nil;
917        Application.ReleaseComponent(box);
918        box:=nil;
919      end;
920      if Assigned(box) then
921        box.LocateShadows;
922      UpdateBoxLocationsAndSizes;
923      SetSelectedMenuItem(nearestMI, False, True);
924      FDesigner.FGui.UpdateStatistics;
925    end;
926  finally
927    FDesigner.FGui.EndUpdate;
928  end;
929end;
930
931procedure TShadowMenu.DeleteBox(aMI: TMenuItem);
932var
933  i: integer;
934  sb: TShadowBoxBase;
935  si: TShadowItemBase;
936begin
937  sb:=GetParentBoxForMenuItem(aMI);
938  sb.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TShadowMenu.DeleteBox'){$ENDIF};
939  for i:=aMI.Count-1 downto 0 do
940    DeleteBox(aMI.Items[i]);
941  Assert(sb<>nil,'TShadowMenu.DeleteBox: internal error');
942  sb.Hide;
943  sb.ShadowList.Remove(GetShadowForMenuItem(aMI));
944  if (sb.ShadowList.Count = 0) then
945  begin
946    FBoxList.Remove(sb);
947    sb.Parent:=nil;
948    si:=GetShadowForMenuItem(sb.ParentMenuItem);
949    if Assigned(si) then
950      si.Invalidate;
951    Application.ReleaseComponent(sb);
952  end;
953end;
954
955procedure TShadowMenu.DeleteItm(anItem: TMenuItem);
956var
957  i: integer;
958begin
959  for i:=anItem.Count-1 downto 0 do
960    DeleteItm(anItem.Items[i]);
961  anItem.Parent.Remove(anItem);
962  GlobalDesignHook.DeletePersistent(TPersistent(anItem));
963  GlobalDesignHook.Modified(anItem);
964end;
965
966procedure TShadowMenu.DeleteShadowAndItemAndChildren(anExistingSI: TShadowItem);
967var
968  firstBoxToDelete: TShadowBoxBase;
969  mi: TMenuItem;
970  i: integer;
971begin
972  if IDEQuestionDialogAb(
973       lisDelete,
974       lisMenuEditorDeleteThisItemAndItsSubitems,
975       mtWarning, [mrYes, mrNo], False) = mrYes then
976  begin
977    firstBoxToDelete:=GetBoxWithParentItem(anExistingSI.RealItem);
978    Assert(firstBoxToDelete<>nil,'TShadowMenu.DeleteShadowAndItemAndChildren: no children');
979    // Delete boxes recursively
980    mi:=firstBoxToDelete.ParentMenuItem;
981    Assert(mi<>nil,'TShadowMenu,DeleteShadowAndItemAndChildren: RecursiveBoxDelete internal error');
982    for i:=mi.Count-1 downto 0 do
983      DeleteBox(mi.Items[i]);
984    // Delete children recursively
985    mi:=anExistingSI.RealItem;
986    for i:=mi.Count-1 downto 0 do
987      DeleteItm(mi.Items[i]);
988    DeleteChildlessShadowAndItem(anExistingSI);
989  end;
990end;
991
992function TShadowMenu.GetSelectedShadowItem: TShadowItem;
993begin
994  Result:=TShadowItem(GetShadowForMenuItem(FSelectedMenuItem));
995end;
996
997function TShadowMenu.GetMenuBarIconWidth(aMI: TMenuItem): integer;
998begin
999  Result:=0;
1000  if aMI.IsInMenuBar then begin
1001    if aMI.HasIcon and (aMI.ImageIndex > -1) and
1002       (FMenu.Images <> nil) then
1003         Inc(Result, FMenu.Images.Width)
1004    else if (aMI.Bitmap <> nil) and not aMI.Bitmap.Empty then
1005      Inc(Result, aMI.Bitmap.Width);
1006    if (Result > 24) then
1007      Result:=24;
1008  end;
1009end;
1010
1011procedure TShadowMenu.AddManyItems(aPrimaries, aDepth: integer);
1012var
1013  p, d: integer;
1014  mi, mi2: TMenuItem;
1015  sb: TShadowBox;
1016
1017  function NewMenuItem(aParentMI: TMenuItem): TMenuItem;
1018  begin
1019    Result:=TMenuItem.Create(FLookupRoot);
1020    Result.Name:=FEditorDesigner.CreateUniqueComponentName('TMenuItem');
1021    Result.Caption:=Result.Name;
1022    if (aParentMI = nil) then
1023      FMenu.Items.Add(Result)
1024    else aParentMI.Add(Result);
1025    FEditorDesigner.PropertyEditorHook.PersistentAdded(Result, False);
1026    FEditorDesigner.PropertyEditorHook.Modified(Result);
1027  end;
1028
1029begin
1030  if not IsMainMenu then
1031    begin
1032      for p:=1 to aPrimaries do
1033        TShadowItem.CreateWithBoxAndItem(Self, FRootBox, NewMenuItem(nil));
1034      UpdateBoxLocationsAndSizes;
1035    end
1036  else
1037    begin
1038      for p:=0 to aPrimaries-1 do
1039        begin
1040          if (p = 0) then
1041            mi:=FMenu.Items[0]
1042          else
1043            begin
1044              mi:=NewMenuItem(nil);
1045              TShadowItem.CreateWithBoxAndItem(Self, FRootBox, mi);
1046            end;
1047          sb:=TShadowBox.CreateWithParentBox(Self, FRootBox, mi);
1048          for d:=1 to aDepth do
1049            begin
1050              mi2:=NewMenuItem(mi);
1051              TShadowItem.CreateWithBoxAndItem(Self, sb, mi2);
1052            end;
1053        end;
1054      UpdateBoxLocationsAndSizes;
1055      HideBoxesAboveLevel(0);
1056    end;
1057  SetSelectedMenuItem(FMenu.Items[0], False, False);
1058  SelectedShadowItem.ShowChildBox;
1059  FDesigner.FGui.UpdateStatistics;
1060end;
1061
1062function TShadowMenu.GetBoxWithParentItem(aParentMI: TMenuItem): TShadowBoxBase;
1063var
1064  sb: TShadowBoxBase;
1065begin
1066  Assert(aParentMI<>nil,'TShadowMenu.GetBoxWithParentItem: parent item is nil');
1067  for sb in FBoxList do
1068    if (sb.ParentMenuItem = aParentMI) then
1069      Exit(sb);
1070  Result:=nil;
1071end;
1072
1073function TShadowMenu.GetMaxVisibleBoxDims(aSB: TShadowBox): TPoint;
1074begin
1075  Result:=Point(0,0);
1076  if (aSB = nil) or not aSB.Visible then
1077    Exit
1078  else Result:=Point(aSB.BoundsRect.Right, aSB.BoundsRect.Bottom);
1079end;
1080
1081function TShadowMenu.GetMaxVisibleFakeDims: TPoint;
1082begin
1083  Result:=Point(0, 0);
1084  if FAddItemFake.Visible then
1085    Result:=Point(FAddItemFake.BoundsRect.Right, FAddItemFake.BoundsRect.Bottom);
1086  if FAddSubMenuFake.Visible then begin
1087    if (FAddSubmenuFake.BoundsRect.Right > Result.x) then
1088      Result.x:=FAddSubmenuFake.BoundsRect.Right;
1089    if (FAddSubmenuFake.BoundsRect.Bottom > Result.y) then
1090      Result.y:=FAddSubmenuFake.BoundsRect.Bottom;
1091  end;
1092end;
1093
1094function TShadowMenu.GetSelectedShadowBox: TShadowBox;
1095var
1096  sel: TShadowItem;
1097begin
1098  sel:=SelectedShadowItem;
1099  if (sel = nil) then
1100    Result:=nil
1101  else
1102    Result:=sel.ParentBox;
1103end;
1104
1105procedure TShadowMenu.AddSubMenuTo(anExistingSI: TShadowItem);
1106var
1107  newMI: TMenuItem;
1108  box: TShadowBox;
1109begin
1110  if (anExistingSI.RealItem.Count <> 0) then
1111    Exit;
1112  newMI:=TMenuItem.Create(FLookupRoot);
1113  newMI.Name:=FEditorDesigner.CreateUniqueComponentName(newMI.ClassName);
1114  newMI.Caption:=newMI.Name;
1115  anExistingSI.RealItem.Add(newMI);
1116  GlobalDesignHook.PersistentAdded(newMI, False);
1117  GlobalDesignHook.Modified(newMI);
1118  box:=TShadowBox.CreateWithParentBox(Self, anExistingSI.ParentBox, anExistingSI.RealItem);
1119  TShadowItem.CreateWithBoxAndItem(Self, box, newMI);
1120  UpdateBoxLocationsAndSizes;
1121  SetSelectedMenuItem(newMI, False, False);
1122  FDesigner.FGui.UpdateStatistics;
1123end;
1124
1125procedure TShadowMenu.SetupPopupMenu;
1126var
1127  pe: TPopEnum;
1128  ac: TAction;
1129  primaryItem, mi: TMenuItem;
1130
1131  procedure NewPopItem(const aCaption: string; anOnClick: TNotifyEvent;
1132                       aShortcut: TShortCut=0); //aShortCut2: String='');
1133  begin
1134    ac:=TAction.Create(Self);
1135    with ac do begin
1136      ac.ActionList:=FActionList;
1137      ac.DisableIfNoHandler:=False;
1138      Tag:=PtrInt(pe);
1139      Caption:=aCaption;
1140      OnExecute:=anOnClick;
1141      ShortCut:=aShortcut;
1142      //if aShortCut2 <> '' then                 Does not work.
1143      //  SecondaryShortCuts.Add(aShortCut2);
1144    end;
1145    mi:=TMenuItem.Create(Self);
1146    FItemsPopupMenu.Items.Add(mi);
1147    mi.Action:=ac;
1148  end;
1149
1150  procedure NewPopPrimary(const aCaption: string);
1151  begin
1152    ac:=TAction.Create(Self);
1153    with ac do begin
1154      ActionList:=FActionList;
1155      DisableIfNoHandler:=False;
1156      Tag:=PtrInt(pe);
1157      Caption:=aCaption;
1158    end;
1159    mi:=TMenuItem.Create(Self);
1160    FItemsPopupMenu.Items.Add(mi);
1161    mi.Action:=ac;
1162    primaryItem:=mi;
1163  end;
1164
1165  procedure NewPopSub(const aPrimary: TMenuItem; const aCaption: string;
1166                      anOnClick: TNotifyEvent; aShortcut: TShortCut=0);
1167  begin
1168    ac:=TAction.Create(Self);
1169    with ac do begin
1170      ActionList:=FActionList;
1171      DisableIfNoHandler:=False;
1172      Tag:=PtrInt(pe);
1173      Caption:=aCaption;
1174      OnExecute:=anOnClick;
1175      ShortCut:=aShortcut;
1176    end;
1177    mi:=TMenuItem.Create(Self);
1178    aPrimary.Add(mi);
1179    mi.Action:=ac;
1180  end;
1181
1182  procedure NewSeparatorAction;
1183  begin
1184    FItemsPopupMenu.Items.AddSeparator;
1185    ac:=TAction.Create(Self);
1186    ac.ActionList:=FActionList;
1187    ac.Tag:=PtrInt(pe);
1188    ac.Name:=GetEnumName(TypeInfo(TPopEnum), PtrInt(pe));
1189  end;
1190
1191begin
1192  for pe in TPopEnum do
1193    with FDesigner.FGui do
1194    case pe of
1195      popItemAddOnClick:
1196        NewPopItem(lisMenuEditorAddOnClickHandler, @AddOnClick);
1197      popItemAddBefore: begin
1198        NewPopItem('', @AddItemBefore, KeyToShortCut(VK_INSERT,[]));
1199        AddItemAboveButton.Action:=ac;
1200      end;
1201      popItemAddAfter: begin
1202        NewPopItem('', @AddItemAfter);
1203        AddItemBelowButton.Action:=ac;
1204      end;
1205      popItemAddSubMenu: begin
1206        NewPopItem('', @AddSubMenu,KeyToShortCut(VK_INSERT,[ssCtrl]));
1207        AddSubMenuButton.Action:=ac;
1208      end;
1209      popItemDelete: begin
1210        NewPopItem(lisMenuEditorDeleteItem, @DeleteItem, KeyToShortCut(VK_DELETE, []));
1211        DeleteItemButton.Action:=ac;
1212      end;
1213      popItemOISep:
1214        NewSeparatorAction;
1215      popItemEditCaption:
1216        NewPopItem(lisMenuEditorEditCaption, @EditCaption, KeyToShortCut(VK_RETURN, []));
1217      popItemMoveBefore: begin
1218        NewPopItem('', @MoveItemBefore, KeyToShortCut(VK_UP,[ssCtrl]));
1219        MoveItemUpButton.Action:=ac;
1220      end;
1221      popItemMoveAfter: begin
1222        NewPopItem('', @MoveItemAfter, KeyToShortCut(VK_DOWN,[ssCtrl]));
1223        MoveItemDownButton.Action:=ac;
1224      end;
1225      popAddImgListIcon: begin
1226        NewPopItem('', @AddImageListIcon);
1227        FAddImgListIconAction:=ac;
1228      end;
1229      popItemSep:
1230        NewSeparatorAction;
1231      popSeparators_:
1232        NewPopPrimary(lisMenuEditorSeParators);
1233      popAddSeparatorBefore: begin
1234        NewPopSub(primaryItem, lisMenuEditorAddSeparatorBefore, @AddSeparatorAbove);
1235        AddSeparatorAboveButton.Action:=ac;
1236        ac.Hint:=lisAddANewSeparatorAboveSelectedItem;
1237      end;
1238      popAddSeparatorAfter: begin
1239        NewPopSub(primaryItem, lisMenuEditorAddSeparatorAfter, @AddSeparatorBelow);
1240        AddSeparatorBelowButton.Action:=ac;
1241        ac.Hint:=lisAddANewSeparatorBelowSelectedItem;
1242      end;
1243      popRemoveAllSeparators:
1244        NewPopSub(primaryItem, lisMenuEditorRemoveAllSeparators, @RemoveAllSeparators);
1245      popShortcuts_:
1246        NewPopPrimary(lisMenuEditorShortcUts2);
1247      popListShortcuts:
1248        NewPopSub(primaryItem, '', @ListShortcuts);
1249      popListShortcutsAccelerators:
1250        NewPopSub(primaryItem, '', @ListShortcutsAndAccelerators);
1251      popResolveShortcutConflicts:
1252        NewPopSub(primaryItem, lisMenuEditorResolveShortcutConflicts, @ResolveshortcutConflicts);
1253      popTemplates_:
1254        NewPopPrimary(lisMenuEditorTemplates);
1255      popSaveAsTemplate:
1256        NewPopSub(primaryItem, lisMenuEditorSaveMenuAsATemplate, @SaveAsTemplate);
1257      popAddFromTemplate:
1258        NewPopSub(primaryItem, lisMenuEditorAddFromTemplate, @AddFromTemplate);
1259      popDeleteTemplate:
1260        NewPopSub(primaryItem, lisMenuEditorDeleteMenuTemplate, @DeleteTemplate);
1261    end; // case
1262end;
1263
1264function TShadowMenu.GetMenuBarCumWidthForItemIndex(anIndex: integer): integer;
1265var
1266  w: integer;
1267  mi: TMenuItem;
1268begin
1269  Result:=0;
1270  if anIndex <> 0 then
1271    repeat
1272      mi:=FMenu.Items[Pred(anIndex)];
1273      w:=GetStringWidth(mi.Caption, mi.Default) +
1274         Double_MenuBar_Text_Offset + GetMenuBarIconWidth(mi);
1275      Inc(Result, w);
1276      Dec(anIndex)
1277    until (anIndex <= 0);
1278end;
1279
1280function TShadowMenu.GetParentItemHeightInBox(aParentItem: TMenuItem): integer;
1281
1282  function HeightOfItem(anIndex: integer): integer;
1283  begin
1284    if aParentItem.Parent.Items[anIndex].IsLine then
1285      Result:=Separator_Height
1286    else
1287      Result:=DropDown_Height;
1288  end;
1289
1290var
1291  idx: integer = 0;
1292begin
1293  Result:=1;
1294  repeat
1295    if (idx < aParentItem.MenuIndex) then
1296      Inc(Result, HeightOfItem(idx));
1297    Inc(idx);
1298  until (idx >= aParentItem.MenuIndex);
1299end;
1300
1301procedure TShadowMenu.UpdateBoxLocationsAndSizes;
1302var
1303  sb: TShadowBoxBase;
1304  si: TShadowItemBase;
1305  lft, w, idx: integer;
1306  pt: TPoint;
1307begin
1308  FBoxList.Sort(@SortByBoxLevel);
1309  for sb in FBoxList do begin
1310    if sb.IsMenuBar then
1311      begin
1312        sb.Align:=alTop;
1313        sb.Height:=MenuBar_Height;
1314        lft:=0;
1315        for si in sb.ShadowList do begin
1316          w:=si.GetWidth;
1317          si.SetBounds(lft, 0, w, MenuBar_Height);
1318          Inc(lft, w);
1319        end;
1320      end
1321    else if IsMainMenu and (sb.Level = 1) then
1322      begin
1323        pt:=sb.GetInnerDims;
1324        idx:=sb.ParentMenuItem.MenuIndex;
1325        lft:=GetMenuBarCumWidthForItemIndex(idx);
1326        sb.SetBounds(lft, MenuBar_Height+1, pt.x+2, pt.y+2);
1327      end
1328    else begin
1329      pt:=sb.GetInnerDims;
1330      if (sb.Level = 0) then
1331        sb.SetBounds(Popup_Origin.x, Popup_Origin.y, pt.x+2, pt.y+2)
1332      else sb.SetBounds(sb.ParentBox.Left+sb.ParentBox.Width,
1333                        sb.ParentBox.Top+GetParentItemHeightInBox(sb.ParentMenuItem),
1334                        pt.x+2, pt.y+2);
1335    end;
1336  end;
1337  RefreshFakes;
1338end;
1339
1340procedure TShadowMenu.RemoveEmptyBox(aSB: TShadowBox);
1341var
1342  miToSelect: TMenuItem;
1343begin
1344  if (aSB.ShadowList.Count = 0) then begin
1345    miToSelect:=aSB.ParentMenuItem;
1346    FBoxList.Remove(aSB);
1347    aSB.Parent:=nil;
1348    Application.ReleaseComponent(aSB);
1349    UpdateBoxLocationsAndSizes;
1350    SetSelectedMenuItem(miToSelect, False, True);
1351  end;
1352end;
1353
1354procedure TShadowMenu.HideFakes;
1355begin
1356  FAddSubmenuFake.Hide;
1357  FAddItemFake.Hide;
1358  FAddFirstItemFake.Hide;
1359end;
1360
1361procedure TShadowMenu.RefreshFakes;
1362begin
1363  // MG: Dont: Application.ProcessMessages; this might free components and trigger events
1364  FAddItemFake.Refresh;
1365  FAddSubmenuFake.Refresh;
1366  FAddFirstItemFake.Refresh;
1367end;
1368
1369procedure TShadowMenu.UpdateButtonGlyphs(isInBar: boolean);
1370begin
1371  if (FSelectedMenuItem <> nil) and (isInBar <> FDesigner.VariableGlyphsInMenuBar) then
1372    FDesigner.FGui.LoadVariableButtonGlyphs(isInBar);
1373end;
1374
1375procedure TShadowMenu.AddFromTemplate(Sender: TObject);
1376var
1377  newItem: TMenuItem;
1378  sb: TShadowBox;
1379  i: integer;
1380begin
1381  if (FSelectedMenuItem <> nil) and (FSelectedMenuItem.Parent.Parent = nil) then
1382  begin
1383    HideFakes;
1384    newItem:=InsertMenuTemplateDlg(FMenu);
1385    if (newItem <> nil) then
1386    begin
1387      FMenu.Items.Add(newItem);
1388      FLookupRoot.InsertComponent(newItem);
1389      newItem.Name:=FEditorDesigner.CreateUniqueComponentName(newItem.ClassName);
1390      FEditorDesigner.PropertyEditorHook.PersistentAdded(TPersistent(newItem), False);
1391      FEditorDesigner.Modified;
1392      TShadowItem.CreateWithBoxAndItem(Self, FRootBox, newItem);
1393      if (newItem.Count > 0) then begin
1394        sb:=TShadowBox.CreateWithParentBox(Self, FRootBox, newItem);
1395        for i:=0 to newItem.Count-1 do
1396        begin
1397          FLookupRoot.InsertComponent(newItem.Items[i]);
1398          newItem.Items[i].Name:=FEditorDesigner.CreateUniqueComponentName(newItem.Items[i].ClassName);
1399          FEditorDesigner.PropertyEditorHook.PersistentAdded(TPersistent(newItem.Items[i]), False);
1400          FEditorDesigner.Modified;
1401          TShadowItem.CreateWithBoxAndItem(Self, sb, newItem.Items[i]);
1402        end;
1403      end;
1404      UpdateBoxLocationsAndSizes;
1405      SetSelectedMenuItem(newItem, False, False);
1406    end;
1407  end;
1408end;
1409
1410procedure TShadowMenu.AddImageListIcon(Sender: TObject);
1411var
1412  idx: integer;
1413  selected: TShadowItem;
1414begin
1415  if FSelectedMenuItem = nil then Exit;
1416  idx := -1;
1417  selected:=SelectedShadowItem;
1418  if (FMenu.Images <> nil) then
1419    idx := ChooseIconFromImageListDlg(FMenu.Images)
1420  else if (selected.Level > 0)
1421  and (FSelectedMenuItem.Parent.SubMenuImages <> nil) then
1422    idx := ChooseIconFromImageListDlg(FSelectedMenuItem.Parent.SubMenuImages);
1423  if idx = -1 then Exit;
1424  FSelectedMenuItem.ImageIndex := idx;
1425  selected.Invalidate;
1426  UpdateActionsEnabledness;
1427  FEditorDesigner.PropertyEditorHook.RefreshPropertyValues;
1428  FEditorDesigner.Modified;
1429end;
1430
1431procedure TShadowMenu.DeleteTemplate(Sender: TObject);
1432begin
1433  if SavedTemplatesExist and DeleteMenuTemplateDlg then begin
1434    FDesigner.UpdateTemplatesCount;
1435    UpdateActionsEnabledness;
1436  end;
1437end;
1438
1439procedure TShadowMenu.ListShortcuts(Sender: TObject);
1440begin
1441  ListShortCutDlg(FDesigner.Shortcuts, True, Self, FMenu);
1442end;
1443
1444procedure TShadowMenu.ListShortcutsAndAccelerators(Sender: TObject);
1445begin
1446  ListShortCutDlg(FDesigner.Shortcuts, False, Self, Nil);
1447end;
1448
1449procedure TShadowMenu.ResolveShortcutConflicts(Sender: TObject);
1450var
1451  dlg: TResolveConflictsDlg;
1452begin
1453  dlg:=TResolveConflictsDlg.Create(FDesigner.Shortcuts, Self);
1454  try
1455    if dlg.ShowModal <> mrCancel then
1456      UpdateActionsEnabledness;
1457  finally
1458    dlg.Free;
1459  end;
1460end;
1461
1462procedure TShadowMenu.SaveAsTemplate(Sender: TObject);
1463var
1464  dlg: TMenuTemplateDialog;
1465begin
1466  if (FSelectedMenuItem <> nil) and LevelZeroAndNoGrandchildren(FSelectedMenuItem) then
1467  begin
1468    //SaveMenuTemplateDlg(FSelectedMenuItem);
1469    dlg:=TMenuTemplateDialog.CreateWithMode(FMenu, dmSave);
1470    try
1471      dlg.MenuToSave:=FSelectedMenuItem;
1472      dlg.ShowModal;
1473    finally
1474      dlg.Free;
1475    end;
1476    FDesigner.UpdateTemplatesCount;
1477    UpdateActionsEnabledness;
1478  end;
1479end;
1480
1481procedure TShadowMenu.OnObjectPropertyChanged(Sender: TObject; NewObject: TPersistent);
1482var
1483  propertyEditor: TPropertyEditor absolute Sender;
1484  i: Integer;
1485  persistent: TPersistent;
1486  mi: TMenuItem absolute persistent;
1487begin
1488  if not (Sender is TPropertyEditor) or (NewObject = nil) then
1489    Exit;
1490  if (NewObject is TAction) then
1491    for i:=0 to propertyEditor.PropCount-1 do begin
1492      persistent:=propertyEditor.GetComponent(i);
1493      if (persistent is TMenuItem) then begin
1494        if GetShadowForMenuItem(mi) <> nil then
1495        begin
1496          UpdateBoxLocationsAndSizes;
1497          RefreshFakes;
1498          if (FSelectedMenuItem <> nil) then
1499            SelectedShadowItem.Invalidate;
1500        end;
1501      end;
1502    end;
1503  if (NewObject is TImageList) and (NewObject = FMenu.Images) then
1504    UpdateActionsEnabledness;
1505end;
1506
1507procedure TShadowMenu.OnDesignerModified(Sender: TObject);
1508var
1509  i: integer;
1510  persistent: TPersistent;
1511  mi: TMenuItem absolute persistent;
1512  refreshNeeded: boolean = False;
1513begin
1514  if FDesigner.FGui.IsUpdate then
1515    Exit;
1516
1517  if (Sender is TPropertyEditor) then begin
1518    for i:=0 to TPropertyEditor(Sender).PropCount-1 do begin
1519      persistent:=TPropertyEditor(Sender).GetComponent(i);
1520      if (persistent is TMenuItem) then begin
1521        if GetShadowForMenuItem(mi) <> nil then
1522          refreshNeeded:=True;
1523      end;
1524    end;
1525    if refreshNeeded then begin
1526      UpdateBoxLocationsAndSizes;
1527      if ((mi.Action <> nil) and (TAction(mi.Action).ShortCut <> 0)) or
1528         (mi.ShortCut <> 0) or (mi.ShortCutKey2 <> 0) then
1529           FDesigner.Shortcuts.UpdateShortcutList(True);
1530      if (FSelectedMenuItem <> nil) then begin
1531        SelectedShadowItem.Invalidate;
1532      end;
1533      FDesigner.FGui.UpdateStatistics;
1534    end;
1535  end;
1536end;
1537
1538procedure TShadowMenu.OnDesignerRefreshPropertyValues;
1539var
1540  comp: TComponent;
1541  mi: TMenuItem absolute comp;
1542  selBox: TShadowBox;
1543begin
1544  if FSelectedMenuItem = nil then
1545    Exit;
1546  comp:=GlobalDesignHook.GetComponent(FSelectedMenuItem.Name);
1547  if comp is TMenuItem then
1548  begin
1549    selBox:=SelectedShadowBox;
1550    if (selBox.LastRIValue <> mi.RadioItem) then
1551      FDesigner.FGui.UpdateSubmenuGroupBox(FSelectedMenuItem, selBox);
1552  end;
1553end;
1554
1555function TShadowMenu.OnClickIsAssigned(aMI: TMenuItem): boolean;
1556begin
1557  if (aMI = nil) then
1558    Exit(False);
1559  Result:=(FEditorDesigner.PropertyEditorHook.GetMethodName(GetMethodProp(aMI, 'OnClick'), aMI) <> '');
1560end;
1561
1562procedure TShadowMenu.Paint;
1563begin
1564  if FInitialising then
1565    Exit;
1566end;
1567
1568procedure TShadowMenu.SetParent(NewParent: TWinControl);
1569begin
1570  inherited SetParent(NewParent);
1571  if (NewParent <> nil) and not (csDestroying in ComponentState) then
1572  begin
1573    Align:=alNone;
1574    CreateShadowBoxesAndItems;
1575    UpdateBoxLocationsAndSizes;
1576    HideBoxesAboveLevel(0);
1577    // MG: Dont: Application.ProcessMessages; this might free components and trigger events
1578    FInitialising:=True;
1579    if (FInitialSelectedMenuItem <> nil) then begin
1580      SetSelectedMenuItem(FInitialSelectedMenuItem, True, False);
1581      UpdateActionsEnabledness;
1582    end;
1583  end;
1584end;
1585
1586procedure TShadowMenu.SetSelectedMenuItem(aMI: TMenuItem;
1587  viaDesigner, prevWasDeleted: boolean);
1588var
1589  prevSelectedMenuItem: TMenuItem;
1590  prevSelectedShadow: TShadowItem;
1591begin
1592  if (aMI = nil) then
1593  begin
1594    if prevWasDeleted then
1595      SetSelectedShadow(nil, nil, False)
1596    else
1597      SetSelectedShadow(FSelectedMenuItem, nil, False);
1598    FSelectedMenuItem:=nil;
1599    RefreshFakes;
1600    Exit;
1601  end;
1602  if (FSelectedMenuItem <> aMI) then
1603  begin
1604    if (FSelectedMenuItem = nil) or prevWasDeleted then begin
1605      prevSelectedMenuItem:=nil;
1606      prevSelectedShadow:=nil;
1607    end
1608    else begin
1609      prevSelectedMenuItem:=FSelectedMenuItem;
1610      prevSelectedShadow:=TShadowItem(GetShadowForMenuItem(prevSelectedMenuItem));
1611    end;
1612    if (prevSelectedShadow <> nil) then begin
1613      if prevSelectedMenuItem.Enabled then
1614        prevSelectedShadow.ShowNormal
1615      else
1616        prevSelectedShadow.ShowDisabled;
1617      if not AIsDescendantOfB(aMI, prevSelectedMenuItem) then
1618        prevSelectedShadow.HideChildren;
1619    end;
1620    FSelectedMenuItem:=aMI;
1621    SetSelectedShadow(prevSelectedMenuItem, FSelectedMenuItem, viaDesigner);
1622  end;
1623end;
1624
1625procedure TShadowMenu.SetSelectedShadow(const prevSelectedItem,
1626  curSelectedItem: TMenuItem; viaDesigner: boolean);
1627var
1628  selectedShadow, prevShadow: TShadowItem;
1629begin
1630  selectedShadow:=TShadowItem(GetShadowForMenuItem(curSelectedItem));
1631  if selectedShadow=nil then
1632  begin
1633    HideFakes;
1634    if (FSelectedMenuItem <> nil) then
1635    begin
1636      SelectedShadowItem.ShowNormal;
1637      FSelectedMenuItem:=nil;
1638    end;
1639    UpdateSelectedItemInfo;
1640    if not viaDesigner and (FMenu<>nil) then
1641      FEditorDesigner.SelectOnlyThisComponent(FMenu);
1642  end else
1643  begin
1644    if (prevSelectedItem <> nil) then
1645    begin
1646      StopEditingCaption;
1647      prevShadow:=TShadowItem(GetShadowForMenuItem(prevSelectedItem));
1648      if (prevShadow <> nil)
1649      and (selectedShadow.ParentBox.ParentMenuItem <> prevSelectedItem)
1650      and (prevShadow.ParentBox <> selectedShadow.ParentBox)
1651      then
1652        prevShadow.HideChainFromRoot;
1653    end;
1654    UpdateButtonGlyphs(FSelectedMenuItem.IsInMenuBar);
1655    selectedShadow.ShowChainToRoot;
1656    selectedShadow.ShowSelected;
1657    HideBoxesAboveLevel(selectedShadow.Level);
1658    selectedShadow.ShowChildBox;
1659
1660    UpdateSelectedItemInfo;
1661    if not viaDesigner then begin
1662      //debugln(['TShadowMenu.SetSelectedShadow ',DbgSName(curSelectedItem)]);
1663      FEditorDesigner.SelectOnlyThisComponent(curSelectedItem);
1664    end;
1665
1666    if not FDesigner.FGui.Visible then
1667      FDesigner.FGui.ShowOnTop;
1668    if selectedShadow<>nil then
1669      selectedShadow.SetFocus;
1670    UpdateActionsEnabledness;
1671    RefreshFakes;
1672  end;
1673end;
1674
1675function TShadowMenu.GetActionForEnum(anEnum: TPopEnum): TAction;
1676var
1677  i: integer;
1678begin
1679  for i:=0 to FActionList.ActionCount do
1680    if TAction(FActionList.Actions[i]).Tag = PtrInt(anEnum) then
1681      Exit(TAction(FActionList.Actions[i]));
1682  Result:=nil;
1683end;
1684
1685procedure TShadowMenu.UpdateActionsEnabledness;
1686var
1687  ac, ac1, ac2, ac3: TAction;
1688  pe: TPopEnum;
1689  isInBar, isFirst, isSeparator, isLast, prevIsSeparator, nextIsSeparator,
1690    levelZero, levelZeroOr1, primarySCEnabled: boolean;
1691begin
1692  if (FSelectedMenuItem = nil) then
1693    Exit;
1694  isInBar:=FSelectedMenuItem.IsInMenuBar;
1695  isFirst:=(FSelectedMenuItem.MenuIndex = 0);
1696  isLast:=(FSelectedMenuItem.MenuIndex = Pred(FSelectedMenuItem.Parent.Count));
1697  isSeparator:=FSelectedMenuItem.IsLine;
1698  prevIsSeparator:=PreviousItemIsSeparator(FSelectedMenuItem);
1699  nextIsSeparator:=NextItemIsSeparator(FSelectedMenuItem);
1700  levelZero:=(FSelectedMenuItem.Parent <> nil) and (FSelectedMenuItem.Parent.Parent = nil);
1701  levelZeroOr1:=LevelZeroAndNoGrandchildren(FSelectedMenuItem);
1702  primarySCEnabled:=not isInBar and (FSelectedMenuItem.Parent.Count > 1);
1703
1704  for pe in TPopEnum do
1705  begin
1706    ac:=GetActionForEnum(pe);
1707    case pe of
1708      popItemAddOnClick: ac.Enabled:=not OnClickIsAssigned(FSelectedMenuItem);
1709      popItemAddBefore:
1710        if isInBar then begin
1711          ac.Caption:=lisMenuEditorAddNewItemBefore;
1712          ac.Hint:=lisMenuEditorAddANewItemBeforeSelectedItem;
1713        end
1714        else begin
1715          ac.Caption:=lisMenuEditorAddNewItemAbove;
1716          ac.Hint:=lisMenuEditorAddANewItemAboveSelectedItem;
1717        end;
1718      popItemAddAfter:
1719        if isInBar then begin
1720          ac.Caption:=lisMenuEditorAddNeWItemAfter;
1721          ac.Hint:=lisMenuEditorAddANewItemAfterSelectedItem;
1722        end
1723        else begin
1724          ac.Caption:=lisMenuEditorAddNeWItemBelow;
1725          ac.Hint:=lisMenuEditorAddANewItemBelowSelectedItem;
1726        end;
1727      popItemAddSubMenu: begin
1728        ac.Enabled:=(FSelectedMenuItem.Count = 0) and not FSelectedMenuItem.IsLine;
1729        if isInBar then begin
1730          ac.Caption:=lisMenuEditorAddSubmenuBelow;
1731          ac.Hint:=lisMenuEditorAddASubmenuBelowSelectedItem;
1732        end
1733        else begin
1734          ac.Caption:=lisMenuEditorAddSubmenuRight;
1735          ac.Hint:=lisMenuEditorAddASubmenuAtTheRightOfSelectedItem;
1736        end;
1737      end;
1738      popItemDelete: ac.Enabled:=(FMenu.Items.Count > 0);
1739      //popItemOISep
1740      //popItemEditCaption
1741      popItemMoveBefore: begin
1742        ac.Enabled:=not isFirst;
1743        if isInBar then begin
1744          ac.Caption:=lisMenuEditorMoveItemLeft;
1745          ac.Hint:=lisMenuEditorMoveSelectedItemToTheLeft;
1746        end
1747        else begin
1748          ac.Caption:=lisMenuEditorMoveItemUp;
1749          ac.Hint:=lisMenuEditorMoveSelectedItemUp; end;
1750      end;
1751      popItemMoveAfter: begin
1752        ac.Enabled:=not isLast;
1753        if isInBar then begin
1754          ac.Caption:=lisMenuEditorMoVeItemRight;
1755          ac.Hint:=lisMenuEditorMoveSelectedItemToTheRight;
1756        end
1757        else begin
1758          ac.Caption:=lisMenuEditorMoVeItemDown;
1759          ac.Hint:=lisMenuEditorMoveSelectedItemDown;
1760        end;
1761      end;
1762      popAddImgListIcon: begin
1763        ac.Enabled:=(FMenu.Images <> nil) and (FMenu.Images.Count > 0);
1764        if ac.Enabled then begin
1765          if (FSelectedMenuItem.ImageIndex < 0) then
1766            ac.Caption:=Format(lisMenuEditorAddIconFromS + ' ...',
1767                               [FMenu.Images.Name])
1768          else ac.Caption:=lisMenuEditorChangeImagelistIcon;
1769          if (FMenu.Images.Count = 1) and (FSelectedMenuItem.ImageIndex = 0) then
1770            ac.Enabled:=False;
1771        end
1772        else
1773          ac.Caption:=lisMenuEditorAddImagelistIcon;
1774      end;
1775      //popItemSep
1776      popSeparators_: ac.Enabled:=primarySCEnabled;
1777      popAddSeparatorBefore:
1778        ac.Enabled:=primarySCEnabled and not isSeparator and not isFirst and not prevIsSeparator;
1779      popAddSeparatorAfter:
1780        ac.Enabled:=not isInBar and not isSeparator and not nextIsSeparator;
1781      popRemoveAllSeparators:
1782        ac.Enabled:=primarySCEnabled and (GetChildSeparatorCount(FSelectedMenuItem.Parent) > 0);
1783      //popShortcuts_
1784      popListShortcuts: begin
1785        ac.Enabled:=(FDesigner.Shortcuts.ShortcutMenuItemsCount > 0);
1786        ac.Caption:=Format(lisMenuEditorListShortcutsForS, [FMenu.Name]);
1787      end;
1788      popListShortcutsAccelerators: begin
1789        ac.Enabled:=(FDesigner.Shortcuts.ShortcutList.AcceleratorsInContainerCount > 0);
1790        ac.Caption:=Format(lisMenuEditorListShortcutsAndAccelerators,[FLookupRoot.Name]);
1791      end;
1792      popResolveShortcutConflicts: ac.Enabled:=
1793        (FDesigner.Shortcuts.ShortcutList.InitialDuplicates.Count > 0);
1794      popTemplates_:          ac.Enabled:=levelZero or FDesigner.TemplatesSaved;
1795      popSaveAsTemplate:      ac.Enabled:=levelZeroOr1;
1796      popAddFromTemplate:     ac.Enabled:=levelZero;
1797      popDeleteTemplate:      ac.Enabled:=FDesigner.TemplatesSaved;
1798    end; // case
1799  end; // for
1800  ac:=GetActionForEnum(popShortcuts_);
1801  ac1:=GetActionForEnum(popListShortcuts);
1802  ac2:=GetActionForEnum(popListShortcutsAccelerators);
1803  ac3:=GetActionForEnum(popResolveShortcutConflicts);
1804  ac.Enabled:=ac1.Enabled or ac2.Enabled or ac3.Enabled;
1805end;
1806
1807constructor TShadowMenu.Create(aDesigner: TMenuDesigner; aForm: TForm;
1808  aMenu: TMenu; aSelect: TMenuItem; aWidth, aHeight: integer);
1809begin
1810  Assert(aMenu<>nil,'TShadowMenu.Create: TMenu parameter is nil');
1811  inherited Create(nil, aMenu);
1812  InitMenuBaseSizes;
1813  FDesigner := aDesigner;
1814  FMainCanvas := aForm.Canvas;
1815  FInitialSelectedMenuItem := aSelect;
1816  SetInitialBounds(0, 0, aWidth, aHeight);
1817  Name := 'ShadowMenu';
1818  DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TShadowMenu.Create'){$ENDIF};
1819  try
1820    FItemsPopupMenu := TPopupMenu.Create(Self);
1821    FItemsPopupMenu.Name := 'ItemsPopupMenu';
1822    FActionList := TActionList.Create(Self);
1823    SetupPopupMenu;
1824    FAddItemFake := TAddSiblingFake.Create(Self);
1825    FAddItemFake.OnClick := @AddItemAfter;
1826    FAddItemFake.Caption := lisMenuEditorAddMenuItem;
1827    FAddItemFake.Name := 'AddItemFake';
1828    FAddSubmenuFake := TAddSubmenuFake.Create(Self);
1829    FAddSubmenuFake.OnClick := @AddSubMenu;
1830    FAddSubmenuFake.Caption := lisMenuEditorAddSubmenu;
1831    FAddSubmenuFake.Name := 'AddSubmenuFake';
1832    FAddFirstItemFake := TAddFirstFake.Create(Self);
1833    FAddFirstItemFake.OnClick := @AddFirstMenu;
1834    FAddFirstItemFake.Caption := lisMenuEditorAddMenuItem;
1835    FAddFirstItemFake.Name := 'AddFirstItemFake';
1836    FAddFirstItemFake.Left := Popup_Origin.x;
1837    FAddFirstItemFake.Top := Popup_Origin.y;
1838    FInPlaceEditor := TEdit.Create(Self);
1839    FInPlaceEditor.OnKeyDown := @InPlaceEditKeyDown;
1840    FInPlaceEditor.Visible := False;
1841    ConnectSpeedButtonOnClickMethods;
1842    GlobalDesignHook.AddHandlerObjectPropertyChanged(@OnObjectPropertyChanged);
1843    GlobalDesignHook.AddHandlerModified(@OnDesignerModified);
1844    GlobalDesignHook.AddHandlerRefreshPropertyValues(@OnDesignerRefreshPropertyValues);
1845    Color := clBtnFace;
1846    BorderStyle := bsNone;
1847    // Parent must be set before the Align property.
1848    // Otherwise ShadowMenu goes on top of ButtonsGroupBox which is Top aligned.
1849    Parent := aForm;
1850    AutoSize := False;
1851    Align := alClient;
1852  finally
1853    EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TShadowMenu.Create'){$ENDIF};
1854  end;
1855end;
1856
1857destructor TShadowMenu.Destroy;
1858begin
1859  Parent := nil;
1860  if Assigned(LazarusIDE) and not LazarusIDE.IDEIsClosing then
1861  begin
1862    GlobalDesignHook.RemoveHandlerRefreshPropertyValues(@OnDesignerRefreshPropertyValues);
1863    GlobalDesignHook.RemoveHandlerModified(@OnDesignerModified);
1864    GlobalDesignHook.RemoveHandlerObjectPropertyChanged(@OnObjectPropertyChanged);
1865  end;
1866  inherited Destroy;
1867end;
1868
1869procedure TShadowMenu.AddFirstMenu(Sender: TObject);
1870var
1871  newMI: TMenuItem;
1872  box: TShadowBox;
1873begin
1874  newMI:=TMenuItem.Create(FLookupRoot);
1875  newMI.Name:=FEditorDesigner.CreateUniqueComponentName(newMI.ClassName);
1876  newMI.Caption:=newMI.Name;
1877  FMenu.Items.Add(newMI);
1878  GlobalDesignHook.PersistentAdded(newMI, False);
1879  GlobalDesignHook.Modified(newMI);
1880  box:=TShadowBox.CreateWithParentBox(Self, nil, FMenu.Items);
1881  FRootBox:=box;
1882  TShadowItem.CreateWithBoxAndItem(Self, box, newMI);
1883  UpdateBoxLocationsAndSizes;
1884  SetSelectedMenuItem(newMI, False, False);
1885  FDesigner.FGui.UpdateStatistics;
1886end;
1887
1888procedure TShadowMenu.HideBoxesAboveLevel(aLevel: integer);
1889var
1890  sb: TShadowBoxBase;
1891begin
1892  for sb in FBoxList do
1893    if sb.Level > aLevel then
1894      sb.Hide;
1895end;
1896
1897procedure TShadowMenu.UpdateSelectedItemInfo;
1898begin
1899  FDesigner.FGui.UpdateItemInfo(FMenu, FSelectedMenuItem, SelectedShadowBox,
1900                                FEditorDesigner.PropertyEditorHook);
1901end;
1902
1903{ TShadowBox }
1904
1905procedure TShadowBox.BeginUpdate;
1906begin
1907  FUpdating:=True;
1908end;
1909
1910procedure TShadowBox.EndUpdate;
1911begin
1912  FUpdating:=False;
1913end;
1914
1915procedure TShadowBox.ShowAllUnSelected;
1916var
1917  si: TShadowItemBase;
1918begin
1919  for si in FShadowList do
1920    si.ShowNormal;
1921end;
1922
1923function TShadowBox.GetIsMainMenu: boolean;
1924begin
1925  Result:=FShadowMenu.IsMainMenu;
1926end;
1927
1928function TShadowBox.GetIsMenuBar: boolean;
1929begin
1930  Result:=(FLevel = 0) and IsMainMenu;
1931end;
1932
1933procedure TShadowBox.Paint;
1934var
1935  r: TRect;
1936  dets: TThemedElementDetails;
1937begin
1938  r:=ClientRect;
1939  BeginUpdate;
1940    if IsMenuBar then begin
1941      dets:=ThemeServices.GetElementDetails(tmBarBackgroundActive);
1942      ThemeServices.DrawElement(Canvas.Handle, dets, r);
1943    end
1944    else begin
1945      Canvas.FillRect(r);
1946      Canvas.Frame(r);
1947    end;
1948  EndUpdate;
1949end;
1950
1951procedure TShadowBox.SelectPrevious(aSI: TShadowItem);
1952var
1953  prevMI: TMenuItem;
1954begin
1955  prevMI:=GetPreviousNonSepItem(aSI.RealItem);
1956  if (prevMI <> nil) then
1957    FShadowMenu.SetSelectedMenuItem(prevMI, False, False);
1958end;
1959
1960procedure TShadowBox.SelectSuccessor(aSI: TShadowItem);
1961var
1962  nextMI: TMenuItem;
1963begin
1964  nextMI:=GetNextNonSepItem(aSI.RealItem);
1965  if (nextMI <> nil) then
1966    FShadowMenu.SetSelectedMenuItem(nextMI, False, False);
1967end;
1968
1969procedure TShadowBox.AddItemAndShadow(existingSI: TShadowItem;
1970  addBefore: boolean; isSeparator: boolean);
1971var
1972  idx: integer;
1973  newMI: TMenuItem;
1974begin
1975  FShadowMenu.HideFakes;
1976  idx:=existingSI.RealItem.MenuIndex;
1977  if not addBefore then
1978    Inc(idx);
1979  newMI:=TMenuItem.Create(FShadowMenu.LookupRoot);
1980  newMI.Name:=FShadowMenu.FEditorDesigner.CreateUniqueComponentName(newMI.ClassName);
1981  if isSeparator then
1982    newMI.Caption:=cLineCaption
1983  else
1984    newMI.Caption:=newMI.Name;
1985  existingSI.RealItem.Parent.Insert(idx, newMI);
1986  TShadowItem.CreateWithBoxAndItem(FShadowMenu, existingSI.ParentBox, newMI);
1987  FShadowMenu.UpdateBoxLocationsAndSizes;
1988  FShadowMenu.FDesigner.FGui.AddingItem := True;
1989  GlobalDesignHook.PersistentAdded(newMI, not isSeparator);
1990  GlobalDesignHook.Modified(newMI);
1991  FShadowMenu.FDesigner.FGui.AddingItem := False;
1992  FShadowMenu.SetSelectedMenuItem(newMI, False, False);
1993  if not isSeparator then
1994    FShadowMenu.FDesigner.FGui.UpdateStatistics;
1995  FShadowMenu.UpdateActionsEnabledness;
1996end;
1997
1998procedure TShadowBox.RemoveAllSeparators;
1999var
2000  mi, nearestMI: TMenuItem;
2001  i, sepCount: integer;
2002  si: TShadowItemBase;
2003begin
2004  if (IsMainMenu and (Self = FShadowMenu.RootBox)) then
2005    Exit;
2006  sepCount:=GetChildSeparatorCount(FParentMenuItem);
2007  if (sepCount > 0) then begin
2008    FShadowMenu.HideFakes;
2009    ShowAllUnSelected;
2010    nearestMI:=FShadowMenu.SelectedMenuItem;
2011    if assigned(nearestMI) and nearestMI.IsLine then begin
2012      nearestMI:=GetNextNonSepItem(FShadowMenu.SelectedMenuItem);
2013      if (nearestMI = nil) then
2014        nearestMI:=GetPreviousNonSepItem(FShadowMenu.SelectedMenuItem);
2015    end
2016    else
2017      FShadowMenu.SelectedMenuItem := nil;
2018    if (nearestMI = nil) then
2019      nearestMI:=FParentMenuItem;
2020    for i:=ParentMenuItem.Count-1 downto 0 do
2021    begin
2022      mi:=ParentMenuItem.Items[i];
2023      if mi.IsLine then
2024      begin
2025        si:=FShadowMenu.GetShadowForMenuItem(mi);
2026        Assert(si<>nil,'TShadowBox.RemoveAllSeparators: shadow for separator is nil');
2027        FShadowList.Remove(si);
2028        Application.ReleaseComponent(si);
2029        ParentMenuItem.Remove(mi);
2030        FShadowMenu.FEditorDesigner.PropertyEditorHook.DeletePersistent(TPersistent(mi));
2031      end;
2032    end;
2033    if (ShadowList.Count = 0) then
2034      FShadowMenu.RemoveEmptyBox(Self)
2035    else begin
2036      FShadowMenu.UpdateBoxLocationsAndSizes;
2037      FShadowMenu.SetSelectedMenuItem(nearestMI, False, True);
2038      LocateShadows;
2039    end;
2040  end;
2041end;
2042
2043procedure TShadowBox.LocateShadows;
2044var
2045  si: TShadowItemBase;
2046  len, t, w, h: integer;
2047begin
2048  if (ShadowList.Count = 0) then
2049    Exit;
2050  FShadowList.Sort(@SortByItemMenuIndex);
2051  DisableAutoSizing;
2052  if IsMenuBar then begin
2053    len:=0;
2054    for si in FShadowList do begin
2055      w:=si.GetWidth;
2056      si.SetBounds(len, 0, w, MenuBar_Height);
2057      Inc(len, w);
2058    end;
2059  end
2060  else begin
2061    w:=GetInnerDims.x;
2062    t:=1;
2063    for si in FShadowList do begin
2064      h:=si.GetHeight;
2065      si.SetBounds(1, t, w, h);
2066      Inc(t, h);
2067    end;
2068  end;
2069  EnableAutoSizing;
2070end;
2071
2072constructor TShadowBox.CreateWithParentBox(aSMenu: TShadowMenu;
2073  aParentBox: TShadowBox; aParentItem: TMenuItem);
2074begin
2075  inherited Create(aSMenu, aParentItem);
2076  Name := 'ShadowBox' + IntToStr(ShadowBoxID);
2077  Inc(ShadowBoxID);
2078  FShadowMenu := aSMenu;
2079  FParentBox := aParentBox;
2080  if (FParentBox = nil) then
2081    FLevel:=0
2082  else
2083    FLevel := aParentBox.Level + 1;
2084  Canvas.Pen.Color := clLtGray;
2085  Canvas.Brush.Color := clBtnFace;
2086  FShadowMenu.BoxList.Add(Self);
2087  Parent := FShadowMenu;
2088end;
2089
2090procedure TShadowBox.SetUnCheckedAllExcept(aMI: TMenuItem);
2091var
2092  i: integer;
2093begin
2094  if (aMI = nil) or (FShadowMenu.GetParentBoxForMenuItem(aMI) <> Self) or
2095     (FParentMenuItem = nil) then
2096    Exit;
2097  for i:=0 to Pred(FParentMenuItem.Count) do
2098    begin
2099      if (FParentMenuItem.Items[i] = aMI) then
2100        Continue;
2101      if FParentMenuItem.Items[i].RadioItem and
2102         (FParentMenuItem.Items[i].GroupIndex = aMI.GroupIndex) then
2103        begin
2104          FParentMenuItem.Items[i].Checked:=False;
2105          FShadowMenu.FEditorDesigner.PropertyEditorHook.RefreshPropertyValues;
2106          FShadowMenu.GetShadowForMenuItem(FParentMenuItem.Items[i]).Invalidate;
2107        end;
2108    end;
2109end;
2110
2111{ TShadowItem }
2112
2113function TShadowItem.GetWidth: integer;
2114var
2115  w: integer;
2116begin
2117  w:=FShadowMenu.GetStringWidth(FRealItem.Caption, FRealItem.Default);
2118  if FRealItem.IsInMenuBar then
2119    Result:=w + Double_MenuBar_Text_Offset + FShadowMenu.GetMenuBarIconWidth(FRealItem)
2120  else
2121    Result:=w + Double_DropDown_Text_Offset + GetShortcutWidth + Add_Icon_Width;
2122end;
2123
2124procedure TShadowItem.Invalidate;
2125var
2126  OldHeight, NewHeight: Integer;
2127begin
2128  OldHeight := Height;
2129  NewHeight := GetHeight;
2130  if OldHeight <> NewHeight then
2131  begin
2132    Height := NewHeight;
2133    FParentBox.LocateShadows;
2134  end;
2135  inherited Invalidate;
2136end;
2137
2138function TShadowItem.HasChildBox(out aChildBox: TShadowBoxBase): boolean;
2139begin
2140  aChildBox:=nil;
2141  Result:=(FRealItem.Count > 0);
2142  if Result then begin
2143    aChildBox:=FShadowMenu.GetBoxWithParentItem(FRealItem);
2144    Assert(aChildBox<>nil,'TShadowItem.HasChildBox: children exist but not the container for them');
2145  end;
2146end;
2147
2148procedure TShadowItem.RecursiveHideChildren(aMI: TMenuItem);
2149var
2150  container: TShadowBoxBase;
2151  firstChild: TMenuItem;
2152begin
2153  container:=FShadowMenu.GetParentBoxForMenuItem(aMI);
2154  Assert(container<>nil,'TShadowItem.HideChildren: missing parent box for '+aMI.Caption);
2155  container.Hide;
2156  if (aMI.Count > 0) then begin
2157    firstChild:=aMI.Items[0];
2158    Assert(firstChild<>nil,'TShadowItem.HideChildren: missing child');
2159    RecursiveHideChildren(firstChild);
2160  end;
2161end;
2162
2163procedure TShadowItem.HideChildren;
2164var
2165  child: TMenuItem;
2166begin
2167  if (FRealItem.Count > 0) then begin
2168    child:=FRealItem.Items[0];
2169    Assert(child<>nil,'TShadowItem.HideChildren: missing child');
2170    RecursiveHideChildren(child);
2171  end;
2172end;
2173
2174procedure TShadowItem.DblClick;
2175begin
2176  inherited DblClick;
2177  FShadowMenu.AddOnClick(nil);
2178end;
2179
2180function TShadowItem.GetIsInMenuBar: boolean;
2181begin
2182  Result:=FRealItem.IsInMenuBar;
2183end;
2184
2185function TShadowItem.GetIsMainMenu: boolean;
2186begin
2187  Result:=FShadowMenu.IsMainMenu;
2188end;
2189
2190function TShadowItem.GetLevel: integer;
2191begin
2192  Result:=FParentBox.Level;
2193end;
2194
2195function TShadowItem.GetBottomFake: TFake;
2196begin
2197  Result:=nil;
2198  if (FShadowMenu.SelectedShadowItem = Self) then
2199    case FRealItem.IsInMenuBar of
2200      False: if (FShadowMenu.AddItemFake.Visible) then
2201               Result:=FBottomFake;
2202      True: if (FShadowMenu.AddSubMenuFake.Visible) then
2203              Result:=FBottomFake;
2204    end;
2205end;
2206
2207function TShadowItem.GetRightFake: TFake;
2208begin
2209  Result:=nil;
2210  if (FShadowMenu.SelectedShadowItem = Self) then
2211    case FRealItem.IsInMenuBar of
2212      False: if (FShadowMenu.AddSubMenuFake.Visible) then
2213               Result:=FRightFake;
2214      True: if FShadowMenu.AddItemFake.Visible then
2215              Result:=FRightFake;
2216    end;
2217end;
2218
2219function TShadowItem.GetShortcutWidth: integer;
2220var
2221  hasSC, hasSC2: boolean;
2222begin
2223  Result:=0;
2224  if FRealItem.IsInMenuBar then
2225    Exit;
2226  hasSC:=(FRealItem.ShortCut <> 0);
2227  if hasSC then
2228    Inc(Result, FShadowMenu.GetStringWidth(ShortCutToText(FRealItem.ShortCut),
2229                                           FRealItem.Default));
2230  hasSC2:=(FRealItem.ShortCutKey2 <> 0);
2231  if hasSC2 then
2232    Inc(Result, FShadowMenu.GetStringWidth(ShortCutToText(FRealItem.ShortCutKey2),
2233                                           FRealItem.Default));
2234  if (hasSC or hasSC2) then
2235    Inc(Result, Shortcut_Offset);
2236  if (hasSC and hasSC2) then
2237    Inc(Result, FShadowMenu.GetStringWidth(', ', False));
2238end;
2239
2240function TShadowItem.GetShowingBottomFake: boolean;
2241begin
2242  Result:=(BottomFake <> nil) and BottomFake.Visible;
2243end;
2244
2245function TShadowItem.GetShowingRightFake: boolean;
2246begin
2247  Result:=(RightFake <> nil) and RightFake.Visible;
2248end;
2249
2250function TShadowItem.GetIconTopLeft: TPoint;
2251begin
2252  Result:=Point(1, 1);
2253  if (FShadowMenu.FMenu.Images.Height < ClientHeight) then
2254    Result.y:=(ClientHeight - FShadowMenu.FMenu.Images.Height) div 2;
2255  if (FShadowMenu.FMenu.Images.Width < Gutter_X) then
2256    Result.x:=(Gutter_X - FShadowMenu.FMenu.Images.Width) div 2;
2257end;
2258
2259function TShadowItem.GetBitmapLeftTop: TPoint;
2260begin
2261  Result:=Point(1, 1);
2262  if (FRealItem.Bitmap.Height < ClientHeight) then
2263    Result.y:=(ClientHeight - FRealItem.Bitmap.Height) div 2;
2264  if (FRealItem.Bitmap.Width < Gutter_X) then
2265    Result.x:=(Gutter_X - FRealItem.Bitmap.Width) div 2;
2266end;
2267
2268function TShadowItem.GetSubImagesIconTopLeft: TPoint;
2269begin
2270  Result:=Point(1, 1);
2271  if (FRealItem.Parent.SubMenuImages.Height < ClientHeight) then
2272    Result.y:=(ClientHeight - FRealItem.Parent.SubMenuImages.Height) div 2;
2273  if (FRealItem.Parent.SubMenuImages.Width < Gutter_X) then
2274    Result.x:=(Gutter_X - FRealItem.Parent.SubMenuImages.Width) div 2;
2275end;
2276
2277procedure TShadowItem.Paint;
2278var
2279  r, gutterR: TRect;
2280  textFlags: integer = DT_VCENTER or DT_SINGLELINE or DT_EXPANDTABS or DT_CENTER;
2281  tStyle: TTextStyle;
2282  s: string;
2283
2284  procedure DrawMenuBarItem;
2285  var
2286    oldFontStyle: TFontStyles;
2287    oldFontColor: TColor;
2288    x, y: integer;
2289    sz: TSize;
2290    pt: TPoint;
2291    dets: TThemedElementDetails;
2292  begin
2293    if (FState = dsSelected) then begin
2294      Canvas.Brush.Color:=clHighlight;
2295      Canvas.FillRect(r);
2296      sz:=Canvas.TextExtent(s);
2297      y:=(r.Bottom - r.Top - sz.cy) div 2;
2298      x:=(r.Right - r.Left - sz.cx) div 2;
2299      if FRealItem.HasIcon and (FRealItem.ImageIndex > -1) and (FShadowMenu.FMenu.Images <> nil) then begin
2300        pt:=GetIconTopLeft;
2301        FShadowMenu.FMenu.Images.DrawForControl(Canvas, 0, pt.y, FRealItem.ImageIndex, FShadowMenu.FMenu.ImagesWidth, Self);
2302        Inc(x, MenuBar_Text_Offset);
2303      end
2304      else if (FRealItem.Bitmap <> nil) and not FRealItem.Bitmap.Empty then begin
2305        pt:=GetBitmapLeftTop;
2306        Canvas.Draw(0, pt.y, RealItem.Bitmap);
2307        Inc(x, MenuBar_Text_Offset);
2308      end;
2309      oldFontStyle:=Canvas.Font.Style;
2310      if FRealItem.Default then
2311        Canvas.Font.Style:=[fsBold]
2312      else Canvas.Font.Style:=[];
2313      oldFontColor:=Canvas.Font.Color;
2314      Canvas.Font.Color:=clHighlightText;
2315      Canvas.TextRect(r, x, y, s, tStyle);
2316      Canvas.Font.Color:=oldFontColor;
2317      Canvas.Font.Style:=oldFontStyle;
2318    end
2319    else begin
2320      InflateRect(r, 1, 0); // hack needed only on Windows?
2321      case FState of
2322        dsNormal:   dets:=ThemeServices.GetElementDetails(tmBarBackgroundActive);
2323        dsSelected: dets:=ThemeServices.GetElementDetails(tmBarItemPushed);
2324        dsDisabled: dets:=ThemeServices.GetElementDetails(tmBarItemDisabled);
2325      end;
2326      ThemeServices.DrawElement(Canvas.Handle, dets, r);
2327      if FRealItem.HasIcon and (FRealItem.ImageIndex > -1) and (FShadowMenu.FMenu.Images <> nil) then
2328        ThemeServices.DrawIcon(Canvas, dets, Point(0,0), FShadowMenu.FMenu.Images, FRealItem.ImageIndex, 0, Self)
2329      else if (FRealItem.Bitmap <> nil) and not FRealItem.Bitmap.Empty then begin
2330        pt:=GetBitmapLeftTop;
2331        Canvas.Draw(pt.x, pt.y, RealItem.Bitmap);
2332      end;
2333      r.Left:=FShadowMenu.GetMenuBarIconWidth(FRealItem);
2334      if FRealItem.Default then begin
2335        oldFontStyle:=Canvas.Font.Style;
2336        Canvas.Font.Style:=[fsBold];
2337      end;
2338      ThemeServices.DrawText(Canvas, dets, FRealItem.Caption, r, textFlags, 0);
2339      if (FState = dsDisabled) then begin // perhaps this display hack is only needed on Windows?
2340        Canvas.Pen.Color:=clBtnShadow;
2341        Canvas.Line(0, MenuBar_Height-1, ClientWidth, MenuBar_Height-1);
2342      end;
2343      if FRealItem.Default then
2344        Canvas.Font.Style:=oldFontStyle;
2345    end;
2346  end;
2347
2348  procedure DrawBackgroundAndGutter;
2349  begin
2350    case FState of
2351      dsNormal, dsDisabled: Canvas.Brush.Color:=clBtnFace;
2352      dsSelected: Canvas.Brush.Color:=clHighlight;
2353    end;
2354    if FRealItem.IsLine and (FState = dsSelected) then
2355      Canvas.FillRect(r.Left, r.Top+2, r.Right, r.Bottom+2)
2356    else
2357      Canvas.FillRect(r);
2358    gutterR:=Rect(Gutter_X, 0, Gutter_X+1, ClientHeight);
2359    LCLIntf.DrawEdge(Canvas.Handle, gutterR, EDGE_ETCHED, BF_LEFT);
2360  end;
2361
2362  procedure DrawCheckMarkIcon;
2363  var
2364    pt: TPoint;
2365    dets: TThemedElementDetails;
2366  begin
2367    if FRealItem.Checked then begin
2368      gutterR:=r;
2369      gutterR.Right:=Gutter_X;
2370      if FRealItem.RadioItem then       // radioItem
2371        case FState of
2372          dsNormal: begin
2373  	      dets:=ThemeServices.GetElementDetails(tmPopupCheckBackgroundNormal);
2374  	      ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2375  	      dets:=ThemeServices.GetElementDetails(tmPopupBulletNormal);
2376  	      ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2377  	    end;
2378          dsSelected: begin
2379              dets:=ThemeServices.GetElementDetails(tmPopupItemHot);
2380              ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2381              dets:=ThemeServices.GetElementDetails(tmPopupBulletNormal);
2382              ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2383            end;
2384          dsDisabled: begin
2385              dets:=ThemeServices.GetElementDetails(tmPopupCheckBackgroundDisabled);
2386  	      ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2387  	      dets:=ThemeServices.GetElementDetails(tmPopupBulletDisabled);
2388  	      ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2389            end;
2390        end
2391      else begin                              // checkmark
2392  	dets:=ThemeServices.GetElementDetails(tmPopupCheckBackgroundNormal);
2393  	ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2394  	dets:=ThemeServices.GetElementDetails(tmPopupCheckMarkNormal);
2395  	ThemeServices.DrawElement(Canvas.Handle, dets, gutterR);
2396      end;
2397    end
2398    else                                     // not checked
2399      if FRealItem.HasIcon and (FRealItem.GlyphShowMode<>gsmNever) and
2400        (FRealItem.ImageIndex > -1) and (FShadowMenu.FMenu.Images <> nil) and
2401        (FRealItem.ImageIndex < FShadowMenu.FMenu.Images.Count) then
2402          ThemeServices.DrawIcon(Canvas, dets, GetIconTopLeft,
2403                                 FShadowMenu.FMenu.Images, FRealItem.ImageIndex, 0, Self)
2404      else
2405        if (FRealItem.ImageIndex > -1) and (FParentBox.Level > 0) and
2406          (FRealItem.Parent.SubMenuImages <> nil) and
2407          (FRealItem.ImageIndex < FRealItem.Parent.SubMenuImages.Count) then
2408            ThemeServices.DrawIcon(Canvas, dets, GetSubImagesIconTopLeft,
2409                                   RealItem.Parent.SubMenuImages, RealItem.ImageIndex, 0, Self)
2410        else if FRealItem.HasBitmap and not FRealItem.Bitmap.Empty then begin
2411  	  pt:=GetBitmapLeftTop;
2412  	  Canvas.Draw(pt.x, pt.y, RealItem.Bitmap);
2413        end;
2414  end;
2415
2416  procedure DrawText;
2417  var
2418    oldFontColor: TColor;
2419    oldFontStyle: TFontStyles;
2420    s1, s2: string;
2421    sc1, sc2: boolean;
2422    x, y: integer;
2423  begin
2424    Canvas.Brush.Style:=bsClear;
2425    if FRealItem.RightJustify then
2426      textFlags:=textFlags or DT_RIGHT
2427    else
2428      textFlags:=textFlags or DT_LEFT;
2429    r.Left:=DropDown_Text_Offset;
2430    oldFontStyle:=Canvas.Font.Style;
2431    if FRealItem.Default then
2432      Canvas.Font.Style:=[fsBold]
2433    else Canvas.Font.Style:=[];
2434    x:=DropDown_Text_Offset;
2435    y:=(Height-Canvas.TextHeight(s)) div 2;
2436    case FState of
2437      dsNormal: Canvas.TextRect(r, x, y, s, tStyle);
2438      dsSelected: begin
2439          OldFontColor:=Canvas.Font.Color;
2440          Canvas.Font.Color:=clHighlightText;
2441          Canvas.TextRect(r, x, y, s, tStyle);
2442          Canvas.Font.Color:=oldFontColor;
2443        end;
2444      dsDisabled: begin
2445          OldFontColor:=Canvas.Font.Color;
2446          Canvas.Font.Color:=clBtnShadow;
2447          Canvas.TextRect(r, x, y, s, tStyle);
2448          Canvas.Font.Color:=OldFontColor;
2449        end;
2450    end;
2451
2452    sc1:=(FRealItem.ShortCut <> 0);
2453    if sc1 then
2454      s1:=ShortCutToText(FRealItem.Shortcut);
2455    sc2:=(FRealItem.ShortCutKey2 <> 0);
2456    if sc2 then
2457      s2:=ShortCutToText(FRealItem.ShortCutKey2);
2458    if sc1 or sc2 then    //#todo allow for rightjustify?
2459    begin
2460      if sc1 and not sc2 then
2461        s:=s1
2462      else if sc2 and not sc1 then
2463        s:=s2
2464      else
2465        s:=s1 + ', ' + s2;
2466      x:=r.Right - Canvas.TextWidth(s) - DropDown_Height;
2467      case FState of
2468        dsNormal: Canvas.TextRect(r, x, y, s, tStyle);
2469        dsSelected: begin
2470            OldFontColor:=Canvas.Font.Color;
2471            Canvas.Font.Color:=clHighlightText;
2472            Canvas.TextRect(r, x, y, s, tStyle);
2473            Canvas.Font.Color:=oldFontColor;
2474          end;
2475        dsDisabled: begin
2476            OldFontColor:=Canvas.Font.Color;
2477            Canvas.Font.Color:=clBtnShadow;
2478            Canvas.TextRect(r, x, y, s, tStyle);
2479            Canvas.Font.Color:=OldFontColor;
2480          end;
2481      end;
2482    end;
2483    Canvas.Font.Style:=oldFontStyle;
2484  end;
2485
2486  procedure DrawChevron;
2487  var
2488    pts: array of TPoint;
2489    oldBrushColor, oldPenColor: TColor;
2490  begin
2491    { ToDo: This should be done by theme services
2492            but it must be implemented for different widgetsets first.
2493    dets:=ThemeServices.GetElementDetails(tmPopupSubmenuNormal);
2494    ThemeServices.DrawElement(Canvas.Handle, dets, r);
2495    }
2496    r.Right:=ClientWidth;
2497    r.Left:=r.Right - MenuBar_Height;
2498    SetLength(pts{%H-}, 4);
2499    pts[0]:=Point(r.Left, ScaleY(9, 96));
2500    pts[1]:=Point(r.Left + ScaleX(4, 96), ScaleY(12, 96));
2501    pts[2]:=Point(r.Left, ScaleY(15, 96));
2502    pts[3]:=pts[0];
2503    oldBrushColor:=Canvas.Brush.Color;
2504    oldPenColor:=Canvas.Pen.Color;
2505    if (FState = dsSelected) then begin
2506      Canvas.Pen.Color:=clHighlightText;
2507      Canvas.Brush.Color:=clHighlightText;
2508    end
2509    else begin
2510      Canvas.Brush.Color:=clBlack;
2511      Canvas.Pen.Color:=clBlack;
2512    end;
2513    Canvas.Polygon(pts);
2514    Canvas.Brush.Color:=oldBrushColor;
2515    Canvas.Pen.Color:=oldPenColor;
2516  end;
2517
2518var
2519  alygn: TAlignment;
2520begin
2521  if FParentBox.Updating then Exit;
2522  r:=ClientRect;
2523  if FRealItem.RightJustify then
2524    alygn:=taRightJustify
2525  else
2526    alygn:=taLeftJustify;
2527  if (FRealItem.Caption = '') then
2528    s:=FRealItem.Name
2529  else
2530    s:=FRealItem.Caption;
2531  FillChar(tStyle{%H-}, SizeOf(tStyle), 0);
2532  with tStyle do begin
2533    Alignment:=BidiFlipAlignment(alygn, UseRightToLeftAlignment);
2534    Layout:=tlCenter;
2535    SingleLine:=True;
2536    Clipping:=True;
2537    ShowPrefix:=True;
2538    RightToLeft:=UseRightToLeftReading;
2539    ExpandTabs:=True;
2540  end;
2541  if FRealItem.IsInMenuBar then
2542    DrawMenuBarItem
2543  else begin
2544    DrawBackgroundAndGutter;
2545    if FRealItem.IsLine then begin
2546      gutterR:=Rect(Gutter_X, Separator_Centre, ClientWidth, Separator_Centre);
2547      LCLIntf.DrawEdge(Canvas.Handle, gutterR, EDGE_ETCHED, BF_TOP);
2548      Exit;
2549    end;
2550    if (FRealItem.Checked or FRealItem.HasIcon) then
2551      DrawCheckMarkIcon;
2552    DrawText;
2553    if (FRealItem.Count > 0) then
2554      DrawChevron;
2555  end;
2556end;
2557
2558procedure TShadowItem.KeyDown(var Key: Word; Shift: TShiftState);
2559begin
2560  if (Shift = []) then
2561    case Key of
2562      VK_LEFT: begin
2563        if IsInMenuBar then
2564          FParentBox.SelectPrevious(Self)
2565        else if (IsMainMenu and (Level > 1)) or (not IsMainMenu and (level > 0)) then
2566          FShadowMenu.SetSelectedMenuItem(FParentBox.ParentMenuItem, False, False);
2567        Key:=0;
2568      end;
2569      VK_RIGHT: begin
2570        if IsInMenuBar then
2571          FParentBox.SelectSuccessor(Self)
2572        else if (FRealItem.Count > 0) then begin
2573          ShowChildBox;
2574          FShadowMenu.SetSelectedMenuItem(FRealItem.Items[0], False, False);
2575        end;
2576        Key:=0;
2577      end;
2578      VK_DOWN: begin
2579        if IsInMenuBar and (FRealItem.Count > 0) then begin
2580          ShowChildBox;
2581          FShadowMenu.SetSelectedMenuItem(FRealItem.Items[0], False, False);
2582        end
2583        else FParentBox.SelectSuccessor(Self);
2584        Key:=0;
2585      end;
2586      VK_UP: begin
2587        if (FRealItem.MenuIndex = 0) and FParentBox.ParentMenuItem.IsInMenuBar then
2588          FShadowMenu.SetSelectedMenuItem(FParentBox.ParentMenuItem, False, False)
2589        else if not IsInMenuBar then
2590          FParentBox.SelectPrevious(Self);
2591        Key:=0;
2592      end;
2593      VK_DELETE: begin
2594        Key:=0;
2595        FShadowMenu.DeleteItem(Self);
2596      end;
2597      else inherited KeyDown(Key, Shift);
2598    end // case
2599  else inherited KeyDown(Key, Shift);
2600end;
2601
2602procedure TShadowItem.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
2603begin
2604  if Button = mbLeft then begin
2605    FRealItem.Click;
2606    FShadowMenu.FEditorDesigner.PropertyEditorHook.RefreshPropertyValues;
2607  end;
2608  if (FState = dsSelected) then
2609    SetFocus
2610  else
2611    FShadowMenu.SetSelectedMenuItem(FRealItem, False, False);
2612  inherited MouseDown(Button, Shift, X, Y);
2613end;
2614
2615procedure TShadowItem.ShowChainToRoot;
2616var
2617  sb: TShadowBoxBase;
2618begin
2619  sb:=FParentBox;
2620  while (sb <> FShadowMenu.RootBox) do begin
2621    sb.Show;
2622    sb:=sb.ParentBox;
2623  end;
2624end;
2625
2626procedure TShadowItem.HideChainFromRoot;
2627var
2628  sb: TShadowBoxBase;
2629begin
2630  sb:=FParentBox;
2631  while (sb <> FShadowMenu.RootBox) do begin
2632    sb.Hide;
2633    sb:=sb.ParentBox;
2634  end;
2635end;
2636
2637procedure TShadowItem.ShowChildBox;
2638var
2639  sb: TShadowBoxBase;
2640begin
2641  if HasChildBox(sb) then
2642    sb.Show;
2643end;
2644
2645constructor TShadowItem.CreateWithBoxAndItem(aSMenu: TShadowMenu;
2646  aParentBox: TShadowBox; aRealItem: TMenuItem);
2647begin
2648  inherited Create(aParentBox, aRealItem);
2649  Name:='ShadowItem' + IntToStr(ShadowItemID);
2650  Inc(ShadowItemID);
2651  FShadowMenu:=aSMenu;
2652  FParentBox:=aParentBox;
2653  FParentBox.ShadowList.Add(Self);
2654  Canvas.Brush.Color:=clBtnFace;
2655  SetInitialBounds(0, 0, GetWidth, GetHeight);
2656  if FRealItem.Enabled then
2657    FState:=dsNormal
2658  else
2659    FState:=dsDisabled;
2660  TabStop:=False;
2661  TabOrder:= -1;
2662  PopupMenu:=FShadowMenu.ItemsPopupMenu;
2663  Parent:=FParentBox;
2664  FParentBox.LocateShadows;
2665end;
2666
2667{ TMenuDesigner }
2668
2669constructor TMenuDesigner.Create;
2670begin
2671  inherited Create;
2672  FGui:=TMenuDesignerForm.Create(Self);
2673end;
2674
2675destructor TMenuDesigner.Destroy;
2676begin
2677  FreeAndNil(FGui);
2678  inherited Destroy;
2679end;
2680
2681procedure TMenuDesigner.CreateShadowMenu(aMenu: TMenu; aSelect: TMenuItem;
2682  aWidth, aHeight: integer);
2683begin
2684  FShadowMenu := TShadowMenu.Create(Self, FGui, aMenu, aSelect, aWidth, aHeight);
2685end;
2686
2687{ TMainMenuComponentEditor}
2688
2689procedure TMainMenuComponentEditor.Edit;
2690begin
2691  ShowMenuEditor(Component as TMenu);
2692end;
2693
2694function TMainMenuComponentEditor.GetVerbCount: Integer;
2695begin
2696  Result:=1;
2697end;
2698
2699function TMainMenuComponentEditor.GetVerb(Index: Integer): string;
2700begin
2701  case Index of
2702    0: Result:=lisMenuEditorMenuEditor + ' ...';
2703    else Result:='';
2704  end;
2705end;
2706
2707procedure TMainMenuComponentEditor.ExecuteVerb(Index: Integer);
2708begin
2709  if (Index = 0) then
2710    Edit;
2711end;
2712
2713
2714initialization
2715  RegisterComponentEditor(TMenu, TMainMenuComponentEditor);
2716
2717finalization
2718  FreeAndNil(MenuDesignerSingleton);
2719
2720end.
2721
2722