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.

506 lines
17KB

  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. #ifdef JUCE_INCLUDED_FILE
  21. //==============================================================================
  22. class JuceMainMenuHandler;
  23. END_JUCE_NAMESPACE
  24. using namespace JUCE_NAMESPACE;
  25. #define JuceMenuCallback MakeObjCClassName(JuceMenuCallback)
  26. @interface JuceMenuCallback : NSObject
  27. {
  28. JuceMainMenuHandler* owner;
  29. }
  30. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_;
  31. - (void) dealloc;
  32. - (void) menuItemInvoked: (id) menu;
  33. - (void) menuNeedsUpdate: (NSMenu*) menu;
  34. @end
  35. BEGIN_JUCE_NAMESPACE
  36. //==============================================================================
  37. class JuceMainMenuHandler : private MenuBarModelListener,
  38. private DeletedAtShutdown
  39. {
  40. public:
  41. static JuceMainMenuHandler* instance;
  42. //==============================================================================
  43. JuceMainMenuHandler() throw()
  44. : currentModel (0),
  45. lastUpdateTime (0)
  46. {
  47. callback = [[JuceMenuCallback alloc] initWithOwner: this];
  48. }
  49. ~JuceMainMenuHandler() throw()
  50. {
  51. setMenu (0);
  52. jassert (instance == this);
  53. instance = 0;
  54. [callback release];
  55. }
  56. void setMenu (MenuBarModel* const newMenuBarModel) throw()
  57. {
  58. if (currentModel != newMenuBarModel)
  59. {
  60. if (currentModel != 0)
  61. currentModel->removeListener (this);
  62. currentModel = newMenuBarModel;
  63. if (currentModel != 0)
  64. currentModel->addListener (this);
  65. menuBarItemsChanged (0);
  66. }
  67. }
  68. void addSubMenu (NSMenu* parent, const PopupMenu& child,
  69. const String& name, const int menuId, const int tag)
  70. {
  71. NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
  72. action: nil
  73. keyEquivalent: @""];
  74. [item setTag: tag];
  75. NSMenu* sub = createMenu (child, name, menuId, tag);
  76. [parent setSubmenu: sub forItem: item];
  77. [sub setAutoenablesItems: false];
  78. [sub release];
  79. }
  80. void updateSubMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy,
  81. const String& name, const int menuId, const int tag)
  82. {
  83. [parentItem setTag: tag];
  84. NSMenu* menu = [parentItem submenu];
  85. [menu setTitle: juceStringToNS (name)];
  86. while ([menu numberOfItems] > 0)
  87. [menu removeItemAtIndex: 0];
  88. PopupMenu::MenuItemIterator iter (menuToCopy);
  89. while (iter.next())
  90. addMenuItem (iter, menu, menuId, tag);
  91. [menu setAutoenablesItems: false];
  92. [menu update];
  93. }
  94. void menuBarItemsChanged (MenuBarModel*)
  95. {
  96. lastUpdateTime = Time::getMillisecondCounter();
  97. StringArray menuNames;
  98. if (currentModel != 0)
  99. menuNames = currentModel->getMenuBarNames();
  100. NSMenu* menuBar = [NSApp mainMenu];
  101. while ([menuBar numberOfItems] > 1 + menuNames.size())
  102. [menuBar removeItemAtIndex: [menuBar numberOfItems] - 1];
  103. int menuId = 1;
  104. for (int i = 0; i < menuNames.size(); ++i)
  105. {
  106. const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames [i]));
  107. if (i >= [menuBar numberOfItems] - 1)
  108. addSubMenu (menuBar, menu, menuNames[i], menuId, i);
  109. else
  110. updateSubMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
  111. }
  112. }
  113. static void flashMenuBar (NSMenu* menu)
  114. {
  115. const unichar f35Key = NSF35FunctionKey;
  116. NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
  117. NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"x"
  118. action: nil
  119. keyEquivalent: f35String];
  120. [item setTarget: nil];
  121. [menu insertItem: item atIndex: [menu numberOfItems]];
  122. [item release];
  123. NSEvent* f35Event = [NSEvent keyEventWithType: NSKeyDown
  124. location: NSZeroPoint
  125. modifierFlags: NSCommandKeyMask
  126. timestamp: 0
  127. windowNumber: 0
  128. context: [NSGraphicsContext currentContext]
  129. characters: f35String
  130. charactersIgnoringModifiers: f35String
  131. isARepeat: NO
  132. keyCode: 0];
  133. [menu performKeyEquivalent: f35Event];
  134. [menu removeItem: item];
  135. }
  136. static NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info)
  137. {
  138. for (int i = [menu numberOfItems]; --i >= 0;)
  139. {
  140. NSMenuItem* m = [menu itemAtIndex: i];
  141. if ([m tag] == info.commandID)
  142. return m;
  143. if ([m submenu] != 0)
  144. {
  145. NSMenuItem* found = findMenuItem ([m submenu], info);
  146. if (found != 0)
  147. return found;
  148. }
  149. }
  150. return 0;
  151. }
  152. void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
  153. {
  154. NSMenuItem* item = findMenuItem ([NSApp mainMenu], info);
  155. if (item != 0)
  156. flashMenuBar ([item menu]);
  157. }
  158. void updateMenus()
  159. {
  160. if (Time::getMillisecondCounter() > lastUpdateTime + 500)
  161. menuBarItemsChanged (0);
  162. }
  163. void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const
  164. {
  165. if (currentModel != 0)
  166. {
  167. if (commandManager != 0)
  168. {
  169. ApplicationCommandTarget::InvocationInfo info (commandId);
  170. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  171. commandManager->invoke (info, true);
  172. }
  173. currentModel->menuItemSelected (commandId, topLevelIndex);
  174. }
  175. }
  176. MenuBarModel* currentModel;
  177. uint32 lastUpdateTime;
  178. void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
  179. const int topLevelMenuId, const int topLevelIndex)
  180. {
  181. NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf (T("<end>"), false, true));
  182. if (text == 0)
  183. text = @"";
  184. if (iter.isSeparator)
  185. {
  186. [menuToAddTo addItem: [NSMenuItem separatorItem]];
  187. }
  188. else if (iter.isSectionHeader)
  189. {
  190. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  191. action: nil
  192. keyEquivalent: @""];
  193. [item setEnabled: false];
  194. }
  195. else if (iter.subMenu != 0)
  196. {
  197. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  198. action: nil
  199. keyEquivalent: @""];
  200. [item setTag: iter.itemId];
  201. [item setEnabled: iter.isEnabled];
  202. NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex);
  203. [menuToAddTo setSubmenu: sub forItem: item];
  204. }
  205. else
  206. {
  207. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  208. action: @selector (menuItemInvoked:)
  209. keyEquivalent: @""];
  210. [item setTag: iter.itemId];
  211. [item setEnabled: iter.isEnabled];
  212. [item setState: iter.isTicked ? NSOnState : NSOffState];
  213. [item setTarget: (id) callback];
  214. NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_int) (void*) iter.commandManager]];
  215. [info addObject: [NSNumber numberWithInt: topLevelIndex]];
  216. [item setRepresentedObject: info];
  217. if (iter.commandManager != 0)
  218. {
  219. const Array <KeyPress> keyPresses (iter.commandManager->getKeyMappings()
  220. ->getKeyPressesAssignedToCommand (iter.itemId));
  221. if (keyPresses.size() > 0)
  222. {
  223. const KeyPress& kp = keyPresses.getReference(0);
  224. juce_wchar key = kp.getTextCharacter();
  225. if (kp.getKeyCode() == KeyPress::backspaceKey)
  226. key = NSBackspaceCharacter;
  227. else if (kp.getKeyCode() == KeyPress::deleteKey)
  228. key = NSDeleteCharacter;
  229. else if (key == 0)
  230. key = (juce_wchar) kp.getKeyCode();
  231. unsigned int mods = 0;
  232. if (kp.getModifiers().isShiftDown())
  233. mods |= NSShiftKeyMask;
  234. if (kp.getModifiers().isCtrlDown())
  235. mods |= NSControlKeyMask;
  236. if (kp.getModifiers().isAltDown())
  237. mods |= NSAlternateKeyMask;
  238. if (kp.getModifiers().isCommandDown())
  239. mods |= NSCommandKeyMask;
  240. [item setKeyEquivalent: juceStringToNS (String::charToString (key))];
  241. [item setKeyEquivalentModifierMask: mods];
  242. }
  243. }
  244. }
  245. }
  246. JuceMenuCallback* callback;
  247. private:
  248. NSMenu* createMenu (const PopupMenu menu,
  249. const String& menuName,
  250. const int topLevelMenuId,
  251. const int topLevelIndex)
  252. {
  253. NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
  254. [m setAutoenablesItems: false];
  255. [m setDelegate: callback];
  256. PopupMenu::MenuItemIterator iter (menu);
  257. while (iter.next())
  258. addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
  259. [m update];
  260. return m;
  261. }
  262. };
  263. JuceMainMenuHandler* JuceMainMenuHandler::instance = 0;
  264. END_JUCE_NAMESPACE
  265. @implementation JuceMenuCallback
  266. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_
  267. {
  268. [super init];
  269. owner = owner_;
  270. return self;
  271. }
  272. - (void) dealloc
  273. {
  274. [super dealloc];
  275. }
  276. - (void) menuItemInvoked: (id) menu
  277. {
  278. NSMenuItem* item = (NSMenuItem*) menu;
  279. if ([[item representedObject] isKindOfClass: [NSArray class]])
  280. {
  281. NSArray* info = (NSArray*) [item representedObject];
  282. owner->invoke ([item tag],
  283. (ApplicationCommandManager*) (pointer_sized_int)
  284. [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
  285. (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
  286. }
  287. }
  288. - (void) menuNeedsUpdate: (NSMenu*) menu;
  289. {
  290. if (JuceMainMenuHandler::instance != 0)
  291. JuceMainMenuHandler::instance->updateMenus();
  292. }
  293. @end
  294. BEGIN_JUCE_NAMESPACE
  295. //==============================================================================
  296. static NSMenu* createStandardAppMenu (NSMenu* menu, const String& appName,
  297. const PopupMenu* extraItems)
  298. {
  299. if (extraItems != 0 && JuceMainMenuHandler::instance != 0 && extraItems->getNumItems() > 0)
  300. {
  301. PopupMenu::MenuItemIterator iter (*extraItems);
  302. while (iter.next())
  303. JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
  304. [menu addItem: [NSMenuItem separatorItem]];
  305. }
  306. NSMenuItem* item;
  307. // Services...
  308. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Services", nil)
  309. action: nil keyEquivalent: @""];
  310. [menu addItem: item];
  311. [item release];
  312. NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle: @"Services"];
  313. [menu setSubmenu: servicesMenu forItem: item];
  314. [NSApp setServicesMenu: servicesMenu];
  315. [servicesMenu release];
  316. [menu addItem: [NSMenuItem separatorItem]];
  317. // Hide + Show stuff...
  318. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Hide " + appName)
  319. action: @selector (hide:) keyEquivalent: @"h"];
  320. [item setTarget: NSApp];
  321. [menu addItem: item];
  322. [item release];
  323. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Hide Others", nil)
  324. action: @selector (hideOtherApplications:) keyEquivalent: @"h"];
  325. [item setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
  326. [item setTarget: NSApp];
  327. [menu addItem: item];
  328. [item release];
  329. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Show All", nil)
  330. action: @selector (unhideAllApplications:) keyEquivalent: @""];
  331. [item setTarget: NSApp];
  332. [menu addItem: item];
  333. [item release];
  334. [menu addItem: [NSMenuItem separatorItem]];
  335. // Quit item....
  336. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Quit " + appName)
  337. action: @selector (terminate:) keyEquivalent: @"q"];
  338. [item setTarget: NSApp];
  339. [menu addItem: item];
  340. [item release];
  341. return menu;
  342. }
  343. // Since our app has no NIB, this initialises a standard app menu...
  344. static void rebuildMainMenu (const PopupMenu* extraItems)
  345. {
  346. // this can't be used in a plugin!
  347. jassert (JUCEApplication::getInstance() != 0);
  348. if (JUCEApplication::getInstance() != 0)
  349. {
  350. const ScopedAutoReleasePool pool;
  351. NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"];
  352. NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""];
  353. NSMenu* appMenu = [[NSMenu alloc] initWithTitle: @"Apple"];
  354. [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
  355. [mainMenu setSubmenu: appMenu forItem: item];
  356. [NSApp setMainMenu: mainMenu];
  357. createStandardAppMenu (appMenu, JUCEApplication::getInstance()->getApplicationName(), extraItems);
  358. [appMenu release];
  359. [mainMenu release];
  360. }
  361. }
  362. void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
  363. const PopupMenu* extraAppleMenuItems) throw()
  364. {
  365. if (getMacMainMenu() != newMenuBarModel)
  366. {
  367. if (newMenuBarModel == 0)
  368. {
  369. delete JuceMainMenuHandler::instance;
  370. jassert (JuceMainMenuHandler::instance == 0); // should be zeroed in the destructor
  371. jassert (extraAppleMenuItems == 0); // you can't specify some extra items without also supplying a model
  372. extraAppleMenuItems = 0;
  373. }
  374. else
  375. {
  376. if (JuceMainMenuHandler::instance == 0)
  377. JuceMainMenuHandler::instance = new JuceMainMenuHandler();
  378. JuceMainMenuHandler::instance->setMenu (newMenuBarModel);
  379. }
  380. }
  381. rebuildMainMenu (extraAppleMenuItems);
  382. if (newMenuBarModel != 0)
  383. newMenuBarModel->menuItemsChanged();
  384. }
  385. MenuBarModel* MenuBarModel::getMacMainMenu() throw()
  386. {
  387. return JuceMainMenuHandler::instance != 0
  388. ? JuceMainMenuHandler::instance->currentModel : 0;
  389. }
  390. void initialiseMainMenu()
  391. {
  392. if (JUCEApplication::getInstance() != 0) // only needed in an app
  393. rebuildMainMenu (0);
  394. }
  395. #endif