GuiMenuItem.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /* GuiMenuItem.cpp
  2. *
  3. * Copyright (C) 1992-2018 Paul Boersma, 2013 Tom Naughton
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. * See the GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "GuiP.h"
  19. #if gtk
  20. #include <gdk/gdkkeysyms.h>
  21. #endif
  22. #define _motif_SHIFT_MASK 1
  23. #define _motif_COMMAND_MASK 2
  24. #define _motif_OPTION_MASK 4
  25. Thing_implement (GuiMenuItem, GuiThing, 0);
  26. #if gtk
  27. #define iam_menuitem GuiMenuItem me = (GuiMenuItem) _GuiObject_getUserData (widget)
  28. #elif motif
  29. #define iam_menuitem GuiMenuItem me = (GuiMenuItem) widget -> userData
  30. #elif cocoa
  31. #define iam_menuitem GuiMenuItem me = (GuiMenuItem) [(GuiCocoaMenuItem *) widget getUserData];
  32. #endif
  33. #if motif
  34. static void NativeMenuItem_setText (GuiObject me) {
  35. int acc = my motiff.pushButton.acceleratorChar, modifiers = my motiff.pushButton.acceleratorModifiers;
  36. static MelderString title { };
  37. if (acc == 0) {
  38. MelderString_copy (& title, _GuiWin_expandAmpersands (my name.get()));
  39. } else {
  40. static const conststring32 keyStrings [256] = {
  41. 0, U"<-", U"->", U"Up", U"Down", U"PAUSE", U"Del", U"Ins", U"Backspace", U"Tab", U"LineFeed", U"Home", U"End", U"Enter", U"PageUp", U"PageDown",
  42. U"Esc", U"F1", U"F2", U"F3", U"F4", U"F5", U"F6", U"F7", U"F8", U"F9", U"F10", U"F11", U"F12", 0, 0, 0,
  43. U"Space", U"!", U"\"", U"#", U"$", U"%", U"&", U"\'", U"(", U")", U"*", U"+", U",", U"-", U".", U"/",
  44. U"0", U"1", U"2", U"3", U"4", U"5", U"6", U"7", U"8", U"9", U":", U";", U"<", U"=", U">", U"?",
  45. U"@", U"A", U"B", U"C", U"D", U"E", U"F", U"G", U"H", U"I", U"J", U"K", U"L", U"M", U"N", U"O",
  46. U"P", U"Q", U"R", U"S", U"T", U"U", U"V", U"W", U"X", U"Y", U"Z", U"[", U"\\", U"]", U"^", U"_",
  47. U"`", U"a", U"b", U"c", U"d", U"e", U"f", U"g", U"h", U"i", U"j", U"k", U"l", U"m", U"n", U"o",
  48. U"p", U"q", U"r", U"s", U"t", U"u", U"v", U"w", U"x", U"y", U"z", U"{", U"|", U"}", U"~", U"Del",
  49. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  50. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  51. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  52. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, U"[", U"]", U",", U"?", U".", U"\\",
  53. U";", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  54. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, U"-", U"`", U"=", U"\'", 0,
  55. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  56. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  57. const conststring32 keyString = keyStrings [acc] ? keyStrings [acc] : U"???";
  58. MelderString_copy (& title, _GuiWin_expandAmpersands (my name.get()), U"\t",
  59. modifiers & _motif_COMMAND_MASK ? U"Ctrl-" : nullptr,
  60. modifiers & _motif_OPTION_MASK ? U"Alt-" : nullptr,
  61. modifiers & _motif_SHIFT_MASK ? U"Shift-" : nullptr, keyString);
  62. }
  63. ModifyMenu (my nat.entry.handle, my nat.entry.id, MF_BYCOMMAND | MF_STRING, my nat.entry.id, Melder_peek32toW (title.string));
  64. }
  65. #endif
  66. #if gtk
  67. static void _guiGtkMenuItem_destroyCallback (GuiObject widget, gpointer void_me) {
  68. (void) widget;
  69. iam (GuiMenuItem);
  70. trace (U"destroying GuiMenuItem ", Melder_pointer (me));
  71. forget (me);
  72. }
  73. static void _guiGtkMenuItem_activateCallback (GuiObject widget, gpointer void_me) {
  74. iam (GuiMenuItem);
  75. if (my d_callbackBlocked) return;
  76. if (G_OBJECT_TYPE (widget) == GTK_TYPE_RADIO_MENU_ITEM && ! gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) return;
  77. struct structGuiMenuItemEvent event { me, false, false, false, false };
  78. if (my d_callback) {
  79. try {
  80. my d_callback (my d_boss, & event);
  81. } catch (MelderError) {
  82. Melder_flushError (U"Your choice of menu item \"", Melder_peek8to32 (GTK_WIDGET (widget) -> name), U"\" was not completely handled.");
  83. }
  84. }
  85. }
  86. #elif motif
  87. static void _guiMotifMenuItem_destroyCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
  88. (void) widget; (void) call;
  89. iam (GuiMenuItem);
  90. forget (me);
  91. }
  92. static void _guiMotifMenuItem_activateCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
  93. iam (GuiMenuItem);
  94. if (my d_callback) {
  95. struct structGuiMenuItemEvent event { me, false, false, false, false };
  96. try {
  97. my d_callback (my d_boss, & event);
  98. } catch (MelderError) {
  99. Melder_flushError (U"Your choice of menu item \"", widget -> name.get(), U"\" was not completely handled.");
  100. }
  101. }
  102. }
  103. #elif cocoa
  104. @implementation GuiCocoaMenuItem {
  105. GuiMenuItem d_userData;
  106. }
  107. - (void) dealloc { // override
  108. GuiMenuItem me = d_userData;
  109. forget (me);
  110. trace (U"deleting a menu item");
  111. [super dealloc];
  112. }
  113. - (GuiThing) getUserData {
  114. return d_userData;
  115. }
  116. - (void) setUserData: (GuiThing) userData {
  117. Melder_assert (userData == nullptr || Thing_isa (userData, classGuiMenuItem));
  118. d_userData = static_cast <GuiMenuItem> (userData);
  119. }
  120. - (void) _guiCocoaMenuItem_activateCallback: (id) widget {
  121. Melder_assert (self == widget); // sender (widget) and receiver (self) happen to be the same object
  122. GuiMenuItem me = d_userData;
  123. if (my d_callback) {
  124. struct structGuiMenuItemEvent event { me, false, false, false, false };
  125. try {
  126. my d_callback (my d_boss, & event);
  127. } catch (MelderError) {
  128. Melder_flushError (U"Your choice of menu item \"", U"xx", U"\" was not completely handled.");
  129. }
  130. }
  131. }
  132. @end
  133. #endif
  134. GuiMenuItem GuiMenu_addItem (GuiMenu menu, conststring32 title, uint32 flags,
  135. GuiMenuItemCallback commandCallback, Thing boss)
  136. {
  137. autoGuiMenuItem me = Thing_new (GuiMenuItem);
  138. my d_shell = menu -> d_shell;
  139. my d_parent = menu;
  140. my d_menu = menu;
  141. trace (U"creating item \"", title, U"\" in menu ", Melder_pointer (menu));
  142. bool toggle = flags & (GuiMenu_CHECKBUTTON | GuiMenu_RADIO_FIRST | GuiMenu_RADIO_NEXT | GuiMenu_TOGGLE_ON) ? true : false;
  143. uint32 accelerator = flags & 127;
  144. Melder_assert (title);
  145. #if gtk
  146. static GSList *group = nullptr;
  147. if (toggle) {
  148. if (flags & (GuiMenu_RADIO_FIRST)) group = nullptr;
  149. if (flags & (GuiMenu_RADIO_FIRST | GuiMenu_RADIO_NEXT)) {
  150. my d_widget = gtk_radio_menu_item_new_with_label (group, Melder_peek32to8 (title));
  151. group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (my d_widget));
  152. trace (U"created a radio menu item with title \"", title, U"\", group ", Melder_pointer (group));
  153. } else {
  154. my d_widget = gtk_check_menu_item_new_with_label (Melder_peek32to8 (title));
  155. }
  156. } else {
  157. my d_widget = gtk_menu_item_new_with_label (Melder_peek32to8 (title));
  158. }
  159. Melder_assert (menu -> d_widget);
  160. gtk_menu_shell_append (GTK_MENU_SHELL (menu -> d_widget), GTK_WIDGET (my d_widget));
  161. _GuiObject_setUserData (my d_widget, me.get());
  162. #elif motif
  163. my d_widget = XtVaCreateManagedWidget (Melder_peek32to8 (title),
  164. toggle ? xmToggleButtonGadgetClass : xmPushButtonGadgetClass, menu -> d_widget, nullptr);
  165. _GuiObject_setUserData (my d_widget, me.get());
  166. #elif cocoa
  167. (void) toggle; // no difference between toggling and normal menu items on Cocoa
  168. NSString *string = (NSString *) Melder_peek32toCfstring (title);
  169. GuiCocoaMenuItem *menuItem = [[GuiCocoaMenuItem alloc]
  170. initWithTitle:string
  171. action: nullptr
  172. keyEquivalent: @""];
  173. //Melder_assert ([string retainCount] == 2 || [string retainCount] == -1); // the menu item retains the string (assertion can fail on 10.6)
  174. trace (U"string retain count = ", [string retainCount]);
  175. my d_widget = menuItem;
  176. trace (
  177. U"installing item in GuiMenu ", Melder_pointer (menu),
  178. U" (NSMenu ", Melder_pointer (menu -> d_cocoaMenu),
  179. U"); retain count = ", [menuItem retainCount]
  180. );
  181. [menu -> d_cocoaMenu addItem: (NSMenuItem *) my d_widget]; // the menu will retain the item...
  182. trace (
  183. U"installed item in GuiMenu ", Melder_pointer (menu),
  184. U" (NSMenu ", Melder_pointer (menu -> d_cocoaMenu),
  185. U"); retain count = ", [menuItem retainCount]
  186. );
  187. trace (U"release the item");
  188. [menuItem release]; // ... so we can release the item already
  189. trace (U"set user data");
  190. [menuItem setUserData: me.get()];
  191. #endif
  192. Melder_assert (my d_widget);
  193. trace (U"set sensitivity");
  194. if (flags & GuiMenu_INSENSITIVE)
  195. GuiThing_setSensitive (me.get(), false);
  196. trace (U"understand toggle menu items");
  197. if (flags & GuiMenu_TOGGLE_ON)
  198. #if gtk
  199. gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (my d_widget), true);
  200. #elif motif
  201. XmToggleButtonGadgetSetState (my d_widget, True, False);
  202. #elif cocoa
  203. [menuItem setState: NSOnState];
  204. #endif
  205. if (accelerator) {
  206. trace (U"adding accelerator ", accelerator);
  207. /*
  208. * For printable characters, the Command key is assumed.
  209. */
  210. if (accelerator >= 32)
  211. flags |= GuiMenu_COMMAND;
  212. #if gtk
  213. static const guint acceleratorKeys [] = { 0,
  214. GDK_Left, GDK_Right, GDK_Up, GDK_Down, GDK_Pause, GDK_Delete, GDK_Insert, GDK_BackSpace,
  215. GDK_Tab, GDK_Return, GDK_Home, GDK_End, GDK_Return, GDK_Page_Up, GDK_Page_Down, GDK_Escape,
  216. GDK_F1, GDK_F2, GDK_F3, GDK_F4, GDK_F5, GDK_F6, GDK_F7, GDK_F8, GDK_F9, GDK_F10, GDK_F11, GDK_F12,
  217. 0, 0, 0 };
  218. GdkModifierType modifiers = (GdkModifierType) 0;
  219. if (flags & GuiMenu_COMMAND) modifiers = (GdkModifierType) (modifiers | GDK_CONTROL_MASK);
  220. if (flags & GuiMenu_SHIFT) modifiers = (GdkModifierType) (modifiers | GDK_SHIFT_MASK);
  221. if (flags & GuiMenu_OPTION) modifiers = (GdkModifierType) (modifiers | GDK_MOD1_MASK);
  222. GtkAccelGroup *ag = gtk_menu_get_accel_group (GTK_MENU (menu -> d_widget));
  223. guint key = accelerator < 32 ? acceleratorKeys [accelerator] : accelerator;
  224. if (key != 0)
  225. gtk_widget_add_accelerator (GTK_WIDGET (my d_widget), toggle ? "YouShouldNotGetHere" : "activate",
  226. ag, key, modifiers, GTK_ACCEL_VISIBLE);
  227. #elif motif
  228. int modifiers = 0;
  229. if (flags & GuiMenu_COMMAND) modifiers |= _motif_COMMAND_MASK;
  230. if (flags & GuiMenu_SHIFT) modifiers |= _motif_SHIFT_MASK;
  231. if (flags & GuiMenu_OPTION) modifiers |= _motif_OPTION_MASK;
  232. if (accelerator > 0 && accelerator < 32) {
  233. if (my d_widget -> shell) {
  234. my d_widget -> shell -> motiff.shell.lowAccelerators [modifiers] |= 1 << accelerator;
  235. } else {
  236. theGuiTopLowAccelerators [modifiers] |= 1 << accelerator;
  237. }
  238. } else if (accelerator == '?' || accelerator == '{' || accelerator == '}' || accelerator == '\"' ||
  239. accelerator == '<' || accelerator == '>' || accelerator == '|' || accelerator == '_' || accelerator == '+' || accelerator == '~')
  240. {
  241. modifiers |= _motif_SHIFT_MASK;
  242. }
  243. my d_widget -> motiff.pushButton.acceleratorChar = accelerator;
  244. my d_widget -> motiff.pushButton.acceleratorModifiers = modifiers;
  245. NativeMenuItem_setText (my d_widget);
  246. #elif cocoa
  247. accelerator = Melder_toLowerCase (accelerator); // otherwise, a Shift key is introduced in the mask
  248. NSUInteger mask = 0;
  249. if (flags & GuiMenu_COMMAND) mask |= NSCommandKeyMask;
  250. if (flags & GuiMenu_SHIFT) mask |= NSShiftKeyMask;
  251. if (flags & GuiMenu_OPTION) mask |= NSAlternateKeyMask;
  252. [menuItem setKeyEquivalentModifierMask: mask];
  253. if (accelerator > 0 && accelerator < 32) {
  254. static unichar acceleratorKeys [] = { 0,
  255. NSLeftArrowFunctionKey, NSRightArrowFunctionKey, NSUpArrowFunctionKey, NSDownArrowFunctionKey, NSPauseFunctionKey, NSDeleteFunctionKey, NSInsertFunctionKey, NSBackspaceCharacter,
  256. NSTabCharacter, NSNewlineCharacter, NSHomeFunctionKey, NSEndFunctionKey, NSCarriageReturnCharacter, NSPageUpFunctionKey, NSPageDownFunctionKey, 27,
  257. NSF1FunctionKey, NSF2FunctionKey, NSF3FunctionKey, NSF4FunctionKey, NSF5FunctionKey, NSF6FunctionKey,
  258. NSF7FunctionKey, NSF8FunctionKey, NSF9FunctionKey, NSF10FunctionKey, NSF11FunctionKey, NSF12FunctionKey,
  259. 0, 0, 0 };
  260. [menuItem setKeyEquivalent: [NSString stringWithCharacters: & acceleratorKeys [accelerator] length: 1]];
  261. if (accelerator == GuiMenu_TAB) {
  262. GuiWindow window = (GuiWindow) my d_shell;
  263. Melder_assert (window -> classInfo == classGuiWindow); // fairly safe, because dialogs have no menus
  264. if (flags & GuiMenu_SHIFT) {
  265. window -> d_shiftTabCallback = commandCallback;
  266. window -> d_shiftTabBoss = boss;
  267. } else {
  268. window -> d_tabCallback = commandCallback;
  269. window -> d_tabBoss = boss;
  270. }
  271. } else if (accelerator == GuiMenu_BACKSPACE) {
  272. GuiWindow window = (GuiWindow) my d_shell;
  273. Melder_assert (window -> classInfo == classGuiWindow); // fairly safe, because dialogs have no menus
  274. if (flags & GuiMenu_OPTION) {
  275. window -> d_optionBackspaceCallback = commandCallback;
  276. window -> d_optionBackspaceBoss = boss;
  277. }
  278. }
  279. } else {
  280. [menuItem setKeyEquivalent: [NSString stringWithFormat: @"%c", accelerator]];
  281. }
  282. #endif
  283. trace (U"added accelerator ", accelerator);
  284. }
  285. trace (U"install the command callback");
  286. my d_callback = commandCallback;
  287. my d_boss = boss;
  288. #if gtk
  289. if (commandCallback) {
  290. if (flags == GuiMenu_TAB) {
  291. GtkWidget *shell = gtk_widget_get_toplevel (gtk_menu_get_attach_widget (GTK_MENU (menu -> d_widget)));
  292. trace (U"tab set in GTK window ", Melder_pointer (shell));
  293. g_object_set_data (G_OBJECT (shell), "tabCallback", (gpointer) _guiGtkMenuItem_activateCallback);
  294. g_object_set_data (G_OBJECT (shell), "tabClosure", (gpointer) me.get());
  295. } else if (flags == (GuiMenu_TAB | GuiMenu_SHIFT)) {
  296. GtkWidget *shell = gtk_widget_get_toplevel (gtk_menu_get_attach_widget (GTK_MENU (menu -> d_widget)));
  297. trace (U"shift-tab set in GTK window ", Melder_pointer (shell));
  298. g_object_set_data (G_OBJECT (shell), "shiftTabCallback", (gpointer) _guiGtkMenuItem_activateCallback);
  299. g_object_set_data (G_OBJECT (shell), "shiftTabClosure", (gpointer) me.get());
  300. } else {
  301. g_signal_connect (G_OBJECT (my d_widget),
  302. toggle ? "toggled" : "activate",
  303. G_CALLBACK (_guiGtkMenuItem_activateCallback), (gpointer) me.get());
  304. }
  305. } else {
  306. gtk_widget_set_sensitive (GTK_WIDGET (my d_widget), false);
  307. }
  308. gtk_widget_show (GTK_WIDGET (my d_widget));
  309. #elif motif
  310. XtAddCallback (my d_widget,
  311. toggle ? XmNvalueChangedCallback : XmNactivateCallback,
  312. _guiMotifMenuItem_activateCallback, (XtPointer) me.get());
  313. #elif cocoa
  314. [(NSMenuItem *) my d_widget setTarget: (id) my d_widget];
  315. [(NSMenuItem *) my d_widget setAction: @selector (_guiCocoaMenuItem_activateCallback:)];
  316. #endif
  317. trace (U"make sure that I will be destroyed when my widget is destroyed");
  318. #if gtk
  319. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_guiGtkMenuItem_destroyCallback), me.get());
  320. #elif motif
  321. XtAddCallback (my d_widget, XmNdestroyCallback, _guiMotifMenuItem_destroyCallback, me.get());
  322. #elif cocoa
  323. #endif
  324. return me.releaseToAmbiguousOwner();
  325. }
  326. GuiMenuItem GuiMenu_addSeparator (GuiMenu menu) {
  327. autoGuiMenuItem me = Thing_new (GuiMenuItem);
  328. my d_shell = menu -> d_shell;
  329. my d_parent = menu;
  330. my d_menu = menu;
  331. #if gtk
  332. my d_widget = gtk_separator_menu_item_new ();
  333. gtk_menu_shell_append (GTK_MENU_SHELL (menu -> d_widget), GTK_WIDGET (my d_widget));
  334. gtk_widget_show (GTK_WIDGET (my d_widget));
  335. #elif motif
  336. my d_widget = XtVaCreateManagedWidget ("menuSeparator", xmSeparatorGadgetClass, menu -> d_widget, nullptr);
  337. #elif cocoa
  338. my d_widget = (GuiObject) [GuiCocoaMenuItem separatorItem];
  339. trace (U"install separator in menu ", Melder_pointer (menu));
  340. trace (
  341. U"installing separator in GuiMenu ", Melder_pointer (menu),
  342. U" (NSMenu ", Melder_pointer (menu -> d_cocoaMenu),
  343. U"); retain count = ", [((NSMenuItem *) my d_widget) retainCount]
  344. );
  345. [menu -> d_cocoaMenu addItem: (NSMenuItem *) my d_widget]; // the menu will retain the item...
  346. trace (
  347. U"installed separator in GuiMenu ", Melder_pointer (menu),
  348. U" (NSMenu ", Melder_pointer (menu -> d_cocoaMenu),
  349. U"); retain count = ", [((NSMenuItem *) my d_widget) retainCount]
  350. );
  351. trace (U"release the item");
  352. //[(NSMenuItem *) my d_widget release]; // ... so we can release the item already
  353. trace (U"set user data");
  354. [(GuiCocoaMenuItem *) my d_widget setUserData: me.get()];
  355. #endif
  356. trace (U"make sure that I will be destroyed when my widget is destroyed");
  357. #if gtk
  358. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_guiGtkMenuItem_destroyCallback), me.get());
  359. #elif cocoa
  360. #elif motif
  361. XtAddCallback (my d_widget, XmNdestroyCallback, _guiMotifMenuItem_destroyCallback, me.get());
  362. #endif
  363. return me.releaseToAmbiguousOwner();
  364. }
  365. void GuiMenuItem_check (GuiMenuItem me, bool check) {
  366. Melder_assert (my d_widget);
  367. #if gtk
  368. my d_callbackBlocked = true;
  369. gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (my d_widget), check);
  370. my d_callbackBlocked = false;
  371. #elif motif
  372. XmToggleButtonGadgetSetState (my d_widget, check, False);
  373. #elif cocoa
  374. GuiCocoaMenuItem *item = (GuiCocoaMenuItem *) my d_widget;
  375. [item setState: check];
  376. #endif
  377. }
  378. /* End of file GuiMenuItem.cpp */