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