Audio plugin host https://kx.studio/carla
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.

780 lines
29KB

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