1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2019 Red Hat, Inc.
3 *
4 * Authors:
5 * - Matthias Clasen <mclasen@redhat.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21/**
22 * GtkPopoverMenuBar:
23 *
24 * `GtkPopoverMenuBar` presents a horizontal bar of items that pop
25 * up popover menus when clicked.
26 *
27 * ![An example GtkPopoverMenuBar](menubar.png)
28 *
29 * The only way to create instances of `GtkPopoverMenuBar` is
30 * from a `GMenuModel`.
31 *
32 * # CSS nodes
33 *
34 * ```
35 * menubar
36 * ├── item[.active]
37 * ┊ ╰── popover
38 * ╰── item
39 * ╰── popover
40 * ```
41 *
42 * `GtkPopoverMenuBar` has a single CSS node with name menubar, below which
43 * each item has its CSS node, and below that the corresponding popover.
44 *
45 * The item whose popover is currently open gets the .active
46 * style class.
47 *
48 * # Accessibility
49 *
50 * `GtkPopoverMenuBar` uses the %GTK_ACCESSIBLE_ROLE_MENU_BAR role,
51 * the menu items use the %GTK_ACCESSIBLE_ROLE_MENU_ITEM role and
52 * the menus use the %GTK_ACCESSIBLE_ROLE_MENU role.
53 */
54
55
56#include "config.h"
57
58#include "gtkpopovermenubar.h"
59#include "gtkpopovermenubarprivate.h"
60#include "gtkpopovermenu.h"
61
62#include "gtkbinlayout.h"
63#include "gtkboxlayout.h"
64#include "gtklabel.h"
65#include "gtkmenubutton.h"
66#include "gtkintl.h"
67#include "gtkprivate.h"
68#include "gtkmarshalers.h"
69#include "gtkgestureclick.h"
70#include "gtkeventcontrollermotion.h"
71#include "gtkactionmuxerprivate.h"
72#include "gtkmenutrackerprivate.h"
73#include "gtkwidgetprivate.h"
74#include "gtkmain.h"
75#include "gtknative.h"
76#include "gtkbuildable.h"
77
78#define GTK_TYPE_POPOVER_MENU_BAR_ITEM (gtk_popover_menu_bar_item_get_type ())
79#define GTK_POPOVER_MENU_BAR_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_POPOVER_MENU_BAR_ITEM, GtkPopoverMenuBarItem))
80#define GTK_IS_POPOVER_MENU_BAR_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_POPOVER_MENU_BAR_ITEM))
81
82GType gtk_popover_menu_bar_item_get_type (void) G_GNUC_CONST;
83
84typedef struct _GtkPopoverMenuBarItem GtkPopoverMenuBarItem;
85
86struct _GtkPopoverMenuBar
87{
88 GtkWidget parent;
89
90 GMenuModel *model;
91 GtkMenuTracker *tracker;
92
93 GtkPopoverMenuBarItem *active_item;
94};
95
96typedef struct _GtkPopoverMenuBarClass GtkPopoverMenuBarClass;
97struct _GtkPopoverMenuBarClass
98{
99 GtkWidgetClass parent_class;
100};
101
102struct _GtkPopoverMenuBarItem
103{
104 GtkWidget parent;
105
106 GtkWidget *label;
107 GtkPopover *popover;
108 GtkMenuTrackerItem *tracker;
109};
110
111typedef struct _GtkPopoverMenuBarItemClass GtkPopoverMenuBarItemClass;
112struct _GtkPopoverMenuBarItemClass
113{
114 GtkWidgetClass parent_class;
115
116 void (* activate) (GtkPopoverMenuBarItem *item);
117};
118
119G_DEFINE_TYPE (GtkPopoverMenuBarItem, gtk_popover_menu_bar_item, GTK_TYPE_WIDGET)
120
121static void
122open_submenu (GtkPopoverMenuBarItem *item)
123{
124 gtk_popover_popup (popover: item->popover);
125}
126
127static void
128close_submenu (GtkPopoverMenuBarItem *item)
129{
130 gtk_popover_popdown (popover: item->popover);
131}
132
133static void
134set_active_item (GtkPopoverMenuBar *bar,
135 GtkPopoverMenuBarItem *item,
136 gboolean popup)
137{
138 gboolean changed;
139 gboolean was_popup;
140
141 changed = item != bar->active_item;
142
143 if (bar->active_item)
144 was_popup = gtk_widget_get_mapped (GTK_WIDGET (bar->active_item->popover));
145 else
146 was_popup = FALSE;
147
148 if (was_popup && changed)
149 close_submenu (item: bar->active_item);
150
151 if (changed)
152 {
153 if (bar->active_item)
154 gtk_widget_unset_state_flags (GTK_WIDGET (bar->active_item), flags: GTK_STATE_FLAG_SELECTED);
155
156 bar->active_item = item;
157
158 if (bar->active_item)
159 gtk_widget_set_state_flags (GTK_WIDGET (bar->active_item), flags: GTK_STATE_FLAG_SELECTED, FALSE);
160 }
161
162 if (bar->active_item)
163 {
164 if (popup || (was_popup && changed))
165 open_submenu (item: bar->active_item);
166 else if (changed)
167 gtk_widget_grab_focus (GTK_WIDGET (bar->active_item));
168 }
169}
170
171static void
172clicked_cb (GtkGesture *gesture,
173 int n,
174 double x,
175 double y,
176 gpointer data)
177{
178 GtkWidget *target;
179 GtkPopoverMenuBar *bar;
180
181 target = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
182 bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU_BAR));
183
184 set_active_item (bar, GTK_POPOVER_MENU_BAR_ITEM (target), TRUE);
185}
186
187static void
188item_enter_cb (GtkEventController *controller,
189 double x,
190 double y,
191 gpointer data)
192{
193 GtkWidget *target;
194 GtkPopoverMenuBar *bar;
195
196 target = gtk_event_controller_get_widget (controller);
197 bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU_BAR));
198
199 set_active_item (bar, GTK_POPOVER_MENU_BAR_ITEM (target), FALSE);
200}
201
202static void
203bar_leave_cb (GtkEventController *controller,
204 gpointer data)
205{
206 GtkWidget *target;
207 GtkPopoverMenuBar *bar;
208
209 target = gtk_event_controller_get_widget (controller);
210 bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU_BAR));
211
212 if (bar->active_item &&
213 !gtk_widget_get_mapped (GTK_WIDGET (bar->active_item->popover)))
214 set_active_item (bar, NULL, FALSE);
215}
216
217static gboolean
218gtk_popover_menu_bar_focus (GtkWidget *widget,
219 GtkDirectionType direction)
220{
221 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (widget);
222 GtkWidget *next;
223
224 if (bar->active_item &&
225 gtk_widget_get_mapped (GTK_WIDGET (bar->active_item->popover)))
226 {
227 if (gtk_widget_child_focus (GTK_WIDGET (bar->active_item->popover), direction))
228 return TRUE;
229 }
230
231 if (direction == GTK_DIR_LEFT)
232 {
233 if (bar->active_item)
234 next = gtk_widget_get_prev_sibling (GTK_WIDGET (bar->active_item));
235 else
236 next = NULL;
237
238 if (next == NULL)
239 next = gtk_widget_get_last_child (GTK_WIDGET (bar));
240 }
241 else if (direction == GTK_DIR_RIGHT)
242 {
243 if (bar->active_item)
244 next = gtk_widget_get_next_sibling (GTK_WIDGET (bar->active_item));
245 else
246 next = NULL;
247
248 if (next == NULL)
249 next = gtk_widget_get_first_child (GTK_WIDGET (bar));
250 }
251 else
252 return FALSE;
253
254 set_active_item (bar, GTK_POPOVER_MENU_BAR_ITEM (next), FALSE);
255
256 return TRUE;
257}
258
259static void
260gtk_popover_menu_bar_item_init (GtkPopoverMenuBarItem *item)
261{
262 GtkEventController *controller;
263
264 gtk_widget_set_focusable (GTK_WIDGET (item), TRUE);
265
266 item->label = g_object_new (GTK_TYPE_LABEL,
267 first_property_name: "use-underline", TRUE,
268 NULL);
269 gtk_widget_set_parent (widget: item->label, GTK_WIDGET (item));
270
271 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
272 g_signal_connect (controller, "pressed", G_CALLBACK (clicked_cb), NULL);
273 gtk_widget_add_controller (GTK_WIDGET (item), controller);
274
275 controller = gtk_event_controller_motion_new ();
276 gtk_event_controller_set_propagation_limit (controller, limit: GTK_LIMIT_NONE);
277 g_signal_connect (controller, "enter", G_CALLBACK (item_enter_cb), NULL);
278 gtk_widget_add_controller (GTK_WIDGET (item), controller);
279}
280
281static void
282gtk_popover_menu_bar_item_dispose (GObject *object)
283{
284 GtkPopoverMenuBarItem *item = GTK_POPOVER_MENU_BAR_ITEM (object);
285
286 g_clear_object (&item->tracker);
287 g_clear_pointer (&item->label, gtk_widget_unparent);
288 g_clear_pointer ((GtkWidget **)&item->popover, gtk_widget_unparent);
289
290 G_OBJECT_CLASS (gtk_popover_menu_bar_item_parent_class)->dispose (object);
291}
292
293static void
294gtk_popover_menu_bar_item_finalize (GObject *object)
295{
296 G_OBJECT_CLASS (gtk_popover_menu_bar_item_parent_class)->finalize (object);
297}
298
299static void
300gtk_popover_menu_bar_item_activate (GtkPopoverMenuBarItem *item)
301{
302 GtkPopoverMenuBar *bar;
303
304 bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (GTK_WIDGET (item), GTK_TYPE_POPOVER_MENU_BAR));
305
306 set_active_item (bar, item, TRUE);
307}
308
309static void
310gtk_popover_menu_bar_item_root (GtkWidget *widget)
311{
312 GtkPopoverMenuBarItem *item = GTK_POPOVER_MENU_BAR_ITEM (widget);
313
314 GTK_WIDGET_CLASS (gtk_popover_menu_bar_item_parent_class)->root (widget);
315
316 gtk_accessible_update_relation (self: GTK_ACCESSIBLE (ptr: widget),
317 first_relation: GTK_ACCESSIBLE_RELATION_LABELLED_BY, item->label, NULL,
318 GTK_ACCESSIBLE_RELATION_CONTROLS, item->popover, NULL,
319 -1);
320 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: widget),
321 first_property: GTK_ACCESSIBLE_PROPERTY_HAS_POPUP, TRUE,
322 -1);
323}
324
325static void
326gtk_popover_menu_bar_item_class_init (GtkPopoverMenuBarItemClass *klass)
327{
328 GObjectClass *object_class = G_OBJECT_CLASS (klass);
329 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
330 guint activate_signal;
331
332 object_class->dispose = gtk_popover_menu_bar_item_dispose;
333 object_class->finalize = gtk_popover_menu_bar_item_finalize;
334
335 widget_class->root = gtk_popover_menu_bar_item_root;
336
337 klass->activate = gtk_popover_menu_bar_item_activate;
338
339 activate_signal =
340 g_signal_new (I_("activate"),
341 G_OBJECT_CLASS_TYPE (object_class),
342 signal_flags: G_SIGNAL_RUN_FIRST,
343 G_STRUCT_OFFSET (GtkPopoverMenuBarItemClass, activate),
344 NULL, NULL,
345 NULL,
346 G_TYPE_NONE, n_params: 0);
347
348 gtk_widget_class_set_css_name (widget_class, I_("item"));
349 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_MENU_ITEM);
350 gtk_widget_class_set_activate_signal (widget_class, signal_id: activate_signal);
351 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
352}
353enum
354{
355 PROP_0,
356 PROP_MENU_MODEL,
357 LAST_PROP
358};
359
360static GParamSpec * bar_props[LAST_PROP];
361
362static void gtk_popover_menu_bar_buildable_iface_init (GtkBuildableIface *iface);
363
364G_DEFINE_TYPE_WITH_CODE (GtkPopoverMenuBar, gtk_popover_menu_bar, GTK_TYPE_WIDGET,
365 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
366 gtk_popover_menu_bar_buildable_iface_init))
367
368static void
369tracker_remove (int position,
370 gpointer user_data)
371{
372 GtkWidget *bar = user_data;
373 GtkWidget *child;
374 int i;
375
376 for (child = gtk_widget_get_first_child (widget: bar), i = 0;
377 child;
378 child = gtk_widget_get_next_sibling (widget: child), i++)
379 {
380 if (i == position)
381 {
382 gtk_widget_unparent (widget: child);
383 break;
384 }
385 }
386}
387
388static void
389popover_unmap (GtkPopover *popover,
390 GtkPopoverMenuBar *bar)
391{
392 if (bar->active_item && bar->active_item->popover == popover)
393 set_active_item (bar, NULL, FALSE);
394}
395
396static void
397popover_shown (GtkPopover *popover,
398 GtkPopoverMenuBarItem *item)
399{
400 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: item),
401 first_state: GTK_ACCESSIBLE_STATE_EXPANDED, TRUE,
402 -1);
403
404 if (gtk_menu_tracker_item_get_should_request_show (self: item->tracker))
405 gtk_menu_tracker_item_request_submenu_shown (self: item->tracker, TRUE);
406}
407
408static void
409popover_hidden (GtkPopover *popover,
410 GtkPopoverMenuBarItem *item)
411{
412 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: item),
413 first_state: GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
414 -1);
415
416 if (gtk_menu_tracker_item_get_should_request_show (self: item->tracker))
417 gtk_menu_tracker_item_request_submenu_shown (self: item->tracker, FALSE);
418}
419
420static void
421tracker_insert (GtkMenuTrackerItem *item,
422 int position,
423 gpointer user_data)
424{
425 GtkPopoverMenuBar *bar = user_data;
426
427 if (gtk_menu_tracker_item_get_has_link (self: item, G_MENU_LINK_SUBMENU))
428 {
429 GtkPopoverMenuBarItem *widget;
430 GMenuModel *model;
431 GtkWidget *sibling;
432 GtkWidget *child;
433 GtkPopover *popover;
434 int i;
435
436 widget = g_object_new (GTK_TYPE_POPOVER_MENU_BAR_ITEM, NULL);
437 g_object_bind_property (source: item, source_property: "label",
438 target: widget->label, target_property: "label",
439 flags: G_BINDING_SYNC_CREATE);
440
441 model = _gtk_menu_tracker_item_get_link (self: item, G_MENU_LINK_SUBMENU);
442 popover = GTK_POPOVER (gtk_popover_menu_new_from_model_full (model, GTK_POPOVER_MENU_NESTED));
443 gtk_widget_set_parent (GTK_WIDGET (popover), GTK_WIDGET (widget));
444 gtk_popover_set_position (popover, position: GTK_POS_BOTTOM);
445 gtk_popover_set_has_arrow (popover, FALSE);
446 gtk_widget_set_halign (GTK_WIDGET (popover), align: GTK_ALIGN_START);
447
448 g_signal_connect (popover, "unmap", G_CALLBACK (popover_unmap), bar);
449 g_signal_connect (popover, "show", G_CALLBACK (popover_shown), widget);
450 g_signal_connect (popover, "hide", G_CALLBACK (popover_hidden), widget);
451
452 widget->popover = popover;
453 widget->tracker = g_object_ref (item);
454
455 sibling = NULL;
456 for (child = gtk_widget_get_first_child (GTK_WIDGET (bar)), i = 1;
457 child;
458 child = gtk_widget_get_next_sibling (widget: child), i++)
459 {
460 if (i == position)
461 {
462 sibling = child;
463 break;
464 }
465 }
466 gtk_widget_insert_after (GTK_WIDGET (widget), GTK_WIDGET (bar), previous_sibling: sibling);
467 }
468 else
469 g_warning ("Don't know how to handle this item");
470}
471
472static void
473gtk_popover_menu_bar_set_property (GObject *object,
474 guint property_id,
475 const GValue *value,
476 GParamSpec *pspec)
477{
478 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (object);
479
480 switch (property_id)
481 {
482 case PROP_MENU_MODEL:
483 gtk_popover_menu_bar_set_menu_model (bar, model: g_value_get_object (value));
484 break;
485
486 default:
487 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
488 }
489}
490
491static void
492gtk_popover_menu_bar_get_property (GObject *object,
493 guint property_id,
494 GValue *value,
495 GParamSpec *pspec)
496{
497 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (object);
498
499 switch (property_id)
500 {
501 case PROP_MENU_MODEL:
502 g_value_set_object (value, v_object: bar->model);
503 break;
504
505 default:
506 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
507 }
508}
509
510static void
511gtk_popover_menu_bar_dispose (GObject *object)
512{
513 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (object);
514 GtkWidget *child;
515
516 g_clear_pointer (&bar->tracker, gtk_menu_tracker_free);
517 g_clear_object (&bar->model);
518
519 while ((child = gtk_widget_get_first_child (GTK_WIDGET (bar))))
520 gtk_widget_unparent (widget: child);
521
522 G_OBJECT_CLASS (gtk_popover_menu_bar_parent_class)->dispose (object);
523}
524
525static GList *
526get_menu_bars (GtkWindow *window)
527{
528 return g_object_get_data (G_OBJECT (window), key: "gtk-menu-bar-list");
529}
530
531GList *
532gtk_popover_menu_bar_get_viewable_menu_bars (GtkWindow *window)
533{
534 GList *menu_bars;
535 GList *viewable_menu_bars = NULL;
536
537 for (menu_bars = get_menu_bars (window);
538 menu_bars;
539 menu_bars = menu_bars->next)
540 {
541 GtkWidget *widget = menu_bars->data;
542 gboolean viewable = TRUE;
543
544 while (widget)
545 {
546 if (!gtk_widget_get_mapped (widget))
547 viewable = FALSE;
548
549 widget = gtk_widget_get_parent (widget);
550 }
551
552 if (viewable)
553 viewable_menu_bars = g_list_prepend (list: viewable_menu_bars, data: menu_bars->data);
554 }
555
556 return g_list_reverse (list: viewable_menu_bars);
557}
558
559static void
560set_menu_bars (GtkWindow *window,
561 GList *menubars)
562{
563 g_object_set_data (G_OBJECT (window), I_("gtk-menu-bar-list"), data: menubars);
564}
565
566static void
567add_to_window (GtkWindow *window,
568 GtkPopoverMenuBar *bar)
569{
570 GList *menubars = get_menu_bars (window);
571
572 set_menu_bars (window, menubars: g_list_prepend (list: menubars, data: bar));
573}
574
575static void
576remove_from_window (GtkWindow *window,
577 GtkPopoverMenuBar *bar)
578{
579 GList *menubars = get_menu_bars (window);
580
581 menubars = g_list_remove (list: menubars, data: bar);
582 set_menu_bars (window, menubars);
583}
584
585static void
586gtk_popover_menu_bar_root (GtkWidget *widget)
587{
588 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (widget);
589 GtkWidget *toplevel;
590
591 GTK_WIDGET_CLASS (gtk_popover_menu_bar_parent_class)->root (widget);
592
593 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
594 add_to_window (GTK_WINDOW (toplevel), bar);
595
596 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: bar),
597 first_property: GTK_ACCESSIBLE_PROPERTY_ORIENTATION, GTK_ORIENTATION_HORIZONTAL,
598 -1);
599}
600
601static void
602gtk_popover_menu_bar_unroot (GtkWidget *widget)
603{
604 GtkPopoverMenuBar *bar = GTK_POPOVER_MENU_BAR (widget);
605 GtkWidget *toplevel;
606
607 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
608 remove_from_window (GTK_WINDOW (toplevel), bar);
609
610 GTK_WIDGET_CLASS (gtk_popover_menu_bar_parent_class)->unroot (widget);
611}
612
613static void
614gtk_popover_menu_bar_class_init (GtkPopoverMenuBarClass *klass)
615{
616 GObjectClass *object_class = G_OBJECT_CLASS (klass);
617 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
618
619 object_class->dispose = gtk_popover_menu_bar_dispose;
620 object_class->set_property = gtk_popover_menu_bar_set_property;
621 object_class->get_property = gtk_popover_menu_bar_get_property;
622
623 widget_class->root = gtk_popover_menu_bar_root;
624 widget_class->unroot = gtk_popover_menu_bar_unroot;
625 widget_class->focus = gtk_popover_menu_bar_focus;
626
627 /**
628 * GtkPopoverMenuBar:menu-model: (attributes org.gtk.Property.get=gtk_popover_menu_bar_get_menu_model org.gtk.Property.set=gtk_popover_menu_bar_set_menu_model)
629 *
630 * The `GMenuModel` from which the menu bar is created.
631 *
632 * The model should only contain submenus as toplevel elements.
633 */
634 bar_props[PROP_MENU_MODEL] =
635 g_param_spec_object (name: "menu-model",
636 P_("Menu model"),
637 P_("The model from which the bar is made."),
638 G_TYPE_MENU_MODEL,
639 GTK_PARAM_READWRITE);
640
641 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: bar_props);
642
643 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
644 gtk_widget_class_set_css_name (widget_class, I_("menubar"));
645 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_MENU_BAR);
646}
647
648static void
649gtk_popover_menu_bar_init (GtkPopoverMenuBar *bar)
650{
651 GtkEventController *controller;
652
653 controller = gtk_event_controller_motion_new ();
654 gtk_event_controller_set_propagation_limit (controller, limit: GTK_LIMIT_NONE);
655 g_signal_connect (controller, "leave", G_CALLBACK (bar_leave_cb), NULL);
656 gtk_widget_add_controller (GTK_WIDGET (bar), controller);
657}
658
659static GtkBuildableIface *parent_buildable_iface;
660
661static void
662gtk_popover_menu_bar_buildable_add_child (GtkBuildable *buildable,
663 GtkBuilder *builder,
664 GObject *child,
665 const char *type)
666{
667 if (GTK_IS_WIDGET (child))
668 {
669 if (!gtk_popover_menu_bar_add_child (GTK_POPOVER_MENU_BAR (buildable), GTK_WIDGET (child), id: type))
670 g_warning ("No such custom attribute: %s", type);
671 }
672 else
673 parent_buildable_iface->add_child (buildable, builder, child, type);
674}
675
676static void
677gtk_popover_menu_bar_buildable_iface_init (GtkBuildableIface *iface)
678{
679 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
680
681 iface->add_child = gtk_popover_menu_bar_buildable_add_child;
682}
683
684/**
685 * gtk_popover_menu_bar_new_from_model:
686 * @model: (nullable): a `GMenuModel`
687 *
688 * Creates a `GtkPopoverMenuBar` from a `GMenuModel`.
689 *
690 * Returns: a new `GtkPopoverMenuBar`
691 */
692GtkWidget *
693gtk_popover_menu_bar_new_from_model (GMenuModel *model)
694{
695 return g_object_new (GTK_TYPE_POPOVER_MENU_BAR,
696 first_property_name: "menu-model", model,
697 NULL);
698}
699
700/**
701 * gtk_popover_menu_bar_set_menu_model: (attributes org.gtk.Method.set_property=menu-model)
702 * @bar: a `GtkPopoverMenuBar`
703 * @model: (nullable): a `GMenuModel`
704 *
705 * Sets a menu model from which @bar should take
706 * its contents.
707 */
708void
709gtk_popover_menu_bar_set_menu_model (GtkPopoverMenuBar *bar,
710 GMenuModel *model)
711{
712 g_return_if_fail (GTK_IS_POPOVER_MENU_BAR (bar));
713 g_return_if_fail (G_IS_MENU_MODEL (model));
714
715 if (g_set_object (&bar->model, model))
716 {
717 GtkWidget *child;
718 GtkActionMuxer *muxer;
719
720 while ((child = gtk_widget_get_first_child (GTK_WIDGET (bar))))
721 gtk_widget_unparent (widget: child);
722
723 g_clear_pointer (&bar->tracker, gtk_menu_tracker_free);
724
725 if (model)
726 {
727 muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (bar), TRUE);
728 bar->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (muxer),
729 model,
730 FALSE,
731 TRUE,
732 FALSE,
733 NULL,
734 insert_func: tracker_insert,
735 remove_func: tracker_remove,
736 user_data: bar);
737 }
738
739 g_object_notify_by_pspec (G_OBJECT (bar), pspec: bar_props[PROP_MENU_MODEL]);
740 }
741}
742
743/**
744 * gtk_popover_menu_bar_get_menu_model: (attributes org.gtk.Method.get_property=menu-model)
745 * @bar: a `GtkPopoverMenuBar`
746 *
747 * Returns the model from which the contents of @bar are taken.
748 *
749 * Returns: (transfer none) (nullable): a `GMenuModel`
750 */
751GMenuModel *
752gtk_popover_menu_bar_get_menu_model (GtkPopoverMenuBar *bar)
753{
754 g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), NULL);
755
756 return bar->model;
757}
758
759void
760gtk_popover_menu_bar_select_first (GtkPopoverMenuBar *bar)
761{
762 GtkPopoverMenuBarItem *item;
763
764 item = GTK_POPOVER_MENU_BAR_ITEM (gtk_widget_get_first_child (GTK_WIDGET (bar)));
765 set_active_item (bar, item, TRUE);
766}
767
768/**
769 * gtk_popover_menu_bar_add_child:
770 * @bar: a `GtkPopoverMenuBar`
771 * @child: the `GtkWidget` to add
772 * @id: the ID to insert @child at
773 *
774 * Adds a custom widget to a generated menubar.
775 *
776 * For this to work, the menu model of @bar must have an
777 * item with a `custom` attribute that matches @id.
778 *
779 * Returns: %TRUE if @id was found and the widget added
780 */
781gboolean
782gtk_popover_menu_bar_add_child (GtkPopoverMenuBar *bar,
783 GtkWidget *child,
784 const char *id)
785{
786 GtkWidget *item;
787
788 g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE);
789 g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
790 g_return_val_if_fail (id != NULL, FALSE);
791
792 for (item = gtk_widget_get_first_child (GTK_WIDGET (bar));
793 item;
794 item = gtk_widget_get_next_sibling (widget: item))
795 {
796 GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover;
797
798 if (gtk_popover_menu_add_child (GTK_POPOVER_MENU (popover), child, id))
799 return TRUE;
800 }
801
802 return FALSE;
803}
804
805/**
806 * gtk_popover_menu_bar_remove_child:
807 * @bar: a `GtkPopoverMenuBar`
808 * @child: the `GtkWidget` to remove
809 *
810 * Removes a widget that has previously been added with
811 * gtk_popover_menu_bar_add_child().
812 *
813 * Returns: %TRUE if the widget was removed
814 */
815gboolean
816gtk_popover_menu_bar_remove_child (GtkPopoverMenuBar *bar,
817 GtkWidget *child)
818{
819 GtkWidget *item;
820
821 g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE);
822 g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
823
824 for (item = gtk_widget_get_first_child (GTK_WIDGET (bar));
825 item;
826 item = gtk_widget_get_next_sibling (widget: item))
827 {
828 GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover;
829
830 if (gtk_popover_menu_remove_child (GTK_POPOVER_MENU (popover), child))
831 return TRUE;
832 }
833
834 return FALSE;
835}
836

source code of gtk/gtk/gtkpopovermenubar.c