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.

725 lines
27KB

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