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