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

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