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.

745 lines
27KB

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