PageRenderTime 24ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/main/src/addins/MacPlatform/MacMainMenu.cs

https://github.com/jfcantin/monodevelop
C# | 735 lines | 641 code | 51 blank | 43 comment | 92 complexity | 651a8f55e952e2125a4bd2815ec3badb MD5 | raw file
  1. //
  2. // MacMainMenu.cs
  3. //
  4. // Author:
  5. // Michael Hutchinson <mhutchinson@novell.com>
  6. // Miguel de Icaza
  7. //
  8. // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining
  11. // a copy of this software and associated documentation files (the
  12. // "Software"), to deal in the Software without restriction, including
  13. // without limitation the rights to use, copy, modify, merge, publish,
  14. // distribute, sublicense, and/or sell copies of the Software, and to
  15. // permit persons to whom the Software is furnished to do so, subject to
  16. // the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be
  19. // included in all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. //
  29. using System;
  30. using System.Collections.Generic;
  31. using MonoDevelop.Components.Commands;
  32. using MonoDevelop.MacInterop;
  33. using System.Text;
  34. using MonoDevelop.Ide;
  35. namespace MonoDevelop.MacIntegration
  36. {
  37. static class MacMainMenu
  38. {
  39. static IntPtr rootMenu;
  40. static IntPtr appMenu;
  41. static IntPtr openingHandlerRef, commandHandlerRef, closedHandlerRef;
  42. static CommandManager manager;
  43. const uint submenuCommandId = 0;
  44. const uint linkCommandId = 1;
  45. const uint autohideSubmenuCommandId = 2;
  46. static uint cmdSeq = 3;
  47. static ushort idSeq;
  48. static HashSet<IntPtr> mainMenus = new HashSet<IntPtr> ();
  49. static Dictionary<uint,object> commands = new Dictionary<uint,object> ();
  50. static Dictionary<object,CarbonCommandID> cmdIdMap = new Dictionary<object, CarbonCommandID> ();
  51. static Dictionary<object,uint> menuIdMap = new Dictionary<object, uint> ();
  52. static List<object> objectsToDestroyOnMenuClose = new List<object> ();
  53. static List<string> linkCommands = new List<string> ();
  54. static int menuOpenDepth = 0;
  55. static Gdk.Keymap keymap = Gdk.Keymap.Default;
  56. private class DestructableMenu
  57. {
  58. public DestructableMenu (IntPtr ptr)
  59. {
  60. Ref = ptr;
  61. }
  62. public void Destroy ()
  63. {
  64. if (Ref != IntPtr.Zero) {
  65. HIToolbox.DeleteMenu (Ref);
  66. CoreFoundation.Release (Ref);
  67. Ref = IntPtr.Zero;
  68. }
  69. }
  70. IntPtr Ref;
  71. }
  72. static string GetName (CommandEntrySet ces)
  73. {
  74. string text = ces.Name ?? "";
  75. return text.Replace ("_", "");
  76. }
  77. static public void AddCommandIDMappings (Dictionary<object,CarbonCommandID> map)
  78. {
  79. if (cmdIdMap.Count > 0)
  80. throw new InvalidOperationException ("This can only be done once, before creating menus");
  81. cmdIdMap = new Dictionary<object, CarbonCommandID> (map);
  82. }
  83. public static void Recreate (CommandManager manager, CommandEntrySet entrySet, HashSet<object> ignoreCommands)
  84. {
  85. if (manager == null)
  86. throw new ArgumentException ("manager");
  87. if (MacMainMenu.manager != null) {
  88. MacMainMenu.manager.CommandActivating -= OnCommandActivating;
  89. }
  90. MacMainMenu.manager = manager;
  91. MacMainMenu.manager.CommandActivating += OnCommandActivating;
  92. if (rootMenu == IntPtr.Zero) {
  93. rootMenu = HIToolbox.CreateMenu (idSeq++, GetName (entrySet), 0);
  94. CreateChildren (rootMenu, entrySet, ignoreCommands);
  95. InstallRootMenu ();
  96. } else {
  97. Destroy (false);
  98. CreateChildren (rootMenu, entrySet, ignoreCommands);
  99. }
  100. }
  101. static void OnCommandActivating (object sender, CommandActivationEventArgs args)
  102. {
  103. uint menuId;
  104. if (args.Source == CommandSource.Keybinding && menuIdMap.TryGetValue (args.CommandId, out menuId)) {
  105. //FIXME: for some reason we have to flash again after a delay to toggle the previous flash off?
  106. //some flashes can be unreliable, e.g. minimize, and modal dialogs don't seem to run timeouts, so the flash comes late
  107. GLib.Timeout.Add (50, delegate {
  108. HIToolbox.FlashMenuBar (menuId);
  109. return false;
  110. });
  111. GLib.Timeout.Add (250, delegate {
  112. HIToolbox.FlashMenuBar (menuId);
  113. return false;
  114. });
  115. }
  116. }
  117. static void InstallRootMenu ()
  118. {
  119. HIToolbox.CheckResult (HIToolbox.SetRootMenu (rootMenu));
  120. openingHandlerRef = Carbon.InstallApplicationEventHandler (HandleMenuOpening, CarbonEventMenu.Opening);
  121. closedHandlerRef = Carbon.InstallApplicationEventHandler (HandleMenuClosed, CarbonEventMenu.Closed);
  122. commandHandlerRef = Carbon.InstallApplicationEventHandler (HandleMenuCommand, CarbonEventCommand.Process);
  123. }
  124. static void CreateChildren (IntPtr parentMenu, CommandEntrySet entrySet, HashSet<object> ignoreCommands)
  125. {
  126. var menuId = HIToolbox.GetMenuID (parentMenu);
  127. foreach (CommandEntry entry in entrySet){
  128. CommandEntrySet ces = entry as CommandEntrySet;
  129. if (ces == null){
  130. ushort pos;
  131. if (ignoreCommands.Contains (entry.CommandId))
  132. continue;
  133. if (entry.CommandId == Command.Separator) {
  134. HIToolbox.AppendMenuSeparator (parentMenu);
  135. continue;
  136. }
  137. if (entry is LinkCommandEntry) {
  138. LinkCommandEntry lce = (LinkCommandEntry)entry;
  139. pos = HIToolbox.AppendMenuItem (parentMenu, (lce.Text ?? "").Replace ("_", ""), 0, linkCommandId);
  140. HIToolbox.SetMenuItemReferenceConstant (new HIMenuItem (parentMenu, pos), (uint)linkCommands.Count);
  141. linkCommands.Add (lce.Url);
  142. continue;
  143. }
  144. Command cmd = manager.GetCommand (entry.CommandId);
  145. if (cmd == null) {
  146. MonoDevelop.Core.LoggingService.LogError (
  147. "Mac main menu '{0}' child '{1}' maps to null command", entrySet.Name, entry.CommandId);
  148. continue;
  149. }
  150. if (cmd is CustomCommand) {
  151. MonoDevelop.Core.LoggingService.LogWarning (
  152. "Mac main menu does not support custom command widgets for command '{0}'", entry.CommandId);
  153. continue;
  154. }
  155. menuIdMap[entry.CommandId] = menuId;
  156. ActionCommand acmd = cmd as ActionCommand;
  157. if (acmd == null) {
  158. MonoDevelop.Core.LoggingService.LogWarning (
  159. "Mac main menu does not support command type '{0}' for command '{1}'", cmd.GetType (), entry.CommandId);
  160. continue;
  161. }
  162. uint macCmdId = GetNewMenuItemId (cmd);
  163. pos = HIToolbox.AppendMenuItem (parentMenu, (cmd.Text ?? "").Replace ("_", ""), 0, macCmdId);
  164. } else {
  165. var macCmdId = (ces.AutoHide) ? autohideSubmenuCommandId : submenuCommandId;
  166. IntPtr menuRef = HIToolbox.CreateMenu (idSeq++, GetName (ces), MenuAttributes.CondenseSeparators);
  167. mainMenus.Add (menuRef);
  168. CreateChildren (menuRef, ces, ignoreCommands);
  169. ushort pos = HIToolbox.AppendMenuItem (parentMenu, GetName (ces), 0, macCmdId);
  170. HIToolbox.CheckResult (HIToolbox.SetMenuItemHierarchicalMenu (parentMenu, pos, menuRef));
  171. }
  172. }
  173. }
  174. static void SetMenuAccelerator (HIMenuItem item, string accelKey)
  175. {
  176. MenuAccelModifier mod;
  177. ushort glyphCode, charCode, hardwareCode;
  178. if (GetAcceleratorKeys (accelKey, out glyphCode, out charCode, out hardwareCode, out mod)) {
  179. if (glyphCode != 0)
  180. HIToolbox.SetMenuItemKeyGlyph (item.MenuRef, item.Index, (short)glyphCode);
  181. else if (hardwareCode != 0)
  182. HIToolbox.SetMenuItemCommandKey (item.MenuRef, item.Index, true, hardwareCode);
  183. else
  184. HIToolbox.SetMenuItemCommandKey (item.MenuRef, item.Index, false, charCode);
  185. HIToolbox.SetMenuItemModifiers (item.MenuRef, item.Index, mod);
  186. }
  187. }
  188. //FIXME: handle the mode key
  189. static bool GetAcceleratorKeys (string accelKey, out ushort glyphCode, out ushort charCode, out ushort virtualHardwareCode,
  190. out MenuAccelModifier outMod)
  191. {
  192. uint modeKey, key;
  193. Gdk.ModifierType modeMod, mod;
  194. glyphCode = charCode = virtualHardwareCode = 0;
  195. outMod = (MenuAccelModifier) 0;
  196. if (!KeyBindingManager.BindingToKeys (accelKey, out modeKey, out modeMod, out key, out mod))
  197. return false;
  198. if (modeKey != 0) {
  199. System.Console.WriteLine("WARNING: Cannot display accelerators with mode keys ({0})", accelKey);
  200. return false;
  201. }
  202. glyphCode = (ushort)GlyphMappings ((Gdk.Key)key);
  203. if (glyphCode == 0) {
  204. charCode = (ushort)Gdk.Keyval.ToUnicode (key);
  205. if (charCode == 0) {
  206. var map = keymap.GetEntriesForKeyval (key);
  207. if (map != null && map.Length > 0)
  208. virtualHardwareCode = (ushort) map [0].Keycode;
  209. if (virtualHardwareCode == 0) {
  210. System.Console.WriteLine("WARNING: Could not map key ({0})", key);
  211. return false;
  212. }
  213. }
  214. }
  215. if ((mod & Gdk.ModifierType.Mod1Mask) != 0) {
  216. outMod |= MenuAccelModifier.OptionModifier;
  217. mod ^= Gdk.ModifierType.Mod1Mask;
  218. }
  219. if ((mod & Gdk.ModifierType.ShiftMask) != 0) {
  220. outMod |= MenuAccelModifier.ShiftModifier;
  221. mod ^= Gdk.ModifierType.ShiftMask;
  222. }
  223. if ((mod & Gdk.ModifierType.ControlMask) != 0) {
  224. outMod |= MenuAccelModifier.ControlModifier;
  225. mod ^= Gdk.ModifierType.ControlMask;
  226. }
  227. // This is inverted, because by default on OSX no setting means use the Command-key
  228. if ((mod & Gdk.ModifierType.MetaMask) == 0) {
  229. outMod |= MenuAccelModifier.None;
  230. } else {
  231. mod ^= Gdk.ModifierType.MetaMask;
  232. }
  233. if (mod != 0) {
  234. System.Console.WriteLine("WARNING: Cannot display accelerators with modifiers: {0}", mod);
  235. return false;
  236. }
  237. return true;
  238. }
  239. static MenuGlyphs GlyphMappings (Gdk.Key key)
  240. {
  241. switch (key) {
  242. case Gdk.Key.Page_Up:
  243. return MenuGlyphs.PageUp;
  244. case Gdk.Key.Page_Down:
  245. return MenuGlyphs.PageDown;
  246. case Gdk.Key.Up:
  247. return MenuGlyphs.UpArrow;
  248. case Gdk.Key.Down:
  249. return MenuGlyphs.DownArrow;
  250. case Gdk.Key.Left:
  251. return MenuGlyphs.LeftArrow;
  252. case Gdk.Key.Right:
  253. return MenuGlyphs.RightArrow;
  254. case Gdk.Key.space:
  255. return MenuGlyphs.Space;
  256. case Gdk.Key.Escape:
  257. return MenuGlyphs.Escape;
  258. case Gdk.Key.Return:
  259. return MenuGlyphs.Return;
  260. default:
  261. return MenuGlyphs.None;
  262. }
  263. }
  264. static uint GetNewMenuItemId (Command cmd)
  265. {
  266. uint macCmdId;
  267. CarbonCommandID standardId;
  268. //use mapped values if possible
  269. if (cmdIdMap.TryGetValue (cmd.Id, out standardId))
  270. macCmdId = (uint) standardId;
  271. //or generate a new value
  272. else {
  273. //but avoid conflicts
  274. do cmdSeq++;
  275. while (commands.ContainsKey (cmdSeq));
  276. macCmdId = cmdSeq;
  277. cmdIdMap[cmd.Id] = (CarbonCommandID)macCmdId;
  278. }
  279. commands[macCmdId] = cmd.Id;
  280. return macCmdId;
  281. }
  282. //NOTE: This is used to disable the whole menu when there's a modal dialog.
  283. // We can justify this because safari 3.2.1 does it ("do you want to close all tabs?").
  284. static bool IsGloballyDisabled {
  285. get {
  286. return !IdeApp.Workbench.HasToplevelFocus;
  287. }
  288. }
  289. static void SetMenuItemAttributes (HIMenuItem item, CommandInfo ci, uint refcon)
  290. {
  291. MenuItemData data = new MenuItemData ();
  292. IntPtr text = IntPtr.Zero;
  293. try {
  294. if (ci.IsArraySeparator) {
  295. data.Attributes |= MenuItemAttributes.Separator;
  296. } else if (!ci.Visible) {
  297. data.Attributes |= MenuItemAttributes.Hidden;
  298. } else {
  299. data.Attributes &= ~MenuItemAttributes.Hidden;
  300. data.CFText = CoreFoundation.CreateString (GetCleanCommandText (ci));
  301. //disable also when MD main window doesn't have toplevel focus, or commands will be
  302. //accessible when modal dialogs are active
  303. bool disabled = !ci.Enabled || IsGloballyDisabled;
  304. data.Enabled = !disabled;
  305. if (disabled)
  306. data.Attributes |= MenuItemAttributes.Disabled;
  307. ushort glyphCode, charCode, hardwareCode;
  308. MenuAccelModifier mod;
  309. if (GetAcceleratorKeys (ci.AccelKey, out glyphCode, out charCode, out hardwareCode, out mod)) {
  310. data.CommandKeyModifiers = mod;
  311. if (glyphCode != 0) {
  312. data.CommandKeyGlyph = glyphCode;
  313. data.Attributes ^= MenuItemAttributes.UseVirtualKey;
  314. } else if (hardwareCode != 0) {
  315. data.CommandVirtualKey = (char)hardwareCode;
  316. data.Attributes |= MenuItemAttributes.UseVirtualKey;
  317. } else {
  318. data.CommandKey = (char)charCode;
  319. data.Attributes ^= MenuItemAttributes.UseVirtualKey;
  320. }
  321. }
  322. //else{
  323. //FIXME: remove existing commands if necessary
  324. data.Mark = ci.Checked
  325. ? ci.CheckedInconsistent
  326. ? '-' //FIXME: is this a good symbol for CheckedInconsistent?
  327. : (char)MenuGlyphs.Checkmark
  328. : '\0';
  329. data.ReferenceConstant = refcon;
  330. }
  331. HIToolbox.SetMenuItemData (item.MenuRef, item.Index, false, ref data);
  332. } finally {
  333. if (text != IntPtr.Zero)
  334. CoreFoundation.Release (text);
  335. }
  336. }
  337. static string GetCleanCommandText (CommandInfo ci)
  338. {
  339. string txt = ci.Text;
  340. if (txt == null)
  341. return "";
  342. if (!ci.UseMarkup)
  343. return txt.Replace ("_", "");
  344. //strip GMarkup
  345. //FIXME: markup stripping could be done better
  346. StringBuilder sb = new StringBuilder ();
  347. for (int i = 0; i < txt.Length; i++) {
  348. char ch = txt[i];
  349. if (ch == '<') {
  350. while (++i < txt.Length && txt[i] != '>');
  351. } else if (ch == '&') {
  352. int j = i;
  353. while (++i < txt.Length && txt[i] != ';');
  354. int len = i - j - 1;
  355. if (len > 0) {
  356. string entityName = txt.Substring (j + 1, i - j - 1);
  357. switch (entityName) {
  358. case "quot":
  359. sb.Append ('"');
  360. break;
  361. case "amp":
  362. sb.Append ('&');
  363. break;
  364. case "apos":
  365. sb.Append ('\'');
  366. break;
  367. case "lt":
  368. sb.Append ('<');
  369. break;
  370. case "gt":
  371. sb.Append ('>');
  372. break;
  373. default:
  374. MonoDevelop.Core.LoggingService.LogWarning ("Could not de-markup entity '{0}'", entityName);
  375. break;
  376. }
  377. }
  378. } else if (ch != '_') {
  379. sb.Append (ch);
  380. }
  381. }
  382. return sb.ToString ();
  383. }
  384. #region App menu
  385. public static void SetAppQuitCommand (object cmdID)
  386. {
  387. commands[(uint)CarbonCommandID.Quit] = cmdID;
  388. }
  389. public static void AddAppMenuItems (CommandManager manager, params object [] cmdIds)
  390. {
  391. //FIXME: we assume we get first pick of cmdIDs
  392. HIMenuItem mnu = HIToolbox.GetMenuItem ((uint)CarbonCommandID.Hide);
  393. appMenu = mnu.MenuRef;
  394. var appMenuId = HIToolbox.GetMenuID (appMenu);
  395. for (int i = cmdIds.Length - 1; i >= 0; i--) {
  396. var cmdId = cmdIds[i];
  397. if (cmdId == Command.Separator) {
  398. HIToolbox.InsertMenuSeparator (mnu.MenuRef, 0);
  399. continue;
  400. }
  401. Command cmd = manager.GetCommand (cmdId);
  402. if (cmd == null){
  403. MonoDevelop.Core.LoggingService.LogError ("Null command in Mac app menu for ID {0}", cmdId);
  404. continue;
  405. }
  406. uint macCmdId = GetNewMenuItemId (cmd);
  407. ushort pos = HIToolbox.InsertMenuItem (mnu.MenuRef, (cmd.Text ?? "").Replace ("_", ""), 0, 0, macCmdId);
  408. SetMenuAccelerator (new HIMenuItem (mnu.MenuRef, pos), cmd.AccelKey);
  409. menuIdMap[cmdId] = appMenuId;
  410. }
  411. }
  412. #endregion
  413. #region Event handlers
  414. //updates commands and populates arrays and dynamic menus
  415. //NOTE: when Help menu is opened, Mac OS calls this for ALL menus because the Help menu can search menu items
  416. static CarbonEventHandlerStatus HandleMenuOpening (IntPtr callRef, IntPtr eventRef, IntPtr user_data)
  417. {
  418. DestroyOldMenuObjects ();
  419. menuOpenDepth++;
  420. try {
  421. IntPtr menuRef = Carbon.GetEventParameter (eventRef, CarbonEventParameterName.DirectObject, CarbonEventParameterType.MenuRef);
  422. //don't update dynamic menus recursively
  423. if (!mainMenus.Contains (menuRef) && menuRef != appMenu)
  424. return CarbonEventHandlerStatus.NotHandled;
  425. // uint cmd = HIToolbox.GetMenuItemCommandID (new HIMenuItem (menuRef, 0));
  426. CommandTargetRoute route = new CommandTargetRoute ();
  427. ushort count = HIToolbox.CountMenuItems (menuRef);
  428. for (ushort i = 1; i <= count; i++) {
  429. HIMenuItem mi = new HIMenuItem (menuRef, i);
  430. uint macCmdID = HIToolbox.GetMenuItemCommandID (mi);
  431. object cmdID;
  432. //link items
  433. if (macCmdID == linkCommandId) {
  434. if (IsGloballyDisabled)
  435. HIToolbox.DisableMenuItem (mi);
  436. else
  437. HIToolbox.EnableMenuItem (mi);
  438. continue;
  439. }
  440. if (macCmdID == submenuCommandId)
  441. continue;
  442. if (macCmdID == autohideSubmenuCommandId) {
  443. UpdateAutoHide (new HIMenuItem (menuRef, i));
  444. continue;
  445. }
  446. //items that map to command objects
  447. if (!commands.TryGetValue (macCmdID, out cmdID) || cmdID == null)
  448. continue;
  449. CommandInfo cinfo = manager.GetCommandInfo (cmdID, route);
  450. menuIdMap[cmdID] = HIToolbox.GetMenuID (menuRef);
  451. UpdateMenuItem (menuRef, menuRef, ref i, ref count, macCmdID, cinfo);
  452. }
  453. } catch (Exception ex) {
  454. System.Console.WriteLine (ex);
  455. }
  456. return CarbonEventHandlerStatus.NotHandled;
  457. }
  458. static void UpdateAutoHide (HIMenuItem item)
  459. {
  460. IntPtr submenu;
  461. if (HIToolbox.GetMenuItemHierarchicalMenu (item.MenuRef, item.Index, out submenu) != CarbonMenuStatus.Ok)
  462. return;
  463. if (HasVisibleItems (submenu)) {
  464. HIToolbox.ChangeMenuItemAttributes (item, 0, MenuItemAttributes.Hidden);
  465. } else {
  466. HIToolbox.ChangeMenuItemAttributes (item, MenuItemAttributes.Hidden, 0);
  467. }
  468. }
  469. static bool HasVisibleItems (IntPtr submenu)
  470. {
  471. var route = new CommandTargetRoute ();
  472. ushort count = HIToolbox.CountMenuItems (submenu);
  473. for (ushort i = 1; i <= count; i++) {
  474. HIMenuItem mi = new HIMenuItem (submenu, i);
  475. uint macCmdID = HIToolbox.GetMenuItemCommandID (mi);
  476. object cmdID;
  477. if (macCmdID == linkCommandId)
  478. return true;
  479. if (!commands.TryGetValue (macCmdID, out cmdID) || cmdID == null)
  480. continue;
  481. CommandInfo cinfo = manager.GetCommandInfo (cmdID, route);
  482. if (cinfo.ArrayInfo != null) {
  483. foreach (CommandInfo ci in cinfo.ArrayInfo)
  484. if (ci.Visible)
  485. return true;
  486. } else if (cinfo.Visible) {
  487. return true;
  488. }
  489. }
  490. return false;
  491. }
  492. static void BuildDynamicSubMenu (IntPtr rootMenu, IntPtr parentMenu, ushort index, uint macCmdID, CommandInfoSet cinfoSet)
  493. {
  494. IntPtr menuRef = HIToolbox.CreateMenu (idSeq++, GetCleanCommandText (cinfoSet), MenuAttributes.CondenseSeparators);
  495. objectsToDestroyOnMenuClose.Add (new DestructableMenu (menuRef));
  496. HIToolbox.CheckResult (HIToolbox.SetMenuItemHierarchicalMenu (parentMenu, index, menuRef));
  497. ushort count = (ushort) cinfoSet.CommandInfos.Count;
  498. for (ushort i = 1, j = 0; i <= count; i++) {
  499. CommandInfo ci = cinfoSet.CommandInfos[j++];
  500. if (ci.IsArraySeparator) {
  501. HIToolbox.AppendMenuSeparator (menuRef);
  502. } else {
  503. HIToolbox.AppendMenuItem (menuRef, ci.Text, 0, macCmdID);
  504. UpdateMenuItem (rootMenu, menuRef, ref i, ref count, macCmdID, ci);
  505. objectsToDestroyOnMenuClose.Add (ci.DataItem);
  506. uint refcon = (uint)objectsToDestroyOnMenuClose.Count;
  507. HIToolbox.SetMenuItemReferenceConstant (new HIMenuItem (menuRef, i), refcon);
  508. }
  509. }
  510. }
  511. static void UpdateMenuItem (IntPtr rootMenu, IntPtr menuRef, ref ushort index, ref ushort count,
  512. uint macCmdID, CommandInfo cinfo)
  513. {
  514. if (cinfo.ArrayInfo != null) {
  515. //remove the existing items, except one, which we hide, so it gets updated next time even if the list is empty
  516. HIToolbox.ChangeMenuItemAttributes (new HIMenuItem (menuRef, index), MenuItemAttributes.Hidden, 0);
  517. index++;
  518. while (index <= count && HIToolbox.GetMenuItemCommandID (new HIMenuItem (menuRef, index)) == macCmdID) {
  519. HIToolbox.DeleteMenuItem (menuRef, index);
  520. count--;
  521. }
  522. index--;
  523. //add the new items
  524. foreach (CommandInfo ci in cinfo.ArrayInfo) {
  525. count++;
  526. HIToolbox.InsertMenuItem (menuRef, ci.Text, index++, 0, macCmdID);
  527. //associate a reference constant with the menu, used to index the DataItem
  528. //it's one-based, so that 0 can be used as a flag that there's no associated object
  529. objectsToDestroyOnMenuClose.Add (ci.DataItem);
  530. uint refcon = (uint)objectsToDestroyOnMenuClose.Count;
  531. SetMenuItemAttributes (new HIMenuItem (menuRef, index), ci, refcon);
  532. if (ci is CommandInfoSet)
  533. BuildDynamicSubMenu (rootMenu, menuRef, index, macCmdID, (CommandInfoSet)ci);
  534. }
  535. } else {
  536. SetMenuItemAttributes (new HIMenuItem (menuRef, index), cinfo, 0);
  537. if (cinfo is CommandInfoSet)
  538. BuildDynamicSubMenu (rootMenu, menuRef, index, macCmdID, (CommandInfoSet)cinfo);
  539. }
  540. }
  541. //this releases resources and gc-prevention handles from dynamic menu creation
  542. static CarbonEventHandlerStatus HandleMenuClosed (IntPtr callRef, IntPtr eventRef, IntPtr user_data)
  543. {
  544. menuOpenDepth--;
  545. //we can't destroy the menu objects instantly, since the command handler is invoked after the close event
  546. //FIXME: maybe we can get the close reason, and handle the command here?
  547. return CarbonEventHandlerStatus.NotHandled;
  548. }
  549. static void DestroyOldMenuObjects ()
  550. {
  551. if (menuOpenDepth > 0)
  552. return;
  553. foreach (object o in objectsToDestroyOnMenuClose) {
  554. if (o is DestructableMenu)
  555. try {
  556. ((DestructableMenu)o).Destroy ();
  557. } catch (Exception ex) {
  558. MonoDevelop.Core.LoggingService.LogError ("Unhandled exception while destroying old menu objects", ex);
  559. }
  560. }
  561. objectsToDestroyOnMenuClose.Clear ();
  562. }
  563. static object GetCommandID (CarbonHICommand cmdEvent)
  564. {
  565. object cmdID;
  566. commands.TryGetValue (cmdEvent.CommandID, out cmdID);
  567. return cmdID;
  568. }
  569. static CarbonHICommand GetCarbonHICommand (IntPtr eventRef)
  570. {
  571. return Carbon.GetEventParameter<CarbonHICommand> (eventRef, CarbonEventParameterName.DirectObject,
  572. CarbonEventParameterType.HICommand);
  573. }
  574. static CarbonEventHandlerStatus HandleMenuCommand (IntPtr callRef, IntPtr eventRef, IntPtr userData)
  575. {
  576. try {
  577. CarbonHICommand hiCmd = GetCarbonHICommand (eventRef);
  578. uint refCon = HIToolbox.GetMenuItemReferenceConstant (hiCmd.MenuItem);
  579. //link commands
  580. if (hiCmd.CommandID == linkCommandId) {
  581. string url = "";
  582. try {
  583. url = linkCommands[(int)refCon];
  584. MacPlatformService.OpenUrl (url);
  585. } catch (Exception ex) {
  586. Gtk.Application.Invoke (delegate {
  587. MonoDevelop.Ide.MessageService.ShowException (ex, MonoDevelop.Core.GettextCatalog.GetString ("Could not open the url {0}", url));
  588. });
  589. }
  590. DestroyOldMenuObjects ();
  591. return CarbonEventHandlerStatus.Handled;
  592. }
  593. //normal commands
  594. object cmdID = GetCommandID (hiCmd);
  595. if (cmdID != null) {
  596. if (refCon > 0) {
  597. object data = objectsToDestroyOnMenuClose[(int)refCon - 1];
  598. //need to return before we execute the command, so that the menu unhighlights
  599. Gtk.Application.Invoke (delegate { manager.DispatchCommand (cmdID, data, CommandSource.MainMenu); });
  600. } else {
  601. Gtk.Application.Invoke (delegate { manager.DispatchCommand (cmdID, CommandSource.MainMenu); });
  602. }
  603. DestroyOldMenuObjects ();
  604. return CarbonEventHandlerStatus.Handled;
  605. }
  606. } catch (Exception ex) {
  607. MonoDevelop.Core.LoggingService.LogError ("Unhandled error handling menu command", ex);
  608. }
  609. return CarbonEventHandlerStatus.NotHandled;
  610. }
  611. #endregion
  612. public static void Destroy (bool removeRoot)
  613. {
  614. if (mainMenus.Count > 0) {
  615. foreach (IntPtr ptr in mainMenus) {
  616. HIToolbox.DeleteMenu (ptr);
  617. CoreFoundation.Release (ptr);
  618. }
  619. DestroyOldMenuObjects ();
  620. mainMenus.Clear ();
  621. linkCommands.Clear ();
  622. idSeq = 1;
  623. }
  624. HIToolbox.ClearMenuBar ();
  625. if (removeRoot && rootMenu != IntPtr.Zero) {
  626. HIToolbox.DeleteMenu (rootMenu);
  627. CoreFoundation.Release (rootMenu);
  628. HIToolbox.CheckResult (HIToolbox.SetRootMenu (IntPtr.Zero));
  629. Carbon.RemoveEventHandler (commandHandlerRef);
  630. Carbon.RemoveEventHandler (openingHandlerRef);
  631. Carbon.RemoveEventHandler (closedHandlerRef);
  632. commandHandlerRef = openingHandlerRef = rootMenu = IntPtr.Zero;
  633. idSeq = 0;
  634. }
  635. }
  636. }
  637. }