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.

495 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. void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info)
  142. {
  143. NSMenuItem* item = [[NSApp mainMenu] itemWithTag: info.commandID];
  144. if (item != 0)
  145. flashMenuBar ([item menu]);
  146. }
  147. void updateMenus()
  148. {
  149. if (Time::getMillisecondCounter() > lastUpdateTime + 500)
  150. menuBarItemsChanged (0);
  151. }
  152. void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const
  153. {
  154. if (currentModel != 0)
  155. {
  156. if (commandManager != 0)
  157. {
  158. ApplicationCommandTarget::InvocationInfo info (commandId);
  159. info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
  160. commandManager->invoke (info, true);
  161. }
  162. currentModel->menuItemSelected (commandId, topLevelIndex);
  163. }
  164. }
  165. MenuBarModel* currentModel;
  166. uint32 lastUpdateTime;
  167. void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
  168. const int topLevelMenuId, const int topLevelIndex)
  169. {
  170. NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf (T("<end>"), false, true));
  171. if (text == 0)
  172. text = @"";
  173. if (iter.isSeparator)
  174. {
  175. [menuToAddTo addItem: [NSMenuItem separatorItem]];
  176. }
  177. else if (iter.isSectionHeader)
  178. {
  179. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  180. action: nil
  181. keyEquivalent: @""];
  182. [item setEnabled: false];
  183. [item setIndentationLevel: 5];
  184. }
  185. else if (iter.subMenu != 0)
  186. {
  187. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  188. action: nil
  189. keyEquivalent: @""];
  190. [item setTag: iter.itemId];
  191. [item setEnabled: iter.isEnabled];
  192. NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex);
  193. [menuToAddTo setSubmenu: sub forItem: item];
  194. [sub release];
  195. }
  196. else
  197. {
  198. NSMenuItem* item = [menuToAddTo addItemWithTitle: text
  199. action: @selector (menuItemInvoked:)
  200. keyEquivalent: @""];
  201. [item setTag: iter.itemId];
  202. [item setEnabled: iter.isEnabled];
  203. [item setState: iter.isTicked ? NSOnState : NSOffState];
  204. [item setTarget: (id) callback];
  205. NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_int) (void*) iter.commandManager]];
  206. [info addObject: [NSNumber numberWithInt: topLevelIndex]];
  207. [item setRepresentedObject: info];
  208. if (iter.commandManager != 0)
  209. {
  210. const Array <KeyPress> keyPresses (iter.commandManager->getKeyMappings()
  211. ->getKeyPressesAssignedToCommand (iter.itemId));
  212. if (keyPresses.size() > 0)
  213. {
  214. const KeyPress& kp = keyPresses.getReference(0);
  215. juce_wchar key = kp.getTextCharacter();
  216. if (kp.getKeyCode() == KeyPress::backspaceKey)
  217. key = NSBackspaceCharacter;
  218. else if (kp.getKeyCode() == KeyPress::deleteKey)
  219. key = NSDeleteCharacter;
  220. else if (key == 0)
  221. key = (juce_wchar) kp.getKeyCode();
  222. unsigned int mods = 0;
  223. if (kp.getModifiers().isShiftDown())
  224. mods |= NSShiftKeyMask;
  225. if (kp.getModifiers().isCtrlDown())
  226. mods |= NSControlKeyMask;
  227. if (kp.getModifiers().isAltDown())
  228. mods |= NSAlternateKeyMask;
  229. if (kp.getModifiers().isCommandDown())
  230. mods |= NSCommandKeyMask;
  231. [item setKeyEquivalent: juceStringToNS (String::charToString (key))];
  232. [item setKeyEquivalentModifierMask: mods];
  233. }
  234. }
  235. }
  236. }
  237. JuceMenuCallback* callback;
  238. private:
  239. NSMenu* createMenu (const PopupMenu menu,
  240. const String& menuName,
  241. const int topLevelMenuId,
  242. const int topLevelIndex)
  243. {
  244. NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
  245. [m setAutoenablesItems: false];
  246. [m setDelegate: callback];
  247. PopupMenu::MenuItemIterator iter (menu);
  248. while (iter.next())
  249. addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
  250. [m update];
  251. return m;
  252. }
  253. };
  254. JuceMainMenuHandler* JuceMainMenuHandler::instance = 0;
  255. END_JUCE_NAMESPACE
  256. @implementation JuceMenuCallback
  257. - (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_
  258. {
  259. [super init];
  260. owner = owner_;
  261. return self;
  262. }
  263. - (void) dealloc
  264. {
  265. [super dealloc];
  266. }
  267. - (void) menuItemInvoked: (id) menu
  268. {
  269. NSMenuItem* item = (NSMenuItem*) menu;
  270. if ([[item representedObject] isKindOfClass: [NSArray class]])
  271. {
  272. NSArray* info = (NSArray*) [item representedObject];
  273. owner->invoke ([item tag],
  274. (ApplicationCommandManager*) (pointer_sized_int)
  275. [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue],
  276. (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]);
  277. }
  278. }
  279. - (void) menuNeedsUpdate: (NSMenu*) menu;
  280. {
  281. if (JuceMainMenuHandler::instance != 0)
  282. JuceMainMenuHandler::instance->updateMenus();
  283. }
  284. @end
  285. BEGIN_JUCE_NAMESPACE
  286. //==============================================================================
  287. static NSMenu* createStandardAppMenu (NSMenu* menu, const String& appName,
  288. const PopupMenu* extraItems)
  289. {
  290. if (extraItems != 0 && JuceMainMenuHandler::instance != 0 && extraItems->getNumItems() > 0)
  291. {
  292. PopupMenu::MenuItemIterator iter (*extraItems);
  293. while (iter.next())
  294. JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
  295. [menu addItem: [NSMenuItem separatorItem]];
  296. }
  297. NSMenuItem* item;
  298. // Services...
  299. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Services", nil)
  300. action: nil keyEquivalent: @""];
  301. [menu addItem: item];
  302. [item release];
  303. NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle: @"Services"];
  304. [menu setSubmenu: servicesMenu forItem: item];
  305. [NSApp setServicesMenu: servicesMenu];
  306. [servicesMenu release];
  307. [menu addItem: [NSMenuItem separatorItem]];
  308. // Hide + Show stuff...
  309. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Hide " + appName)
  310. action: @selector (hide:) keyEquivalent: @"h"];
  311. [item setTarget: NSApp];
  312. [menu addItem: item];
  313. [item release];
  314. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Hide Others", nil)
  315. action: @selector (hideOtherApplications:) keyEquivalent: @"h"];
  316. [item setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
  317. [item setTarget: NSApp];
  318. [menu addItem: item];
  319. [item release];
  320. item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Show All", nil)
  321. action: @selector (unhideAllApplications:) keyEquivalent: @""];
  322. [item setTarget: NSApp];
  323. [menu addItem: item];
  324. [item release];
  325. [menu addItem: [NSMenuItem separatorItem]];
  326. // Quit item....
  327. item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Quit " + appName)
  328. action: @selector (terminate:) keyEquivalent: @"q"];
  329. [item setTarget: NSApp];
  330. [menu addItem: item];
  331. [item release];
  332. return menu;
  333. }
  334. // Since our app has no NIB, this initialises a standard app menu...
  335. static void rebuildMainMenu (const PopupMenu* extraItems)
  336. {
  337. // this can't be used in a plugin!
  338. jassert (JUCEApplication::getInstance() != 0);
  339. if (JUCEApplication::getInstance() != 0)
  340. {
  341. const ScopedAutoReleasePool pool;
  342. NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"];
  343. NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""];
  344. NSMenu* appMenu = [[NSMenu alloc] initWithTitle: @"Apple"];
  345. [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
  346. [mainMenu setSubmenu: appMenu forItem: item];
  347. [NSApp setMainMenu: mainMenu];
  348. createStandardAppMenu (appMenu, JUCEApplication::getInstance()->getApplicationName(), extraItems);
  349. [appMenu release];
  350. [mainMenu release];
  351. }
  352. }
  353. void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
  354. const PopupMenu* extraAppleMenuItems) throw()
  355. {
  356. if (getMacMainMenu() != newMenuBarModel)
  357. {
  358. if (newMenuBarModel == 0)
  359. {
  360. delete JuceMainMenuHandler::instance;
  361. jassert (JuceMainMenuHandler::instance == 0); // should be zeroed in the destructor
  362. jassert (extraAppleMenuItems == 0); // you can't specify some extra items without also supplying a model
  363. extraAppleMenuItems = 0;
  364. }
  365. else
  366. {
  367. if (JuceMainMenuHandler::instance == 0)
  368. JuceMainMenuHandler::instance = new JuceMainMenuHandler();
  369. JuceMainMenuHandler::instance->setMenu (newMenuBarModel);
  370. }
  371. }
  372. rebuildMainMenu (extraAppleMenuItems);
  373. if (newMenuBarModel != 0)
  374. newMenuBarModel->menuItemsChanged();
  375. }
  376. MenuBarModel* MenuBarModel::getMacMainMenu() throw()
  377. {
  378. return JuceMainMenuHandler::instance != 0
  379. ? JuceMainMenuHandler::instance->currentModel : 0;
  380. }
  381. void initialiseMainMenu()
  382. {
  383. if (JUCEApplication::getInstance() != 0) // only needed in an app
  384. rebuildMainMenu (0);
  385. }
  386. #endif