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.

858 lines
31KB

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