The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. class JuceMainMenuHandler : private MenuBarModel::Listener,
  22. private DeletedAtShutdown
  23. {
  24. public:
  25. JuceMainMenuHandler()
  26. {
  27. static JuceMenuCallbackClass cls;
  28. callback = [cls.createInstance() init];
  29. JuceMenuCallbackClass::setOwner (callback, this);
  30. }
  31. ~JuceMainMenuHandler()
  32. {
  33. setMenu (nullptr, nullptr, String());
  34. jassert (instance == this);
  35. instance = nullptr;
  36. [callback release];
  37. }
  38. void setMenu (MenuBarModel* const newMenuBarModel,
  39. const PopupMenu* newExtraAppleMenuItems,
  40. const String& recentItemsName)
  41. {
  42. recentItemsMenuName = recentItemsName;
  43. if (currentModel != newMenuBarModel)
  44. {
  45. if (currentModel != nullptr)
  46. currentModel->removeListener (this);
  47. currentModel = newMenuBarModel;
  48. if (currentModel != nullptr)
  49. currentModel->addListener (this);
  50. menuBarItemsChanged (nullptr);
  51. }
  52. extraAppleMenuItems = createCopyIfNotNull (newExtraAppleMenuItems);
  53. }
  54. void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)
  55. {
  56. NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
  57. action: nil
  58. keyEquivalent: nsEmptyString()];
  59. NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);
  60. [parent setSubmenu: sub forItem: item];
  61. [sub setAutoenablesItems: false];
  62. [sub release];
  63. }
  64. void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)
  65. {
  66. // Note: This method used to update the contents of the existing menu in-place, but that caused
  67. // weird side-effects which messed-up keyboard focus when switching between windows. By creating
  68. // a new menu and replacing the old one with it, that problem seems to be avoided..
  69. NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)];
  70. for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)
  71. addMenuItem (iter, menu, menuId, topLevelIndex);
  72. [menu setAutoenablesItems: false];
  73. [menu update];
  74. [parentItem setSubmenu: menu];
  75. [menu release];
  76. }
  77. void updateTopLevelMenu (NSMenu* menu)
  78. {
  79. NSMenu* superMenu = [menu supermenu];
  80. auto menuNames = currentModel->getMenuBarNames();
  81. auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1;
  82. [menu removeAllItems];
  83. auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]);
  84. for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();)
  85. addMenuItem (iter, menu, 1, indexOfMenu);
  86. [menu update];
  87. }
  88. void menuBarItemsChanged (MenuBarModel*)
  89. {
  90. if (isOpen)
  91. {
  92. defferedUpdateRequested = true;
  93. return;
  94. }
  95. lastUpdateTime = Time::getMillisecondCounter();
  96. StringArray menuNames;
  97. if (currentModel != nullptr)
  98. menuNames = currentModel->getMenuBarNames();
  99. NSMenu* menuBar = [[NSApp mainMenu] retain];
  100. while ([menuBar numberOfItems] > 1 + menuNames.size())
  101. [menuBar removeItemAtIndex: [menuBar numberOfItems] - 1];
  102. int menuId = 1;
  103. for (int i = 0; i < menuNames.size(); ++i)
  104. {
  105. const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames[i]));
  106. if (i >= [menuBar numberOfItems] - 1)
  107. addTopLevelMenu (menuBar, menu, menuNames[i], menuId, i);
  108. else
  109. updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
  110. }
  111. [menuBar release];
  112. }
  113. void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
  114. {
  115. if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0
  116. && info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress)
  117. if (auto* item = findMenuItemWithTag ([NSApp mainMenu], info.commandID))
  118. flashMenuBar ([item menu]);
  119. }
  120. void invoke (int commandId, ApplicationCommandManager* commandManager, int topLevelIndex) const
  121. {
  122. if (currentModel != nullptr)
  123. {
  124. if (commandManager != nullptr)
  125. {
  126. ApplicationCommandTarget::InvocationInfo info (commandId);
  127. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  128. commandManager->invoke (info, true);
  129. }
  130. MessageManager::callAsync ([=]()
  131. {
  132. if (instance != nullptr)
  133. instance->invokeDirectly (commandId, topLevelIndex);
  134. });
  135. }
  136. }
  137. void invokeDirectly (int commandId, int topLevelIndex)
  138. {
  139. if (currentModel != nullptr)
  140. currentModel->menuItemSelected (commandId, topLevelIndex);
  141. }
  142. void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
  143. const int topLevelMenuId, const int topLevelIndex)
  144. {
  145. const PopupMenu::Item& i = iter.getItem();
  146. NSString* text = juceStringToNS (i.text);
  147. if (text == nil)
  148. text = nsEmptyString();
  149. if (i.isSeparator)
  150. {
  151. [menuToAddTo addItem: [NSMenuItem separatorItem]];
  152. }
  153. else if (i.isSectionHeader)
  154. {
  155. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  156. action: nil
  157. keyEquivalent: nsEmptyString()];
  158. [item setEnabled: false];
  159. }
  160. else if (i.subMenu != nullptr)
  161. {
  162. if (i.text == recentItemsMenuName)
  163. {
  164. if (recent == nullptr)
  165. recent = new RecentFilesMenuItem();
  166. if (recent->recentItem != nil)
  167. {
  168. if (NSMenu* parent = [recent->recentItem menu])
  169. [parent removeItem: recent->recentItem];
  170. [menuToAddTo addItem: recent->recentItem];
  171. return;
  172. }
  173. }
  174. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  175. action: nil
  176. keyEquivalent: nsEmptyString()];
  177. [item setTag: i.itemID];
  178. [item setEnabled: i.isEnabled];
  179. NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);
  180. [menuToAddTo setSubmenu: sub forItem: item];
  181. [sub release];
  182. }
  183. else
  184. {
  185. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  186. action: @selector (menuItemInvoked:)
  187. keyEquivalent: nsEmptyString()];
  188. [item setTag: i.itemID];
  189. [item setEnabled: i.isEnabled];
  190. [item setState: i.isTicked ? NSOnState : NSOffState];
  191. [item setTarget: (id) callback];
  192. NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) i.commandManager]];
  193. [info addObject: [NSNumber numberWithInt: topLevelIndex]];
  194. [item setRepresentedObject: info];
  195. if (i.commandManager != nullptr)
  196. {
  197. for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))
  198. {
  199. if (kp != KeyPress::backspaceKey // (adding these is annoying because it flashes the menu bar
  200. && kp != KeyPress::deleteKey) // every time you press the key while editing text)
  201. {
  202. juce_wchar key = kp.getTextCharacter();
  203. if (key == 0)
  204. key = (juce_wchar) kp.getKeyCode();
  205. [item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];
  206. [item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];
  207. }
  208. break;
  209. }
  210. }
  211. }
  212. }
  213. NSMenu* createMenu (const PopupMenu menu,
  214. const String& menuName,
  215. const int topLevelMenuId,
  216. const int topLevelIndex,
  217. const bool addDelegate)
  218. {
  219. NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
  220. [m setAutoenablesItems: false];
  221. if (addDelegate)
  222. {
  223. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  224. [m setDelegate: (id<NSMenuDelegate>) callback];
  225. #else
  226. [m setDelegate: callback];
  227. #endif
  228. }
  229. for (PopupMenu::MenuItemIterator iter (menu); iter.next();)
  230. addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
  231. [m update];
  232. return m;
  233. }
  234. static JuceMainMenuHandler* instance;
  235. MenuBarModel* currentModel = nullptr;
  236. ScopedPointer<PopupMenu> extraAppleMenuItems;
  237. uint32 lastUpdateTime = 0;
  238. NSObject* callback = nil;
  239. String recentItemsMenuName;
  240. bool isOpen = false, defferedUpdateRequested = false;
  241. private:
  242. struct RecentFilesMenuItem
  243. {
  244. RecentFilesMenuItem() : recentItem (nil)
  245. {
  246. if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])
  247. {
  248. NSArray* array = nil;
  249. #if (! defined (MAC_OS_X_VERSION_10_8)) || MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
  250. [menuNib instantiateNibWithOwner: NSApp topLevelObjects: &array];
  251. #else
  252. [menuNib instantiateWithOwner: NSApp topLevelObjects: &array];
  253. #endif
  254. for (id object in array)
  255. {
  256. if ([object isKindOfClass: [NSMenu class]])
  257. {
  258. if (NSArray* items = [object itemArray])
  259. {
  260. if (NSMenuItem* item = findRecentFilesItem (items))
  261. {
  262. recentItem = [item retain];
  263. break;
  264. }
  265. }
  266. }
  267. }
  268. }
  269. }
  270. ~RecentFilesMenuItem()
  271. {
  272. [recentItem release];
  273. }
  274. static NSMenuItem* findRecentFilesItem (NSArray* const items)
  275. {
  276. for (id object in items)
  277. if (NSArray* subMenuItems = [[object submenu] itemArray])
  278. for (id subObject in subMenuItems)
  279. if ([subObject isKindOfClass: [NSMenuItem class]])
  280. return subObject;
  281. return nil;
  282. }
  283. NSMenuItem* recentItem;
  284. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)
  285. };
  286. ScopedPointer<RecentFilesMenuItem> recent;
  287. //==============================================================================
  288. static NSMenuItem* findMenuItemWithTag (NSMenu* const menu, int tag)
  289. {
  290. for (NSInteger i = [menu numberOfItems]; --i >= 0;)
  291. {
  292. NSMenuItem* m = [menu itemAtIndex: i];
  293. if ([m tag] == tag)
  294. return m;
  295. if (NSMenu* sub = [m submenu])
  296. if (NSMenuItem* found = findMenuItemWithTag (sub, tag))
  297. return found;
  298. }
  299. return nil;
  300. }
  301. static void flashMenuBar (NSMenu* menu)
  302. {
  303. if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])
  304. return;
  305. [menu retain];
  306. const unichar f35Key = NSF35FunctionKey;
  307. NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
  308. NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: nsStringLiteral ("x")
  309. action: nil
  310. keyEquivalent: f35String];
  311. [item setTarget: nil];
  312. [menu insertItem: item atIndex: [menu numberOfItems]];
  313. [item release];
  314. if ([menu indexOfItem: item] >= 0)
  315. {
  316. NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown
  317. location: NSZeroPoint
  318. modifierFlags: NSEventModifierFlagCommand
  319. timestamp: 0
  320. windowNumber: 0
  321. context: [NSGraphicsContext currentContext]
  322. characters: f35String
  323. charactersIgnoringModifiers: f35String
  324. isARepeat: NO
  325. keyCode: 0];
  326. [menu performKeyEquivalent: f35Event];
  327. if ([menu indexOfItem: item] >= 0)
  328. [menu removeItem: item]; // (this throws if the item isn't actually in the menu)
  329. }
  330. [menu release];
  331. }
  332. static unsigned int juceModsToNSMods (const ModifierKeys mods)
  333. {
  334. unsigned int m = 0;
  335. if (mods.isShiftDown()) m |= NSEventModifierFlagShift;
  336. if (mods.isCtrlDown()) m |= NSEventModifierFlagControl;
  337. if (mods.isAltDown()) m |= NSEventModifierFlagOption;
  338. if (mods.isCommandDown()) m |= NSEventModifierFlagCommand;
  339. return m;
  340. }
  341. //==============================================================================
  342. struct JuceMenuCallbackClass : public ObjCClass<NSObject>
  343. {
  344. JuceMenuCallbackClass() : ObjCClass<NSObject> ("JUCEMainMenu_")
  345. {
  346. addIvar<JuceMainMenuHandler*> ("owner");
  347. addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@");
  348. addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@");
  349. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  350. addProtocol (@protocol (NSMenuDelegate));
  351. #endif
  352. registerClass();
  353. }
  354. static void setOwner (id self, JuceMainMenuHandler* owner)
  355. {
  356. object_setInstanceVariable (self, "owner", owner);
  357. }
  358. private:
  359. static void menuItemInvoked (id self, SEL, NSMenuItem* item)
  360. {
  361. auto owner = getIvar<JuceMainMenuHandler*> (self, "owner");
  362. if ([[item representedObject] isKindOfClass: [NSArray class]])
  363. {
  364. // If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to
  365. // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back
  366. // into the focused component and let it trigger the menu item indirectly.
  367. NSEvent* e = [NSApp currentEvent];
  368. if ([e type] == NSEventTypeKeyDown || [e type] == NSEventTypeKeyUp)
  369. {
  370. if (auto* focused = juce::Component::getCurrentlyFocusedComponent())
  371. {
  372. if (auto peer = dynamic_cast<juce::NSViewComponentPeer*> (focused->getPeer()))
  373. {
  374. if ([e type] == NSEventTypeKeyDown)
  375. peer->redirectKeyDown (e);
  376. else
  377. peer->redirectKeyUp (e);
  378. return;
  379. }
  380. }
  381. }
  382. NSArray* info = (NSArray*) [item representedObject];
  383. owner->invoke ((int) [item tag],
  384. (ApplicationCommandManager*) (pointer_sized_int)
  385. [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
  386. (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
  387. }
  388. }
  389. static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
  390. {
  391. getIvar<JuceMainMenuHandler*> (self, "owner")->updateTopLevelMenu (menu);
  392. }
  393. };
  394. };
  395. JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
  396. //==============================================================================
  397. class TemporaryMainMenuWithStandardCommands
  398. {
  399. public:
  400. TemporaryMainMenuWithStandardCommands()
  401. : oldMenu (MenuBarModel::getMacMainMenu())
  402. {
  403. if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())
  404. oldAppleMenu = new PopupMenu (*appleMenu);
  405. if (auto* handler = JuceMainMenuHandler::instance)
  406. oldRecentItems = handler->recentItemsMenuName;
  407. MenuBarModel::setMacMainMenu (nullptr);
  408. NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];
  409. NSMenuItem* item;
  410. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)
  411. action: @selector (cut:) keyEquivalent: nsStringLiteral ("x")];
  412. [menu addItem: item];
  413. [item release];
  414. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)
  415. action: @selector (copy:) keyEquivalent: nsStringLiteral ("c")];
  416. [menu addItem: item];
  417. [item release];
  418. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)
  419. action: @selector (paste:) keyEquivalent: nsStringLiteral ("v")];
  420. [menu addItem: item];
  421. [item release];
  422. item = [[NSApp mainMenu] addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)
  423. action: nil keyEquivalent: nsEmptyString()];
  424. [[NSApp mainMenu] setSubmenu: menu forItem: item];
  425. [menu release];
  426. // use a dummy modal component so that apps can tell that something is currently modal.
  427. dummyModalComponent.enterModalState (false);
  428. }
  429. ~TemporaryMainMenuWithStandardCommands()
  430. {
  431. MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu, oldRecentItems);
  432. }
  433. private:
  434. MenuBarModel* const oldMenu;
  435. ScopedPointer<PopupMenu> oldAppleMenu;
  436. String oldRecentItems;
  437. // The OS view already plays an alert when clicking outside
  438. // the modal comp, so this override avoids adding extra
  439. // inappropriate noises when the cancel button is pressed.
  440. // This override is also important because it stops the base class
  441. // calling ModalComponentManager::bringToFront, which can get
  442. // recursive when file dialogs are involved
  443. struct SilentDummyModalComp : public Component
  444. {
  445. SilentDummyModalComp() {}
  446. void inputAttemptWhenModal() override {}
  447. };
  448. SilentDummyModalComp dummyModalComponent;
  449. };
  450. //==============================================================================
  451. namespace MainMenuHelpers
  452. {
  453. static NSString* translateMenuName (const String& name)
  454. {
  455. return NSLocalizedString (juceStringToNS (TRANS (name)), nil);
  456. }
  457. static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)
  458. {
  459. NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)
  460. action: sel
  461. keyEquivalent: key] autorelease];
  462. [item setTarget: NSApp];
  463. [menu addItem: item];
  464. return item;
  465. }
  466. static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)
  467. {
  468. if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)
  469. {
  470. for (PopupMenu::MenuItemIterator iter (*extraItems); iter.next();)
  471. JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
  472. [menu addItem: [NSMenuItem separatorItem]];
  473. }
  474. // Services...
  475. NSMenuItem* services = [[[NSMenuItem alloc] initWithTitle: translateMenuName ("Services")
  476. action: nil keyEquivalent: nsEmptyString()] autorelease];
  477. [menu addItem: services];
  478. NSMenu* servicesMenu = [[[NSMenu alloc] initWithTitle: translateMenuName ("Services")] autorelease];
  479. [menu setSubmenu: servicesMenu forItem: services];
  480. [NSApp setServicesMenu: servicesMenu];
  481. [menu addItem: [NSMenuItem separatorItem]];
  482. createMenuItem (menu, TRANS("Hide") + String (" ") + appName, @selector (hide:), nsStringLiteral ("h"));
  483. [createMenuItem (menu, TRANS("Hide Others"), @selector (hideOtherApplications:), nsStringLiteral ("h"))
  484. setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
  485. createMenuItem (menu, TRANS("Show All"), @selector (unhideAllApplications:), nsEmptyString());
  486. [menu addItem: [NSMenuItem separatorItem]];
  487. createMenuItem (menu, TRANS("Quit") + String (" ") + appName, @selector (terminate:), nsStringLiteral ("q"));
  488. }
  489. // Since our app has no NIB, this initialises a standard app menu...
  490. static void rebuildMainMenu (const PopupMenu* extraItems)
  491. {
  492. // this can't be used in a plugin!
  493. jassert (JUCEApplicationBase::isStandaloneApp());
  494. if (auto* app = JUCEApplicationBase::getInstance())
  495. {
  496. JUCE_AUTORELEASEPOOL
  497. {
  498. NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")];
  499. NSMenuItem* item = [mainMenu addItemWithTitle: nsStringLiteral ("Apple")
  500. action: nil
  501. keyEquivalent: nsEmptyString()];
  502. NSMenu* appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];
  503. [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
  504. [mainMenu setSubmenu: appMenu forItem: item];
  505. [NSApp setMainMenu: mainMenu];
  506. MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
  507. [appMenu release];
  508. [mainMenu release];
  509. }
  510. }
  511. }
  512. }
  513. void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
  514. const PopupMenu* extraAppleMenuItems,
  515. const String& recentItemsMenuName)
  516. {
  517. if (getMacMainMenu() != newMenuBarModel)
  518. {
  519. JUCE_AUTORELEASEPOOL
  520. {
  521. if (newMenuBarModel == nullptr)
  522. {
  523. delete JuceMainMenuHandler::instance;
  524. jassert (JuceMainMenuHandler::instance == nullptr); // should be zeroed in the destructor
  525. jassert (extraAppleMenuItems == nullptr); // you can't specify some extra items without also supplying a model
  526. extraAppleMenuItems = nullptr;
  527. }
  528. else
  529. {
  530. if (JuceMainMenuHandler::instance == nullptr)
  531. JuceMainMenuHandler::instance = new JuceMainMenuHandler();
  532. JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);
  533. }
  534. }
  535. }
  536. MainMenuHelpers::rebuildMainMenu (extraAppleMenuItems);
  537. if (newMenuBarModel != nullptr)
  538. newMenuBarModel->menuItemsChanged();
  539. }
  540. MenuBarModel* MenuBarModel::getMacMainMenu()
  541. {
  542. if (auto* mm = JuceMainMenuHandler::instance)
  543. return mm->currentModel;
  544. return nullptr;
  545. }
  546. const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
  547. {
  548. if (auto* mm = JuceMainMenuHandler::instance)
  549. return mm->extraAppleMenuItems.get();
  550. return nullptr;
  551. }
  552. typedef void (*MenuTrackingChangedCallback) (bool);
  553. extern MenuTrackingChangedCallback menuTrackingChangedCallback;
  554. static void mainMenuTrackingChanged (bool isTracking)
  555. {
  556. PopupMenu::dismissAllActiveMenus();
  557. if (auto* menuHandler = JuceMainMenuHandler::instance)
  558. {
  559. menuHandler->isOpen = isTracking;
  560. if (auto* model = menuHandler->currentModel)
  561. model->handleMenuBarActivate (isTracking);
  562. if (menuHandler->defferedUpdateRequested && ! isTracking)
  563. {
  564. menuHandler->defferedUpdateRequested = false;
  565. menuHandler->menuBarItemsChanged (menuHandler->currentModel);
  566. }
  567. }
  568. }
  569. void juce_initialiseMacMainMenu()
  570. {
  571. menuTrackingChangedCallback = mainMenuTrackingChanged;
  572. if (JuceMainMenuHandler::instance == nullptr)
  573. MainMenuHelpers::rebuildMainMenu (nullptr);
  574. }
  575. // (used from other modules that need to create an NSMenu)
  576. NSMenu* createNSMenu (const PopupMenu& menu, const String& name,
  577. int topLevelMenuId, int topLevelIndex, bool addDelegate)
  578. {
  579. juce_initialiseMacMainMenu();
  580. if (auto* mm = JuceMainMenuHandler::instance)
  581. return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);
  582. jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?
  583. return nil;
  584. }
  585. } // namespace juce