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 | |
150 | typedef struct _GtkPopoverMenuClass ; |
151 | |
152 | struct |
153 | { |
154 | GtkPopover ; |
155 | |
156 | GtkWidget *; |
157 | GtkWidget *; |
158 | GtkWidget *; |
159 | GMenuModel *; |
160 | GtkPopoverMenuFlags ; |
161 | }; |
162 | |
163 | struct |
164 | { |
165 | GtkPopoverClass ; |
166 | }; |
167 | |
168 | enum { |
169 | = 1, |
170 | |
171 | }; |
172 | |
173 | static void gtk_popover_menu_buildable_iface_init (GtkBuildableIface *iface); |
174 | |
175 | G_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 | |
179 | GtkWidget * |
180 | (GtkPopoverMenu *) |
181 | { |
182 | return menu->parent_menu; |
183 | } |
184 | |
185 | void |
186 | (GtkPopoverMenu *, |
187 | GtkWidget *parent) |
188 | { |
189 | menu->parent_menu = parent; |
190 | } |
191 | |
192 | GtkWidget * |
193 | (GtkPopoverMenu *) |
194 | { |
195 | return menu->open_submenu; |
196 | } |
197 | |
198 | void |
199 | (GtkPopoverMenu *, |
200 | GtkWidget *) |
201 | { |
202 | menu->open_submenu = submenu; |
203 | } |
204 | |
205 | void |
206 | (GtkPopoverMenu *) |
207 | { |
208 | GtkWidget *; |
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 | |
219 | GtkWidget * |
220 | (GtkPopoverMenu *) |
221 | { |
222 | return menu->active_item; |
223 | } |
224 | |
225 | void |
226 | (GtkPopoverMenu *, |
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 | |
259 | static void |
260 | (GObject *object, |
261 | GParamSpec *pspec, |
262 | GtkPopoverMenu *popover) |
263 | { |
264 | g_object_notify (G_OBJECT (popover), property_name: "visible-submenu" ); |
265 | } |
266 | |
267 | static void |
268 | focus_out (GtkEventController *controller, |
269 | GtkPopoverMenu *) |
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 | |
290 | static void |
291 | leave_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 | |
302 | static void |
303 | (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 | |
349 | GtkWidget * |
350 | (GtkPopoverMenu *) |
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 | |
359 | static void |
360 | (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 | |
375 | static void |
376 | (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 | |
382 | static void |
383 | (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 | |
389 | static void |
390 | (GObject *object, |
391 | guint property_id, |
392 | GValue *value, |
393 | GParamSpec *pspec) |
394 | { |
395 | GtkPopoverMenu * = 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 | |
413 | static void |
414 | (GObject *object, |
415 | guint property_id, |
416 | const GValue *value, |
417 | GParamSpec *pspec) |
418 | { |
419 | GtkPopoverMenu * = 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 | |
437 | static gboolean |
438 | (GtkWidget *widget, |
439 | GtkDirectionType direction) |
440 | { |
441 | GtkPopoverMenu * = 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 | |
513 | static void |
514 | add_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 | |
526 | static void |
527 | add_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 | |
547 | static void |
548 | (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 | |
555 | static void |
556 | (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 | |
564 | static void |
565 | (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 | |
574 | static void |
575 | (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 | |
641 | static GtkBuildableIface *parent_buildable_iface; |
642 | |
643 | static void |
644 | (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 | |
658 | static void |
659 | (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 | */ |
673 | GtkWidget * |
674 | (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 | */ |
700 | void |
701 | (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 | |
712 | void |
713 | (GtkPopoverMenu *popover, |
714 | GtkWidget *, |
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 | */ |
741 | GtkWidget * |
742 | (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 | */ |
767 | GtkWidget * |
768 | (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 | */ |
793 | void |
794 | (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 | */ |
824 | GMenuModel * |
825 | (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 | */ |
845 | gboolean |
846 | (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 | */ |
868 | gboolean |
869 | (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 | |