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

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