VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_basics / native / juce_mac_MainMenu.mm
blob38f89b7825157470be3c83046bc847d5f7ae9e56
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2022 - Raw Material Software Limited\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \r
10    By using JUCE, you agree to the terms of both the JUCE 7 End-User License\r
11    Agreement and JUCE Privacy Policy.\r
13    End User License Agreement: www.juce.com/juce-7-licence\r
14    Privacy Policy: www.juce.com/juce-privacy-policy\r
16    Or: You may also use this code under the terms of the GPL v3 (see\r
17    www.gnu.org/licenses).\r
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER\r
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE\r
21    DISCLAIMED.\r
23   ==============================================================================\r
24 */\r
26 namespace juce\r
27 {\r
29 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")\r
30 const auto menuItemInvokedSelector = @selector (menuItemInvoked:);\r
31 JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
33 //==============================================================================\r
34 struct JuceMainMenuBarHolder : private DeletedAtShutdown\r
35 {\r
36     JuceMainMenuBarHolder()\r
37         : mainMenuBar ([[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")])\r
38     {\r
39         auto item = [mainMenuBar addItemWithTitle: nsStringLiteral ("Apple")\r
40                                            action: nil\r
41                                      keyEquivalent: nsEmptyString()];\r
43         auto appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];\r
45         JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")\r
46         [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];\r
47         JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
49         [mainMenuBar setSubmenu: appMenu forItem: item];\r
50         [appMenu release];\r
52         [NSApp setMainMenu: mainMenuBar];\r
53     }\r
55     ~JuceMainMenuBarHolder()\r
56     {\r
57         clearSingletonInstance();\r
59         [NSApp setMainMenu: nil];\r
60         [mainMenuBar release];\r
61     }\r
63     NSMenu* mainMenuBar = nil;\r
65     JUCE_DECLARE_SINGLETON_SINGLETHREADED (JuceMainMenuBarHolder, true)\r
66 };\r
68 JUCE_IMPLEMENT_SINGLETON (JuceMainMenuBarHolder)\r
70 //==============================================================================\r
71 class JuceMainMenuHandler   : private MenuBarModel::Listener,\r
72                               private DeletedAtShutdown\r
73 {\r
74 public:\r
75     JuceMainMenuHandler()\r
76     {\r
77         static JuceMenuCallbackClass cls;\r
78         callback = [cls.createInstance() init];\r
79         JuceMenuCallbackClass::setOwner (callback, this);\r
80     }\r
82     ~JuceMainMenuHandler() override\r
83     {\r
84         setMenu (nullptr, nullptr, String());\r
86         jassert (instance == this);\r
87         instance = nullptr;\r
89         [callback release];\r
90     }\r
92     void setMenu (MenuBarModel* const newMenuBarModel,\r
93                   const PopupMenu* newExtraAppleMenuItems,\r
94                   const String& recentItemsName)\r
95     {\r
96         recentItemsMenuName = recentItemsName;\r
98         if (currentModel != newMenuBarModel)\r
99         {\r
100             if (currentModel != nullptr)\r
101                 currentModel->removeListener (this);\r
103             currentModel = newMenuBarModel;\r
105             if (currentModel != nullptr)\r
106                 currentModel->addListener (this);\r
108             menuBarItemsChanged (nullptr);\r
109         }\r
111         extraAppleMenuItems.reset (createCopyIfNotNull (newExtraAppleMenuItems));\r
112     }\r
114     void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)\r
115     {\r
116         NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)\r
117                                              action: nil\r
118                                       keyEquivalent: nsEmptyString()];\r
120         NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);\r
122         [parent setSubmenu: sub forItem: item];\r
123         [sub release];\r
124     }\r
126     void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)\r
127     {\r
128         // Note: This method used to update the contents of the existing menu in-place, but that caused\r
129         // weird side-effects which messed-up keyboard focus when switching between windows. By creating\r
130         // a new menu and replacing the old one with it, that problem seems to be avoided..\r
131         NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)];\r
133         for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)\r
134             addMenuItem (iter, menu, menuId, topLevelIndex);\r
136         [menu update];\r
138         removeItemRecursive ([parentItem submenu]);\r
139         [parentItem setSubmenu: menu];\r
141         [menu release];\r
142     }\r
144     void updateTopLevelMenu (NSMenu* menu)\r
145     {\r
146         NSMenu* superMenu = [menu supermenu];\r
147         auto menuNames = currentModel->getMenuBarNames();\r
148         auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1;\r
150         if (indexOfMenu >= 0)\r
151         {\r
152             removeItemRecursive (menu);\r
154             auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]);\r
156             for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();)\r
157                 addMenuItem (iter, menu, 1, indexOfMenu);\r
159             [menu update];\r
160         }\r
161     }\r
163     void menuBarItemsChanged (MenuBarModel*) override\r
164     {\r
165         if (isOpen)\r
166         {\r
167             defferedUpdateRequested = true;\r
168             return;\r
169         }\r
171         lastUpdateTime = Time::getMillisecondCounter();\r
173         StringArray menuNames;\r
174         if (currentModel != nullptr)\r
175             menuNames = currentModel->getMenuBarNames();\r
177         auto* menuBar = getMainMenuBar();\r
179         while ([menuBar numberOfItems] > 1 + menuNames.size())\r
180             removeItemRecursive (menuBar, static_cast<int> ([menuBar numberOfItems] - 1));\r
182         int menuId = 1;\r
184         for (int i = 0; i < menuNames.size(); ++i)\r
185         {\r
186             const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames[i]));\r
188             if (i >= [menuBar numberOfItems] - 1)\r
189                 addTopLevelMenu (menuBar, menu, menuNames[i], menuId, i);\r
190             else\r
191                 updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);\r
192         }\r
193     }\r
195     void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) override\r
196     {\r
197         if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0\r
198               && info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress)\r
199             if (auto* item = findMenuItemWithCommandID (getMainMenuBar(), info.commandID))\r
200                 flashMenuBar ([item menu]);\r
201     }\r
203     void invoke (const PopupMenu::Item& item, int topLevelIndex) const\r
204     {\r
205         if (currentModel != nullptr)\r
206         {\r
207             if (item.action != nullptr)\r
208             {\r
209                 MessageManager::callAsync (item.action);\r
210                 return;\r
211             }\r
213             if (item.customCallback != nullptr)\r
214                 if (! item.customCallback->menuItemTriggered())\r
215                     return;\r
217             if (item.commandManager != nullptr)\r
218             {\r
219                 ApplicationCommandTarget::InvocationInfo info (item.itemID);\r
220                 info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;\r
222                 item.commandManager->invoke (info, true);\r
223             }\r
225             MessageManager::callAsync ([=]\r
226             {\r
227                 if (instance != nullptr)\r
228                     instance->invokeDirectly (item.itemID, topLevelIndex);\r
229             });\r
230         }\r
231     }\r
233     void invokeDirectly (int commandId, int topLevelIndex)\r
234     {\r
235         if (currentModel != nullptr)\r
236             currentModel->menuItemSelected (commandId, topLevelIndex);\r
237     }\r
239     void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,\r
240                       const int topLevelMenuId, const int topLevelIndex)\r
241     {\r
242         const PopupMenu::Item& i = iter.getItem();\r
243         NSString* text = juceStringToNS (i.text);\r
245         if (text == nil)\r
246             text = nsEmptyString();\r
248         if (i.isSeparator)\r
249         {\r
250             [menuToAddTo addItem: [NSMenuItem separatorItem]];\r
251         }\r
252         else if (i.isSectionHeader)\r
253         {\r
254             NSMenuItem* item = [menuToAddTo addItemWithTitle: text\r
255                                                       action: nil\r
256                                                keyEquivalent: nsEmptyString()];\r
258             [item setEnabled: false];\r
259         }\r
260         else if (i.subMenu != nullptr)\r
261         {\r
262             if (recentItemsMenuName.isNotEmpty() && i.text == recentItemsMenuName)\r
263             {\r
264                 if (recent == nullptr)\r
265                     recent = std::make_unique<RecentFilesMenuItem>();\r
267                 if (recent->recentItem != nil)\r
268                 {\r
269                     if (NSMenu* parent = [recent->recentItem menu])\r
270                         [parent removeItem: recent->recentItem];\r
272                     [menuToAddTo addItem: recent->recentItem];\r
273                     return;\r
274                 }\r
275             }\r
277             NSMenuItem* item = [menuToAddTo addItemWithTitle: text\r
278                                                       action: nil\r
279                                                keyEquivalent: nsEmptyString()];\r
281             [item setTag: i.itemID];\r
282             [item setEnabled: i.isEnabled];\r
284             NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);\r
285             [menuToAddTo setSubmenu: sub forItem: item];\r
286             [sub release];\r
287         }\r
288         else\r
289         {\r
290             auto item = [[NSMenuItem alloc] initWithTitle: text\r
291                                                    action: menuItemInvokedSelector\r
292                                             keyEquivalent: nsEmptyString()];\r
294             [item setTag: topLevelIndex];\r
295             [item setEnabled: i.isEnabled];\r
296            #if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13\r
297             [item setState: i.isTicked ? NSControlStateValueOn : NSControlStateValueOff];\r
298            #else\r
299             [item setState: i.isTicked ? NSOnState : NSOffState];\r
300            #endif\r
301             [item setTarget: (id) callback];\r
303             auto* juceItem = new PopupMenu::Item (i);\r
304             juceItem->customComponent = nullptr;\r
306             [item setRepresentedObject: [createNSObjectFromJuceClass (juceItem) autorelease]];\r
308             if (i.commandManager != nullptr)\r
309             {\r
310                 for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))\r
311                 {\r
312                     if (kp != KeyPress::backspaceKey   // (adding these is annoying because it flashes the menu bar\r
313                          && kp != KeyPress::deleteKey) // every time you press the key while editing text)\r
314                     {\r
315                         juce_wchar key = kp.getTextCharacter();\r
317                         if (key == 0)\r
318                             key = (juce_wchar) kp.getKeyCode();\r
320                         [item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];\r
321                         [item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];\r
322                     }\r
324                     break;\r
325                 }\r
326             }\r
328             [menuToAddTo addItem: item];\r
329             [item release];\r
330         }\r
331     }\r
333     NSMenu* createMenu (const PopupMenu menu,\r
334                         const String& menuName,\r
335                         const int topLevelMenuId,\r
336                         const int topLevelIndex,\r
337                         const bool addDelegate)\r
338     {\r
339         NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];\r
341         if (addDelegate)\r
342             [m setDelegate: (id<NSMenuDelegate>) callback];\r
344         for (PopupMenu::MenuItemIterator iter (menu); iter.next();)\r
345             addMenuItem (iter, m, topLevelMenuId, topLevelIndex);\r
347         [m update];\r
348         return m;\r
349     }\r
351     static JuceMainMenuHandler* instance;\r
353     MenuBarModel* currentModel = nullptr;\r
354     std::unique_ptr<PopupMenu> extraAppleMenuItems;\r
355     uint32 lastUpdateTime = 0;\r
356     NSObject* callback = nil;\r
357     String recentItemsMenuName;\r
358     bool isOpen = false, defferedUpdateRequested = false;\r
360 private:\r
361     struct RecentFilesMenuItem\r
362     {\r
363         RecentFilesMenuItem() : recentItem (nil)\r
364         {\r
365             if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])\r
366             {\r
367                 NSArray* array = nil;\r
369                 if (@available (macOS 10.11, *))\r
370                 {\r
371                     [menuNib instantiateWithOwner: NSApp\r
372                                   topLevelObjects: &array];\r
373                 }\r
374                 else\r
375                 {\r
376                     JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")\r
377                     [menuNib instantiateNibWithOwner: NSApp\r
378                                      topLevelObjects: &array];\r
379                     JUCE_END_IGNORE_WARNINGS_GCC_LIKE\r
380                 }\r
382                 for (id object in array)\r
383                 {\r
384                     if ([object isKindOfClass: [NSMenu class]])\r
385                     {\r
386                         if (NSArray* items = [object itemArray])\r
387                         {\r
388                             if (NSMenuItem* item = findRecentFilesItem (items))\r
389                             {\r
390                                 recentItem = [item retain];\r
391                                 break;\r
392                             }\r
393                         }\r
394                     }\r
395                 }\r
396             }\r
397         }\r
399         ~RecentFilesMenuItem()\r
400         {\r
401             [recentItem release];\r
402         }\r
404         static NSMenuItem* findRecentFilesItem (NSArray* const items)\r
405         {\r
406             for (id object in items)\r
407                 if (NSArray* subMenuItems = [[object submenu] itemArray])\r
408                     for (id subObject in subMenuItems)\r
409                         if ([subObject isKindOfClass: [NSMenuItem class]])\r
410                             return subObject;\r
411             return nil;\r
412         }\r
414         NSMenuItem* recentItem;\r
416         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)\r
417     };\r
419     std::unique_ptr<RecentFilesMenuItem> recent;\r
421     //==============================================================================\r
422     static NSMenuItem* findMenuItemWithCommandID (NSMenu* const menu, int commandID)\r
423     {\r
424         for (NSInteger i = [menu numberOfItems]; --i >= 0;)\r
425         {\r
426             NSMenuItem* m = [menu itemAtIndex: i];\r
427             if (auto* menuItem = getJuceClassFromNSObject<PopupMenu::Item> ([m representedObject]))\r
428                 if (menuItem->itemID == commandID)\r
429                     return m;\r
431             if (NSMenu* sub = [m submenu])\r
432                 if (NSMenuItem* found = findMenuItemWithCommandID (sub, commandID))\r
433                     return found;\r
434         }\r
436         return nil;\r
437     }\r
439     static void flashMenuBar (NSMenu* menu)\r
440     {\r
441         if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])\r
442             return;\r
444         [menu retain];\r
446         const unichar f35Key = NSF35FunctionKey;\r
447         NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];\r
449         NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: nsStringLiteral ("x")\r
450                                                       action: menuItemInvokedSelector\r
451                                                keyEquivalent: f35String];\r
453         // When the f35Event is invoked, the item's enablement is checked and a\r
454         // NSBeep is triggered if the item appears to be disabled.\r
455         // This ValidatorClass exists solely to return YES from validateMenuItem.\r
456         struct ValidatorClass   : public ObjCClass<NSObject>\r
457         {\r
458             ValidatorClass()  : ObjCClass ("JUCEMenuValidator_")\r
459             {\r
460                 addMethod (menuItemInvokedSelector,       menuItemInvoked);\r
461                 addMethod (@selector (validateMenuItem:), validateMenuItem);\r
463                #if defined (MAC_OS_X_VERSION_10_14)\r
464                 addProtocol (@protocol (NSMenuItemValidation));\r
465                #endif\r
467                 registerClass();\r
468             }\r
470         private:\r
471             static BOOL validateMenuItem (id, SEL, NSMenuItem*)      { return YES; }\r
472             static void menuItemInvoked  (id, SEL, NSMenuItem*)      {}\r
473         };\r
475         static ValidatorClass validatorClass;\r
476         static auto* instance = validatorClass.createInstance();\r
478         [item setTarget: instance];\r
479         [menu insertItem: item atIndex: [menu numberOfItems]];\r
480         [item release];\r
482         if ([menu indexOfItem: item] >= 0)\r
483         {\r
484             NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown\r
485                                                  location: NSZeroPoint\r
486                                             modifierFlags: NSEventModifierFlagCommand\r
487                                                 timestamp: 0\r
488                                              windowNumber: 0\r
489                                                   context: [NSGraphicsContext currentContext]\r
490                                                characters: f35String\r
491                               charactersIgnoringModifiers: f35String\r
492                                                 isARepeat: NO\r
493                                                   keyCode: 0];\r
495             [menu performKeyEquivalent: f35Event];\r
497             if ([menu indexOfItem: item] >= 0)\r
498                 [menu removeItem: item]; // (this throws if the item isn't actually in the menu)\r
499         }\r
501         [menu release];\r
502     }\r
504     static unsigned int juceModsToNSMods (const ModifierKeys mods)\r
505     {\r
506         unsigned int m = 0;\r
507         if (mods.isShiftDown())    m |= NSEventModifierFlagShift;\r
508         if (mods.isCtrlDown())     m |= NSEventModifierFlagControl;\r
509         if (mods.isAltDown())      m |= NSEventModifierFlagOption;\r
510         if (mods.isCommandDown())  m |= NSEventModifierFlagCommand;\r
511         return m;\r
512     }\r
514     // Apple Bug: For some reason [NSMenu removeAllItems] seems to leak it's objects\r
515     // on shutdown, so we need this method to release the items one-by-one manually\r
516     static void removeItemRecursive (NSMenu* parentMenu, int menuItemIndex)\r
517     {\r
518         if (isPositiveAndBelow (menuItemIndex, (int) [parentMenu numberOfItems]))\r
519         {\r
520             auto menuItem = [parentMenu itemAtIndex:menuItemIndex];\r
522             if (auto submenu = [menuItem submenu])\r
523                 removeItemRecursive (submenu);\r
525             [parentMenu removeItem:menuItem];\r
526         }\r
527         else\r
528             jassertfalse;\r
529     }\r
531     static void removeItemRecursive (NSMenu* menu)\r
532     {\r
533         if (menu != nullptr)\r
534         {\r
535             auto n = static_cast<int> ([menu numberOfItems]);\r
537             for (auto i = n; --i >= 0;)\r
538                 removeItemRecursive (menu, i);\r
539         }\r
540     }\r
542     static NSMenu* getMainMenuBar()\r
543     {\r
544         return JuceMainMenuBarHolder::getInstance()->mainMenuBar;\r
545     }\r
547     //==============================================================================\r
548     struct JuceMenuCallbackClass   : public ObjCClass<NSObject>\r
549     {\r
550         JuceMenuCallbackClass()  : ObjCClass ("JUCEMainMenu_")\r
551         {\r
552             addIvar<JuceMainMenuHandler*> ("owner");\r
554             addMethod (menuItemInvokedSelector,       menuItemInvoked);\r
555             addMethod (@selector (menuNeedsUpdate:),  menuNeedsUpdate);\r
556             addMethod (@selector (validateMenuItem:), validateMenuItem);\r
558             addProtocol (@protocol (NSMenuDelegate));\r
560            #if defined (MAC_OS_X_VERSION_10_14)\r
561             addProtocol (@protocol (NSMenuItemValidation));\r
562            #endif\r
564             registerClass();\r
565         }\r
567         static void setOwner (id self, JuceMainMenuHandler* owner)\r
568         {\r
569             object_setInstanceVariable (self, "owner", owner);\r
570         }\r
572     private:\r
573         static auto* getPopupMenuItem (NSMenuItem* item)\r
574         {\r
575             return getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]);\r
576         }\r
578         static auto* getOwner (id self)\r
579         {\r
580             return getIvar<JuceMainMenuHandler*> (self, "owner");\r
581         }\r
583         static void menuItemInvoked (id self, SEL, NSMenuItem* item)\r
584         {\r
585             if (auto* juceItem = getPopupMenuItem (item))\r
586                 getOwner (self)->invoke (*juceItem, static_cast<int> ([item tag]));\r
587         }\r
589         static void menuNeedsUpdate (id self, SEL, NSMenu* menu)\r
590         {\r
591             getOwner (self)->updateTopLevelMenu (menu);\r
592         }\r
594         static BOOL validateMenuItem (id, SEL, NSMenuItem* item)\r
595         {\r
596             if (auto* juceItem = getPopupMenuItem (item))\r
597                 return juceItem->isEnabled;\r
599             return YES;\r
600         }\r
601     };\r
602 };\r
604 JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;\r
606 //==============================================================================\r
607 class TemporaryMainMenuWithStandardCommands\r
609 public:\r
610     explicit TemporaryMainMenuWithStandardCommands (FilePreviewComponent* filePreviewComponent)\r
611         : oldMenu (MenuBarModel::getMacMainMenu()), dummyModalComponent (filePreviewComponent)\r
612     {\r
613         if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())\r
614             oldAppleMenu = std::make_unique<PopupMenu> (*appleMenu);\r
616         if (auto* handler = JuceMainMenuHandler::instance)\r
617             oldRecentItems = handler->recentItemsMenuName;\r
619         MenuBarModel::setMacMainMenu (nullptr);\r
621         if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)\r
622         {\r
623             NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];\r
624             NSMenuItem* item;\r
626             item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)\r
627                                               action: @selector (cut:)  keyEquivalent: nsStringLiteral ("x")];\r
628             [menu addItem: item];\r
629             [item release];\r
631             item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)\r
632                                               action: @selector (copy:)  keyEquivalent: nsStringLiteral ("c")];\r
633             [menu addItem: item];\r
634             [item release];\r
636             item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)\r
637                                               action: @selector (paste:)  keyEquivalent: nsStringLiteral ("v")];\r
638             [menu addItem: item];\r
639             [item release];\r
641             editMenuIndex = [mainMenu numberOfItems];\r
643             item = [mainMenu addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)\r
644                                        action: nil  keyEquivalent: nsEmptyString()];\r
645             [mainMenu setSubmenu: menu forItem: item];\r
646             [menu release];\r
647         }\r
649         // use a dummy modal component so that apps can tell that something is currently modal.\r
650         dummyModalComponent.enterModalState (false);\r
651     }\r
653     ~TemporaryMainMenuWithStandardCommands()\r
654     {\r
655         if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)\r
656             [mainMenu removeItemAtIndex:editMenuIndex];\r
658         MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu.get(), oldRecentItems);\r
659     }\r
661     static bool checkModalEvent (FilePreviewComponent* preview, const Component* targetComponent)\r
662     {\r
663         if (targetComponent == nullptr)\r
664             return false;\r
666         return (targetComponent == preview\r
667                || targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr);\r
668     }\r
670 private:\r
671     MenuBarModel* const oldMenu = nullptr;\r
672     std::unique_ptr<PopupMenu> oldAppleMenu;\r
673     String oldRecentItems;\r
674     NSInteger editMenuIndex;\r
676     // The OS view already plays an alert when clicking outside\r
677     // the modal comp, so this override avoids adding extra\r
678     // inappropriate noises when the cancel button is pressed.\r
679     // This override is also important because it stops the base class\r
680     // calling ModalComponentManager::bringToFront, which can get\r
681     // recursive when file dialogs are involved\r
682     struct SilentDummyModalComp  : public Component\r
683     {\r
684         explicit SilentDummyModalComp (FilePreviewComponent* p)\r
685             : preview (p) {}\r
687         void inputAttemptWhenModal() override {}\r
689         bool canModalEventBeSentToComponent (const Component* targetComponent) override\r
690         {\r
691             return checkModalEvent (preview, targetComponent);\r
692         }\r
694         FilePreviewComponent* preview = nullptr;\r
695     };\r
697     SilentDummyModalComp dummyModalComponent;\r
698 };\r
700 //==============================================================================\r
701 namespace MainMenuHelpers\r
703     static NSString* translateMenuName (const String& name)\r
704     {\r
705         return NSLocalizedString (juceStringToNS (TRANS (name)), nil);\r
706     }\r
708     static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)\r
709     {\r
710         NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)\r
711                                                        action: sel\r
712                                                 keyEquivalent: key] autorelease];\r
713         [item setTarget: NSApp];\r
714         [menu addItem: item];\r
715         return item;\r
716     }\r
718     static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)\r
719     {\r
720         if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)\r
721         {\r
722             for (PopupMenu::MenuItemIterator iter (*extraItems); iter.next();)\r
723                 JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);\r
725             [menu addItem: [NSMenuItem separatorItem]];\r
726         }\r
728         // Services...\r
729         NSMenuItem* services = [[[NSMenuItem alloc] initWithTitle: translateMenuName ("Services")\r
730                                                            action: nil  keyEquivalent: nsEmptyString()] autorelease];\r
731         [menu addItem: services];\r
733         NSMenu* servicesMenu = [[[NSMenu alloc] initWithTitle: translateMenuName ("Services")] autorelease];\r
734         [menu setSubmenu: servicesMenu forItem: services];\r
735         [NSApp setServicesMenu: servicesMenu];\r
736         [menu addItem: [NSMenuItem separatorItem]];\r
738         createMenuItem (menu, TRANS("Hide") + String (" ") + appName, @selector (hide:), nsStringLiteral ("h"));\r
740         [createMenuItem (menu, TRANS("Hide Others"), @selector (hideOtherApplications:), nsStringLiteral ("h"))\r
741             setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];\r
743         createMenuItem (menu, TRANS("Show All"), @selector (unhideAllApplications:), nsEmptyString());\r
745         [menu addItem: [NSMenuItem separatorItem]];\r
747         createMenuItem (menu, TRANS("Quit") + String (" ") + appName, @selector (terminate:), nsStringLiteral ("q"));\r
748     }\r
750     // Since our app has no NIB, this initialises a standard app menu...\r
751     static void rebuildMainMenu (const PopupMenu* extraItems)\r
752     {\r
753         // this can't be used in a plugin!\r
754         jassert (JUCEApplicationBase::isStandaloneApp());\r
756         if (auto* app = JUCEApplicationBase::getInstance())\r
757         {\r
758             if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)\r
759             {\r
760                 if ([mainMenu numberOfItems] > 0)\r
761                 {\r
762                     if (auto appMenu = [[mainMenu itemAtIndex: 0] submenu])\r
763                     {\r
764                         [appMenu removeAllItems];\r
765                         MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);\r
766                     }\r
767                 }\r
768             }\r
769         }\r
770     }\r
773 void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,\r
774                                    const PopupMenu* extraAppleMenuItems,\r
775                                    const String& recentItemsMenuName)\r
777     if (getMacMainMenu() != newMenuBarModel)\r
778     {\r
779         JUCE_AUTORELEASEPOOL\r
780         {\r
781             if (newMenuBarModel == nullptr)\r
782             {\r
783                 delete JuceMainMenuHandler::instance;\r
784                 jassert (JuceMainMenuHandler::instance == nullptr); // should be zeroed in the destructor\r
785                 jassert (extraAppleMenuItems == nullptr); // you can't specify some extra items without also supplying a model\r
787                 extraAppleMenuItems = nullptr;\r
788             }\r
789             else\r
790             {\r
791                 if (JuceMainMenuHandler::instance == nullptr)\r
792                     JuceMainMenuHandler::instance = new JuceMainMenuHandler();\r
794                 JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);\r
795             }\r
796         }\r
797     }\r
799     MainMenuHelpers::rebuildMainMenu (extraAppleMenuItems);\r
801     if (newMenuBarModel != nullptr)\r
802         newMenuBarModel->menuItemsChanged();\r
805 MenuBarModel* MenuBarModel::getMacMainMenu()\r
807     if (auto* mm = JuceMainMenuHandler::instance)\r
808         return mm->currentModel;\r
810     return nullptr;\r
813 const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()\r
815     if (auto* mm = JuceMainMenuHandler::instance)\r
816         return mm->extraAppleMenuItems.get();\r
818     return nullptr;\r
821 using MenuTrackingChangedCallback = void (*)(bool);\r
822 extern MenuTrackingChangedCallback menuTrackingChangedCallback;\r
824 static void mainMenuTrackingChanged (bool isTracking)\r
826     PopupMenu::dismissAllActiveMenus();\r
828     if (auto* menuHandler = JuceMainMenuHandler::instance)\r
829     {\r
830         menuHandler->isOpen = isTracking;\r
832         if (auto* model = menuHandler->currentModel)\r
833             model->handleMenuBarActivate (isTracking);\r
835         if (menuHandler->defferedUpdateRequested && ! isTracking)\r
836         {\r
837             menuHandler->defferedUpdateRequested = false;\r
838             menuHandler->menuBarItemsChanged (menuHandler->currentModel);\r
839         }\r
840     }\r
843 void juce_initialiseMacMainMenu()\r
845     menuTrackingChangedCallback = mainMenuTrackingChanged;\r
847     if (JuceMainMenuHandler::instance == nullptr)\r
848         MainMenuHelpers::rebuildMainMenu (nullptr);\r
851 // (used from other modules that need to create an NSMenu)\r
852 NSMenu* createNSMenu (const PopupMenu&, const String&, int, int, bool);\r
853 NSMenu* createNSMenu (const PopupMenu& menu, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate)\r
855     juce_initialiseMacMainMenu();\r
857     if (auto* mm = JuceMainMenuHandler::instance)\r
858         return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);\r
860     jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?\r
861     return nil;\r
864 } // namespace juce\r