2 ==============================================================================
\r
4 This file is part of the JUCE library.
\r
5 Copyright (c) 2022 - Raw Material Software Limited
\r
7 JUCE is an open source library subject to commercial or open-source
\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
23 ==============================================================================
\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
36 JuceMainMenuBarHolder()
\r
37 : mainMenuBar ([[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")])
\r
39 auto item = [mainMenuBar addItemWithTitle: nsStringLiteral ("Apple")
\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
52 [NSApp setMainMenu: mainMenuBar];
\r
55 ~JuceMainMenuBarHolder()
\r
57 clearSingletonInstance();
\r
59 [NSApp setMainMenu: nil];
\r
60 [mainMenuBar release];
\r
63 NSMenu* mainMenuBar = nil;
\r
65 JUCE_DECLARE_SINGLETON_SINGLETHREADED (JuceMainMenuBarHolder, true)
\r
68 JUCE_IMPLEMENT_SINGLETON (JuceMainMenuBarHolder)
\r
70 //==============================================================================
\r
71 class JuceMainMenuHandler : private MenuBarModel::Listener,
\r
72 private DeletedAtShutdown
\r
75 JuceMainMenuHandler()
\r
77 static JuceMenuCallbackClass cls;
\r
78 callback = [cls.createInstance() init];
\r
79 JuceMenuCallbackClass::setOwner (callback, this);
\r
82 ~JuceMainMenuHandler() override
\r
84 setMenu (nullptr, nullptr, String());
\r
86 jassert (instance == this);
\r
92 void setMenu (MenuBarModel* const newMenuBarModel,
\r
93 const PopupMenu* newExtraAppleMenuItems,
\r
94 const String& recentItemsName)
\r
96 recentItemsMenuName = recentItemsName;
\r
98 if (currentModel != newMenuBarModel)
\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
111 extraAppleMenuItems.reset (createCopyIfNotNull (newExtraAppleMenuItems));
\r
114 void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)
\r
116 NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
\r
118 keyEquivalent: nsEmptyString()];
\r
120 NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);
\r
122 [parent setSubmenu: sub forItem: item];
\r
126 void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)
\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
138 removeItemRecursive ([parentItem submenu]);
\r
139 [parentItem setSubmenu: menu];
\r
144 void updateTopLevelMenu (NSMenu* menu)
\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
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
163 void menuBarItemsChanged (MenuBarModel*) override
\r
167 defferedUpdateRequested = true;
\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
184 for (int i = 0; i < menuNames.size(); ++i)
\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
191 updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
\r
195 void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) override
\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
203 void invoke (const PopupMenu::Item& item, int topLevelIndex) const
\r
205 if (currentModel != nullptr)
\r
207 if (item.action != nullptr)
\r
209 MessageManager::callAsync (item.action);
\r
213 if (item.customCallback != nullptr)
\r
214 if (! item.customCallback->menuItemTriggered())
\r
217 if (item.commandManager != nullptr)
\r
219 ApplicationCommandTarget::InvocationInfo info (item.itemID);
\r
220 info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
\r
222 item.commandManager->invoke (info, true);
\r
225 MessageManager::callAsync ([=]
\r
227 if (instance != nullptr)
\r
228 instance->invokeDirectly (item.itemID, topLevelIndex);
\r
233 void invokeDirectly (int commandId, int topLevelIndex)
\r
235 if (currentModel != nullptr)
\r
236 currentModel->menuItemSelected (commandId, topLevelIndex);
\r
239 void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
\r
240 const int topLevelMenuId, const int topLevelIndex)
\r
242 const PopupMenu::Item& i = iter.getItem();
\r
243 NSString* text = juceStringToNS (i.text);
\r
246 text = nsEmptyString();
\r
250 [menuToAddTo addItem: [NSMenuItem separatorItem]];
\r
252 else if (i.isSectionHeader)
\r
254 NSMenuItem* item = [menuToAddTo addItemWithTitle: text
\r
256 keyEquivalent: nsEmptyString()];
\r
258 [item setEnabled: false];
\r
260 else if (i.subMenu != nullptr)
\r
262 if (recentItemsMenuName.isNotEmpty() && i.text == recentItemsMenuName)
\r
264 if (recent == nullptr)
\r
265 recent = std::make_unique<RecentFilesMenuItem>();
\r
267 if (recent->recentItem != nil)
\r
269 if (NSMenu* parent = [recent->recentItem menu])
\r
270 [parent removeItem: recent->recentItem];
\r
272 [menuToAddTo addItem: recent->recentItem];
\r
277 NSMenuItem* item = [menuToAddTo addItemWithTitle: text
\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
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
299 [item setState: i.isTicked ? NSOnState : NSOffState];
\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
310 for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))
\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
315 juce_wchar key = kp.getTextCharacter();
\r
318 key = (juce_wchar) kp.getKeyCode();
\r
320 [item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];
\r
321 [item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];
\r
328 [menuToAddTo addItem: item];
\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
339 NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
\r
342 [m setDelegate: (id<NSMenuDelegate>) callback];
\r
344 for (PopupMenu::MenuItemIterator iter (menu); iter.next();)
\r
345 addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
\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
361 struct RecentFilesMenuItem
\r
363 RecentFilesMenuItem() : recentItem (nil)
\r
365 if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])
\r
367 NSArray* array = nil;
\r
369 if (@available (macOS 10.11, *))
\r
371 [menuNib instantiateWithOwner: NSApp
\r
372 topLevelObjects: &array];
\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
382 for (id object in array)
\r
384 if ([object isKindOfClass: [NSMenu class]])
\r
386 if (NSArray* items = [object itemArray])
\r
388 if (NSMenuItem* item = findRecentFilesItem (items))
\r
390 recentItem = [item retain];
\r
399 ~RecentFilesMenuItem()
\r
401 [recentItem release];
\r
404 static NSMenuItem* findRecentFilesItem (NSArray* const items)
\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
414 NSMenuItem* recentItem;
\r
416 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)
\r
419 std::unique_ptr<RecentFilesMenuItem> recent;
\r
421 //==============================================================================
\r
422 static NSMenuItem* findMenuItemWithCommandID (NSMenu* const menu, int commandID)
\r
424 for (NSInteger i = [menu numberOfItems]; --i >= 0;)
\r
426 NSMenuItem* m = [menu itemAtIndex: i];
\r
427 if (auto* menuItem = getJuceClassFromNSObject<PopupMenu::Item> ([m representedObject]))
\r
428 if (menuItem->itemID == commandID)
\r
431 if (NSMenu* sub = [m submenu])
\r
432 if (NSMenuItem* found = findMenuItemWithCommandID (sub, commandID))
\r
439 static void flashMenuBar (NSMenu* menu)
\r
441 if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])
\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
458 ValidatorClass() : ObjCClass ("JUCEMenuValidator_")
\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
471 static BOOL validateMenuItem (id, SEL, NSMenuItem*) { return YES; }
\r
472 static void menuItemInvoked (id, SEL, NSMenuItem*) {}
\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
482 if ([menu indexOfItem: item] >= 0)
\r
484 NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown
\r
485 location: NSZeroPoint
\r
486 modifierFlags: NSEventModifierFlagCommand
\r
489 context: [NSGraphicsContext currentContext]
\r
490 characters: f35String
\r
491 charactersIgnoringModifiers: f35String
\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
504 static unsigned int juceModsToNSMods (const ModifierKeys mods)
\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
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
518 if (isPositiveAndBelow (menuItemIndex, (int) [parentMenu numberOfItems]))
\r
520 auto menuItem = [parentMenu itemAtIndex:menuItemIndex];
\r
522 if (auto submenu = [menuItem submenu])
\r
523 removeItemRecursive (submenu);
\r
525 [parentMenu removeItem:menuItem];
\r
531 static void removeItemRecursive (NSMenu* menu)
\r
533 if (menu != nullptr)
\r
535 auto n = static_cast<int> ([menu numberOfItems]);
\r
537 for (auto i = n; --i >= 0;)
\r
538 removeItemRecursive (menu, i);
\r
542 static NSMenu* getMainMenuBar()
\r
544 return JuceMainMenuBarHolder::getInstance()->mainMenuBar;
\r
547 //==============================================================================
\r
548 struct JuceMenuCallbackClass : public ObjCClass<NSObject>
\r
550 JuceMenuCallbackClass() : ObjCClass ("JUCEMainMenu_")
\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
567 static void setOwner (id self, JuceMainMenuHandler* owner)
\r
569 object_setInstanceVariable (self, "owner", owner);
\r
573 static auto* getPopupMenuItem (NSMenuItem* item)
\r
575 return getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]);
\r
578 static auto* getOwner (id self)
\r
580 return getIvar<JuceMainMenuHandler*> (self, "owner");
\r
583 static void menuItemInvoked (id self, SEL, NSMenuItem* item)
\r
585 if (auto* juceItem = getPopupMenuItem (item))
\r
586 getOwner (self)->invoke (*juceItem, static_cast<int> ([item tag]));
\r
589 static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
\r
591 getOwner (self)->updateTopLevelMenu (menu);
\r
594 static BOOL validateMenuItem (id, SEL, NSMenuItem* item)
\r
596 if (auto* juceItem = getPopupMenuItem (item))
\r
597 return juceItem->isEnabled;
\r
604 JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
\r
606 //==============================================================================
\r
607 class TemporaryMainMenuWithStandardCommands
\r
610 explicit TemporaryMainMenuWithStandardCommands (FilePreviewComponent* filePreviewComponent)
\r
611 : oldMenu (MenuBarModel::getMacMainMenu()), dummyModalComponent (filePreviewComponent)
\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
623 NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];
\r
626 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)
\r
627 action: @selector (cut:) keyEquivalent: nsStringLiteral ("x")];
\r
628 [menu addItem: item];
\r
631 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)
\r
632 action: @selector (copy:) keyEquivalent: nsStringLiteral ("c")];
\r
633 [menu addItem: item];
\r
636 item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)
\r
637 action: @selector (paste:) keyEquivalent: nsStringLiteral ("v")];
\r
638 [menu addItem: item];
\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
649 // use a dummy modal component so that apps can tell that something is currently modal.
\r
650 dummyModalComponent.enterModalState (false);
\r
653 ~TemporaryMainMenuWithStandardCommands()
\r
655 if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
\r
656 [mainMenu removeItemAtIndex:editMenuIndex];
\r
658 MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu.get(), oldRecentItems);
\r
661 static bool checkModalEvent (FilePreviewComponent* preview, const Component* targetComponent)
\r
663 if (targetComponent == nullptr)
\r
666 return (targetComponent == preview
\r
667 || targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr);
\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
684 explicit SilentDummyModalComp (FilePreviewComponent* p)
\r
687 void inputAttemptWhenModal() override {}
\r
689 bool canModalEventBeSentToComponent (const Component* targetComponent) override
\r
691 return checkModalEvent (preview, targetComponent);
\r
694 FilePreviewComponent* preview = nullptr;
\r
697 SilentDummyModalComp dummyModalComponent;
\r
700 //==============================================================================
\r
701 namespace MainMenuHelpers
\r
703 static NSString* translateMenuName (const String& name)
\r
705 return NSLocalizedString (juceStringToNS (TRANS (name)), nil);
\r
708 static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)
\r
710 NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)
\r
712 keyEquivalent: key] autorelease];
\r
713 [item setTarget: NSApp];
\r
714 [menu addItem: item];
\r
718 static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)
\r
720 if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)
\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
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
750 // Since our app has no NIB, this initialises a standard app menu...
\r
751 static void rebuildMainMenu (const PopupMenu* extraItems)
\r
753 // this can't be used in a plugin!
\r
754 jassert (JUCEApplicationBase::isStandaloneApp());
\r
756 if (auto* app = JUCEApplicationBase::getInstance())
\r
758 if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
\r
760 if ([mainMenu numberOfItems] > 0)
\r
762 if (auto appMenu = [[mainMenu itemAtIndex: 0] submenu])
\r
764 [appMenu removeAllItems];
\r
765 MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
\r
773 void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
\r
774 const PopupMenu* extraAppleMenuItems,
\r
775 const String& recentItemsMenuName)
\r
777 if (getMacMainMenu() != newMenuBarModel)
\r
779 JUCE_AUTORELEASEPOOL
\r
781 if (newMenuBarModel == nullptr)
\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
791 if (JuceMainMenuHandler::instance == nullptr)
\r
792 JuceMainMenuHandler::instance = new JuceMainMenuHandler();
\r
794 JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);
\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
813 const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
\r
815 if (auto* mm = JuceMainMenuHandler::instance)
\r
816 return mm->extraAppleMenuItems.get();
\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
830 menuHandler->isOpen = isTracking;
\r
832 if (auto* model = menuHandler->currentModel)
\r
833 model->handleMenuBarActivate (isTracking);
\r
835 if (menuHandler->defferedUpdateRequested && ! isTracking)
\r
837 menuHandler->defferedUpdateRequested = false;
\r
838 menuHandler->menuBarItemsChanged (menuHandler->currentModel);
\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
864 } // namespace juce
\r