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.

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