1/* GTK - The GIMP Toolkit
2 * Copyright © 2014 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19#include "gtkpopovermenu.h"
20#include "gtkpopovermenuprivate.h"
21
22#include "gtkstack.h"
23#include "gtkintl.h"
24#include "gtkmenusectionboxprivate.h"
25#include "gtkmenubutton.h"
26#include "gtkactionmuxerprivate.h"
27#include "gtkmenutrackerprivate.h"
28#include "gtkpopoverprivate.h"
29#include "gtkwidgetprivate.h"
30#include "gtkeventcontrollerfocus.h"
31#include "gtkeventcontrollermotion.h"
32#include "gtkmain.h"
33#include "gtktypebuiltins.h"
34#include "gtkmodelbuttonprivate.h"
35#include "gtkpopovermenubar.h"
36#include "gtkshortcutmanager.h"
37#include "gtkshortcutcontroller.h"
38#include "gtkbuildable.h"
39#include "gtkscrolledwindow.h"
40#include "gtkviewport.h"
41
42
43/**
44 * GtkPopoverMenu:
45 *
46 * `GtkPopoverMenu` is a subclass of `GtkPopover` that implements menu
47 * behavior.
48 *
49 * ![An example GtkPopoverMenu](menu.png)
50 *
51 * `GtkPopoverMenu` treats its children like menus and allows switching
52 * between them. It can open submenus as traditional, nested submenus,
53 * or in a more touch-friendly sliding fashion.
54 *
55 * `GtkPopoverMenu` is meant to be used primarily with menu models,
56 * using [ctor@Gtk.PopoverMenu.new_from_model]. If you need to put
57 * other widgets such as a `GtkSpinButton` or a `GtkSwitch` into a popover,
58 * you can use [method@Gtk.PopoverMenu.add_child].
59 *
60 * For more dialog-like behavior, use a plain `GtkPopover`.
61 *
62 * ## Menu models
63 *
64 * The XML format understood by `GtkBuilder` for `GMenuModel` consists
65 * of a toplevel `<menu>` element, which contains one or more `<item>`
66 * elements. Each `<item>` element contains `<attribute>` and `<link>`
67 * elements with a mandatory name attribute. `<link>` elements have the
68 * same content model as `<menu>`. Instead of `<link name="submenu">`
69 * or `<link name="section">`, you can use `<submenu>` or `<section>`
70 * elements.
71 *
72 * ```xml
73 * <menu id='app-menu'>
74 * <section>
75 * <item>
76 * <attribute name='label' translatable='yes'>_New Window</attribute>
77 * <attribute name='action'>app.new</attribute>
78 * </item>
79 * <item>
80 * <attribute name='label' translatable='yes'>_About Sunny</attribute>
81 * <attribute name='action'>app.about</attribute>
82 * </item>
83 * <item>
84 * <attribute name='label' translatable='yes'>_Quit</attribute>
85 * <attribute name='action'>app.quit</attribute>
86 * </item>
87 * </section>
88 * </menu>
89 * ```
90 *
91 * Attribute values can be translated using gettext, like other `GtkBuilder`
92 * content. `<attribute>` elements can be marked for translation with a
93 * `translatable="yes"` attribute. It is also possible to specify message
94 * context and translator comments, using the context and comments attributes.
95 * To make use of this, the `GtkBuilder` must have been given the gettext
96 * domain to use.
97 *
98 * The following attributes are used when constructing menu items:
99 *
100 * - "label": a user-visible string to display
101 * - "use-markup": whether the text in the menu item includes [Pango markup](https://docs.gtk.org/Pango/pango_markup.html)
102 * - "action": the prefixed name of the action to trigger
103 * - "target": the parameter to use when activating the action
104 * - "icon" and "verb-icon": names of icons that may be displayed
105 * - "submenu-action": name of an action that may be used to track
106 * whether a submenu is open
107 * - "hidden-when": a string used to determine when the item will be hidden.
108 * Possible values include "action-disabled", "action-missing", "macos-menubar".
109 * This is mainly useful for exported menus, see [method@Gtk.Application.set_menubar].
110 * - "custom": a string used to match against the ID of a custom child added with
111 * [method@Gtk.PopoverMenu.add_child], [method@Gtk.PopoverMenuBar.add_child],
112 * or in the ui file with `<child type="ID">`.
113 *
114 * The following attributes are used when constructing sections:
115 *
116 * - "label": a user-visible string to use as section heading
117 * - "display-hint": a string used to determine special formatting for the section.
118 * Possible values include "horizontal-buttons", "circular-buttons" and
119 * "inline-buttons". They all indicate that section should be
120 * displayed as a horizontal row of buttons.
121 * - "text-direction": a string used to determine the `GtkTextDirection` to use
122 * when "display-hint" is set to "horizontal-buttons". Possible values
123 * include "rtl", "ltr", and "none".
124 *
125 * The following attributes are used when constructing submenus:
126 *
127 * - "label": a user-visible string to display
128 * - "icon": icon name to display
129 *
130 * Menu items will also show accelerators, which are usually associated
131 * with actions via [method@Gtk.Application.set_accels_for_action],
132 * [id@gtk_widget_class_add_binding_action] or
133 * [method@Gtk.ShortcutController.add_shortcut].
134 *
135 * # CSS Nodes
136 *
137 * `GtkPopoverMenu` is just a subclass of `GtkPopover` that adds custom content
138 * to it, therefore it has the same CSS nodes. It is one of the cases that add
139 * a .menu style class to the popover's main node.
140 *
141 * # Accessibility
142 *
143 * `GtkPopoverMenu` uses the %GTK_ACCESSIBLE_ROLE_MENU role, and its
144 * items use the %GTK_ACCESSIBLE_ROLE_MENU_ITEM,
145 * %GTK_ACCESSIBLE_ROLE_MENU_ITEM_CHECKBOX or
146 * %GTK_ACCESSIBLE_ROLE_MENU_ITEM_RADIO roles, depending on the
147 * action they are connected to.
148 */
149
150typedef struct _GtkPopoverMenuClass GtkPopoverMenuClass;
151
152struct _GtkPopoverMenu
153{
154 GtkPopover parent_instance;
155
156 GtkWidget *active_item;
157 GtkWidget *open_submenu;
158 GtkWidget *parent_menu;
159 GMenuModel *model;
160 GtkPopoverMenuFlags flags;
161};
162
163struct _GtkPopoverMenuClass
164{
165 GtkPopoverClass parent_class;
166};
167
168enum {
169 PROP_VISIBLE_SUBMENU = 1,
170 PROP_MENU_MODEL
171};
172
173static void gtk_popover_menu_buildable_iface_init (GtkBuildableIface *iface);
174
175G_DEFINE_TYPE_WITH_CODE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER,
176 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
177 gtk_popover_menu_buildable_iface_init))
178
179GtkWidget *
180gtk_popover_menu_get_parent_menu (GtkPopoverMenu *menu)
181{
182 return menu->parent_menu;
183}
184
185void
186gtk_popover_menu_set_parent_menu (GtkPopoverMenu *menu,
187 GtkWidget *parent)
188{
189 menu->parent_menu = parent;
190}
191
192GtkWidget *
193gtk_popover_menu_get_open_submenu (GtkPopoverMenu *menu)
194{
195 return menu->open_submenu;
196}
197
198void
199gtk_popover_menu_set_open_submenu (GtkPopoverMenu *menu,
200 GtkWidget *submenu)
201{
202 menu->open_submenu = submenu;
203}
204
205void
206gtk_popover_menu_close_submenus (GtkPopoverMenu *menu)
207{
208 GtkWidget *submenu;
209
210 submenu = menu->open_submenu;
211 if (submenu)
212 {
213 gtk_popover_menu_close_submenus (GTK_POPOVER_MENU (submenu));
214 gtk_widget_hide (widget: submenu);
215 gtk_popover_menu_set_open_submenu (menu, NULL);
216 }
217}
218
219GtkWidget *
220gtk_popover_menu_get_active_item (GtkPopoverMenu *menu)
221{
222 return menu->active_item;
223}
224
225void
226gtk_popover_menu_set_active_item (GtkPopoverMenu *menu,
227 GtkWidget *item)
228{
229 if (menu->active_item != item)
230 {
231 if (menu->active_item)
232 {
233 gtk_widget_unset_state_flags (widget: menu->active_item, flags: GTK_STATE_FLAG_SELECTED);
234 g_object_remove_weak_pointer (G_OBJECT (menu->active_item), weak_pointer_location: (gpointer *)&menu->active_item);
235 }
236
237 menu->active_item = item;
238
239 if (menu->active_item)
240 {
241 GtkWidget *popover;
242
243 g_object_add_weak_pointer (G_OBJECT (menu->active_item), weak_pointer_location: (gpointer *)&menu->active_item);
244
245 gtk_widget_set_state_flags (widget: menu->active_item, flags: GTK_STATE_FLAG_SELECTED, FALSE);
246 if (GTK_IS_MODEL_BUTTON (item))
247 g_object_get (object: item, first_property_name: "popover", &popover, NULL);
248 else
249 popover = NULL;
250
251 if (!popover || popover != menu->open_submenu)
252 gtk_widget_grab_focus (widget: menu->active_item);
253
254 g_clear_object (&popover);
255 }
256 }
257}
258
259static void
260visible_submenu_changed (GObject *object,
261 GParamSpec *pspec,
262 GtkPopoverMenu *popover)
263{
264 g_object_notify (G_OBJECT (popover), property_name: "visible-submenu");
265}
266
267static void
268focus_out (GtkEventController *controller,
269 GtkPopoverMenu *menu)
270{
271 GtkRoot *root;
272 GtkWidget *new_focus;
273
274 root = gtk_widget_get_root (GTK_WIDGET (menu));
275 if (!root)
276 return;
277
278 new_focus = gtk_root_get_focus (self: root);
279
280 if (!gtk_event_controller_focus_contains_focus (GTK_EVENT_CONTROLLER_FOCUS (controller)) &&
281 new_focus != NULL)
282 {
283 if (menu->parent_menu &&
284 GTK_POPOVER_MENU (menu->parent_menu)->open_submenu == (GtkWidget*) menu)
285 GTK_POPOVER_MENU (menu->parent_menu)->open_submenu = NULL;
286 gtk_popover_popdown (GTK_POPOVER (menu));
287 }
288}
289
290static void
291leave_cb (GtkEventController *controller,
292 gpointer data)
293{
294 if (!gtk_event_controller_motion_contains_pointer (GTK_EVENT_CONTROLLER_MOTION (controller)))
295 {
296 GtkWidget *target = gtk_event_controller_get_widget (controller);
297
298 gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (target), NULL);
299 }
300}
301
302static void
303gtk_popover_menu_init (GtkPopoverMenu *popover)
304{
305 GtkWidget *sw;
306 GtkWidget *stack;
307 GtkEventController *controller;
308 GtkEventController **controllers;
309 guint n_controllers, i;
310
311 sw = gtk_scrolled_window_new ();
312 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), hscrollbar_policy: GTK_POLICY_NEVER, vscrollbar_policy: GTK_POLICY_AUTOMATIC);
313 gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE);
314 gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
315 gtk_popover_set_child (GTK_POPOVER (popover), child: sw);
316
317 stack = gtk_stack_new ();
318 gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
319 gtk_stack_set_transition_type (GTK_STACK (stack), transition: GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
320 gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
321 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: stack);
322 g_signal_connect (stack, "notify::visible-child-name",
323 G_CALLBACK (visible_submenu_changed), popover);
324
325 gtk_widget_add_css_class (GTK_WIDGET (popover), css_class: "menu");
326
327 controller = gtk_event_controller_focus_new ();
328 g_signal_connect (controller, "leave", G_CALLBACK (focus_out), popover);
329 gtk_widget_add_controller (GTK_WIDGET (popover), controller);
330
331 controller = gtk_event_controller_motion_new ();
332 g_signal_connect (controller, "notify::contains-pointer", G_CALLBACK (leave_cb), popover);
333 gtk_widget_add_controller (GTK_WIDGET (popover), controller);
334
335 controllers = gtk_widget_list_controllers (GTK_WIDGET (popover), phase: GTK_PHASE_CAPTURE, out_n_controllers: &n_controllers);
336 for (i = 0; i < n_controllers; i ++)
337 {
338 controller = controllers[i];
339 if (GTK_IS_SHORTCUT_CONTROLLER (controller) &&
340 strcmp (s1: gtk_event_controller_get_name (controller), s2: "gtk-shortcut-manager-capture") == 0)
341 gtk_shortcut_controller_set_mnemonics_modifiers (GTK_SHORTCUT_CONTROLLER (controller), modifiers: 0);
342 }
343 g_free (mem: controllers);
344
345 gtk_popover_disable_auto_mnemonics (GTK_POPOVER (popover));
346 gtk_popover_set_cascade_popdown (GTK_POPOVER (popover), TRUE);
347}
348
349GtkWidget *
350gtk_popover_menu_get_stack (GtkPopoverMenu *menu)
351{
352 GtkWidget *sw = gtk_popover_get_child (GTK_POPOVER (menu));
353 GtkWidget *vp = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (sw));
354 GtkWidget *stack = gtk_viewport_get_child (GTK_VIEWPORT (vp));
355
356 return stack;
357}
358
359static void
360gtk_popover_menu_dispose (GObject *object)
361{
362 GtkPopoverMenu *popover = GTK_POPOVER_MENU (object);
363
364 if (popover->active_item)
365 {
366 g_object_remove_weak_pointer (G_OBJECT (popover->active_item), weak_pointer_location: (gpointer *)&popover->active_item);
367 popover->active_item = NULL;
368 }
369
370 g_clear_object (&popover->model);
371
372 G_OBJECT_CLASS (gtk_popover_menu_parent_class)->dispose (object);
373}
374
375static void
376gtk_popover_menu_map (GtkWidget *widget)
377{
378 gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), name: "main");
379 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->map (widget);
380}
381
382static void
383gtk_popover_menu_unmap (GtkWidget *widget)
384{
385 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->unmap (widget);
386 gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), name: "main");
387}
388
389static void
390gtk_popover_menu_get_property (GObject *object,
391 guint property_id,
392 GValue *value,
393 GParamSpec *pspec)
394{
395 GtkPopoverMenu *menu = GTK_POPOVER_MENU (object);
396
397 switch (property_id)
398 {
399 case PROP_VISIBLE_SUBMENU:
400 g_value_set_string (value, v_string: gtk_stack_get_visible_child_name (GTK_STACK (gtk_popover_menu_get_stack (menu))));
401 break;
402
403 case PROP_MENU_MODEL:
404 g_value_set_object (value, v_object: gtk_popover_menu_get_menu_model (popover: menu));
405 break;
406
407 default:
408 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
409 break;
410 }
411}
412
413static void
414gtk_popover_menu_set_property (GObject *object,
415 guint property_id,
416 const GValue *value,
417 GParamSpec *pspec)
418{
419 GtkPopoverMenu *menu = GTK_POPOVER_MENU (object);
420
421 switch (property_id)
422 {
423 case PROP_VISIBLE_SUBMENU:
424 gtk_stack_set_visible_child_name (GTK_STACK (gtk_popover_menu_get_stack (menu)), name: g_value_get_string (value));
425 break;
426
427 case PROP_MENU_MODEL:
428 gtk_popover_menu_set_menu_model (popover: menu, model: g_value_get_object (value));
429 break;
430
431 default:
432 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
433 break;
434 }
435}
436
437static gboolean
438gtk_popover_menu_focus (GtkWidget *widget,
439 GtkDirectionType direction)
440{
441 GtkPopoverMenu *menu = GTK_POPOVER_MENU (widget);
442
443 if (gtk_widget_get_first_child (widget) == NULL)
444 {
445 return FALSE;
446 }
447 else
448 {
449 if (menu->open_submenu)
450 {
451 if (gtk_widget_child_focus (widget: menu->open_submenu, direction))
452 return TRUE;
453 if (direction == GTK_DIR_LEFT)
454 {
455 if (menu->open_submenu)
456 {
457 gtk_popover_popdown (GTK_POPOVER (menu->open_submenu));
458 menu->open_submenu = NULL;
459 }
460
461 gtk_widget_grab_focus (widget: menu->active_item);
462
463 return TRUE;
464 }
465 return FALSE;
466 }
467
468 if (gtk_widget_focus_move (widget, direction))
469 return TRUE;
470
471 if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
472 {
473 /* If we are part of a menubar, we want to let the
474 * menubar use left/right arrows for cycling, else
475 * we eat them.
476 */
477 if (gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER_MENU_BAR) ||
478 (gtk_popover_menu_get_parent_menu (menu) &&
479 direction == GTK_DIR_LEFT))
480 return FALSE;
481 else
482 return TRUE;
483 }
484 /* Cycle around with up/down arrows and (Shift+)Tab when modal */
485 else if (gtk_popover_get_autohide (GTK_POPOVER (menu)))
486 {
487 GtkWidget *p = gtk_root_get_focus (self: gtk_widget_get_root (widget));
488
489 /* In the case where the popover doesn't have any focusable child, if
490 * the menu doesn't have any item for example, then the focus will end
491 * up out of the popover, hence creating an infinite loop below. To
492 * avoid this, just say we had focus and stop here.
493 */
494 if (!gtk_widget_is_ancestor (widget: p, ancestor: widget) && p != widget)
495 return TRUE;
496
497 /* cycle around */
498 for (;
499 p != widget;
500 p = gtk_widget_get_parent (widget: p))
501 {
502 gtk_widget_set_focus_child (widget: p, NULL);
503 }
504 if (gtk_widget_focus_move (widget, direction))
505 return TRUE;
506 }
507 }
508
509 return FALSE;
510}
511
512
513static void
514add_tab_bindings (GtkWidgetClass *widget_class,
515 GdkModifierType modifiers,
516 GtkDirectionType direction)
517{
518 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Tab, mods: modifiers,
519 signal: "move-focus",
520 format_string: "(i)", direction);
521 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Tab, mods: modifiers,
522 signal: "move-focus",
523 format_string: "(i)", direction);
524}
525
526static void
527add_arrow_bindings (GtkWidgetClass *widget_class,
528 guint keysym,
529 GtkDirectionType direction)
530{
531 guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
532
533 gtk_widget_class_add_binding_signal (widget_class, keyval: keysym, mods: 0,
534 signal: "move-focus",
535 format_string: "(i)", direction);
536 gtk_widget_class_add_binding_signal (widget_class, keyval: keysym, mods: GDK_CONTROL_MASK,
537 signal: "move-focus",
538 format_string: "(i)", direction);
539 gtk_widget_class_add_binding_signal (widget_class, keyval: keypad_keysym, mods: 0,
540 signal: "move-focus",
541 format_string: "(i)", direction);
542 gtk_widget_class_add_binding_signal (widget_class, keyval: keypad_keysym, mods: GDK_CONTROL_MASK,
543 signal: "move-focus",
544 format_string: "(i)", direction);
545}
546
547static void
548gtk_popover_menu_show (GtkWidget *widget)
549{
550 gtk_popover_menu_set_open_submenu (GTK_POPOVER_MENU (widget), NULL);
551
552 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->show (widget);
553}
554
555static void
556gtk_popover_menu_move_focus (GtkWidget *widget,
557 GtkDirectionType direction)
558{
559 gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), TRUE);
560
561 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->move_focus (widget, direction);
562}
563
564static void
565gtk_popover_menu_root (GtkWidget *widget)
566{
567 GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->root (widget);
568
569 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: widget),
570 first_property: GTK_ACCESSIBLE_PROPERTY_ORIENTATION, GTK_ORIENTATION_VERTICAL,
571 -1);
572}
573
574static void
575gtk_popover_menu_class_init (GtkPopoverMenuClass *klass)
576{
577 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
578 GObjectClass *object_class = G_OBJECT_CLASS (klass);
579
580 object_class->dispose = gtk_popover_menu_dispose;
581 object_class->set_property = gtk_popover_menu_set_property;
582 object_class->get_property = gtk_popover_menu_get_property;
583
584 widget_class->root = gtk_popover_menu_root;
585 widget_class->map = gtk_popover_menu_map;
586 widget_class->unmap = gtk_popover_menu_unmap;
587 widget_class->focus = gtk_popover_menu_focus;
588 widget_class->show = gtk_popover_menu_show;
589 widget_class->move_focus = gtk_popover_menu_move_focus;
590
591 /**
592 * GtkPopoverMenu:visible-submenu:
593 *
594 * The name of the visible submenu.
595 */
596 g_object_class_install_property (oclass: object_class,
597 property_id: PROP_VISIBLE_SUBMENU,
598 pspec: g_param_spec_string (name: "visible-submenu",
599 P_("Visible submenu"),
600 P_("The name of the visible submenu"),
601 NULL,
602 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
603
604 /**
605 * GtkPopoverMenu:menu-model: (attributes org.gtk.Property.get=gtk_popover_menu_get_menu_model org.gtk.Property.set=gtk_popover_menu_set_menu_model)
606 *
607 * The model from which the menu is made.
608 */
609 g_object_class_install_property (oclass: object_class,
610 property_id: PROP_MENU_MODEL,
611 pspec: g_param_spec_object (name: "menu-model",
612 P_("Menu model"),
613 P_("The model from which the menu is made."),
614 G_TYPE_MENU_MODEL,
615 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
616
617 add_arrow_bindings (widget_class, GDK_KEY_Up, direction: GTK_DIR_UP);
618 add_arrow_bindings (widget_class, GDK_KEY_Down, direction: GTK_DIR_DOWN);
619 add_arrow_bindings (widget_class, GDK_KEY_Left, direction: GTK_DIR_LEFT);
620 add_arrow_bindings (widget_class, GDK_KEY_Right, direction: GTK_DIR_RIGHT);
621
622 add_tab_bindings (widget_class, modifiers: 0, direction: GTK_DIR_TAB_FORWARD);
623 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK, direction: GTK_DIR_TAB_FORWARD);
624 add_tab_bindings (widget_class, modifiers: GDK_SHIFT_MASK, direction: GTK_DIR_TAB_BACKWARD);
625 add_tab_bindings (widget_class, modifiers: GDK_CONTROL_MASK | GDK_SHIFT_MASK, direction: GTK_DIR_TAB_BACKWARD);
626
627 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Return, mods: 0,
628 signal: "activate-default", NULL);
629 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_ISO_Enter, mods: 0,
630 signal: "activate-default", NULL);
631 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Enter, mods: 0,
632 signal: "activate-default", NULL);
633 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_space, mods: 0,
634 signal: "activate-default", NULL);
635 gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_KP_Space, mods: 0,
636 signal: "activate-default", NULL);
637
638 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_MENU);
639}
640
641static GtkBuildableIface *parent_buildable_iface;
642
643static void
644gtk_popover_menu_buildable_add_child (GtkBuildable *buildable,
645 GtkBuilder *builder,
646 GObject *child,
647 const char *type)
648{
649 if (GTK_IS_WIDGET (child))
650 {
651 if (!gtk_popover_menu_add_child (GTK_POPOVER_MENU (buildable), GTK_WIDGET (child), id: type))
652 g_warning ("No such custom attribute: %s", type);
653 }
654 else
655 parent_buildable_iface->add_child (buildable, builder, child, type);
656}
657
658static void
659gtk_popover_menu_buildable_iface_init (GtkBuildableIface *iface)
660{
661 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
662
663 iface->add_child = gtk_popover_menu_buildable_add_child;
664}
665
666/**
667 * gtk_popover_menu_new:
668 *
669 * Creates a new popover menu.
670 *
671 * Returns: a new `GtkPopoverMenu`
672 */
673GtkWidget *
674gtk_popover_menu_new (void)
675{
676 GtkWidget *popover;
677
678 popover = g_object_new (GTK_TYPE_POPOVER_MENU,
679 first_property_name: "autohide", TRUE,
680 NULL);
681
682 return popover;
683}
684
685/*<private>
686 * gtk_popover_menu_open_submenu:
687 * @popover: a `GtkPopoverMenu`
688 * @name: the name of the menu to switch to
689 *
690 * Opens a submenu of the @popover. The @name
691 * must be one of the names given to the submenus
692 * of @popover with `GtkPopoverMenu:submenu`, or
693 * "main" to switch back to the main menu.
694 *
695 * `GtkModelButton` will open submenus automatically
696 * when the `GtkModelButton:menu-name` property is set,
697 * so this function is only needed when you are using
698 * other kinds of widgets to initiate menu changes.
699 */
700void
701gtk_popover_menu_open_submenu (GtkPopoverMenu *popover,
702 const char *name)
703{
704 GtkWidget *stack;
705
706 g_return_if_fail (GTK_IS_POPOVER_MENU (popover));
707
708 stack = gtk_popover_menu_get_stack (menu: popover);
709 gtk_stack_set_visible_child_name (GTK_STACK (stack), name);
710}
711
712void
713gtk_popover_menu_add_submenu (GtkPopoverMenu *popover,
714 GtkWidget *submenu,
715 const char *name)
716{
717 GtkWidget *stack = gtk_popover_menu_get_stack (menu: popover);
718 gtk_stack_add_named (GTK_STACK (stack), child: submenu, name);
719}
720
721/**
722 * gtk_popover_menu_new_from_model:
723 * @model: (nullable): a `GMenuModel`
724 *
725 * Creates a `GtkPopoverMenu` and populates it according to @model.
726 *
727 * The created buttons are connected to actions found in the
728 * `GtkApplicationWindow` to which the popover belongs - typically
729 * by means of being attached to a widget that is contained within
730 * the `GtkApplicationWindow`s widget hierarchy.
731 *
732 * Actions can also be added using [method@Gtk.Widget.insert_action_group]
733 * on the menus attach widget or on any of its parent widgets.
734 *
735 * This function creates menus with sliding submenus.
736 * See [ctor@Gtk.PopoverMenu.new_from_model_full] for a way
737 * to control this.
738 *
739 * Returns: the new `GtkPopoverMenu`
740 */
741GtkWidget *
742gtk_popover_menu_new_from_model (GMenuModel *model)
743
744{
745 return gtk_popover_menu_new_from_model_full (model, flags: 0);
746}
747
748/**
749 * gtk_popover_menu_new_from_model_full:
750 * @model: a `GMenuModel`
751 * @flags: flags that affect how the menu is created
752 *
753 * Creates a `GtkPopoverMenu` and populates it according to @model.
754 *
755 * The created buttons are connected to actions found in the
756 * action groups that are accessible from the parent widget.
757 * This includes the `GtkApplicationWindow` to which the popover
758 * belongs. Actions can also be added using [method@Gtk.Widget.insert_action_group]
759 * on the parent widget or on any of its parent widgets.
760 *
761 * The only flag that is supported currently is
762 * %GTK_POPOVER_MENU_NESTED, which makes GTK create traditional,
763 * nested submenus instead of the default sliding submenus.
764 *
765 * Returns: (transfer full): the new `GtkPopoverMenu`
766 */
767GtkWidget *
768gtk_popover_menu_new_from_model_full (GMenuModel *model,
769 GtkPopoverMenuFlags flags)
770{
771 GtkWidget *popover;
772
773 g_return_val_if_fail (model == NULL || G_IS_MENU_MODEL (model), NULL);
774
775 popover = gtk_popover_menu_new ();
776 GTK_POPOVER_MENU (popover)->flags = flags;
777 gtk_popover_menu_set_menu_model (GTK_POPOVER_MENU (popover), model);
778
779 return popover;
780}
781
782/**
783 * gtk_popover_menu_set_menu_model: (attributes org.gtk.Method.set_property=menu-model)
784 * @popover: a `GtkPopoverMenu`
785 * @model: (nullable): a `GMenuModel`
786 *
787 * Sets a new menu model on @popover.
788 *
789 * The existing contents of @popover are removed, and
790 * the @popover is populated with new contents according
791 * to @model.
792 */
793void
794gtk_popover_menu_set_menu_model (GtkPopoverMenu *popover,
795 GMenuModel *model)
796{
797 g_return_if_fail (GTK_IS_POPOVER_MENU (popover));
798 g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
799
800 if (g_set_object (&popover->model, model))
801 {
802 GtkWidget *stack;
803 GtkWidget *child;
804
805 stack = gtk_popover_menu_get_stack (menu: popover);
806 while ((child = gtk_widget_get_first_child (widget: stack)))
807 gtk_stack_remove (GTK_STACK (stack), child);
808
809 if (model)
810 gtk_menu_section_box_new_toplevel (popover, model, flags: popover->flags);
811
812 g_object_notify (G_OBJECT (popover), property_name: "menu-model");
813 }
814}
815
816/**
817 * gtk_popover_menu_get_menu_model: (attributes org.gtk.Method.get_property=menu-model)
818 * @popover: a `GtkPopoverMenu`
819 *
820 * Returns the menu model used to populate the popover.
821 *
822 * Returns: (transfer none) (nullable): the menu model of @popover
823 */
824GMenuModel *
825gtk_popover_menu_get_menu_model (GtkPopoverMenu *popover)
826{
827 g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), NULL);
828
829 return popover->model;
830}
831
832/**
833 * gtk_popover_menu_add_child:
834 * @popover: a `GtkPopoverMenu`
835 * @child: the `GtkWidget` to add
836 * @id: the ID to insert @child at
837 *
838 * Adds a custom widget to a generated menu.
839 *
840 * For this to work, the menu model of @popover must have
841 * an item with a `custom` attribute that matches @id.
842 *
843 * Returns: %TRUE if @id was found and the widget added
844 */
845gboolean
846gtk_popover_menu_add_child (GtkPopoverMenu *popover,
847 GtkWidget *child,
848 const char *id)
849{
850
851 g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE);
852 g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
853 g_return_val_if_fail (id != NULL, FALSE);
854
855 return gtk_menu_section_box_add_custom (popover, child, id);
856}
857
858/**
859 * gtk_popover_menu_remove_child:
860 * @popover: a `GtkPopoverMenu`
861 * @child: the `GtkWidget` to remove
862 *
863 * Removes a widget that has previously been added with
864 * gtk_popover_menu_add_child().
865 *
866 * Returns: %TRUE if the widget was removed
867 */
868gboolean
869gtk_popover_menu_remove_child (GtkPopoverMenu *popover,
870 GtkWidget *child)
871{
872
873 g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE);
874 g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
875
876 return gtk_menu_section_box_remove_custom (popover, child);
877}
878

source code of gtk/gtk/gtkpopovermenu.c