1/*
2 * Copyright © 2014 Canonical Limited
3 * Copyright © 2013 Carlos Garnacho
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the licence, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Ryan Lortie <desrt@desrt.ca>
19 */
20
21#include "config.h"
22
23#include "gtkmenusectionboxprivate.h"
24
25#include "gtkwidgetprivate.h"
26#include "gtklabel.h"
27#include "gtkmenutrackerprivate.h"
28#include "gtkmodelbuttonprivate.h"
29#include "gtkseparator.h"
30#include "gtksizegroup.h"
31#include "gtkstack.h"
32#include "gtkpopovermenuprivate.h"
33#include "gtkorientable.h"
34#include "gtkbuiltiniconprivate.h"
35#include "gtkgizmoprivate.h"
36#include "gtkbinlayout.h"
37#include "gtkprivate.h"
38
39typedef GtkBoxClass GtkMenuSectionBoxClass;
40
41struct _GtkMenuSectionBox
42{
43 GtkBox parent_instance;
44
45 GtkMenuSectionBox *toplevel;
46 GtkMenuTracker *tracker;
47 GtkBox *item_box;
48 GtkWidget *separator;
49 guint separator_sync_idle;
50 gboolean iconic;
51 gboolean inline_buttons;
52 gboolean circular;
53 int depth;
54 GtkPopoverMenuFlags flags;
55 GtkSizeGroup *indicators;
56 GHashTable *custom_slots;
57};
58
59typedef struct
60{
61 int n_items;
62 gboolean previous_is_iconic;
63} MenuData;
64
65G_DEFINE_TYPE (GtkMenuSectionBox, gtk_menu_section_box, GTK_TYPE_BOX)
66
67static void gtk_menu_section_box_sync_separators (GtkMenuSectionBox *box,
68 MenuData *data);
69static void gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
70 GtkMenuSectionBox *toplevel,
71 GtkWidget *focus,
72 const char *name);
73static GtkWidget * gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
74 GtkMenuSectionBox *parent);
75
76/* We are trying to implement the following rules here:
77 *
78 * rule 1: never ever show separators for empty sections
79 * rule 2: always show a separator if there is a label
80 * rule 3: don't show a separator for the first section
81 * rule 4: don't show a separator for the following sections if there are
82 * no items before it
83 * rule 5: never show separators directly above or below an iconic box
84 * (rule 6: these rules don't apply exactly the same way for subsections)
85 */
86static void
87gtk_menu_section_box_sync_separators (GtkMenuSectionBox *box,
88 MenuData *data)
89{
90 gboolean previous_section_is_iconic;
91 gboolean should_have_separator;
92 gboolean should_have_top_margin = FALSE;
93 gboolean is_not_empty_item;
94 gboolean has_separator;
95 gboolean has_label;
96 gboolean separator_condition;
97 int n_items_before;
98 GtkWidget *child;
99
100 n_items_before = data->n_items;
101 previous_section_is_iconic = data->previous_is_iconic;
102
103 for (child = gtk_widget_get_first_child (GTK_WIDGET (box->item_box));
104 child != NULL;
105 child = gtk_widget_get_next_sibling (widget: child))
106 {
107 if (GTK_IS_MENU_SECTION_BOX (child))
108 gtk_menu_section_box_sync_separators (GTK_MENU_SECTION_BOX (child), data);
109 else
110 data->n_items++;
111 }
112
113 is_not_empty_item = (data->n_items > n_items_before);
114
115 if (is_not_empty_item)
116 data->previous_is_iconic = box->iconic;
117
118 if (box->separator == NULL)
119 return;
120
121 has_separator = gtk_widget_get_parent (widget: box->separator) != NULL;
122 has_label = !GTK_IS_SEPARATOR (box->separator);
123
124 separator_condition = has_label ? TRUE : n_items_before > 0 &&
125 box->depth <= 1 &&
126 !previous_section_is_iconic &&
127 !box->iconic;
128
129 should_have_separator = separator_condition && is_not_empty_item;
130
131 should_have_top_margin = !should_have_separator &&
132 (box->depth <= 1 || box->iconic || box->circular) &&
133 n_items_before > 0 &&
134 is_not_empty_item;
135
136 gtk_widget_set_margin_top (GTK_WIDGET (box->item_box), margin: should_have_top_margin ? 10 : 0);
137
138 if (has_label)
139 {
140 GtkWidget *separator = gtk_widget_get_first_child (widget: box->separator);
141
142 gtk_widget_set_visible (widget: separator, visible: n_items_before > 0);
143 }
144
145 if (should_have_separator == has_separator)
146 return;
147
148 if (should_have_separator)
149 gtk_box_insert_child_after (GTK_BOX (box), child: box->separator, NULL);
150 else
151 gtk_box_remove (GTK_BOX (box), child: box->separator);
152}
153
154static gboolean
155gtk_menu_section_box_handle_sync_separators (gpointer user_data)
156{
157 GtkMenuSectionBox *box = user_data;
158 MenuData data;
159
160 data.n_items = 0;
161 data.previous_is_iconic = FALSE;
162 gtk_menu_section_box_sync_separators (box, data: &data);
163
164 box->separator_sync_idle = 0;
165
166 return G_SOURCE_REMOVE;
167}
168
169static void
170gtk_menu_section_box_schedule_separator_sync (GtkMenuSectionBox *box)
171{
172 box = box->toplevel;
173
174 if (!box->separator_sync_idle)
175 {
176 box->separator_sync_idle = g_idle_add_full (G_PRIORITY_HIGH_IDLE, /* before resize... */
177 function: gtk_menu_section_box_handle_sync_separators,
178 data: box, NULL);
179 gdk_source_set_static_name_by_id (tag: box->separator_sync_idle, name: "[gtk] menu section box handle sync separators");
180 }
181}
182
183static void
184gtk_popover_item_activate (GtkWidget *button,
185 gpointer user_data)
186{
187 GtkMenuTrackerItem *item = user_data;
188 GtkWidget *popover = NULL;
189
190 if (gtk_menu_tracker_item_get_role (self: item) == GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
191 {
192 /* Activating the item could cause the popover
193 * to be free'd, for example if it is a Quit item
194 */
195 popover = gtk_widget_get_ancestor (widget: button, GTK_TYPE_POPOVER);
196 if (popover)
197 g_object_ref (popover);
198 }
199
200 gtk_menu_tracker_item_activated (self: item);
201
202 if (popover != NULL)
203 {
204 gtk_widget_hide (widget: popover);
205 g_object_unref (object: popover);
206 }
207}
208
209static void
210gtk_menu_section_box_remove_func (int position,
211 gpointer user_data)
212{
213 GtkMenuSectionBox *box = user_data;
214 GtkMenuTrackerItem *item;
215 GtkWidget *widget;
216 int pos;
217
218 for (widget = gtk_widget_get_first_child (GTK_WIDGET (box->item_box)), pos = 0;
219 widget != NULL;
220 widget = gtk_widget_get_next_sibling (widget), pos++)
221 {
222 if (pos == position)
223 break;
224 }
225
226 item = g_object_get_data (G_OBJECT (widget), key: "GtkMenuTrackerItem");
227 if (gtk_menu_tracker_item_get_has_link (self: item, G_MENU_LINK_SUBMENU))
228 {
229 GtkWidget *stack, *subbox;
230
231 stack = gtk_widget_get_ancestor (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK);
232 subbox = gtk_stack_get_child_by_name (GTK_STACK (stack), name: gtk_menu_tracker_item_get_label (self: item));
233 if (subbox != NULL)
234 gtk_stack_remove (GTK_STACK (stack), child: subbox);
235 }
236
237 gtk_box_remove (GTK_BOX (box->item_box), child: widget);
238
239 gtk_menu_section_box_schedule_separator_sync (box);
240}
241
242static gboolean
243get_ancestors (GtkWidget *widget,
244 GType widget_type,
245 GtkWidget **ancestor,
246 GtkWidget **below)
247{
248 GtkWidget *a, *b;
249
250 a = NULL;
251 b = widget;
252 while (b != NULL)
253 {
254 a = gtk_widget_get_parent (widget: b);
255 if (!a)
256 return FALSE;
257 if (g_type_is_a (G_OBJECT_TYPE (a), is_a_type: widget_type))
258 break;
259 b = a;
260 }
261
262 *below = b;
263 *ancestor = a;
264
265 return TRUE;
266}
267
268static void
269close_submenu (GtkWidget *button,
270 gpointer data)
271{
272 GtkMenuTrackerItem *item = data;
273 GtkWidget *focus;
274
275 if (gtk_menu_tracker_item_get_should_request_show (self: item))
276 gtk_menu_tracker_item_request_submenu_shown (self: item, FALSE);
277
278 focus = GTK_WIDGET (g_object_get_data (G_OBJECT (button), "focus"));
279 gtk_widget_grab_focus (widget: focus);
280}
281
282static void
283open_submenu (GtkWidget *button,
284 gpointer data)
285{
286 GtkMenuTrackerItem *item = data;
287 GtkWidget *focus;
288
289 if (gtk_menu_tracker_item_get_should_request_show (self: item))
290 gtk_menu_tracker_item_request_submenu_shown (self: item, TRUE);
291
292 focus = GTK_WIDGET (g_object_get_data (G_OBJECT (button), "focus"));
293 gtk_widget_grab_focus (widget: focus);
294}
295
296static void
297submenu_shown (GtkPopoverMenu *popover,
298 GtkMenuTrackerItem *item)
299{
300 if (gtk_menu_tracker_item_get_should_request_show (self: item))
301 gtk_menu_tracker_item_request_submenu_shown (self: item, TRUE);
302}
303
304static void
305submenu_hidden (GtkPopoverMenu *popover,
306 GtkMenuTrackerItem *item)
307{
308 if (gtk_menu_tracker_item_get_should_request_show (self: item))
309 gtk_menu_tracker_item_request_submenu_shown (self: item, FALSE);
310}
311
312/* We're using the gizmo as an easy single child container, not as
313 * a custom widget to draw some visual indicators (like markers).
314 * As such its default focus functions blocks focus through the children,
315 * so we need to handle it correctly here so that custom widgets inside
316 * menus can be focused.
317 */
318static gboolean
319custom_widget_focus (GtkGizmo *gizmo,
320 GtkDirectionType direction)
321{
322 return gtk_widget_focus_child (GTK_WIDGET (gizmo), direction);
323}
324
325static gboolean
326custom_widget_grab_focus (GtkGizmo *gizmo)
327{
328 return gtk_widget_grab_focus_child (GTK_WIDGET (gizmo));
329}
330
331static void
332gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item,
333 int position,
334 gpointer user_data)
335{
336 GtkMenuSectionBox *box = user_data;
337 GtkWidget *widget;
338
339 if (gtk_menu_tracker_item_get_is_separator (self: item))
340 {
341 widget = gtk_menu_section_box_new_section (item, parent: box);
342 }
343 else if (gtk_menu_tracker_item_get_has_link (self: item, G_MENU_LINK_SUBMENU))
344 {
345 if (box->flags & GTK_POPOVER_MENU_NESTED)
346 {
347 GMenuModel *model;
348 GtkWidget *submenu;
349
350 model = _gtk_menu_tracker_item_get_link (self: item, G_MENU_LINK_SUBMENU);
351
352 submenu = gtk_popover_menu_new_from_model_full (model, flags: box->flags);
353 gtk_popover_set_has_arrow (GTK_POPOVER (submenu), FALSE);
354 gtk_widget_set_valign (widget: submenu, align: GTK_ALIGN_START);
355
356 widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
357 first_property_name: "popover", submenu,
358 "indicator-size-group", box->indicators,
359 NULL);
360 g_object_bind_property (source: item, source_property: "label", target: widget, target_property: "text", flags: G_BINDING_SYNC_CREATE);
361 g_object_bind_property (source: item, source_property: "icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
362 g_object_bind_property (source: item, source_property: "sensitive", target: widget, target_property: "sensitive", flags: G_BINDING_SYNC_CREATE);
363
364 g_signal_connect (submenu, "show", G_CALLBACK (submenu_shown), item);
365 g_signal_connect (submenu, "hide", G_CALLBACK (submenu_hidden), item);
366 }
367 else
368 {
369 GtkWidget *stack = NULL;
370 GtkWidget *parent = NULL;
371 char *name;
372
373 widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
374 first_property_name: "menu-name", gtk_menu_tracker_item_get_label (self: item),
375 "indicator-size-group", box->indicators,
376 NULL);
377 g_object_bind_property (source: item, source_property: "label", target: widget, target_property: "text", flags: G_BINDING_SYNC_CREATE);
378 g_object_bind_property (source: item, source_property: "icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
379 g_object_bind_property (source: item, source_property: "sensitive", target: widget, target_property: "sensitive", flags: G_BINDING_SYNC_CREATE);
380
381 get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, ancestor: &stack, below: &parent);
382 g_object_get (object: gtk_stack_get_page (GTK_STACK (stack), child: parent), first_property_name: "name", &name, NULL);
383 gtk_menu_section_box_new_submenu (item, toplevel: box->toplevel, focus: widget, name);
384 g_free (mem: name);
385 }
386 }
387 else if (gtk_menu_tracker_item_get_custom (self: item))
388 {
389 const char *id = gtk_menu_tracker_item_get_custom (self: item);
390
391 widget = gtk_gizmo_new (css_name: "widget", NULL, NULL, NULL, NULL, focus_func: custom_widget_focus, grab_focus_func: custom_widget_grab_focus);
392 gtk_widget_set_layout_manager (widget, layout_manager: gtk_bin_layout_new ());
393
394 if (g_hash_table_lookup (hash_table: box->custom_slots, key: id))
395 g_warning ("Duplicate custom ID: %s", id);
396 else
397 {
398 char *slot_id = g_strdup (str: id);
399 g_object_set_data_full (G_OBJECT (widget), key: "slot-id", data: slot_id, destroy: g_free);
400 g_hash_table_insert (hash_table: box->custom_slots, key: slot_id, value: widget);
401 }
402 }
403 else
404 {
405 widget = g_object_new (GTK_TYPE_MODEL_BUTTON,
406 first_property_name: "indicator-size-group", box->indicators,
407 NULL);
408 g_object_bind_property (source: item, source_property: "label", target: widget, target_property: "text", flags: G_BINDING_SYNC_CREATE);
409
410 if (box->iconic)
411 {
412 g_object_bind_property (source: item, source_property: "verb-icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
413 g_object_set (object: widget, first_property_name: "iconic", TRUE, NULL);
414 }
415 else if (box->inline_buttons)
416 {
417 g_object_bind_property (source: item, source_property: "verb-icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
418 g_object_set (object: widget, first_property_name: "iconic", TRUE, NULL);
419 gtk_widget_add_css_class (widget, css_class: "flat");
420 }
421 else if (box->circular)
422 {
423 g_object_bind_property (source: item, source_property: "verb-icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
424 g_object_set (object: widget, first_property_name: "iconic", TRUE, NULL);
425 gtk_widget_add_css_class (widget, css_class: "circular");
426 }
427 else
428 g_object_bind_property (source: item, source_property: "icon", target: widget, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
429
430 g_object_bind_property (source: item, source_property: "use-markup", target: widget, target_property: "use-markup", flags: G_BINDING_SYNC_CREATE);
431 g_object_bind_property (source: item, source_property: "sensitive", target: widget, target_property: "sensitive", flags: G_BINDING_SYNC_CREATE);
432 g_object_bind_property (source: item, source_property: "role", target: widget, target_property: "role", flags: G_BINDING_SYNC_CREATE);
433 g_object_bind_property (source: item, source_property: "toggled", target: widget, target_property: "active", flags: G_BINDING_SYNC_CREATE);
434 g_object_bind_property (source: item, source_property: "accel", target: widget, target_property: "accel", flags: G_BINDING_SYNC_CREATE);
435 g_signal_connect (widget, "clicked", G_CALLBACK (gtk_popover_item_activate), item);
436 }
437
438 g_object_set_data_full (G_OBJECT (widget), key: "GtkMenuTrackerItem", g_object_ref (item), destroy: g_object_unref);
439
440 if (box->circular)
441 {
442 gtk_widget_set_hexpand (widget, TRUE);
443 gtk_widget_set_halign (widget, align: GTK_ALIGN_CENTER);
444 }
445 else
446 {
447 gtk_widget_set_halign (widget, align: GTK_ALIGN_FILL);
448 }
449 gtk_box_append (GTK_BOX (box->item_box), child: widget);
450
451 if (position == 0)
452 gtk_box_reorder_child_after (GTK_BOX (box->item_box), child: widget, NULL);
453 else
454 {
455 GtkWidget *sibling = gtk_widget_get_first_child (GTK_WIDGET (box->item_box));
456 int i;
457 for (i = 1; i < position; i++)
458 sibling = gtk_widget_get_next_sibling (widget: sibling);
459 gtk_box_reorder_child_after (GTK_BOX (box->item_box), child: widget, sibling);
460 }
461
462 if (box->circular)
463 {
464 GtkWidget *c1, *c2, *c3;
465
466 /* special-case the n > 2 case */
467 c1 = gtk_widget_get_first_child (GTK_WIDGET (box->item_box));
468 if ((c2 = gtk_widget_get_next_sibling (widget: c1)) != NULL &&
469 (c3 = gtk_widget_get_next_sibling (widget: c2)) != NULL)
470 {
471 gtk_widget_set_halign (widget: c1, align: GTK_ALIGN_START);
472 while (c3 != NULL)
473 {
474 gtk_widget_set_halign (widget: c2, align: GTK_ALIGN_CENTER);
475 c2 = c3;
476 c3 = gtk_widget_get_next_sibling (widget: c3);
477 }
478 gtk_widget_set_halign (widget: c2, align: GTK_ALIGN_END);
479 }
480 }
481
482 gtk_menu_section_box_schedule_separator_sync (box);
483}
484
485static void
486gtk_menu_section_box_init (GtkMenuSectionBox *box)
487{
488 GtkWidget *item_box;
489
490 gtk_orientable_set_orientation (GTK_ORIENTABLE (box), orientation: GTK_ORIENTATION_VERTICAL);
491
492 box->toplevel = box;
493
494 item_box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
495 box->item_box = GTK_BOX (item_box);
496 gtk_box_append (GTK_BOX (box), child: item_box);
497 gtk_widget_set_halign (GTK_WIDGET (item_box), align: GTK_ALIGN_FILL);
498 gtk_widget_set_halign (GTK_WIDGET (box), align: GTK_ALIGN_FILL);
499}
500
501static void
502gtk_menu_section_box_dispose (GObject *object)
503{
504 GtkMenuSectionBox *box = GTK_MENU_SECTION_BOX (object);
505
506 if (box->separator_sync_idle)
507 {
508 g_source_remove (tag: box->separator_sync_idle);
509 box->separator_sync_idle = 0;
510 }
511
512 g_clear_object (&box->separator);
513
514 if (box->tracker)
515 {
516 gtk_menu_tracker_free (tracker: box->tracker);
517 box->tracker = NULL;
518 }
519
520 g_clear_object (&box->indicators);
521 g_clear_pointer (&box->custom_slots, g_hash_table_unref);
522
523 G_OBJECT_CLASS (gtk_menu_section_box_parent_class)->dispose (object);
524}
525
526static void
527gtk_menu_section_box_class_init (GtkMenuSectionBoxClass *class)
528{
529 G_OBJECT_CLASS (class)->dispose = gtk_menu_section_box_dispose;
530}
531
532static void
533update_popover_position_cb (GObject *source,
534 GParamSpec *spec,
535 gpointer *user_data)
536{
537 GtkPopover *popover = GTK_POPOVER (source);
538 GtkMenuSectionBox *box = GTK_MENU_SECTION_BOX (user_data);
539 GtkWidget *w;
540
541 GtkPositionType new_pos = gtk_popover_get_position (popover);
542
543 for (w = gtk_widget_get_first_child (widget: gtk_widget_get_parent (GTK_WIDGET (box)));
544 w != NULL;
545 w = gtk_widget_get_next_sibling (widget: w))
546 {
547 if (new_pos == GTK_POS_BOTTOM)
548 gtk_widget_set_valign (widget: w, align: GTK_ALIGN_START);
549 else if (new_pos == GTK_POS_TOP)
550 gtk_widget_set_valign (widget: w, align: GTK_ALIGN_END);
551 else
552 gtk_widget_set_valign (widget: w, align: GTK_ALIGN_CENTER);
553 }
554}
555
556void
557gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover,
558 GMenuModel *model,
559 GtkPopoverMenuFlags flags)
560{
561 GtkMenuSectionBox *box;
562
563 box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
564 box->indicators = gtk_size_group_new (mode: GTK_SIZE_GROUP_HORIZONTAL);
565 box->custom_slots = g_hash_table_new (hash_func: g_str_hash, key_equal_func: g_str_equal);
566 box->flags = flags;
567
568 gtk_popover_menu_add_submenu (popover, GTK_WIDGET (box), name: "main");
569
570 box->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (_gtk_widget_get_action_muxer (GTK_WIDGET (box), TRUE)),
571 model, TRUE, FALSE, FALSE, NULL,
572 insert_func: gtk_menu_section_box_insert_func,
573 remove_func: gtk_menu_section_box_remove_func, user_data: box);
574
575 g_signal_connect (G_OBJECT (popover), "notify::position", G_CALLBACK (update_popover_position_cb), box);
576}
577
578static void
579gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
580 GtkMenuSectionBox *toplevel,
581 GtkWidget *focus,
582 const char *name)
583{
584 GtkMenuSectionBox *box;
585 GtkWidget *button;
586
587 box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
588 box->indicators = gtk_size_group_new (mode: GTK_SIZE_GROUP_HORIZONTAL);
589 box->custom_slots = g_hash_table_ref (hash_table: toplevel->custom_slots);
590 box->flags = toplevel->flags;
591
592 button = g_object_new (GTK_TYPE_MODEL_BUTTON,
593 first_property_name: "menu-name", name,
594 "role", GTK_BUTTON_ROLE_TITLE,
595 NULL);
596
597 g_object_bind_property (source: item, source_property: "label", target: button, target_property: "text", flags: G_BINDING_SYNC_CREATE);
598 g_object_bind_property (source: item, source_property: "icon", target: button, target_property: "icon", flags: G_BINDING_SYNC_CREATE);
599
600 g_object_set_data (G_OBJECT (button), key: "focus", data: focus);
601 g_object_set_data (G_OBJECT (focus), key: "focus", data: button);
602
603 gtk_box_insert_child_after (GTK_BOX (box), child: button, NULL);
604
605 g_signal_connect (focus, "clicked", G_CALLBACK (open_submenu), item);
606 g_signal_connect (button, "clicked", G_CALLBACK (close_submenu), item);
607
608 gtk_stack_add_named (GTK_STACK (gtk_widget_get_ancestor (GTK_WIDGET (toplevel), GTK_TYPE_STACK)),
609 GTK_WIDGET (box), name: gtk_menu_tracker_item_get_label (self: item));
610
611 box->tracker = gtk_menu_tracker_new_for_item_link (item, G_MENU_LINK_SUBMENU, FALSE, FALSE,
612 insert_func: gtk_menu_section_box_insert_func,
613 remove_func: gtk_menu_section_box_remove_func,
614 user_data: box);
615}
616
617static GtkWidget *
618gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
619 GtkMenuSectionBox *parent)
620{
621 GtkMenuSectionBox *box;
622 const char *label;
623 const char *hint;
624 const char *text_direction;
625
626 box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
627 box->indicators = g_object_ref (parent->indicators);
628 box->custom_slots = g_hash_table_ref (hash_table: parent->toplevel->custom_slots);
629 box->toplevel = parent->toplevel;
630 box->depth = parent->depth + 1;
631 box->flags = parent->flags;
632
633 label = gtk_menu_tracker_item_get_label (self: item);
634 hint = gtk_menu_tracker_item_get_display_hint (self: item);
635 text_direction = gtk_menu_tracker_item_get_text_direction (self: item);
636
637 if (hint && g_str_equal (v1: hint, v2: "horizontal-buttons"))
638 {
639 gtk_box_set_homogeneous (box: box->item_box, TRUE);
640 gtk_orientable_set_orientation (GTK_ORIENTABLE (box->item_box), orientation: GTK_ORIENTATION_HORIZONTAL);
641 gtk_widget_add_css_class (GTK_WIDGET (box->item_box), css_class: "linked");
642 gtk_widget_add_css_class (GTK_WIDGET (box->item_box), css_class: "horizontal-buttons");
643 box->iconic = TRUE;
644
645 if (text_direction)
646 {
647 GtkTextDirection dir = GTK_TEXT_DIR_NONE;
648
649 if (g_str_equal (v1: text_direction, v2: "rtl"))
650 dir = GTK_TEXT_DIR_RTL;
651 else if (g_str_equal (v1: text_direction, v2: "ltr"))
652 dir = GTK_TEXT_DIR_LTR;
653
654 gtk_widget_set_direction (GTK_WIDGET (box->item_box), dir);
655 }
656 }
657 else if (hint && g_str_equal (v1: hint, v2: "inline-buttons"))
658 {
659 GtkWidget *item_box;
660 GtkWidget *spacer;
661
662 box->inline_buttons = TRUE;
663
664 gtk_orientable_set_orientation (GTK_ORIENTABLE (box->item_box), orientation: GTK_ORIENTATION_HORIZONTAL);
665 gtk_widget_add_css_class (GTK_WIDGET (box->item_box), css_class: "inline-buttons");
666
667 spacer = gtk_builtin_icon_new (css_name: "none");
668 gtk_box_append (GTK_BOX (box->item_box), child: spacer);
669 gtk_size_group_add_widget (size_group: box->indicators, widget: spacer);
670
671 if (label != NULL)
672 {
673 GtkWidget *title;
674
675 title = gtk_label_new (str: label);
676 gtk_widget_set_hexpand (widget: title, TRUE);
677 gtk_widget_set_halign (widget: title, align: GTK_ALIGN_START);
678 g_object_bind_property (source: item, source_property: "label", target: title, target_property: "label", flags: G_BINDING_SYNC_CREATE);
679 gtk_box_append (GTK_BOX (box->item_box), child: title);
680 }
681
682 item_box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
683 gtk_box_append (GTK_BOX (box->item_box), child: item_box);
684 box->item_box = GTK_BOX (item_box);
685 }
686 else if (hint && g_str_equal (v1: hint, v2: "circular-buttons"))
687 {
688 gtk_box_set_homogeneous (box: box->item_box, TRUE);
689 gtk_orientable_set_orientation (GTK_ORIENTABLE (box->item_box), orientation: GTK_ORIENTATION_HORIZONTAL);
690 gtk_widget_add_css_class (GTK_WIDGET (box->item_box), css_class: "circular-buttons");
691 box->circular = TRUE;
692 }
693
694 if (label != NULL && !box->inline_buttons)
695 {
696 GtkWidget *separator;
697 GtkWidget *title;
698
699 box->separator = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
700 g_object_ref_sink (box->separator);
701
702 separator = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL);
703 gtk_widget_set_valign (widget: separator, align: GTK_ALIGN_CENTER);
704 gtk_widget_set_hexpand (widget: separator, TRUE);
705 gtk_box_append (GTK_BOX (box->separator), child: separator);
706
707 title = gtk_label_new (str: label);
708 g_object_bind_property (source: item, source_property: "label", target: title, target_property: "label", flags: G_BINDING_SYNC_CREATE);
709 gtk_widget_add_css_class (widget: title, css_class: "separator");
710 gtk_widget_set_halign (widget: title, align: GTK_ALIGN_START);
711 gtk_label_set_xalign (GTK_LABEL (title), xalign: 0.0);
712 gtk_widget_add_css_class (widget: title, css_class: "title");
713 gtk_box_append (GTK_BOX (box->separator), child: title);
714 }
715 else
716 {
717 box->separator = gtk_separator_new (orientation: GTK_ORIENTATION_HORIZONTAL);
718 g_object_ref_sink (box->separator);
719 }
720
721 box->tracker = gtk_menu_tracker_new_for_item_link (item, G_MENU_LINK_SECTION, FALSE, FALSE,
722 insert_func: gtk_menu_section_box_insert_func,
723 remove_func: gtk_menu_section_box_remove_func,
724 user_data: box);
725
726 return GTK_WIDGET (box);
727}
728
729gboolean
730gtk_menu_section_box_add_custom (GtkPopoverMenu *popover,
731 GtkWidget *child,
732 const char *id)
733{
734 GtkWidget *stack;
735 GtkMenuSectionBox *box;
736 GtkWidget *slot;
737
738 stack = gtk_popover_menu_get_stack (menu: popover);
739 box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"));
740 if (box == NULL)
741 return FALSE;
742
743 slot = (GtkWidget *)g_hash_table_lookup (hash_table: box->custom_slots, key: id);
744
745 if (slot == NULL)
746 return FALSE;
747
748 if (gtk_widget_get_first_child (widget: slot))
749 return FALSE;
750
751 gtk_widget_insert_before (widget: child, parent: slot, NULL);
752 return TRUE;
753}
754
755gboolean
756gtk_menu_section_box_remove_custom (GtkPopoverMenu *popover,
757 GtkWidget *child)
758{
759 GtkWidget *stack;
760 GtkMenuSectionBox *box;
761 GtkWidget *parent;
762 const char *id;
763 GtkWidget *slot;
764
765 stack = gtk_popover_menu_get_stack (menu: popover);
766 box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"));
767 if (box == NULL)
768 return FALSE;
769
770 parent = gtk_widget_get_parent (widget: child);
771
772 id = (const char *) g_object_get_data (G_OBJECT (parent), key: "slot-id");
773 g_return_val_if_fail (id != NULL, FALSE);
774
775 slot = (GtkWidget *)g_hash_table_lookup (hash_table: box->custom_slots, key: id);
776
777 if (slot != parent)
778 return FALSE;
779
780 gtk_widget_unparent (widget: child);
781
782 return TRUE;
783}
784

source code of gtk/gtk/gtkmenusectionbox.c