From 8157abe591059116cbea1a8ad7007fdd061ab0ba Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 20 Oct 2020 14:26:45 -0400 Subject: [PATCH 1/3] popovermenu: Allow custom items Add a way to add children at certain places in the generated menu for both GtkPopoverMenu and GtkPopoverMenuBar. New apis: gtk_popover_menu_add_child gtk_popover_menu_remove_child gtk_popover_menu_bar_add_child gtk_popover_menu_bar_remove_child Fixes: #3260 --- docs/reference/gtk/gtk4-sections.txt | 4 ++ gtk/gtkmenusectionbox.c | 76 +++++++++++++++++++++++++++- gtk/gtkmenusectionboxprivate.h | 7 +++ gtk/gtkmenutrackeritem.c | 10 ++++ gtk/gtkmenutrackeritemprivate.h | 2 + gtk/gtkpopovermenu.c | 49 ++++++++++++++++++ gtk/gtkpopovermenu.h | 8 +++ gtk/gtkpopovermenubar.c | 70 +++++++++++++++++++++++++ gtk/gtkpopovermenubar.h | 8 +++ 9 files changed, 233 insertions(+), 1 deletion(-) 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 From af6c3017fb0010dcba884da9bef686a92ae7215f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 26 Oct 2020 13:21:28 -0400 Subject: [PATCH 2/3] popovermenu: Allow adding custom items in ui files Support to fill custom child slots in both GtkPopoverMenus and GtkPopoverMenuBars that are created in ui files. --- gtk/gtkpopovermenu.c | 35 +++++++++++++++++++++++++++++++++-- gtk/gtkpopovermenubar.c | 32 +++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index 4c33b0011d..751d14c176 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -35,6 +35,7 @@ #include "gtkpopovermenubar.h" #include "gtkshortcutmanager.h" #include "gtkshortcutcontroller.h" +#include "gtkbuildable.h" /** @@ -99,7 +100,8 @@ * 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(). + * with gtk_popover_menu_add_child(), gtk_popover_menu_bar_add_child(), or + * in the ui file with ``. * * The following attributes are used when constructing sections: * - "label": a user-visible string to use as section heading @@ -155,7 +157,11 @@ enum { PROP_MENU_MODEL }; -G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER) +static void gtk_popover_menu_buildable_iface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_popover_menu_buildable_iface_init)) GtkWidget * gtk_popover_menu_get_parent_menu (GtkPopoverMenu *menu) @@ -568,6 +574,31 @@ gtk_popover_menu_class_init (GtkPopoverMenuClass *klass) gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_MENU); } +static GtkBuildableIface *parent_buildable_iface; + +static void +gtk_popover_menu_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + if (GTK_IS_WIDGET (child)) + { + if (!gtk_popover_menu_add_child (GTK_POPOVER_MENU (buildable), GTK_WIDGET (child), type)) + g_warning ("No such custom attribute: %s", type); + } + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +gtk_popover_menu_buildable_iface_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->add_child = gtk_popover_menu_buildable_add_child; +} + /** * gtk_popover_menu_new: * diff --git a/gtk/gtkpopovermenubar.c b/gtk/gtkpopovermenubar.c index 0d5658c224..8e7f2a22d0 100644 --- a/gtk/gtkpopovermenubar.c +++ b/gtk/gtkpopovermenubar.c @@ -73,6 +73,7 @@ #include "gtkwidgetprivate.h" #include "gtkmain.h" #include "gtknative.h" +#include "gtkbuildable.h" #define GTK_TYPE_POPOVER_MENU_BAR_ITEM (gtk_popover_menu_bar_item_get_type ()) #define GTK_POPOVER_MENU_BAR_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_POPOVER_MENU_BAR_ITEM, GtkPopoverMenuBarItem)) @@ -384,7 +385,11 @@ enum static GParamSpec * bar_props[LAST_PROP]; -G_DEFINE_TYPE (GtkPopoverMenuBar, gtk_popover_menu_bar, GTK_TYPE_WIDGET) +static void gtk_popover_menu_bar_buildable_iface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkPopoverMenuBar, gtk_popover_menu_bar, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_popover_menu_bar_buildable_iface_init)) static void tracker_remove (int position, @@ -650,6 +655,31 @@ gtk_popover_menu_bar_init (GtkPopoverMenuBar *bar) gtk_widget_add_controller (GTK_WIDGET (bar), controller); } +static GtkBuildableIface *parent_buildable_iface; + +static void +gtk_popover_menu_bar_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + if (GTK_IS_WIDGET (child)) + { + if (!gtk_popover_menu_bar_add_child (GTK_POPOVER_MENU_BAR (buildable), GTK_WIDGET (child), type)) + g_warning ("No such custom attribute: %s", type); + } + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +gtk_popover_menu_bar_buildable_iface_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + + iface->add_child = gtk_popover_menu_bar_buildable_add_child; +} + /** * gtk_popover_menu_bar_new_from_model: * @model: (allow-none): a #GMenuModel, or %NULL From 0faadef36f5bbc62013327dea8264ec82ade8f34 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 26 Oct 2020 12:58:23 -0400 Subject: [PATCH 3/3] widget-factory: Add a scale to the gear menu This tests the custom menuitem support, and lets you control the transition duration for the main stack. --- demos/widget-factory/widget-factory.c | 11 +++++++++++ demos/widget-factory/widget-factory.ui | 26 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/demos/widget-factory/widget-factory.c b/demos/widget-factory/widget-factory.c index 021b69e640..1d81031a8e 100644 --- a/demos/widget-factory/widget-factory.c +++ b/demos/widget-factory/widget-factory.c @@ -91,6 +91,16 @@ change_theme_state (GSimpleAction *action, static GtkWidget *page_stack; +static void +transition_speed_changed (GtkRange *range, + gpointer data) +{ + double value; + + value = gtk_range_get_value (range); + gtk_stack_set_transition_duration (GTK_STACK (page_stack), (int)value); +} + static void change_transition_state (GSimpleAction *action, GVariant *state, @@ -2022,6 +2032,7 @@ activate (GApplication *app) "validate_more_details", (GCallback)validate_more_details, "mode_switch_state_set", (GCallback)mode_switch_state_set, "level_scale_value_changed", (GCallback)level_scale_value_changed, + "transition_speed_changed", (GCallback)transition_speed_changed, NULL); gtk_builder_set_scope (builder, scope); g_object_unref (scope); diff --git a/demos/widget-factory/widget-factory.ui b/demos/widget-factory/widget-factory.ui index 0b383655ee..36bee582cf 100644 --- a/demos/widget-factory/widget-factory.ui +++ b/demos/widget-factory/widget-factory.ui @@ -1,6 +1,6 @@ - +
Get Busy @@ -40,6 +40,9 @@ Transition Pages win.transition + + transition-speed +
@@ -444,7 +447,26 @@ Suspendisse feugiat quam quis dolor accumsan cursus. center - gear_menu + + + gear_menu_model + + + + + 0 + 1000 + 250 + 50 + 50 + 0 + + + + + + + open-menu-symbolic