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.

538 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  19. // compiled on its own).
  20. #if JUCE_INCLUDED_FILE
  21. //==============================================================================
  22. class JuceMainMenuHandler;
  23. END_JUCE_NAMESPACE
  24. using namespace JUCE_NAMESPACE;
  25. #define JuceMenuCallback MakeObjCClassName(JuceMenuCallback)
  26. #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  27. @interface JuceMenuCallback : NSObject <NSMenuDelegate>
  28. #else
  29. @interface JuceMenuCallback : NSObject
  30. #endif
  31. {
  32. JuceMainMenuHandler* owner;
  33. }
  34. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_;
  35. - (void) dealloc;
  36. - (void) menuItemInvoked: (id) menu;
  37. - (void) menuNeedsUpdate: (NSMenu*) menu;
  38. @end
  39. BEGIN_JUCE_NAMESPACE
  40. //==============================================================================
  41. class JuceMainMenuHandler : private MenuBarModelListener,
  42. private DeletedAtShutdown
  43. {
  44. public:
  45. static JuceMainMenuHandler* instance;
  46. //==============================================================================
  47. JuceMainMenuHandler()
  48. : currentModel (0),
  49. lastUpdateTime (0)
  50. {
  51. callback = [[JuceMenuCallback alloc] initWithOwner: this];
  52. }
  53. ~JuceMainMenuHandler()
  54. {
  55. setMenu (0);
  56. jassert (instance == this);
  57. instance = 0;
  58. [callback release];
  59. }
  60. void setMenu (MenuBarModel* const newMenuBarModel)
  61. {
  62. if (currentModel != newMenuBarModel)
  63. {
  64. if (currentModel != 0)
  65. currentModel->removeListener (this);
  66. currentModel = newMenuBarModel;
  67. if (currentModel != 0)
  68. currentModel->addListener (this);
  69. menuBarItemsChanged (0);
  70. }
  71. }
  72. void addSubMenu (NSMenu* parent, const PopupMenu& child,
  73. const String& name, const int menuId, const int tag)
  74. {
  75. NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
  76. action: nil
  77. keyEquivalent: @""];
  78. [item setTag: tag];
  79. NSMenu* sub = createMenu (child, name, menuId, tag);
  80. [parent setSubmenu: sub forItem: item];
  81. [sub setAutoenablesItems: false];
  82. [sub release];
  83. }
  84. void updateSubMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy,
  85. const String& name, const int menuId, const int tag)
  86. {
  87. [parentItem setTag: tag];
  88. NSMenu* menu = [parentItem submenu];
  89. [menu setTitle: juceStringToNS (name)];
  90. while ([menu numberOfItems] > 0)
  91. [menu removeItemAtIndex: 0];
  92. PopupMenu::MenuItemIterator iter (menuToCopy);
  93. while (iter.next())
  94. addMenuItem (iter, menu, menuId, tag);
  95. [menu setAutoenablesItems: false];
  96. [menu update];
  97. }
  98. void menuBarItemsChanged (MenuBarModel*)
  99. {
  100. lastUpdateTime = Time::getMillisecondCounter();
  101. StringArray menuNames;
  102. if (currentModel != 0)
  103. menuNames = currentModel->getMenuBarNames();
  104. NSMenu* menuBar = [NSApp mainMenu];
  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. addSubMenu (menuBar, menu, menuNames[i], menuId, i);
  113. else
  114. updateSubMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
  115. }
  116. }
  117. static void flashMenuBar (NSMenu* menu)
  118. {
  119. const unichar f35Key = NSF35FunctionKey;
  120. NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
  121. NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"x"
  122. action: nil
  123. keyEquivalent: f35String];
  124. [item setTarget: nil];
  125. [menu insertItem: item atIndex: [menu numberOfItems]];
  126. [item release];
  127. NSEvent* f35Event = [NSEvent keyEventWithType: NSKeyDown
  128. location: NSZeroPoint
  129. modifierFlags: NSCommandKeyMask
  130. timestamp: 0
  131. windowNumber: 0
  132. context: [NSGraphicsContext currentContext]
  133. characters: f35String
  134. charactersIgnoringModifiers: f35String
  135. isARepeat: NO
  136. keyCode: 0];
  137. [menu performKeyEquivalent: f35Event];
  138. if ([menu indexOfItem: item] >= 0)
  139. [menu removeItem: item]; // (this throws if the item isn't actually in the menu)
  140. }
  141. static NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info)
  142. {
  143. for (NSInteger i = [menu numberOfItems]; --i >= 0;)
  144. {
  145. NSMenuItem* m = [menu itemAtIndex: i];
  146. if ([m tag] == info.commandID)
  147. return m;
  148. if ([m submenu] != 0)
  149. {
  150. NSMenuItem* found = findMenuItem ([m submenu], info);
  151. if (found != 0)
  152. return found;
  153. }
  154. }
  155. return 0;
  156. }
  157. void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
  158. {
  159. NSMenuItem* item = findMenuItem ([NSApp mainMenu], info);
  160. if (item != 0)
  161. flashMenuBar ([item menu]);
  162. }
  163. void updateMenus()
  164. {
  165. if (Time::getMillisecondCounter() > lastUpdateTime + 500)
  166. menuBarItemsChanged (0);
  167. }
  168. void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const
  169. {
  170. if (currentModel != 0)
  171. {
  172. if (commandManager != 0)
  173. {
  174. ApplicationCommandTarget::InvocationInfo info (commandId);
  175. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  176. commandManager->invoke (info, true);
  177. }
  178. currentModel->menuItemSelected (commandId, topLevelIndex);
  179. }
  180. }
  181. MenuBarModel* currentModel;
  182. uint32 lastUpdateTime;
  183. void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
  184. const int topLevelMenuId, const int topLevelIndex)
  185. {
  186. NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf (T("<end>"), false, true));
  187. if (text == 0)
  188. text = @"";
  189. if (iter.isSeparator)
  190. {
  191. [menuToAddTo addItem: [NSMenuItem separatorItem]];
  192. }
  193. else if (iter.isSectionHeader)
  194. {
  195. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  196. action: nil
  197. keyEquivalent: @""];
  198. [item setEnabled: false];
  199. }
  200. else if (iter.subMenu != 0)
  201. {
  202. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  203. action: nil
  204. keyEquivalent: @""];
  205. [item setTag: iter.itemId];
  206. [item setEnabled: iter.isEnabled];
  207. NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex);
  208. [sub setDelegate: nil];
  209. [menuToAddTo setSubmenu: sub forItem: item];
  210. [sub release];
  211. }
  212. else
  213. {
  214. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  215. action: @selector (menuItemInvoked:)
  216. keyEquivalent: @""];
  217. [item setTag: iter.itemId];
  218. [item setEnabled: iter.isEnabled];
  219. [item setState: iter.isTicked ? NSOnState : NSOffState];
  220. [item setTarget: (id) callback];
  221. NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_int) (void*) iter.commandManager]];
  222. [info addObject: [NSNumber numberWithInt: topLevelIndex]];
  223. [item setRepresentedObject: info];
  224. if (iter.commandManager != 0)
  225. {
  226. const Array <KeyPress> keyPresses (iter.commandManager->getKeyMappings()
  227. ->getKeyPressesAssignedToCommand (iter.itemId));
  228. if (keyPresses.size() > 0)
  229. {
  230. const KeyPress& kp = keyPresses.getReference(0);
  231. juce_wchar key = kp.getTextCharacter();
  232. if (kp.getKeyCode() == KeyPress::backspaceKey)
  233. key = NSBackspaceCharacter;
  234. else if (kp.getKeyCode() == KeyPress::deleteKey)
  235. key = NSDeleteCharacter;
  236. else if (key == 0)
  237. key = (juce_wchar) kp.getKeyCode();
  238. unsigned int mods = 0;
  239. if (kp.getModifiers().isShiftDown())
  240. mods |= NSShiftKeyMask;
  241. if (kp.getModifiers().isCtrlDown())
  242. mods |= NSControlKeyMask;
  243. if (kp.getModifiers().isAltDown())
  244. mods |= NSAlternateKeyMask;
  245. if (kp.getModifiers().isCommandDown())
  246. mods |= NSCommandKeyMask;
  247. [item setKeyEquivalent: juceStringToNS (String::charToString (key))];
  248. [item setKeyEquivalentModifierMask: mods];
  249. }
  250. }
  251. }
  252. }
  253. JuceMenuCallback* callback;
  254. private:
  255. NSMenu* createMenu (const PopupMenu menu,
  256. const String& menuName,
  257. const int topLevelMenuId,
  258. const int topLevelIndex)
  259. {
  260. NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
  261. [m setAutoenablesItems: false];
  262. [m setDelegate: callback];
  263. PopupMenu::MenuItemIterator iter (menu);
  264. while (iter.next())
  265. addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
  266. [m update];
  267. return m;
  268. }
  269. };
  270. JuceMainMenuHandler* JuceMainMenuHandler::instance = 0;
  271. END_JUCE_NAMESPACE
  272. @implementation JuceMenuCallback
  273. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_
  274. {
  275. [super init];
  276. owner = owner_;
  277. return self;
  278. }
  279. - (void) dealloc
  280. {
  281. [super dealloc];
  282. }
  283. - (void) menuItemInvoked: (id) menu
  284. {
  285. NSMenuItem* item = (NSMenuItem*) menu;
  286. if ([[item representedObject] isKindOfClass: [NSArray class]])
  287. {
  288. // 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
  289. // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back
  290. // into the focused component and let it trigger the menu item indirectly.
  291. NSEvent* e = [NSApp currentEvent];
  292. if ([e type] == NSKeyDown || [e type] == NSKeyUp)
  293. {
  294. if (JUCE_NAMESPACE::Component::getCurrentlyFocusedComponent()->isValidComponent())
  295. {
  296. JUCE_NAMESPACE::NSViewComponentPeer* peer = dynamic_cast <JUCE_NAMESPACE::NSViewComponentPeer*> (JUCE_NAMESPACE::Component::getCurrentlyFocusedComponent()->getPeer());
  297. if (peer != 0)
  298. {
  299. if ([e type] == NSKeyDown)
  300. peer->redirectKeyDown (e);
  301. else
  302. peer->redirectKeyUp (e);
  303. return;
  304. }
  305. }
  306. }
  307. NSArray* info = (NSArray*) [item representedObject];
  308. owner->invoke ((int) [item tag],
  309. (ApplicationCommandManager*) (pointer_sized_int)
  310. [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
  311. (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
  312. }
  313. }
  314. - (void) menuNeedsUpdate: (NSMenu*) menu;
  315. {
  316. if (JuceMainMenuHandler::instance != 0)
  317. JuceMainMenuHandler::instance->updateMenus();
  318. }
  319. @end
  320. BEGIN_JUCE_NAMESPACE
  321. //==============================================================================
  322. static NSMenu* createStandardAppMenu (NSMenu* menu, const String& appName,
  323. const PopupMenu* extraItems)
  324. {
  325. if (extraItems != 0 && JuceMainMenuHandler::instance != 0 && extraItems->getNumItems() > 0)
  326. {
  327. PopupMenu::MenuItemIterator iter (*extraItems);
  328. while (iter.next())
  329. JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
  330. [menu addItem: [NSMenuItem separatorItem]];
  331. }
  332. NSMenuItem* item;
  333. // Services...
  334. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Services", nil)
  335. action: nil keyEquivalent: @""];
  336. [menu addItem: item];
  337. [item release];
  338. NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle: @"Services"];
  339. [menu setSubmenu: servicesMenu forItem: item];
  340. [NSApp setServicesMenu: servicesMenu];
  341. [servicesMenu release];
  342. [menu addItem: [NSMenuItem separatorItem]];
  343. // Hide + Show stuff...
  344. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Hide " + appName)
  345. action: @selector (hide:) keyEquivalent: @"h"];
  346. [item setTarget: NSApp];
  347. [menu addItem: item];
  348. [item release];
  349. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Hide Others", nil)
  350. action: @selector (hideOtherApplications:) keyEquivalent: @"h"];
  351. [item setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
  352. [item setTarget: NSApp];
  353. [menu addItem: item];
  354. [item release];
  355. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Show All", nil)
  356. action: @selector (unhideAllApplications:) keyEquivalent: @""];
  357. [item setTarget: NSApp];
  358. [menu addItem: item];
  359. [item release];
  360. [menu addItem: [NSMenuItem separatorItem]];
  361. // Quit item....
  362. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Quit " + appName)
  363. action: @selector (terminate:) keyEquivalent: @"q"];
  364. [item setTarget: NSApp];
  365. [menu addItem: item];
  366. [item release];
  367. return menu;
  368. }
  369. // Since our app has no NIB, this initialises a standard app menu...
  370. static void rebuildMainMenu (const PopupMenu* extraItems)
  371. {
  372. // this can't be used in a plugin!
  373. jassert (JUCEApplication::getInstance() != 0);
  374. if (JUCEApplication::getInstance() != 0)
  375. {
  376. const ScopedAutoReleasePool pool;
  377. NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"];
  378. NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""];
  379. NSMenu* appMenu = [[NSMenu alloc] initWithTitle: @"Apple"];
  380. [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
  381. [mainMenu setSubmenu: appMenu forItem: item];
  382. [NSApp setMainMenu: mainMenu];
  383. createStandardAppMenu (appMenu, JUCEApplication::getInstance()->getApplicationName(), extraItems);
  384. [appMenu release];
  385. [mainMenu release];
  386. }
  387. }
  388. void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
  389. const PopupMenu* extraAppleMenuItems)
  390. {
  391. if (getMacMainMenu() != newMenuBarModel)
  392. {
  393. const ScopedAutoReleasePool pool;
  394. if (newMenuBarModel == 0)
  395. {
  396. delete JuceMainMenuHandler::instance;
  397. jassert (JuceMainMenuHandler::instance == 0); // should be zeroed in the destructor
  398. jassert (extraAppleMenuItems == 0); // you can't specify some extra items without also supplying a model
  399. extraAppleMenuItems = 0;
  400. }
  401. else
  402. {
  403. if (JuceMainMenuHandler::instance == 0)
  404. JuceMainMenuHandler::instance = new JuceMainMenuHandler();
  405. JuceMainMenuHandler::instance->setMenu (newMenuBarModel);
  406. }
  407. }
  408. rebuildMainMenu (extraAppleMenuItems);
  409. if (newMenuBarModel != 0)
  410. newMenuBarModel->menuItemsChanged();
  411. }
  412. MenuBarModel* MenuBarModel::getMacMainMenu()
  413. {
  414. return JuceMainMenuHandler::instance != 0
  415. ? JuceMainMenuHandler::instance->currentModel : 0;
  416. }
  417. void initialiseMainMenu()
  418. {
  419. if (JUCEApplication::getInstance() != 0) // only needed in an app
  420. rebuildMainMenu (0);
  421. }
  422. #endif