diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index a0e123947b..b6f7cf2d25 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -6177,6 +6177,8 @@ gtk_popover_menu_new_from_model gtk_popover_menu_new_from_model_full gtk_popover_menu_set_menu_model gtk_popover_menu_get_menu_model +gtk_popover_menu_add_child +gtk_popover_menu_remove_child GTK_TYPE_POPOVER_MENU @@ -6195,6 +6197,8 @@ GtkPopoverMenuBar gtk_popover_menu_bar_new_from_model gtk_popover_menu_bar_set_menu_model gtk_popover_menu_bar_get_menu_model +gtk_popover_menu_bar_add_child +gtk_popover_menu_bar_remove_child GTK_TYPE_POPOVER_MENU_BAR diff --git a/gtk/gtkmenusectionbox.c b/gtk/gtkmenusectionbox.c index 08f5ca1288..9f988d98f5 100644 --- a/gtk/gtkmenusectionbox.c +++ b/gtk/gtkmenusectionbox.c @@ -32,6 +32,8 @@ #include "gtkpopovermenuprivate.h" #include "gtkorientable.h" #include "gtkbuiltiniconprivate.h" +#include "gtkgizmoprivate.h" +#include "gtkbinlayout.h" typedef GtkBoxClass GtkMenuSectionBoxClass; @@ -50,6 +52,7 @@ struct _GtkMenuSectionBox int depth; GtkPopoverMenuFlags flags; GtkSizeGroup *indicators; + GHashTable *custom_slots; }; typedef struct @@ -341,6 +344,22 @@ gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item, g_free (name); } } + else if (gtk_menu_tracker_item_get_custom (item)) + { + const char *id = gtk_menu_tracker_item_get_custom (item); + + widget = gtk_gizmo_new ("widget", NULL, NULL, NULL, NULL, NULL, NULL); + gtk_widget_set_layout_manager (widget, gtk_bin_layout_new ()); + + if (g_hash_table_lookup (box->custom_slots, id)) + g_warning ("Duplicate custom ID: %s", id); + else + { + char *slot_id = g_strdup (id); + g_object_set_data_full (G_OBJECT (widget), "slot-id", slot_id, g_free); + g_hash_table_insert (box->custom_slots, slot_id, widget); + } + } else { widget = g_object_new (GTK_TYPE_MODEL_BUTTON, @@ -458,6 +477,7 @@ gtk_menu_section_box_dispose (GObject *object) } g_clear_object (&box->indicators); + g_clear_pointer (&box->custom_slots, g_hash_table_unref); G_OBJECT_CLASS (gtk_menu_section_box_parent_class)->dispose (object); } @@ -499,8 +519,9 @@ gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover, { GtkMenuSectionBox *box; - box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); + box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + box->custom_slots = g_hash_table_new (g_str_hash, g_str_equal); box->flags = flags; gtk_popover_menu_add_submenu (popover, GTK_WIDGET (box), "main"); @@ -524,6 +545,7 @@ gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item, box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + box->custom_slots = g_hash_table_ref (toplevel->custom_slots); box->flags = toplevel->flags; button = g_object_new (GTK_TYPE_MODEL_BUTTON, @@ -562,6 +584,7 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item, box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); box->indicators = g_object_ref (parent->indicators); + box->custom_slots = g_hash_table_ref (parent->toplevel->custom_slots); box->toplevel = parent->toplevel; box->depth = parent->depth + 1; box->flags = parent->flags; @@ -661,3 +684,54 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item, return GTK_WIDGET (box); } + +gboolean +gtk_menu_section_box_add_custom (GtkPopoverMenu *popover, + GtkWidget *child, + const char *id) +{ + GtkWidget *stack; + GtkMenuSectionBox *box; + GtkWidget *slot; + + stack = gtk_popover_get_child (GTK_POPOVER (popover)); + box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main")); + + slot = (GtkWidget *)g_hash_table_lookup (box->custom_slots, id); + + if (slot == NULL) + return FALSE; + + if (gtk_widget_get_first_child (slot)) + return FALSE; + + gtk_widget_insert_before (child, slot, NULL); + return TRUE; +} + +gboolean +gtk_menu_section_box_remove_custom (GtkPopoverMenu *popover, + GtkWidget *child) +{ + GtkWidget *stack; + GtkMenuSectionBox *box; + GtkWidget *parent; + const char *id; + GtkWidget *slot; + + stack = gtk_popover_get_child (GTK_POPOVER (popover)); + box = GTK_MENU_SECTION_BOX (gtk_stack_get_child_by_name (GTK_STACK (stack), "main")); + parent = gtk_widget_get_parent (child); + + id = (const char *) g_object_get_data (G_OBJECT (parent), "slot-id"); + g_return_val_if_fail (id != NULL, FALSE); + + slot = (GtkWidget *)g_hash_table_lookup (box->custom_slots, id); + + if (slot != parent) + return FALSE; + + gtk_widget_unparent (child); + + return TRUE; +} diff --git a/gtk/gtkmenusectionboxprivate.h b/gtk/gtkmenusectionboxprivate.h index 05d6bd294e..232d62ee0c 100644 --- a/gtk/gtkmenusectionboxprivate.h +++ b/gtk/gtkmenusectionboxprivate.h @@ -45,6 +45,13 @@ void gtk_menu_section_box_new_toplevel (GtkPopo GMenuModel *model, GtkPopoverMenuFlags flags); +gboolean gtk_menu_section_box_add_custom (GtkPopoverMenu *popover, + GtkWidget *child, + const char *id); + +gboolean gtk_menu_section_box_remove_custom (GtkPopoverMenu *popover, + GtkWidget *child); + G_END_DECLS #endif /* __GTK_MENU_SECTION_BOX_PRIVATE_H__ */ diff --git a/gtk/gtkmenutrackeritem.c b/gtk/gtkmenutrackeritem.c index f88009c7d3..93536df960 100644 --- a/gtk/gtkmenutrackeritem.c +++ b/gtk/gtkmenutrackeritem.c @@ -715,6 +715,16 @@ gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self) return special; } +const char * +gtk_menu_tracker_item_get_custom (GtkMenuTrackerItem *self) +{ + const char *custom = NULL; + + g_menu_item_get_attribute (self->item, "custom", "&s", &custom); + + return custom; +} + const char * gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self) { diff --git a/gtk/gtkmenutrackeritemprivate.h b/gtk/gtkmenutrackeritemprivate.h index a3c9164053..6ebbc3764c 100644 --- a/gtk/gtkmenutrackeritemprivate.h +++ b/gtk/gtkmenutrackeritemprivate.h @@ -51,6 +51,8 @@ GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActi const char * gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self); +const char * gtk_menu_tracker_item_get_custom (GtkMenuTrackerItem *self); + const char * gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self); const char * gtk_menu_tracker_item_get_text_direction (GtkMenuTrackerItem *self); diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index dab6635530..4c33b0011d 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -98,6 +98,8 @@ * - "hidden-when": a string used to determine when the item will be hidden. * Possible values include "action-disabled", "action-missing", "macos-menubar". * This is mainly useful for exported menus, see gtk_application_set_menubar(). + * - "custom": a string used to match against the ID of a custom child added + * with gtk_popover_menu_add_child() or gtk_popover_menu_bar_add_child(). * * The following attributes are used when constructing sections: * - "label": a user-visible string to use as section heading @@ -733,3 +735,50 @@ gtk_popover_menu_get_menu_model (GtkPopoverMenu *popover) return popover->model; } + +/** + * gtk_popover_menu_add_child: + * @popover: a #GtkPopoverMenu + * @child: the #GtkWidget to add + * @id: the ID to insert @child at + * + * Adds a custom widget to a generated menu. + * + * For this to work, the menu model of @popover must have an + * item with a `custom` attribute that matches @id. + * + * Returns: %TRUE if @id was found and the widget added + */ +gboolean +gtk_popover_menu_add_child (GtkPopoverMenu *popover, + GtkWidget *child, + const char *id) +{ + + g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + return gtk_menu_section_box_add_custom (popover, child, id); +} + +/** + * gtk_popover_menu_remove_child: + * @popover: a #GtkPopoverMenu + * @child: the #GtkWidget to remove + * + * Removes a widget that has previously been added with + * gtk_popover_menu_add_child(). + * + * Returns: %TRUE if the widget was removed + */ +gboolean +gtk_popover_menu_remove_child (GtkPopoverMenu *popover, + GtkWidget *child) +{ + + g_return_val_if_fail (GTK_IS_POPOVER_MENU (popover), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE); + + return gtk_menu_section_box_remove_custom (popover, child); +} diff --git a/gtk/gtkpopovermenu.h b/gtk/gtkpopovermenu.h index f9f8457b27..2973102be1 100644 --- a/gtk/gtkpopovermenu.h +++ b/gtk/gtkpopovermenu.h @@ -61,6 +61,14 @@ void gtk_popover_menu_set_menu_model (GtkPopoverMenu *popover, GDK_AVAILABLE_IN_ALL GMenuModel *gtk_popover_menu_get_menu_model (GtkPopoverMenu *popover); +GDK_AVAILABLE_IN_ALL +gboolean gtk_popover_menu_add_child (GtkPopoverMenu *popover, + GtkWidget *child, + const char *id); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_popover_menu_remove_child (GtkPopoverMenu *popover, + GtkWidget *child); G_END_DECLS diff --git a/gtk/gtkpopovermenubar.c b/gtk/gtkpopovermenubar.c index 1401c998eb..0d5658c224 100644 --- a/gtk/gtkpopovermenubar.c +++ b/gtk/gtkpopovermenubar.c @@ -733,3 +733,73 @@ gtk_popover_menu_bar_select_first (GtkPopoverMenuBar *bar) item = GTK_POPOVER_MENU_BAR_ITEM (gtk_widget_get_first_child (GTK_WIDGET (bar))); set_active_item (bar, item, TRUE); } + +/** + * gtk_popover_menu_bar_add_child: + * @bar: a #GtkPopoverMenuBar + * @child: the #GtkWidget to add + * @id: the ID to insert @child at + * + * Adds a custom widget to a generated menubar. + * + * For this to work, the menu model of @bar must have an + * item with a `custom` attribute that matches @id. + * + * Returns: %TRUE if @id was found and the widget added + */ +gboolean +gtk_popover_menu_bar_add_child (GtkPopoverMenuBar *bar, + GtkWidget *child, + const char *id) +{ + GtkWidget *item; + + g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE); + g_return_val_if_fail (id != NULL, FALSE); + + for (item = gtk_widget_get_first_child (GTK_WIDGET (bar)); + item; + item = gtk_widget_get_next_sibling (item)) + { + GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover; + + if (gtk_popover_menu_add_child (GTK_POPOVER_MENU (popover), child, id)) + return TRUE; + } + + return FALSE; +} + +/** + * gtk_popover_menu_bar_remove_child: + * @bar: a #GtkPopoverMenuBar + * @child: the #GtkWidget to remove + * + * Removes a widget that has previously been added with + * gtk_popover_menu_bar_add_child(). + * + * Returns: %TRUE if the widget was removed + */ +gboolean +gtk_popover_menu_bar_remove_child (GtkPopoverMenuBar *bar, + GtkWidget *child) +{ + GtkWidget *item; + + g_return_val_if_fail (GTK_IS_POPOVER_MENU_BAR (bar), FALSE); + g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE); + + for (item = gtk_widget_get_first_child (GTK_WIDGET (bar)); + item; + item = gtk_widget_get_next_sibling (item)) + { + GtkPopover *popover = GTK_POPOVER_MENU_BAR_ITEM (item)->popover; + + if (gtk_popover_menu_remove_child (GTK_POPOVER_MENU (popover), child)) + return TRUE; + } + + return FALSE; +} + diff --git a/gtk/gtkpopovermenubar.h b/gtk/gtkpopovermenubar.h index 7fa1253128..08126f9d63 100644 --- a/gtk/gtkpopovermenubar.h +++ b/gtk/gtkpopovermenubar.h @@ -47,6 +47,14 @@ void gtk_popover_menu_bar_set_menu_model (GtkPopoverMenuBar *bar, GDK_AVAILABLE_IN_ALL GMenuModel * gtk_popover_menu_bar_get_menu_model (GtkPopoverMenuBar *bar); +GDK_AVAILABLE_IN_ALL +gboolean gtk_popover_menu_bar_add_child (GtkPopoverMenuBar *bar, + GtkWidget *child, + const char *id); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_popover_menu_bar_remove_child (GtkPopoverMenuBar *bar, + GtkWidget *child); G_END_DECLS