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.

754 lines
28KB

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