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.

512 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-7 by Raw Material Software ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the
  7. GNU General Public License, as published by the Free Software Foundation;
  8. either version 2 of the License, or (at your option) any later version.
  9. JUCE is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with JUCE; if not, visit www.gnu.org/licenses or write to the
  15. Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  16. Boston, MA 02111-1307 USA
  17. ------------------------------------------------------------------------------
  18. If you'd like to release a closed-source product which uses JUCE, commercial
  19. licenses are also available: visit www.rawmaterialsoftware.com/juce for
  20. more information.
  21. ==============================================================================
  22. */
  23. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  24. // compiled on its own).
  25. #ifdef JUCE_INCLUDED_FILE
  26. //==============================================================================
  27. class JuceMainMenuHandler;
  28. END_JUCE_NAMESPACE
  29. using namespace JUCE_NAMESPACE;
  30. #define JuceMenuCallback MakeObjCClassName(JuceMenuCallback)
  31. @interface JuceMenuCallback : NSObject
  32. {
  33. JuceMainMenuHandler* owner;
  34. }
  35. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_;
  36. - (void) dealloc;
  37. - (void) menuItemInvoked: (id) menu;
  38. - (void) menuNeedsUpdate: (NSMenu*) menu;
  39. @end
  40. BEGIN_JUCE_NAMESPACE
  41. //==============================================================================
  42. class JuceMainMenuHandler : private MenuBarModelListener,
  43. private DeletedAtShutdown
  44. {
  45. public:
  46. static JuceMainMenuHandler* instance;
  47. //==============================================================================
  48. JuceMainMenuHandler() throw()
  49. : currentModel (0),
  50. lastUpdateTime (0)
  51. {
  52. callback = [[JuceMenuCallback alloc] initWithOwner: this];
  53. }
  54. ~JuceMainMenuHandler() throw()
  55. {
  56. setMenu (0);
  57. jassert (instance == this);
  58. instance = 0;
  59. [callback release];
  60. }
  61. void setMenu (MenuBarModel* const newMenuBarModel) throw()
  62. {
  63. if (currentModel != newMenuBarModel)
  64. {
  65. if (currentModel != 0)
  66. currentModel->removeListener (this);
  67. currentModel = newMenuBarModel;
  68. if (currentModel != 0)
  69. currentModel->addListener (this);
  70. menuBarItemsChanged (0);
  71. }
  72. }
  73. void addSubMenu (NSMenu* parent, const PopupMenu& child,
  74. const String& name, const int menuId, const int tag)
  75. {
  76. NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
  77. action: nil
  78. keyEquivalent: @""];
  79. [item setTag: tag];
  80. NSMenu* sub = createMenu (child, name, menuId, tag);
  81. [parent setSubmenu: sub forItem: item];
  82. [sub setAutoenablesItems: false];
  83. [sub release];
  84. }
  85. void updateSubMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy,
  86. const String& name, const int menuId, const int tag)
  87. {
  88. [parentItem setTag: tag];
  89. NSMenu* menu = [parentItem submenu];
  90. [menu setTitle: juceStringToNS (name)];
  91. while ([menu numberOfItems] > 0)
  92. [menu removeItemAtIndex: 0];
  93. PopupMenu::MenuItemIterator iter (menuToCopy);
  94. while (iter.next())
  95. addMenuItem (iter, menu, menuId, tag);
  96. [menu setAutoenablesItems: false];
  97. [menu update];
  98. }
  99. void menuBarItemsChanged (MenuBarModel*)
  100. {
  101. lastUpdateTime = Time::getMillisecondCounter();
  102. StringArray menuNames;
  103. if (currentModel != 0)
  104. menuNames = currentModel->getMenuBarNames();
  105. NSMenu* menuBar = [NSApp mainMenu];
  106. while ([menuBar numberOfItems] > 1 + menuNames.size())
  107. [menuBar removeItemAtIndex: [menuBar numberOfItems] - 1];
  108. int menuId = 1;
  109. for (int i = 0; i < menuNames.size(); ++i)
  110. {
  111. const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames [i]));
  112. if (i >= [menuBar numberOfItems] - 1)
  113. addSubMenu (menuBar, menu, menuNames[i], menuId, i);
  114. else
  115. updateSubMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
  116. }
  117. }
  118. static void flashMenuBar (NSMenu* menu)
  119. {
  120. const unichar f35Key = NSF35FunctionKey;
  121. NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
  122. NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"x"
  123. action: nil
  124. keyEquivalent: f35String];
  125. [item setTarget: nil];
  126. [menu insertItem: item atIndex: [menu numberOfItems]];
  127. [item release];
  128. NSEvent* f35Event = [NSEvent keyEventWithType: NSKeyDown
  129. location: NSZeroPoint
  130. modifierFlags: NSCommandKeyMask
  131. timestamp: 0
  132. windowNumber: 0
  133. context: [NSGraphicsContext currentContext]
  134. characters: f35String
  135. charactersIgnoringModifiers: f35String
  136. isARepeat: NO
  137. keyCode: 0];
  138. [menu performKeyEquivalent: f35Event];
  139. [menu removeItem: item];
  140. }
  141. static NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info)
  142. {
  143. for (int 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. [menuToAddTo setSubmenu: sub forItem: item];
  209. }
  210. else
  211. {
  212. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  213. action: @selector (menuItemInvoked:)
  214. keyEquivalent: @""];
  215. [item setTag: iter.itemId];
  216. [item setEnabled: iter.isEnabled];
  217. [item setState: iter.isTicked ? NSOnState : NSOffState];
  218. [item setTarget: (id) callback];
  219. NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_int) (void*) iter.commandManager]];
  220. [info addObject: [NSNumber numberWithInt: topLevelIndex]];
  221. [item setRepresentedObject: info];
  222. if (iter.commandManager != 0)
  223. {
  224. const Array <KeyPress> keyPresses (iter.commandManager->getKeyMappings()
  225. ->getKeyPressesAssignedToCommand (iter.itemId));
  226. if (keyPresses.size() > 0)
  227. {
  228. const KeyPress& kp = keyPresses.getReference(0);
  229. juce_wchar key = kp.getTextCharacter();
  230. if (kp.getKeyCode() == KeyPress::backspaceKey)
  231. key = NSBackspaceCharacter;
  232. else if (kp.getKeyCode() == KeyPress::deleteKey)
  233. key = NSDeleteCharacter;
  234. else if (key == 0)
  235. key = (juce_wchar) kp.getKeyCode();
  236. unsigned int mods = 0;
  237. if (kp.getModifiers().isShiftDown())
  238. mods |= NSShiftKeyMask;
  239. if (kp.getModifiers().isCtrlDown())
  240. mods |= NSControlKeyMask;
  241. if (kp.getModifiers().isAltDown())
  242. mods |= NSAlternateKeyMask;
  243. if (kp.getModifiers().isCommandDown())
  244. mods |= NSCommandKeyMask;
  245. [item setKeyEquivalent: juceStringToNS (String::charToString (key))];
  246. [item setKeyEquivalentModifierMask: mods];
  247. }
  248. }
  249. }
  250. }
  251. JuceMenuCallback* callback;
  252. private:
  253. NSMenu* createMenu (const PopupMenu menu,
  254. const String& menuName,
  255. const int topLevelMenuId,
  256. const int topLevelIndex)
  257. {
  258. NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
  259. [m setAutoenablesItems: false];
  260. [m setDelegate: callback];
  261. PopupMenu::MenuItemIterator iter (menu);
  262. while (iter.next())
  263. addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
  264. [m update];
  265. return m;
  266. }
  267. };
  268. JuceMainMenuHandler* JuceMainMenuHandler::instance = 0;
  269. END_JUCE_NAMESPACE
  270. @implementation JuceMenuCallback
  271. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_
  272. {
  273. [super init];
  274. owner = owner_;
  275. return self;
  276. }
  277. - (void) dealloc
  278. {
  279. [super dealloc];
  280. }
  281. - (void) menuItemInvoked: (id) menu
  282. {
  283. NSMenuItem* item = (NSMenuItem*) menu;
  284. if ([[item representedObject] isKindOfClass: [NSArray class]])
  285. {
  286. NSArray* info = (NSArray*) [item representedObject];
  287. owner->invoke ([item tag],
  288. (ApplicationCommandManager*) (pointer_sized_int)
  289. [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
  290. (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
  291. }
  292. }
  293. - (void) menuNeedsUpdate: (NSMenu*) menu;
  294. {
  295. if (JuceMainMenuHandler::instance != 0)
  296. JuceMainMenuHandler::instance->updateMenus();
  297. }
  298. @end
  299. BEGIN_JUCE_NAMESPACE
  300. //==============================================================================
  301. static NSMenu* createStandardAppMenu (NSMenu* menu, const String& appName,
  302. const PopupMenu* extraItems)
  303. {
  304. if (extraItems != 0 && JuceMainMenuHandler::instance != 0 && extraItems->getNumItems() > 0)
  305. {
  306. PopupMenu::MenuItemIterator iter (*extraItems);
  307. while (iter.next())
  308. JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
  309. [menu addItem: [NSMenuItem separatorItem]];
  310. }
  311. NSMenuItem* item;
  312. // Services...
  313. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Services", nil)
  314. action: nil keyEquivalent: @""];
  315. [menu addItem: item];
  316. [item release];
  317. NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle: @"Services"];
  318. [menu setSubmenu: servicesMenu forItem: item];
  319. [NSApp setServicesMenu: servicesMenu];
  320. [servicesMenu release];
  321. [menu addItem: [NSMenuItem separatorItem]];
  322. // Hide + Show stuff...
  323. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Hide " + appName)
  324. action: @selector (hide:) keyEquivalent: @"h"];
  325. [item setTarget: NSApp];
  326. [menu addItem: item];
  327. [item release];
  328. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Hide Others", nil)
  329. action: @selector (hideOtherApplications:) keyEquivalent: @"h"];
  330. [item setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
  331. [item setTarget: NSApp];
  332. [menu addItem: item];
  333. [item release];
  334. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Show All", nil)
  335. action: @selector (unhideAllApplications:) keyEquivalent: @""];
  336. [item setTarget: NSApp];
  337. [menu addItem: item];
  338. [item release];
  339. [menu addItem: [NSMenuItem separatorItem]];
  340. // Quit item....
  341. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Quit " + appName)
  342. action: @selector (terminate:) keyEquivalent: @"q"];
  343. [item setTarget: NSApp];
  344. [menu addItem: item];
  345. [item release];
  346. return menu;
  347. }
  348. // Since our app has no NIB, this initialises a standard app menu...
  349. static void rebuildMainMenu (const PopupMenu* extraItems)
  350. {
  351. // this can't be used in a plugin!
  352. jassert (JUCEApplication::getInstance() != 0);
  353. if (JUCEApplication::getInstance() != 0)
  354. {
  355. const ScopedAutoReleasePool pool;
  356. NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"];
  357. NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""];
  358. NSMenu* appMenu = [[NSMenu alloc] initWithTitle: @"Apple"];
  359. [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
  360. [mainMenu setSubmenu: appMenu forItem: item];
  361. [NSApp setMainMenu: mainMenu];
  362. createStandardAppMenu (appMenu, JUCEApplication::getInstance()->getApplicationName(), extraItems);
  363. [appMenu release];
  364. [mainMenu release];
  365. }
  366. }
  367. void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
  368. const PopupMenu* extraAppleMenuItems) throw()
  369. {
  370. if (getMacMainMenu() != newMenuBarModel)
  371. {
  372. if (newMenuBarModel == 0)
  373. {
  374. delete JuceMainMenuHandler::instance;
  375. jassert (JuceMainMenuHandler::instance == 0); // should be zeroed in the destructor
  376. jassert (extraAppleMenuItems == 0); // you can't specify some extra items without also supplying a model
  377. extraAppleMenuItems = 0;
  378. }
  379. else
  380. {
  381. if (JuceMainMenuHandler::instance == 0)
  382. JuceMainMenuHandler::instance = new JuceMainMenuHandler();
  383. JuceMainMenuHandler::instance->setMenu (newMenuBarModel);
  384. }
  385. }
  386. rebuildMainMenu (extraAppleMenuItems);
  387. if (newMenuBarModel != 0)
  388. newMenuBarModel->menuItemsChanged();
  389. }
  390. MenuBarModel* MenuBarModel::getMacMainMenu() throw()
  391. {
  392. return JuceMainMenuHandler::instance != 0
  393. ? JuceMainMenuHandler::instance->currentModel : 0;
  394. }
  395. void initialiseMainMenu()
  396. {
  397. if (JUCEApplication::getInstance() != 0) // only needed in an app
  398. rebuildMainMenu (0);
  399. }
  400. #endif