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
28KB

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