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_popover_menu_bar_item_get_type ()) |
79 | #define (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_POPOVER_MENU_BAR_ITEM, GtkPopoverMenuBarItem)) |
80 | #define (obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_POPOVER_MENU_BAR_ITEM)) |
81 | |
82 | GType gtk_popover_menu_bar_item_get_type (void) G_GNUC_CONST; |
83 | |
84 | typedef struct _GtkPopoverMenuBarItem ; |
85 | |
86 | struct |
87 | { |
88 | GtkWidget ; |
89 | |
90 | GMenuModel *; |
91 | GtkMenuTracker *; |
92 | |
93 | GtkPopoverMenuBarItem *; |
94 | }; |
95 | |
96 | typedef struct _GtkPopoverMenuBarClass ; |
97 | struct |
98 | { |
99 | GtkWidgetClass ; |
100 | }; |
101 | |
102 | struct |
103 | { |
104 | GtkWidget ; |
105 | |
106 | GtkWidget *; |
107 | GtkPopover *; |
108 | GtkMenuTrackerItem *; |
109 | }; |
110 | |
111 | typedef struct _GtkPopoverMenuBarItemClass ; |
112 | struct |
113 | { |
114 | GtkWidgetClass ; |
115 | |
116 | void (* ) (GtkPopoverMenuBarItem *item); |
117 | }; |
118 | |
119 | G_DEFINE_TYPE (GtkPopoverMenuBarItem, gtk_popover_menu_bar_item, GTK_TYPE_WIDGET) |
120 | |
121 | static void |
122 | (GtkPopoverMenuBarItem *item) |
123 | { |
124 | gtk_popover_popup (popover: item->popover); |
125 | } |
126 | |
127 | static void |
128 | (GtkPopoverMenuBarItem *item) |
129 | { |
130 | gtk_popover_popdown (popover: item->popover); |
131 | } |
132 | |
133 | static void |
134 | set_active_item (GtkPopoverMenuBar *bar, |
135 | GtkPopoverMenuBarItem *item, |
136 | gboolean ) |
137 | { |
138 | gboolean changed; |
139 | gboolean ; |
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 | |
171 | static void |
172 | clicked_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 | |
187 | static void |
188 | item_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 | |
202 | static void |
203 | bar_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 | |
217 | static gboolean |
218 | (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 | |
259 | static void |
260 | (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 | |
281 | static void |
282 | (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 | |
293 | static void |
294 | (GObject *object) |
295 | { |
296 | G_OBJECT_CLASS (gtk_popover_menu_bar_item_parent_class)->finalize (object); |
297 | } |
298 | |
299 | static void |
300 | (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 | |
309 | static void |
310 | (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 | |
325 | static void |
326 | (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 | } |
353 | enum |
354 | { |
355 | PROP_0, |
356 | , |
357 | LAST_PROP |
358 | }; |
359 | |
360 | static GParamSpec * bar_props[LAST_PROP]; |
361 | |
362 | static void gtk_popover_menu_bar_buildable_iface_init (GtkBuildableIface *iface); |
363 | |
364 | G_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 | |
368 | static void |
369 | tracker_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 | |
388 | static void |
389 | popover_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 | |
396 | static void |
397 | popover_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 | |
408 | static void |
409 | popover_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 | |
420 | static void |
421 | tracker_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 | |
472 | static void |
473 | (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 | |
491 | static void |
492 | (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 | |
510 | static void |
511 | (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 | |
525 | static GList * |
526 | (GtkWindow *window) |
527 | { |
528 | return g_object_get_data (G_OBJECT (window), key: "gtk-menu-bar-list" ); |
529 | } |
530 | |
531 | GList * |
532 | (GtkWindow *window) |
533 | { |
534 | GList *; |
535 | GList * = 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 | |
559 | static void |
560 | (GtkWindow *window, |
561 | GList *) |
562 | { |
563 | g_object_set_data (G_OBJECT (window), I_("gtk-menu-bar-list" ), data: menubars); |
564 | } |
565 | |
566 | static void |
567 | add_to_window (GtkWindow *window, |
568 | GtkPopoverMenuBar *bar) |
569 | { |
570 | GList * = get_menu_bars (window); |
571 | |
572 | set_menu_bars (window, menubars: g_list_prepend (list: menubars, data: bar)); |
573 | } |
574 | |
575 | static void |
576 | remove_from_window (GtkWindow *window, |
577 | GtkPopoverMenuBar *bar) |
578 | { |
579 | GList * = get_menu_bars (window); |
580 | |
581 | menubars = g_list_remove (list: menubars, data: bar); |
582 | set_menu_bars (window, menubars); |
583 | } |
584 | |
585 | static void |
586 | (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 | |
601 | static void |
602 | (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 | |
613 | static void |
614 | (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 | |
648 | static void |
649 | (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 | |
659 | static GtkBuildableIface *parent_buildable_iface; |
660 | |
661 | static void |
662 | (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 | |
676 | static void |
677 | (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 | */ |
692 | GtkWidget * |
693 | (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 | */ |
708 | void |
709 | (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 | */ |
751 | GMenuModel * |
752 | (GtkPopoverMenuBar *bar) |
753 | { |
754 | g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), NULL); |
755 | |
756 | return bar->model; |
757 | } |
758 | |
759 | void |
760 | (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 | */ |
781 | gboolean |
782 | (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 | */ |
815 | gboolean |
816 | (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 | |