GuiMenu.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. /* GuiMenu.cpp
  2. *
  3. * Copyright (C) 1992-2012,2013,2015,2016,2017 Paul Boersma,
  4. * 2008 Stefan de Konink, 2010 Franz Brausse, 2013 Tom Naughton
  5. *
  6. * This code is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or (at
  9. * your option) any later version.
  10. *
  11. * This code is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  14. * See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "GuiP.h"
  20. #include "praatP.h" // BUG
  21. Thing_implement (GuiMenu, GuiThing, 0);
  22. void structGuiMenu :: v_destroy () noexcept {
  23. our GuiMenu_Parent :: v_destroy (); // if (d_widget) { _GuiObject_setUserData (d_widget, nullptr); GuiObject_destroy (d_widget); }
  24. }
  25. #if gtk
  26. static void _guiGtkMenu_destroyCallback (GuiObject widget, gpointer void_me) {
  27. (void) void_me;
  28. GuiMenu me = (GuiMenu) _GuiObject_getUserData (widget);
  29. trace (U"destroying GuiMenu ", Melder_pointer (me));
  30. if (! me) return; // we could be destroying me
  31. my d_widget = nullptr; // undangle
  32. if (my d_cascadeButton) my d_cascadeButton -> d_widget = nullptr; // undangle
  33. if (my d_menuItem) my d_menuItem -> d_widget = nullptr; // undangle
  34. forget (me);
  35. }
  36. static void _guiGtkMenuCascadeButton_destroyCallback (GuiObject widget, gpointer void_me) {
  37. (void) void_me;
  38. GuiMenu me = (GuiMenu) _GuiObject_getUserData (widget);
  39. if (! me) return;
  40. trace (U"destroying GuiButton ", Melder_pointer (my d_cascadeButton.get()));
  41. gtk_widget_destroy (GTK_WIDGET (my d_widget));
  42. }
  43. #elif motif
  44. static void _guiMotifMenu_destroyCallback (GuiObject widget, XtPointer void_me, XtPointer call) {
  45. (void) void_me;
  46. (void) call;
  47. GuiMenu me = (GuiMenu) _GuiObject_getUserData (widget);
  48. trace (U"destroying GuiMenu ", Melder_pointer (me));
  49. if (! me) return; // we could be destroying me
  50. my d_widget = nullptr; // undangle
  51. if (my d_cascadeButton) my d_cascadeButton -> d_widget = nullptr; // undangle
  52. if (my d_menuItem) my d_menuItem -> d_widget = nullptr; // undangle
  53. forget (me);
  54. }
  55. #elif cocoa
  56. static void (*theOpenDocumentCallback) (MelderFile file);
  57. void Gui_setOpenDocumentCallback (void (*openDocumentCallback) (MelderFile file)) {
  58. theOpenDocumentCallback = openDocumentCallback;
  59. }
  60. static NSMenu *theMenuBar;
  61. static int theNumberOfMenuBarItems = 0;
  62. static NSMenuItem *theMenuBarItems [30];
  63. @implementation GuiCocoaApplication
  64. /*
  65. * Override sendEvent() to capture navigation keys (Tab and Shift-Tab) as menu shortcuts
  66. * and to capture text-editing keys (Option-Backspace) as menu shortcuts.
  67. */
  68. - (void) sendEvent: (NSEvent *) nsEvent {
  69. if ([nsEvent type] == NSKeyDown) {
  70. NSString *characters = [nsEvent characters];
  71. if ([characters length] == 0) { // only modifiers?
  72. [super sendEvent: nsEvent]; // the default action
  73. return;
  74. }
  75. unichar character = [characters characterAtIndex: 0]; // there is now at least one character, so no range exception can be raised
  76. if (Melder_isTracing) {
  77. for (NSUInteger i = 0; i < [characters length]; i ++) {
  78. unichar kar = [characters characterAtIndex: 0];
  79. trace (U"character [", i, U"]: ", (int) kar);
  80. }
  81. trace (U"modifiers: ", [nsEvent modifierFlags]);
  82. }
  83. if (character == NSTabCharacter) {
  84. NSWindow *cocoaKeyWindow = [NSApp keyWindow];
  85. if ([cocoaKeyWindow class] == [GuiCocoaShell class]) {
  86. GuiShell shell = (GuiShell) [(GuiCocoaShell *) cocoaKeyWindow getUserData];
  87. if (shell -> classInfo == classGuiWindow) {
  88. GuiWindow window = (GuiWindow) shell;
  89. if (window -> d_tabCallback) {
  90. try {
  91. struct structGuiMenuItemEvent event { nullptr, false, false, false, false };
  92. window -> d_tabCallback (window -> d_tabBoss, & event);
  93. } catch (MelderError) {
  94. Melder_flushError (U"Tab key not completely handled.");
  95. }
  96. return;
  97. }
  98. } else {
  99. /*
  100. We're in a dialog. Do nothing. Send the event on.
  101. */
  102. }
  103. }
  104. } else if (character == NSBackTabCharacter) {
  105. /*
  106. * One can get here by pressing Shift-Tab.
  107. *
  108. * But that is not the only way to get here:
  109. * NSBackTabCharacter equals 25, which may be the reason why
  110. * one can get here as well by pressing Ctrl-Y (Y is the 25th letter in the alphabet).
  111. */
  112. NSWindow *cocoaKeyWindow = [NSApp keyWindow];
  113. if ([cocoaKeyWindow class] == [GuiCocoaShell class]) {
  114. GuiShell shell = (GuiShell) [(GuiCocoaShell *) cocoaKeyWindow getUserData];
  115. if (shell -> classInfo == classGuiWindow) {
  116. GuiWindow window = (GuiWindow) shell;
  117. if ([nsEvent modifierFlags] & NSShiftKeyMask) {
  118. /*
  119. Make sure we got here by Shift-Tab rather than Ctrl-Y.
  120. */
  121. if (window -> d_shiftTabCallback) {
  122. try {
  123. struct structGuiMenuItemEvent event { nullptr, false, false, false, false };
  124. window -> d_shiftTabCallback (window -> d_shiftTabBoss, & event);
  125. } catch (MelderError) {
  126. Melder_flushError (U"Tab key not completely handled.");
  127. }
  128. return;
  129. }
  130. } else {
  131. /*
  132. * We probably got in this branch by pressing Ctrl-Y.
  133. * People sometimes press that because it means "yank" (= Paste) in Emacs,
  134. * and indeed sending this key combination on, as we do here,
  135. * implements (together with Ctrl-K = "kil" = Cut)
  136. * a special cut & paste operation in text fields.
  137. */
  138. // do nothing, i.e. send on
  139. }
  140. } else {
  141. /*
  142. We're in a dialog. Do nothing. Send on.
  143. */
  144. }
  145. }
  146. } else if (character == NSDeleteCharacter) {
  147. NSWindow *cocoaKeyWindow = [NSApp keyWindow];
  148. if ([cocoaKeyWindow class] == [GuiCocoaShell class]) {
  149. GuiShell shell = (GuiShell) [(GuiCocoaShell *) cocoaKeyWindow getUserData];
  150. if (shell -> classInfo == classGuiWindow) {
  151. GuiWindow window = (GuiWindow) shell;
  152. if (([nsEvent modifierFlags] & NSAlternateKeyMask) && window -> d_optionBackspaceCallback) {
  153. try {
  154. struct structGuiMenuItemEvent event { nullptr, false, false, false, false };
  155. window -> d_optionBackspaceCallback (window -> d_optionBackspaceBoss, & event);
  156. } catch (MelderError) {
  157. Melder_flushError (U"Option-Backspace not completely handled.");
  158. }
  159. return;
  160. }
  161. } else {
  162. /*
  163. We're in a dialog. Do nothing. Send on.
  164. */
  165. }
  166. }
  167. }
  168. }
  169. [super sendEvent: nsEvent]; // the default action: send on
  170. }
  171. /*
  172. * The delegate methods.
  173. */
  174. - (void) applicationWillFinishLaunching: (NSNotification *) note
  175. {
  176. (void) note;
  177. for (int imenu = 1; imenu <= theNumberOfMenuBarItems; imenu ++) {
  178. [[NSApp mainMenu] addItem: theMenuBarItems [imenu]]; // the menu will retain the item...
  179. [theMenuBarItems [imenu] release]; // ... so we can release the item
  180. }
  181. }
  182. - (void) application: (NSApplication *) sender openFiles: (NSArray *) fileNames
  183. {
  184. (void) sender;
  185. if (praatP.userWantsToOpen) return;
  186. for (NSUInteger i = 1; i <= [fileNames count]; i ++) {
  187. NSString *cocoaFileName = [fileNames objectAtIndex: i - 1];
  188. structMelderFile file { };
  189. Melder_8bitFileRepresentationToStr32_inplace ([cocoaFileName UTF8String], file. path);
  190. if (theOpenDocumentCallback)
  191. theOpenDocumentCallback (& file);
  192. }
  193. }
  194. @end
  195. #endif
  196. void structGuiMenu :: v_hide () {
  197. #if gtk
  198. gtk_widget_hide (GTK_WIDGET (our d_gtkMenuTitle));
  199. #elif motif
  200. XtUnmanageChild (our d_xmMenuTitle);
  201. #elif cocoa
  202. [our d_cocoaMenuButton setHidden: YES];
  203. #endif
  204. }
  205. void structGuiMenu :: v_setSensitive (bool sensitive) {
  206. #if gtk
  207. gtk_widget_set_sensitive (GTK_WIDGET (our d_gtkMenuTitle), sensitive);
  208. #elif motif
  209. XtSetSensitive (our d_xmMenuTitle, sensitive);
  210. #elif cocoa
  211. [our d_cocoaMenuButton setEnabled: sensitive];
  212. #endif
  213. }
  214. void structGuiMenu :: v_show () {
  215. trace (U"begin");
  216. #if gtk
  217. gtk_widget_show (GTK_WIDGET (our d_gtkMenuTitle));
  218. #elif motif
  219. XtManageChild (our d_xmMenuTitle);
  220. #elif cocoa
  221. [our d_cocoaMenuButton setHidden: NO];
  222. #endif
  223. trace (U"end");
  224. }
  225. void GuiMenu_empty (GuiMenu me) {
  226. #if gtk
  227. trace (U"begin");
  228. Melder_assert (my d_widget);
  229. /*
  230. * Destroy my widget, but prevent forgetting me.
  231. */
  232. _GuiObject_setUserData (my d_widget, nullptr);
  233. gtk_widget_destroy (GTK_WIDGET (my d_widget));
  234. my d_widget = gtk_menu_new ();
  235. trace (U"shell ", Melder_pointer (my d_shell));
  236. Melder_assert (my d_shell);
  237. trace (U"shell class name ", Thing_className (my d_shell));
  238. Melder_assert (my d_shell -> classInfo == classGuiWindow);
  239. Melder_assert (((GuiWindow) my d_shell) -> d_gtkMenuBar);
  240. GtkAccelGroup *ag = (GtkAccelGroup *) g_object_get_data (G_OBJECT (((GuiWindow) my d_shell) -> d_gtkMenuBar), "accel-group");
  241. gtk_menu_set_accel_group (GTK_MENU (my d_widget), ag);
  242. Melder_assert (ag);
  243. gtk_menu_item_set_submenu (GTK_MENU_ITEM (my d_gtkMenuTitle), GTK_WIDGET (my d_widget));
  244. gtk_widget_show (GTK_WIDGET (my d_widget));
  245. _GuiObject_setUserData (my d_widget, me);
  246. #elif motif
  247. #elif cocoa
  248. #endif
  249. }
  250. #if cocoa
  251. @implementation GuiCocoaMenuButton {
  252. GuiMenu d_userData;
  253. }
  254. - (void) dealloc { // override
  255. GuiMenu me = d_userData;
  256. forget (me);
  257. trace (U"deleting a menu button");
  258. [super dealloc];
  259. }
  260. - (GuiThing) getUserData {
  261. return d_userData;
  262. }
  263. - (void) setUserData: (GuiThing) userData {
  264. Melder_assert (userData == nullptr || Thing_isa (userData, classGuiMenu));
  265. d_userData = static_cast <GuiMenu> (userData);
  266. }
  267. @end
  268. @implementation GuiCocoaMenu {
  269. GuiMenu d_userData;
  270. }
  271. - (void) dealloc { // override
  272. GuiMenu me = d_userData;
  273. forget (me);
  274. trace (U"deleting a menu");
  275. [super dealloc];
  276. }
  277. - (GuiThing) getUserData {
  278. return d_userData;
  279. }
  280. - (void) setUserData: (GuiThing) userData {
  281. Melder_assert (userData == nullptr || Thing_isa (userData, classGuiMenu));
  282. d_userData = static_cast <GuiMenu> (userData);
  283. }
  284. @end
  285. #endif
  286. GuiMenu GuiMenu_createInWindow (GuiWindow window, conststring32 title, uint32 flags) {
  287. autoGuiMenu me = Thing_new (GuiMenu);
  288. my d_shell = window;
  289. my d_parent = window;
  290. #if gtk
  291. Melder_assert (window);
  292. trace (U"create and show the menu title");
  293. my d_gtkMenuTitle = (GtkMenuItem *) gtk_menu_item_new_with_label (Melder_peek32to8 (title));
  294. gtk_menu_shell_append (GTK_MENU_SHELL (window -> d_gtkMenuBar), GTK_WIDGET (my d_gtkMenuTitle));
  295. if (flags & GuiMenu_INSENSITIVE)
  296. gtk_widget_set_sensitive (GTK_WIDGET (my d_gtkMenuTitle), false);
  297. gtk_widget_show (GTK_WIDGET (my d_gtkMenuTitle));
  298. trace (U"create the menu");
  299. my d_widget = gtk_menu_new ();
  300. GtkAccelGroup *ag = (GtkAccelGroup *) g_object_get_data (G_OBJECT (window -> d_gtkMenuBar), "accel-group");
  301. gtk_menu_set_accel_group (GTK_MENU (my d_widget), ag);
  302. gtk_menu_item_set_submenu (GTK_MENU_ITEM (my d_gtkMenuTitle), GTK_WIDGET (my d_widget));
  303. _GuiObject_setUserData (my d_widget, me.get());
  304. #elif motif
  305. Melder_assert (window);
  306. my d_xmMenuTitle = XmCreateCascadeButton (window -> d_xmMenuBar, Melder_peek32to8 (title), nullptr, 0);
  307. if (str32equ (title, U"Help"))
  308. XtVaSetValues (window -> d_xmMenuBar, XmNmenuHelpWidget, my d_xmMenuTitle, nullptr);
  309. my d_widget = XmCreatePulldownMenu (window -> d_xmMenuBar, Melder_peek32to8 (title), nullptr, 0);
  310. if (flags & GuiMenu_INSENSITIVE)
  311. XtSetSensitive (my d_xmMenuTitle, False);
  312. XtVaSetValues (my d_xmMenuTitle, XmNsubMenuId, my d_widget, nullptr);
  313. XtManageChild (my d_xmMenuTitle);
  314. _GuiObject_setUserData (my d_widget, me.get());
  315. #elif cocoa
  316. if (! theMenuBar) {
  317. int numberOfMenus = [[[NSApp mainMenu] itemArray] count];
  318. trace (U"Number of menus: ", numberOfMenus);
  319. [NSApp setDelegate: NSApp]; // the app is its own delegate
  320. theMenuBar = [[NSMenu alloc] init];
  321. [NSApp setMainMenu: theMenuBar];
  322. }
  323. my d_cocoaMenu = [[GuiCocoaMenu alloc]
  324. initWithTitle: (NSString *) Melder_peek32toCfstring (title)];
  325. my d_widget = my d_cocoaMenu;
  326. [my d_cocoaMenu setUserData: me.get()];
  327. [my d_cocoaMenu setAutoenablesItems: NO];
  328. if (! window) {
  329. /*
  330. * Install the menu in the main OS X menu bar along the top of the screen.
  331. * This is done by creating a menu item for the main menu bar,
  332. * and during applicationWillFinishLaunching installing that item.
  333. */
  334. NSString *itemTitle = (NSString *) Melder_peek32toCfstring (title);
  335. my d_cocoaMenuItem = [[GuiCocoaMenuItem alloc]
  336. initWithTitle: itemTitle action: nullptr keyEquivalent: @""];
  337. [my d_cocoaMenuItem setSubmenu: my d_cocoaMenu]; // the item will retain the menu...
  338. [my d_cocoaMenu release]; // ... so we can release the menu already (before even returning it!)
  339. theMenuBarItems [++ theNumberOfMenuBarItems] = my d_cocoaMenuItem;
  340. } else if ([(NSView *) window -> d_widget isKindOfClass: [NSView class]]) {
  341. /*
  342. * Install the menu at the top of a window.
  343. * Menu title positioning information is maintained in that GuiWindow.
  344. */
  345. NSRect parentRect = [(NSView *) window -> d_widget frame]; // this is the window's top form
  346. int parentWidth = parentRect.size.width, parentHeight = parentRect.size.height;
  347. if (window -> d_menuBarWidth == 0)
  348. window -> d_menuBarWidth = -1;
  349. int width = 18 + 7 * str32len (title), height = 35 /*25*/;
  350. int x = window -> d_menuBarWidth, y = parentHeight + 1 - height;
  351. NSUInteger resizingMask = NSViewMinYMargin;
  352. if (Melder_equ (title, U"Help")) {
  353. x = parentWidth + 1 - width;
  354. resizingMask |= NSViewMinXMargin;
  355. } else {
  356. window -> d_menuBarWidth += width - 1;
  357. }
  358. NSRect rect = { { (CGFloat) x, (CGFloat) y }, { (CGFloat) width, (CGFloat) height } };
  359. my d_cocoaMenuButton = [[GuiCocoaMenuButton alloc]
  360. initWithFrame: rect pullsDown: YES];
  361. [my d_cocoaMenuButton setAutoenablesItems: NO];
  362. [my d_cocoaMenuButton setBezelStyle: NSShadowlessSquareBezelStyle];
  363. [my d_cocoaMenuButton setImagePosition: NSImageAbove]; // this centers the text
  364. //[nsPopupButton setBordered: NO];
  365. [my d_cocoaMenuButton setAutoresizingMask: resizingMask]; // stick to top
  366. if (flags & GuiMenu_INSENSITIVE)
  367. [my d_cocoaMenuButton setEnabled: NO];
  368. [[my d_cocoaMenuButton cell] setArrowPosition: NSPopUpNoArrow /*NSPopUpArrowAtBottom*/];
  369. [[my d_cocoaMenuButton cell] setPreferredEdge: NSMaxYEdge];
  370. /*
  371. * Apparently, Cocoa swallows title setting only if there is already a menu with a dummy item.
  372. */
  373. GuiCocoaMenuItem *item = [[GuiCocoaMenuItem alloc] initWithTitle: @"-you should never get to see this-" action: nullptr keyEquivalent: @""];
  374. [my d_cocoaMenu addItem: item]; // the menu will retain the item...
  375. [item release]; // ... so we can release the item already
  376. /*
  377. * Install the menu button in the form.
  378. */
  379. [(NSView *) window -> d_widget addSubview: my d_cocoaMenuButton]; // parent will retain the button...
  380. [my d_cocoaMenuButton release]; // ... so we can release the button already
  381. /*
  382. * Attach the menu to the button.
  383. */
  384. [my d_cocoaMenuButton setMenu: my d_cocoaMenu]; // the button will retain the menu...
  385. [my d_cocoaMenu release]; // ... so we can release the menu already (before even returning it!)
  386. [my d_cocoaMenuButton setTitle: (NSString *) Melder_peek32toCfstring (title)];
  387. }
  388. #endif
  389. #if gtk
  390. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_guiGtkMenu_destroyCallback), me.get());
  391. #elif motif
  392. XtAddCallback (my d_widget, XmNdestroyCallback, _guiMotifMenu_destroyCallback, me.get());
  393. #elif cocoa
  394. #endif
  395. return me.releaseToAmbiguousOwner();
  396. }
  397. GuiMenu GuiMenu_createInMenu (GuiMenu supermenu, conststring32 title, uint32 flags) {
  398. autoGuiMenu me = Thing_new (GuiMenu);
  399. my d_shell = supermenu -> d_shell;
  400. my d_parent = supermenu;
  401. my d_menuItem = Thing_new (GuiMenuItem);
  402. my d_menuItem -> d_shell = my d_shell;
  403. my d_menuItem -> d_parent = supermenu;
  404. my d_menuItem -> d_menu = me.get();
  405. #if gtk
  406. my d_menuItem -> d_widget = gtk_menu_item_new_with_label (Melder_peek32to8 (title));
  407. my d_widget = gtk_menu_new ();
  408. GtkAccelGroup *ag = (GtkAccelGroup *) gtk_menu_get_accel_group (GTK_MENU (supermenu -> d_widget));
  409. gtk_menu_set_accel_group (GTK_MENU (my d_widget), ag);
  410. if (flags & GuiMenu_INSENSITIVE)
  411. gtk_widget_set_sensitive (GTK_WIDGET (my d_widget), false);
  412. gtk_menu_item_set_submenu (GTK_MENU_ITEM (my d_menuItem -> d_widget), GTK_WIDGET (my d_widget));
  413. gtk_menu_shell_append (GTK_MENU_SHELL (supermenu -> d_widget), GTK_WIDGET (my d_menuItem -> d_widget));
  414. gtk_widget_show (GTK_WIDGET (my d_widget));
  415. gtk_widget_show (GTK_WIDGET (my d_menuItem -> d_widget));
  416. _GuiObject_setUserData (my d_widget, me.get());
  417. #elif motif
  418. my d_menuItem -> d_widget = XmCreateCascadeButton (supermenu -> d_widget, Melder_peek32to8 (title), nullptr, 0);
  419. my d_widget = XmCreatePulldownMenu (supermenu -> d_widget, Melder_peek32to8 (title), nullptr, 0);
  420. if (flags & GuiMenu_INSENSITIVE)
  421. XtSetSensitive (my d_menuItem -> d_widget, False);
  422. XtVaSetValues (my d_menuItem -> d_widget, XmNsubMenuId, my d_widget, nullptr);
  423. XtManageChild (my d_menuItem -> d_widget);
  424. _GuiObject_setUserData (my d_widget, me.get());
  425. #elif cocoa
  426. trace (U"creating menu item ", title);
  427. NSMenuItem *item = [[NSMenuItem alloc]
  428. initWithTitle: (NSString *) Melder_peek32toCfstring (title)
  429. action: nullptr
  430. keyEquivalent: @""];
  431. trace (U"adding the item to its supermenu ", Melder_pointer (supermenu));
  432. [supermenu -> d_cocoaMenu addItem: item]; // the menu will retain the item...
  433. trace (U"release the item");
  434. [item release]; // ... so we can release the item already
  435. trace (U"creating menu ", title);
  436. my d_cocoaMenu = [[GuiCocoaMenu alloc]
  437. initWithTitle: (NSString *) Melder_peek32toCfstring (title)];
  438. [my d_cocoaMenu setUserData: me.get()];
  439. [my d_cocoaMenu setAutoenablesItems: NO];
  440. trace (U"adding the new menu ", Melder_pointer (me.get()), U" to its supermenu ", Melder_pointer (supermenu));
  441. [supermenu -> d_cocoaMenu setSubmenu: my d_cocoaMenu forItem: item]; // the supermenu will retain the menu...
  442. Melder_assert ([my d_cocoaMenu retainCount] == 2);
  443. [my d_cocoaMenu release]; // ... so we can release the menu already, even before returning it
  444. my d_widget = my d_cocoaMenu;
  445. my d_menuItem -> d_widget = (GuiObject) item;
  446. #endif
  447. #if gtk
  448. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_guiGtkMenu_destroyCallback), me.get());
  449. #elif motif
  450. XtAddCallback (my d_widget, XmNdestroyCallback, _guiMotifMenu_destroyCallback, me.get());
  451. #elif cocoa
  452. #endif
  453. return me.releaseToAmbiguousOwner();
  454. }
  455. #if gtk
  456. static void set_position (GtkMenu *menu, gint *px, gint *py, gpointer data)
  457. {
  458. gint w, h;
  459. GtkWidget *button = (GtkWidget *) g_object_get_data (G_OBJECT (menu), "button");
  460. if (GTK_WIDGET (menu) -> requisition. width < button->allocation.width)
  461. gtk_widget_set_size_request (GTK_WIDGET (menu), button->allocation.width, -1);
  462. gdk_window_get_origin (button->window, px, py);
  463. *px += button->allocation.x;
  464. *py += button->allocation.y + button->allocation.height; /* Dit is vreemd */
  465. }
  466. static gint button_press (GtkWidget *widget, GdkEvent *event)
  467. {
  468. gint w, h;
  469. GtkWidget *button = (GtkWidget *) g_object_get_data (G_OBJECT (widget), "button");
  470. /* gdk_window_get_size (button->window, &w, &h);
  471. gtk_widget_set_usize (widget, w, 0);*/
  472. if (event->type == GDK_BUTTON_PRESS) {
  473. GdkEventButton *bevent = (GdkEventButton *) event;
  474. gtk_menu_popup (GTK_MENU (widget), nullptr, nullptr, (GtkMenuPositionFunc) set_position, nullptr, bevent->button, bevent->time);
  475. return true;
  476. }
  477. return false;
  478. }
  479. #endif
  480. GuiMenu GuiMenu_createInForm (GuiForm form, int left, int right, int top, int bottom, conststring32 title, uint32 flags) {
  481. autoGuiMenu me = Thing_new (GuiMenu);
  482. my d_shell = form -> d_shell;
  483. my d_parent = form;
  484. my d_cascadeButton = Thing_new (GuiButton);
  485. my d_cascadeButton -> d_shell = my d_shell;
  486. my d_cascadeButton -> d_parent = form;
  487. my d_cascadeButton -> d_menu = me.get();
  488. #if gtk
  489. my d_cascadeButton -> d_widget = gtk_button_new_with_label (Melder_peek32to8 (title));
  490. my d_cascadeButton -> v_positionInForm (my d_cascadeButton -> d_widget, left, right, top, bottom, form);
  491. gtk_widget_show (GTK_WIDGET (my d_cascadeButton -> d_widget));
  492. my d_widget = gtk_menu_new ();
  493. if (flags & GuiMenu_INSENSITIVE)
  494. gtk_widget_set_sensitive (GTK_WIDGET (my d_widget), false);
  495. g_signal_connect_object (G_OBJECT (my d_cascadeButton -> d_widget), "event",
  496. GTK_SIGNAL_FUNC (button_press), G_OBJECT (my d_widget), G_CONNECT_SWAPPED);
  497. g_object_set_data (G_OBJECT (my d_widget), "button", my d_cascadeButton -> d_widget);
  498. gtk_menu_attach_to_widget (GTK_MENU (my d_widget), GTK_WIDGET (my d_cascadeButton -> d_widget), nullptr);
  499. gtk_button_set_alignment (GTK_BUTTON (my d_cascadeButton -> d_widget), 0.0f, 0.5f);
  500. _GuiObject_setUserData (my d_widget, me.get());
  501. _GuiObject_setUserData (my d_cascadeButton -> d_widget, me.get());
  502. #elif motif
  503. my d_xmMenuBar = XmCreateMenuBar (form -> d_widget, "dynamicSubmenuBar", 0, 0);
  504. form -> v_positionInForm (my d_xmMenuBar, left, right, top, bottom, form);
  505. my d_cascadeButton -> d_widget = XmCreateCascadeButton (my d_xmMenuBar, Melder_peek32to8 (title), nullptr, 0);
  506. form -> v_positionInForm (my d_cascadeButton -> d_widget, 0, right - left - 4, 0, bottom - top, form);
  507. my d_widget = XmCreatePulldownMenu (my d_xmMenuBar, Melder_peek32to8 (title), nullptr, 0);
  508. if (flags & GuiMenu_INSENSITIVE)
  509. XtSetSensitive (my d_cascadeButton -> d_widget, False);
  510. XtVaSetValues (my d_cascadeButton -> d_widget, XmNsubMenuId, my d_widget, nullptr);
  511. XtManageChild (my d_cascadeButton -> d_widget);
  512. XtManageChild (my d_xmMenuBar);
  513. _GuiObject_setUserData (my d_widget, me.get());
  514. #elif cocoa
  515. my d_cascadeButton -> d_widget = my d_cocoaMenuButton = [[GuiCocoaMenuButton alloc] init];
  516. my d_cascadeButton -> v_positionInForm (my d_cocoaMenuButton, left, right, top, bottom, form);
  517. [my d_cocoaMenuButton setUserData: me.get()];
  518. [my d_cocoaMenuButton setPullsDown: YES];
  519. [my d_cocoaMenuButton setAutoenablesItems: NO];
  520. [my d_cocoaMenuButton setBezelStyle: NSShadowlessSquareBezelStyle];
  521. [my d_cocoaMenuButton setImagePosition: NSImageAbove]; // this centers the text
  522. [[my d_cocoaMenuButton cell] setArrowPosition: NSPopUpNoArrow /*NSPopUpArrowAtBottom*/];
  523. NSString *menuTitle = (NSString*) Melder_peek32toCfstring (title);
  524. my d_widget = my d_cocoaMenu = [[GuiCocoaMenu alloc] initWithTitle:menuTitle];
  525. [my d_cocoaMenu setAutoenablesItems: NO];
  526. /*
  527. * Apparently, Cocoa swallows title setting only if there is already a menu with a dummy item.
  528. */
  529. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle: @"-you should never get to see this-" action: nullptr keyEquivalent: @""];
  530. [my d_cocoaMenu addItem: item]; // the menu will retain the item...
  531. [item release]; // ... so we can release the item already
  532. /*
  533. * Attach the menu to the button.
  534. */
  535. [my d_cocoaMenuButton setMenu: my d_cocoaMenu]; // the button will retain the menu...
  536. [my d_cocoaMenu release]; // ... so we can release the menu already (before even returning it!)
  537. [my d_cocoaMenuButton setTitle: (NSString *) Melder_peek32toCfstring (title)];
  538. #endif
  539. #if gtk
  540. g_signal_connect (G_OBJECT (my d_widget), "destroy", G_CALLBACK (_guiGtkMenu_destroyCallback), me.get());
  541. g_signal_connect (G_OBJECT (my d_cascadeButton -> d_widget), "destroy", G_CALLBACK (_guiGtkMenuCascadeButton_destroyCallback), me.get());
  542. #elif motif
  543. XtAddCallback (my d_widget, XmNdestroyCallback, _guiMotifMenu_destroyCallback, me.get());
  544. #elif cocoa
  545. #endif
  546. return me.releaseToAmbiguousOwner();
  547. };
  548. /* End of file GuiMenu.cpp */