1 { $Id: Cocoawsmenus.pp 15309 2008-06-04 22:12:59Z vincents $}
2 {
3 *****************************************************************************
4 * CocoaWSMenus.pp *
5 * ------------ *
6 * *
7 * *
8 *****************************************************************************
9
10 *****************************************************************************
11 This file is part of the Lazarus Component Library (LCL)
12
13 See the file COPYING.modifiedLGPL.txt, included in this distribution,
14 for details about the license.
15 *****************************************************************************
16 }
17 unit CocoaWSMenus;
18
19 {$mode objfpc}{$H+}
20 {$modeswitch objectivec2}
21 {$include cocoadefines.inc}
22
23 interface
24
25 uses
26 // Libs
27 CocoaAll,
28 MacOSAll,
29 // RTL
30 sysutils,
31 // LCL
32 Controls, Forms, Menus, Graphics, LCLType, LMessages, LCLProc, Classes,
33 LCLMessageGlue, LCLStrConsts,
34 // Widgetset
35 WSMenus, WSLCLClasses,
36 // LCL Cocoa
37 Cocoa_extra,
38 CocoaPrivate, CocoaWSCommon, CocoaUtils, CocoaGDIObjects;
39
40 type
41
42 IMenuItemCallback = interface(ICommonCallBack)
43 procedure ItemSelected;
MenuItemTargetnull44 function MenuItemTarget: TMenuItem;
45 end;
46
47 { TLCLMenuItemCallback }
48
49 TLCLMenuItemCallback = class(TLCLCommonCallback, IMenuItemCallback)
50 private
51 FMenuItemTarget: TMenuItem;
52 public
53 constructor Create(AOwner: NSObject; AMenuItemTarget: TMenuItem); reintroduce;
54 procedure ItemSelected;
MenuItemTargetnull55 function MenuItemTarget: TMenuItem;
56 end;
57
58 TCocoaMenuItem = objcclass;
59
60 { TCocoaMenu }
61
62 TCocoaMenu = objcclass(NSMenu)
63 private
64 appleMenu: TCocoaMenuItem;
65 attachedAppleMenu: Boolean;
66 public
67 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
68 procedure createAppleMenu(); message 'createAppleMenu';
69 procedure overrideAppleMenu(AItem: TCocoaMenuItem); message 'overrideAppleMenu:';
70 procedure attachAppleMenu(); message 'attachAppleMenu';
71 end;
72
73 { TCocoaMenuItem }
74
75 TCocoaMenuItem = objcclass(NSMenuItem, NSMenuDelegateProtocol)
76 public
77 menuItemCallback: IMenuItemCallback;
78 attachedAppleMenuItems: Boolean;
79 FMenuItemTarget: TMenuItem;
80 procedure UncheckSiblings(AIsChangingToChecked: LCLObjCBoolean = False); message 'UncheckSiblings:';
GetMenuItemHandlenull81 function GetMenuItemHandle(): TMenuItem; message 'GetMenuItemHandle';
82 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
lclGetCallbacknull83 function lclGetCallback: IMenuItemCallback; override;
84 procedure lclClearCallback; override;
85 procedure attachAppleMenuItems(); message 'attachAppleMenuItems';
isValidAppleMenunull86 function isValidAppleMenu(): LCLObjCBoolean; message 'isValidAppleMenu';
87 // menuWillOpen cannot be used. Because it SHOULD NOT change the contents
88 // of the menu. While LCL allows to modify the menu contents when the submenu
89 // is about to be activated.
90 procedure menuNeedsUpdate(AMenu: NSMenu); message 'menuNeedsUpdate:';
91 //procedure menuDidClose(AMenu: NSMenu); message 'menuDidClose:';
worksWhenModalnull92 function worksWhenModal: LCLObjCBoolean; message 'worksWhenModal';
93 end;
94
95 TCocoaMenuItem_HideApp = objcclass(NSMenuItem)
96 public
97 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
98 end;
99
100 TCocoaMenuItem_HideOthers = objcclass(NSMenuItem)
101 public
102 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
103 end;
104
105 TCocoaMenuItem_ShowAllApp = objcclass(NSMenuItem)
106 public
107 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
108 end;
109
110 TCocoaMenuItem_Quit = objcclass(NSMenuItem)
111 public
112 procedure lclItemSelected(sender: id); message 'lclItemSelected:';
113 end;
114
115 { TCocoaWSMenuItem }
116
117 TCocoaWSMenuItem = class(TWSMenuItem)
118 private
119 class procedure Do_SetCheck(const ANSMenuItem: NSMenuItem; const Checked: boolean);
120 // used from the MenuMadness example
NSMenuCheckmarknull121 class function NSMenuCheckmark: NSImage;
NSMenuRadionull122 class function NSMenuRadio: NSImage;
isSeparatornull123 class function isSeparator(const ACaption: AnsiString): Boolean;
124 published
125 class procedure AttachMenu(const AMenuItem: TMenuItem); override;
CreateHandlenull126 class function CreateHandle(const AMenuItem: TMenuItem): HMENU; override;
127 class procedure DestroyHandle(const AMenuItem: TMenuItem); override;
128 class procedure SetCaption(const AMenuItem: TMenuItem; const ACaption: string); override;
129 class procedure SetShortCut(const AMenuItem: TMenuItem; const ShortCutK1, ShortCutK2: TShortCut); override;
130 class procedure SetVisible(const AMenuItem: TMenuItem; const Visible: boolean); override;
SetChecknull131 class function SetCheck(const AMenuItem: TMenuItem; const Checked: boolean): boolean; override;
SetEnablenull132 class function SetEnable(const AMenuItem: TMenuItem; const Enabled: boolean): boolean; override;
SetRadioItemnull133 class function SetRadioItem(const AMenuItem: TMenuItem; const RadioItem: boolean): boolean; override;
SetRightJustifynull134 //class function SetRightJustify(const AMenuItem: TMenuItem; const Justified: boolean): boolean; override;
135 class procedure UpdateMenuIcon(const AMenuItem: TMenuItem; const HasIcon: Boolean; const AIcon: TBitmap); override;
136 end;
137
138 { TCocoaWSMenu }
139
140 TCocoaWSMenu = class(TWSMenu)
141 published
CreateHandlenull142 class function CreateHandle(const AMenu: TMenu): HMENU; override;
143 end;
144
145 { TCocoaWSMainMenu }
146
147 TCocoaWSMainMenu = class(TWSMainMenu)
148 published
CreateHandlenull149 class function CreateHandle(const AMenu: TMenu): HMENU; override;
150 end;
151
152 { TCocoaWSPopupMenu }
153
154 TCocoaWSPopupMenu = class(TWSPopupMenu)
155 published
156 class procedure Popup(const APopupMenu: TPopupMenu; const X, Y: Integer); override;
157 end;
158
159 procedure NSMenuItemSetBitmap(mn: NSMenuItem; bmp: TBitmap);
160
161 // the returned "Key" should not be released, as it's not memory owned
162 procedure ShortcutToKeyEquivalent(const AShortCut: TShortcut; out Key: NSString; out shiftKeyMask: NSUInteger);
163
164 procedure ToggleAppMenu(ALogicalEnabled: Boolean);
165
166 function AllocCocoaMenu(const atitle: string = ''): TCocoaMenu;
167 function LCLMenuItemInit(item: NSMenuItem; const atitle: string; ashortCut: TShortCut): id;
168 function LCLMenuItemInit(item: NSMenuItem; const atitle: string; VKKey: Word = 0; State: TShiftState = []): id;
169
170 implementation
171
172 uses
173 CocoaInt;
174
175 function LCLMenuItemInit(item: NSMenuItem; const atitle: string; ashortCut: TShortCut): id;
176 var
177 key : NSString;
178 mask : NSUInteger;
179 begin
180 ShortcutToKeyEquivalent(ashortCut, key, mask);
181
182 Result := item.initWithTitle_action_keyEquivalent(
183 ControlTitleToNSStr(Atitle),
184 objcselector('lclItemSelected:'), // Selector is Hard-coded, that's why it's LCLMenuItemInit
185 key);
186 NSMenuItem(Result).setKeyEquivalentModifierMask(mask);
187 NSMenuItem(Result).setTarget(Result);
188 end;
189
190 function LCLMenuItemInit(item: NSMenuItem; const atitle: string; VKKey: Word; State: TShiftState): id;
191 var
192 key : NSString;
193 mask : NSUInteger;
194 begin
195 Result := LCLMenuItemInit(item, atitle, ShortCut(VKKey, State));
196 end;
197
198 function AllocCocoaMenu(const atitle: string = ''): TCocoaMenu;
199 begin
200 Result := TCocoaMenu.alloc.initWithTitle(ControlTitleToNSStr(atitle));
201 Result.setAutoenablesItems(false);
202 end;
203
204 { TCocoaMenuItem_ShowAllApp }
205
206 procedure TCocoaMenuItem_ShowAllApp.lclItemSelected(sender: id);
207 begin
208 NSApplication(NSApp).unhideAllApplications(sender);
209 end;
210
211 { TLCLMenuItemCallback }
212
213 constructor TLCLMenuItemCallback.Create(AOwner: NSObject; AMenuItemTarget: TMenuItem);
214 begin
215 Owner := AOwner;
216 FMenuItemTarget := AMenuItemTarget;
217 end;
218
219 procedure TLCLMenuItemCallback.ItemSelected;
220 var
221 Msg:TLMessage;
222 begin
223 FillChar(Msg{%H-}, SizeOf(Msg), 0);
224 Msg.msg := LM_ACTIVATE;
225 // debugln('send LM_Activate');
226 LCLMessageGlue.DeliverMessage(FMenuItemTarget,Msg);
227 end;
228
MenuItemTargetnull229 function TLCLMenuItemCallback.MenuItemTarget: TMenuItem;
230 begin
231 Result:=FMenuItemTarget;
232 end;
233
234 { TCocoaMenu }
235
236 procedure TCocoaMenu.lclItemSelected(sender:id);
237 begin
238
239 end;
240
241 // For when there is no menu item with title
242 procedure TCocoaMenu.createAppleMenu();
243 var
244 nskey, nstitle, nssubmeykey: NSString;
245 lNSSubmenu: NSMenu;
246 begin
247 // create the menu item
248 nstitle := NSStringUtf8('');
249 appleMenu := TCocoaMenuItem.alloc.initWithTitle_action_keyEquivalent(nstitle,
250 objcselector('lclItemSelected:'), NSString.string_);
251 nstitle.release;
252
253 // add the submenu
254 lNSSubmenu := NSMenu.alloc.initWithTitle(NSString.string_);
255 appleMenu.setSubmenu(lNSSubmenu);
256
257 appleMenu.attachAppleMenuItems();
258 end;
259
260 // For when there is a menu item with title
261 procedure TCocoaMenu.overrideAppleMenu(AItem: TCocoaMenuItem);
262 begin
263 if appleMenu <> nil then
264 begin
265 if indexOfItem(appleMenu) >= 0 then
266 removeItem(appleMenu);
267 appleMenu.release;
268 appleMenu := nil;
269 end;
270 attachedAppleMenu := False;
271 AItem.attachAppleMenuItems();
272 end;
273
274 procedure TCocoaMenu.attachAppleMenu();
275 begin
276 if attachedAppleMenu then Exit;
277 if appleMenu = nil then Exit;
278 attachedAppleMenu := True;
279 insertItem_atIndex(appleMenu, 0);
280 end;
281
282 { TCocoaMenuITem }
283
284 procedure TCocoaMenuItem.UncheckSiblings(AIsChangingToChecked: LCLObjCBoolean);
285 var
286 i: Integer;
287 lMenuItem, lSibling, lParentMenu: TMenuItem;
288 lSiblingHandle: NSMenuItem;
289 begin
290 //lMenuItem := GetMenuItemHandle();
291 lMenuItem := FMenuItemTarget;
292 if lMenuItem = nil then Exit;
293 if not lMenuItem.RadioItem then Exit;
294 if (not AIsChangingToChecked) and (not lMenuItem.Checked) then Exit;
295 lParentMenu := lMenuItem.Parent;
296 if lParentMenu = nil then Exit;
297 for i := 0 to lParentMenu.Count - 1 do
298 begin
299 lSibling := lParentMenu.Items[i];
300 if lSibling = nil then Continue;
301 if lSibling = lMenuItem then Continue;
302
303 if lSibling.RadioItem and (lSibling.GroupIndex = lMenuItem.GroupIndex) and
304 lSibling.HandleAllocated() then
305 begin
306 lSiblingHandle := NSMenuItem(lSibling.Handle);
307 TCocoaWSMenuItem.Do_SetCheck(lSiblingHandle, False);
308 end;
309 end;
310 end;
311
GetMenuItemHandlenull312 function TCocoaMenuItem.GetMenuItemHandle(): TMenuItem;
313 begin
314 Result := nil;
315 if menuItemCallback = nil then Exit;
316 Result := menuItemCallback.MenuItemTarget;
317 end;
318
319 procedure TCocoaMenuItem.lclItemSelected(sender:id);
320 begin
321 menuItemCallback.ItemSelected;
322 UncheckSiblings();
323 end;
324
lclGetCallbacknull325 function TCocoaMenuItem.lclGetCallback: IMenuItemCallback;
326 begin
327 result:=menuItemCallback;
328 end;
329
330 procedure TCocoaMenuItem.lclClearCallback;
331 begin
332 menuItemCallback := nil;
333 end;
334
335 procedure TCocoaMenuItem.attachAppleMenuItems();
336 var
337 item : NSMenuItem;
338 begin
339 if attachedAppleMenuItems then Exit;
340 if not hasSubmenu() then Exit;
341
342 // Separator
343 submenu.insertItem_atIndex(NSMenuItem.separatorItem, submenu.itemArray.count);
344
345 // Services
346 item := LCLMenuItemInit( TCocoaMenuItem.alloc, rsMacOSMenuServices);
347 item.setTarget(nil);
348 item.setAction(nil);
349 submenu.insertItem_atIndex(item, submenu.itemArray.count);
350 item.setSubmenu(NSMenu.alloc.initWithTitle( ControlTitleToNSStr(rsMacOSMenuServices)));
351 NSApplication(NSApp).setServicesMenu(item.submenu);
352
353 // Separator
354 submenu.insertItem_atIndex(NSMenuItem.separatorItem, submenu.itemArray.count);
355
356 // Hide App Meta-H
357 item := LCLMenuItemInit( TCocoaMenuItem_HideApp.alloc, Format(rsMacOSMenuHide, [Application.Title]), VK_H, [ssMeta]);
358 submenu.insertItem_atIndex(item, submenu.itemArray.count);
359
360 // Hide Others Meta-Alt-H
361 item := LCLMenuItemInit( TCocoaMenuItem_HideOthers.alloc, rsMacOSMenuHideOthers, VK_H, [ssMeta, ssAlt]);
362 submenu.insertItem_atIndex(item, submenu.itemArray.count);
363
364 // Show All
365 item := LCLMenuItemInit( TCocoaMenuItem_ShowAllApp.alloc, rsMacOSMenuShowAll);
366 submenu.insertItem_atIndex(item, submenu.itemArray.count);
367
368 // Separator
369 submenu.insertItem_atIndex(NSMenuItem.separatorItem, submenu.itemArray.count);
370
371 // Quit Meta-Q
372 item := LCLMenuItemInit( TCocoaMenuItem_Quit.alloc, Format(rsMacOSMenuQuit, [Application.Title]), VK_Q, [ssMeta]);
373 submenu.insertItem_atIndex(item, submenu.itemArray.count);
374
375 attachedAppleMenuItems := True;
376 end;
377
isValidAppleMenunull378 function TCocoaMenuItem.isValidAppleMenu(): LCLObjCBoolean;
379 begin
380 Result := hasSubmenu() and (submenu() <> nil);
381 Result := Result and ('' = NSStringToString(title));
382 end;
383
384 procedure TCocoaMenuItem.menuNeedsUpdate(AMenu: NSMenu);
385 begin
386 if not Assigned(menuItemCallback) then Exit;
387 //todo: call "measureItem"
388 menuItemCallback.ItemSelected;
389 end;
390
worksWhenModalnull391 function TCocoaMenuItem.worksWhenModal: LCLObjCBoolean;
392 begin
393 // refer to NSMenuItem.target (Apple) documentation
394 // the method must be implemented in target and return TRUE
395 // otherwise it won't work for modal!
396 //
397 // The method COULD be used to protect the main menu from being clicked
398 // if a modal window doesn't have a menu.
399 // But LCL disables (is it?) the app menu manually on modal
400 Result := true;
401 end;
402
403 { menuDidClose should not change the structure of the menu.
404 The restructuring is causing issues on Apple's special menus (i.e. HELP menu)
405 See bug #35625
406
407 procedure TCocoaMenuItem.menuDidClose(AMenu: NSMenu);
408 var
409 par : NSMenu;
410 idx : NSInteger;
411 mn : NSMenuItem;
412 begin
413 // the only purpose of this code is to "invalidate" the submenu of the item.
414 // an invalidated menu will call menuNeedsUpdate.
415 // There's no other way in Cocoa to do the "invalidate"
416 par := amenu.supermenu;
417 if Assigned(par) then
418 begin
419 idx := par.indexOfItemWithSubmenu(AMenu);
420 if idx<>NSNotFound then
421 begin
422 mn := par.itemAtIndex(idx);
423 mn.setSubmenu(nil);
424 mn.setSubmenu(AMenu);
425 end;
426 end;
427 end;
428 }
429
430 procedure TCocoaMenuItem_HideApp.lclItemSelected(sender: id);
431 begin
432 // Applicaiton.Minimize, calls WidgetSet.AppMinimize;
433 // which calls NSApplication.hide() anyway
434 Application.Minimize;
435 end;
436
437 procedure TCocoaMenuItem_HideOthers.lclItemSelected(sender: id);
438 begin
439 NSApplication(NSApp).hideOtherApplications(sender);
440 end;
441
442 procedure TCocoaMenuItem_Quit.lclItemSelected(sender: id);
443 begin
444 {$ifdef COCOALOOPHIJACK}
445 // see bug #36265. if hot-key (Cmd+Q) is used the menu item
446 // would be called once. 1) in LCL controlled loop 2) after the loop finished
447 // The following if statement prevents "double" form close
448 if LoopHiJackEnded then Exit;
449 {$endif}
450 // Should be used instead of Application.Terminate to allow events to be sent, see bug 32148
451 Application.MainForm.Close;
452 end;
453
454 { TCocoaWSMenu }
455
456 {------------------------------------------------------------------------------
457 Method: TCocoaWSMenu.CreateHandle
458 Params: AMenu - LCL menu
459 Returns: Handle to the menu in Cocoa interface
460
461 Creates new menu in Cocoa interface
462 ------------------------------------------------------------------------------}
463 class function TCocoaWSMenu.CreateHandle(const AMenu: TMenu): HMENU;
464 begin
465 //WriteLn(':>[TCocoaWSMenu.CreateHandle]');
466 Result := HMENU(AllocCocoaMenu);
467 end;
468
469 { TCocoaWSMainMenu }
470
471 class function TCocoaWSMainMenu.CreateHandle(const AMenu: TMenu): HMENU;
472 begin
473 Result := HMENU(AllocCocoaMenu);
474 TCocoaMenu(Result).createAppleMenu();
475 end;
476
477 { TCocoaWSMenuItem }
478
479 class procedure TCocoaWSMenuItem.Do_SetCheck(const ANSMenuItem: NSMenuItem; const Checked: boolean);
480 const
481 menustate : array [Boolean] of NSInteger = (NSOffState, NSOnState);
482 begin
483 ANSMenuItem.setState( menustate[Checked] );
484 end;
485
486 // used from the MenuMadness example
487 class function TCocoaWSMenuItem.NSMenuCheckmark: NSImage;
488 begin
489 Result:=NSImage.imageNamed(NSStringUtf8('NSMenuCheckmark'));
490 end;
491
492 class function TCocoaWSMenuItem.NSMenuRadio: NSImage;
493 begin
494 Result:=NSImage.imageNamed(NSStringUtf8('NSMenuRadio'))
495 end;
496
497 class function TCocoaWSMenuItem.isSeparator(const ACaption: AnsiString): Boolean;
498 begin
499 Result:=ACaption='-';
500 end;
501
502 {------------------------------------------------------------------------------
503 Method: TCocoaWSMenuItem.AttachMenu
504 Params: AMenuItem - LCL menu item
505
506 Attaches menu item to its parent menu in Cocoa interface
507 ------------------------------------------------------------------------------}
508 class procedure TCocoaWSMenuItem.AttachMenu(const AMenuItem: TMenuItem);
509 var
510 ParObj : NSObject;
511 Parent : TCocoaMenu;
512 item : NSMenuItem;
513 MenuObj : NSObject;
514 Menu : NSMenu;
515 idx : Integer;
516 begin
517 if not Assigned(AMenuItem) or (AMenuItem.Handle=0) or not Assigned(AMenuItem.Parent) or (AMenuItem.Parent.Handle=0) then Exit;
518 ParObj:=NSObject(AMenuItem.Parent.Handle);
519 item:=NSMenuItem(AMenuItem.Handle);
520
521 if ParObj.isKindOfClass(NSMenuItem) then
522 begin
523 if not NSMenuItem(ParObj).hasSubmenu then
524 begin
525 Parent := AllocCocoaMenu(AMenuItem.Parent.Caption);
526 Parent.setDelegate(TCocoaMenuItem(ParObj));
527 NSMenuItem(ParObj).setSubmenu(Parent);
528
529 // no longer respond to clicks. LCL might still need to get an event
530 // yet the menu should not close
531 NSMenuItem(ParObj).setAction(nil);
532 end
533 else
534 Parent:=TCocoaMenu(NSMenuItem(ParObj).submenu);
535 end else if ParObj.isKindOfClass(NSMenu) then
536 Parent:=TCocoaMenu(ParObj)
537 else
538 Exit;
539
540 item := nil;
541 MenuObj := NSObject(AMenuItem.Handle);
542 if MenuObj.isKindOfClass(NSMenuItem) then
543 item := NSMenuItem(MenuObj)
544 else if MenuObj.isKindOfClass(NSMenu) then
545 begin
546 Menu := NSMenu(MenuObj);
547 item := NSMenuItem(NSMenuItem.alloc).initWithTitle_action_keyEquivalent(
548 ControlTitleToNSStr(AMenuItem.Caption), nil, NSString.string_ );
549 item.setSubmenu( Menu );
550 end;
551
552 if Assigned(item) then
553 begin
554 idx := AMenuItem.MenuVisibleIndex;
555 if idx < 0 then idx := Parent.numberOfItems;
556 Parent.insertItem_atIndex(NSMenuItem(item), idx)
557 end;
558 end;
559
560 {------------------------------------------------------------------------------
561 Method: TCocoaWSMenuItem.CreateHandle
562 Params: AMenuItem - LCL menu item
563 Returns: Handle to the menu item in Cocoa interface
564
565 Creates new menu item in Cocoa interface
566 ------------------------------------------------------------------------------}
567 class function TCocoaWSMenuItem.CreateHandle(const AMenuItem: TMenuItem): HMENU;
568 var
569 item : NSMenuItem;
570 ANSMenu : NSMenu;
571 begin
572 if not Assigned(AMenuItem) then
573 begin
574 Result:=0;
575 Exit;
576 end;
577
578 // A handle of TMenu.fItems (TMenuItem) could be recreated.
579 // in this case LCL calls TCocoaWSMenuItem.CreateHandle
580 // instead of the proper owner.
581 if (AMenuItem.Owner is TMainMenu) and (TMainMenu(AMenuItem.Owner).Items = AMenuItem) then begin
582 Result:=TCocoaWSMainMenu.CreateHandle(TMenu(AMenuItem.Owner));
583 Exit;
584 end else if (AMenuItem.Owner is TMenu) and (TMenu(AMenuItem.Owner).Items = AMenuItem) then begin
585 Result:=TCocoaWSMenu.CreateHandle(TMenu(AMenuItem.Owner));
586 Exit;
587 end;
588
589 if AMenuItem.Caption = '-' then
590 begin
591 item := NSMenuItem.separatorItem;
592 end
593 else
594 begin
595 item := LCLMenuItemInit(TCocoaMenuItem.alloc, AMenuItem.Caption, AMenuItem.ShortCut);
596 TCocoaMenuItem(item).FMenuItemTarget := AMenuItem;
597
598 if AMenuItem.IsInMenuBar then
599 begin
600 ANSMenu := AllocCocoaMenu(AMenuItem.Caption);
601 ANSMenu.setDelegate(TCocoaMenuItem(item));
602 item.setSubmenu(ANSMenu);
603 end;
604
605 TCocoaMenuItem(item).menuItemCallback:=TLCLMenuItemCallback.Create(item, AMenuItem);
606
607 // initial set of properties
608 {$ifdef BOOLFIX}
609 item.setEnabled_(Ord(AMenuItem.Enabled));
610 {$else}
611 item.setEnabled(AMenuItem.Enabled);
612 {$endif}
613
614 if AMenuItem.RadioItem then
615 item.setOnStateImage( NSMenuRadio )
616 else
617 item.setOnStateImage(NSMenuCheckmark);
618
619 Do_SetCheck(item, AMenuItem.Checked);
620
621 if AMenuItem.HasIcon and ((AMenuItem.ImageIndex>=0) or (AMenuItem.HasBitmap)) then
622 NSMenuItemSetBitmap(item, AMenuItem.Bitmap);
623 end;
624
625 Result:=HMENU(item);
626 end;
627
628 {------------------------------------------------------------------------------
629 Method: TCocoaWSMenuItem.DestroyHandle
630 Params: AMenuItem - LCL menu item
631
632 Destroys menu item in Cocoa interface
633 ------------------------------------------------------------------------------}
634 class procedure TCocoaWSMenuItem.DestroyHandle(const AMenuItem: TMenuItem);
635 var
636 callback: IMenuItemCallback;
637 callbackObject: TObject;
638 item : NSObject;
639 menuitem : TCocoaMenuItem;
640 nsitem : NSMenuItem;
641 begin
642 item:=NSObject(AMenuItem.Handle);
643 if item.isKindOfClass_(TCocoaMenuItem) then
644 begin
645 menuitem := TCocoaMenuItem(item);
646 callback := menuitem.lclGetCallback;
647 if Assigned(callback) then
648 begin
649 callbackObject := callback.GetCallbackObject;
650 callback := nil;
651 menuitem.lclClearCallback;
652 callbackObject.Free;
653 end;
654 if Assigned(menuitem.menu) then
655 menuitem.menu.removeItem(menuitem);
656 AMenuItem.Handle := 0;
657 menuitem.release; // TCocoaMenuItems are "alloced" - thus should be released;
658 end else if item.isKindOfClass_(NSMenuItem) then begin
659 nsitem := NSMenuItem(item);
660 if nsitem.isSeparatorItem and Assigned(nsitem.menu) then
661 nsitem.menu.removeItem(nsitem);
662 // separator items are not "alloced", thus should not be released
663 end;
664
665 end;
666
667 {------------------------------------------------------------------------------
668 Method: TCocoaWSMenuItem.SetCaption
669 Params: AMenuItem - LCL menu item
670 ACaption - Menu item caption
671
672 Sets the caption of menu item in Cocoa interface
673 ------------------------------------------------------------------------------}
674 class procedure TCocoaWSMenuItem.SetCaption(const AMenuItem: TMenuItem; const ACaption: string);
675 var
676 ns : NSString;
677 s: string;
678 begin
679 if not Assigned(AMenuItem) or (AMenuItem.Handle=0) then Exit;
680 if NSMenuItem(AMenuItem.Handle).isSeparatorItem <> (ACaption='-') then
681 AMenuItem.RecreateHandle
682 else
683 begin
684 s := ACaption;
685 DeleteAmpersands(s);
686 ns:=NSStringUtf8(s);
687 NSMenuItem(AMenuItem.Handle).setTitle(ns);
688 if NSMenuItem(AMenuItem.Handle).hasSubmenu then
689 NSMenuItem(AMenuItem.Handle).submenu.setTitle(ns);
690 ns.release;
691 end;
692 end;
693
694 {------------------------------------------------------------------------------
695 Method: TCocoaWSMenuItem.SetShortCut
696 Params: AMenuItem - LCL menu item
697 ShortCutK1 and ShortCutK2 - New shortcut key1 and key2
698
699 Sets the shortcut of menu item in Cocoa interface
700 ------------------------------------------------------------------------------}
701 class procedure TCocoaWSMenuItem.SetShortCut(const AMenuItem: TMenuItem;
702 const ShortCutK1, ShortCutK2: TShortCut);
703 var
704 ShiftState: NSUInteger;
705 ns: NSString;
706 begin
707 ShortcutToKeyEquivalent(ShortCutK1, ns, ShiftState);
708 TCocoaMenuItem(AMenuItem.Handle).setKeyEquivalentModifierMask(ShiftState);
709 TCocoaMenuItem(AMenuItem.Handle).setKeyEquivalent(ns);
710 end;
711
712 {------------------------------------------------------------------------------
713 Method: TCocoaWSMenuItem.SetVisible
714 Params: AMenuItem - LCL menu item
715 Visible - Menu item visibility
716
717 Sets the visibility of menu item in Cocoa interface
718 ------------------------------------------------------------------------------}
719 class procedure TCocoaWSMenuItem.SetVisible(const AMenuItem: TMenuItem;
720 const Visible: boolean);
721 begin
722 if not Assigned(AMenuItem) or (AMenuItem.Handle=0) then Exit;
723 {$ifdef BOOLFIX}
724 NSMenuItem(AMenuItem.Handle).setHidden_( Ord(not Visible) );
725 {$else}
726 NSMenuItem(AMenuItem.Handle).setHidden( not Visible );
727 {$endif}
728 end;
729
730 {------------------------------------------------------------------------------
731 Method: TCocoaWSMenuItem.SetCheck
732 Params: AMenuItem - LCL menu item
733 Checked - Menu item checked
734 Returns: If the function succeeds
735
736 Sets the check of menu item in Cocoa interface
737 ------------------------------------------------------------------------------}
738 class function TCocoaWSMenuItem.SetCheck(const AMenuItem: TMenuItem;
739 const Checked: boolean): boolean;
740 var
741 lHandle: NSMenuItem;
742 lCocoaHandle: TCocoaMenuItem absolute lHandle;
743 begin
744 Result := Assigned(AMenuItem) and AMenuItem.HandleAllocated() and (AMenuItem.Handle<>0);
745 if not Result then Exit;
746 lHandle := NSMenuItem(AMenuItem.Handle);
747 Result := Result and lHandle.isKindOfClass_(TCocoaMenuItem);
748 if not Result then Exit;
749 TCocoaWSMenuItem.Do_SetCheck(lHandle, Checked);
750 lCocoaHandle.UncheckSiblings(True);
751 end;
752
753 {------------------------------------------------------------------------------
754 Method: TCocoaWSMenuItem.SetEnable
755 Params: AMenuItem - LCL menu item
756 Enabled - Menu item enabled
757 Returns: If the function succeeds
758
759 Sets the enabled of menu item in Cocoa interface
760 ------------------------------------------------------------------------------}
761 class function TCocoaWSMenuItem.SetEnable(const AMenuItem: TMenuItem;
762 const Enabled: boolean): boolean;
763 begin
764 Result:=Assigned(AMenuItem) and (AMenuItem.Handle<>0);
765 if not Result then Exit;
766 {$ifdef BOOLFIX}
767 NSMenuItem(AMenuItem.Handle).setEnabled_( Ord(Enabled) );
768 {$else}
769 NSMenuItem(AMenuItem.Handle).setEnabled( Enabled );
770 {$endif}
771 end;
772
773 {------------------------------------------------------------------------------
774 Method: TCocoaWSMenuItem.SetRadioItem
775 Params: AMenuItem - LCL menu item
776 RadioItem - Menu item has radio
777 Returns: If the function succeeds
778
779 Sets the radio behaviour of menu item in Cocoa interface
780 ------------------------------------------------------------------------------}
781 class function TCocoaWSMenuItem.SetRadioItem(const AMenuItem: TMenuItem;
782 const RadioItem: boolean): boolean;
783 const
784 menustate : array [Boolean] of NSInteger = (NSOffState, NSOnState);
785 begin
786 Result:=Assigned(AMenuItem) and (AMenuItem.Handle<>0);
787 if not Result then Exit;
788 //todo: disable relative radio items
789 if RadioItem then
790 NSMenuItem(AMenuItem.Handle).setOnStateImage( NSMenuRadio )
791 else
792 NSMenuItem(AMenuItem.Handle).setOnStateImage(NSMenuCheckmark);
793
794 NSMenuItem(AMenuItem.Handle).setState( menustate[RadioItem] );
795 end;
796
797 procedure NSMenuItemSetBitmap(mn: NSMenuItem; bmp: TBitmap);
798 begin
799 if not Assigned(mn) then Exit;
800 if not Assigned(bmp) or (bmp.Handle = 0) then
801 mn.setImage(nil)
802 else
803 mn.setImage(TCocoaBitmap(bmp.Handle).Image);
804 end;
805
806 class procedure TCocoaWSMenuItem.UpdateMenuIcon(const AMenuItem: TMenuItem;
807 const HasIcon: Boolean; const AIcon: TBitmap);
808 var
809 mn : NSMenuItem;
810 begin
811 if not Assigned(AMenuItem) or (AMenuItem.Handle=0) then Exit;
812
813 if NSObject(AMenuItem.Handle).isKindOfClass(NSMenuItem) then
814 NSMenuItemSetBitmap( NSMenuItem(AMenuItem.Handle), AIcon);
815 end;
816
817 { TCocoaWSPopupMenu }
818
819 {------------------------------------------------------------------------------
820 Method: TCocoaWSPopupMenu.Popup
821 Params: APopupMenu - LCL popup menu
822 X, Y - Screen coordinates to popup
823
824 Popups menu in Cocoa interface
825 ------------------------------------------------------------------------------}
826 class procedure TCocoaWSPopupMenu.Popup(const APopupMenu: TPopupMenu; const X,
827 Y: Integer);
828 var
829 res : Boolean;
830 mnu : NSMenuItem;
831 view : NSView;
832 w : NSWindow;
833 px, py: Integer;
834 begin
835 if Assigned(APopupMenu) and (APopupMenu.Handle<>0) then
836 begin
837 // old method which doesn't consider position but supports 10.0+ (useless since we target 10.6+)
838 {w:=NSApp.keyWindow;
839 if Assigned(w) then
840 begin
841 NSMenu.popUpContextMenu_withEvent_forView( TCocoaMenu(APopupMenu.Handle),
842 NSApp.currentEvent, NSView(w.contentView));
843 end;}
844
845 // New method for 10.6+
846 px := x;
847 py := y;
848 view := nil;
849 w :=NSApp.keyWindow;
850 if Assigned(w) then
851 begin
852 view := w.contentView;
853 if Assigned(view) then
854 begin
855 view.lclScreenToLocal(px, py);
856 // have to flip again, because popUpMenuPositioningItem expects point
857 // to be in View coordinates and it does respect Flipped flag
858 if not view.isFlipped then
859 py := Round(view.frame.size.height - py);
860 end;
861 end;
862 res := TCocoaMenu(APopupMenu.Handle).popUpMenuPositioningItem_atLocation_inView(
863 nil, NSMakePoint(px, py), view);
864 APopupMenu.Close; // notify LCL popup menu
865 end;
866 end;
867
868 procedure ShortcutToKeyEquivalent(const AShortCut: TShortcut; out Key: NSString; out shiftKeyMask: NSUInteger);
869 var
870 w: word;
871 s: TShiftState;
872 begin
873 ShortCutToKey(AShortCut, w, s);
874 key := VirtualKeyCodeToMacString(w);
875 shiftKeyMask := 0;
876 if ssShift in s then
877 ShiftKeyMask := ShiftKeyMask + NSShiftKeyMask;
878 if ssAlt in s then
879 ShiftKeyMask := ShiftKeyMask + NSAlternateKeyMask;
880 if ssCtrl in s then
881 ShiftKeyMask := ShiftKeyMask + NSControlKeyMask;
882 if ssMeta in s then
883 ShiftKeyMask := ShiftKeyMask + NSCommandKeyMask;
884 end;
885
886 procedure ToggleAppNSMenu(mn: NSMenu; ALogicalEnabled: Boolean);
887 var
888 it : NSMenuItem;
889 obj : NSObject;
890 enb : Boolean;
891 begin
892 if not Assigned(mn) then Exit;
893 for obj in mn.itemArray do begin
894 if not obj.isKindOfClass(NSMenuItem) then continue;
895 it := NSMenuItem(obj);
896 enb := ALogicalEnabled;
897 if enb and (it.isKindOfClass(TCocoaMenuItem)) then
898 begin
899 enb := not Assigned(TCocoaMenuItem(it).FMenuItemTarget)
900 or ( TCocoaMenuItem(it).FMenuItemTarget.Enabled );
901 end;
902 {$ifdef BOOLFIX}
903 it.setEnabled_( Ord(enb));
904 {$else}
905 it.setEnabled(enb);
906 {$endif}
907 if (it.hasSubmenu) then
908 begin
909 ToggleAppNSMenu(it.submenu, ALogicalEnabled);
910 end;
911 end;
912 end;
913
914 procedure ToggleAppMenu(ALogicalEnabled: Boolean);
915 begin
916 ToggleAppNSMenu( NSApplication(NSApp).mainMenu, ALogicalEnabled );
917 end;
918
919 end.
920