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.

juce_mac_MainMenu.mm 28KB

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