From 7b08d13aeaf329a8d1ecb24b7c28087c2e6c8f83 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 10 Feb 2019 18:22:54 -0500 Subject: [PATCH] stack: Provide a selection model Make GtkStack expose a selection model for its pages. The model can be used to track changes to the pages, and to change what child is visible. --- gtk/gtkstack.c | 240 +++++++++++++++++++++++++++++++++++++++++++++++-- gtk/gtkstack.h | 5 ++ 2 files changed, 239 insertions(+), 6 deletions(-) diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c index 5e430f6709..7a1c4cb91f 100644 --- a/gtk/gtkstack.c +++ b/gtk/gtkstack.c @@ -30,6 +30,8 @@ #include "gtksettingsprivate.h" #include "gtksnapshot.h" #include "gtkwidgetprivate.h" +#include "gtksingleselection.h" +#include "gtklistlistmodelprivate.h" #include "a11y/gtkstackaccessible.h" #include "a11y/gtkstackaccessibleprivate.h" #include @@ -138,6 +140,8 @@ typedef struct { GtkStackTransitionType active_transition_type; + GtkSelectionModel *pages; + } GtkStackPrivate; static void gtk_stack_buildable_interface_init (GtkBuildableIface *iface); @@ -432,6 +436,124 @@ gtk_stack_page_class_init (GtkStackPageClass *class) g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_child_props); } +#define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ()) +G_DECLARE_FINAL_TYPE (GtkStackPages, gtk_stack_pages, GTK, STACK_PAGES, GObject) + +struct _GtkStackPages +{ + GObject parent_instance; + GtkStack *stack; +}; + +struct _GtkStackPagesClass +{ + GObjectClass parent_class; +}; + +static GType +gtk_stack_pages_get_item_type (GListModel *model) +{ + return GTK_TYPE_STACK_PAGE; +} + +static guint +gtk_stack_pages_get_n_items (GListModel *model) +{ + GtkStackPages *pages = GTK_STACK_PAGES (model); + GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack); + + return g_list_length (priv->children); +} + +static gpointer +gtk_stack_pages_get_item (GListModel *model, + guint position) +{ + GtkStackPages *pages = GTK_STACK_PAGES (model); + GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack); + GtkStackPage *page; + + page = g_list_nth_data (priv->children, position); + + return g_object_ref (page); +} + +static void +gtk_stack_pages_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_stack_pages_get_item_type; + iface->get_n_items = gtk_stack_pages_get_n_items; + iface->get_item = gtk_stack_pages_get_item; +} + +static gboolean +gtk_stack_pages_is_selected (GtkSelectionModel *model, + guint position) +{ + GtkStackPages *pages = GTK_STACK_PAGES (model); + GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack); + GtkStackPage *page; + + page = GTK_STACK_PAGE (g_list_model_get_item (G_LIST_MODEL (model), position)); + g_object_unref (page); + + return page == priv->visible_child; +} + +static void set_visible_child (GtkStack *stack, + GtkStackPage *child_info, + GtkStackTransitionType transition_type, + guint transition_duration); + +static gboolean +gtk_stack_pages_select_item (GtkSelectionModel *model, + guint position, + gboolean exclusive) +{ + GtkStackPages *pages = GTK_STACK_PAGES (model); + GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack); + GtkStackPage *page; + + page = GTK_STACK_PAGE (g_list_model_get_item (G_LIST_MODEL (model), position)); + g_object_unref (page); + + set_visible_child (pages->stack, page, priv->transition_type, priv->transition_duration); + + return TRUE; +} + +static void +gtk_stack_pages_selection_model_init (GtkSelectionModelInterface *iface) +{ + iface->is_selected = gtk_stack_pages_is_selected; + iface->select_item = gtk_stack_pages_select_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkStackPages, gtk_stack_pages, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_stack_pages_list_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, gtk_stack_pages_selection_model_init)) + +static void +gtk_stack_pages_init (GtkStackPages *pages) +{ +} + +static void +gtk_stack_pages_class_init (GtkStackPagesClass *class) +{ +} + +static GtkStackPages * +gtk_stack_pages_new (GtkStack *stack) +{ + GtkStackPages *pages; + + pages = g_object_new (GTK_TYPE_STACK_PAGES, NULL); + pages->stack = stack; + + return pages; +} + static void gtk_stack_add (GtkContainer *widget, GtkWidget *child); static void gtk_stack_remove (GtkContainer *widget, @@ -455,6 +577,7 @@ static void gtk_stack_measure (GtkWidget *widget int *natural, int *minimum_baseline, int *natural_baseline); +static void gtk_stack_dispose (GObject *obj); static void gtk_stack_finalize (GObject *obj); static void gtk_stack_get_property (GObject *object, guint property_id, @@ -490,6 +613,29 @@ gtk_stack_buildable_interface_init (GtkBuildableIface *iface) iface->add_child = gtk_stack_buildable_add_child; } +static void stack_remove (GtkStack *stack, + GtkWidget *child, + gboolean in_dispose); + +static void +remove_child (GtkWidget *child, gpointer user_data) +{ + stack_remove (GTK_STACK (user_data), child, TRUE); +} + +static void +gtk_stack_dispose (GObject *obj) +{ + GtkStack *stack = GTK_STACK (obj); + GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); + + if (priv->pages) + g_list_model_items_changed (G_LIST_MODEL (priv->pages), 0, g_list_length (priv->children), 0); + gtk_container_foreach (GTK_CONTAINER (obj), remove_child, obj); + + G_OBJECT_CLASS (gtk_stack_parent_class)->dispose (obj); +} + static void gtk_stack_finalize (GObject *obj) { @@ -595,6 +741,7 @@ gtk_stack_class_init (GtkStackClass *klass) object_class->get_property = gtk_stack_get_property; object_class->set_property = gtk_stack_set_property; + object_class->dispose = gtk_stack_dispose; object_class->finalize = gtk_stack_finalize; widget_class->size_allocate = gtk_stack_size_allocate; @@ -1052,6 +1199,8 @@ set_visible_child (GtkStack *stack, GtkWidget *toplevel; GtkWidget *focus; gboolean contains_focus = FALSE; + guint old_pos = 0; + guint new_pos = 0; /* if we are being destroyed, do not bother with transitions * and notifications @@ -1076,6 +1225,22 @@ set_visible_child (GtkStack *stack, if (child_info == priv->visible_child) return; + if (child_info == NULL) + return; + + if (priv->pages) + { + guint position; + for (l = priv->children, position = 0; l != NULL; l = l->next, position++) + { + info = l->data; + if (info == priv->visible_child) + old_pos = position; + else if (info == child_info) + new_pos = position; + } + } + toplevel = gtk_widget_get_toplevel (widget); if (GTK_IS_WINDOW (toplevel)) { @@ -1169,6 +1334,11 @@ set_visible_child (GtkStack *stack, g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_VISIBLE_CHILD_NAME]); + if (priv->pages) + gtk_selection_model_selection_changed (priv->pages, + MIN (old_pos, new_pos), + MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1); + gtk_stack_start_transition (stack, transition_type, transition_duration); } @@ -1305,6 +1475,9 @@ gtk_stack_add_page (GtkStack *stack, gtk_widget_set_child_visible (child_info->widget, FALSE); gtk_widget_set_parent (child_info->widget, GTK_WIDGET (stack)); + if (priv->pages) + g_list_model_items_changed (G_LIST_MODEL (priv->pages), g_list_length (priv->children) - 1, 0, 1); + g_signal_connect (child_info->widget, "notify::visible", G_CALLBACK (stack_child_visibility_notify_cb), stack); @@ -1319,10 +1492,10 @@ gtk_stack_add_page (GtkStack *stack, } static void -gtk_stack_remove (GtkContainer *container, - GtkWidget *child) +stack_remove (GtkStack *stack, + GtkWidget *child, + gboolean in_dispose) { - GtkStack *stack = GTK_STACK (container); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); GtkStackPage *child_info; gboolean was_visible; @@ -1332,7 +1505,7 @@ gtk_stack_remove (GtkContainer *container, return; priv->children = g_list_remove (priv->children, child_info); - + g_signal_handlers_disconnect_by_func (child, stack_child_visibility_notify_cb, stack); @@ -1342,7 +1515,12 @@ gtk_stack_remove (GtkContainer *container, child_info->widget = NULL; if (priv->visible_child == child_info) - set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration); + { + if (in_dispose) + priv->visible_child = NULL; + else + set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration); + } if (priv->last_visible_child == child_info) priv->last_visible_child = NULL; @@ -1351,10 +1529,33 @@ gtk_stack_remove (GtkContainer *container, g_object_unref (child_info); - if ((priv->hhomogeneous || priv->vhomogeneous) && was_visible) + if (!in_dispose && + (priv->hhomogeneous || priv->vhomogeneous) && + was_visible) gtk_widget_queue_resize (GTK_WIDGET (stack)); } +static void +gtk_stack_remove (GtkContainer *container, + GtkWidget *child) +{ + GtkStackPrivate *priv = gtk_stack_get_instance_private (GTK_STACK (container)); + GList *l; + guint position; + + for (l = priv->children, position = 0; l; l = l->next, position++) + { + GtkStackPage *page = l->data; + if (page->widget == child) + break; + } + + stack_remove (GTK_STACK (container), child, FALSE); + + if (priv->pages) + g_list_model_items_changed (G_LIST_MODEL (priv->pages), position, 1, 0); +} + /** * gtk_stack_get_page: * @stack: a #GtkStack @@ -2318,3 +2519,30 @@ gtk_stack_init (GtkStack *stack) priv->transition_duration = 200; priv->transition_type = GTK_STACK_TRANSITION_TYPE_NONE; } + +/** + * gtk_stack_get_pages: + * @stack: a #GtkStack + * + * Returns a #GListModel that contains the pages of the stack, + * and can be used to keep and up-to-date view. The model also + * implements #GtkSelectionModel and can be used to track and + * modify the visible page.. + * + * Returns: (transfer full): a #GtkSelectionModel for the stack's children + */ +GtkSelectionModel * +gtk_stack_get_pages (GtkStack *stack) +{ + GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); + + g_return_val_if_fail (GTK_IS_STACK (stack), NULL); + + if (priv->pages) + return g_object_ref (priv->pages); + + priv->pages = GTK_SELECTION_MODEL (gtk_stack_pages_new (stack)); + g_object_add_weak_pointer (G_OBJECT (priv->pages), (gpointer *)&priv->pages); + + return priv->pages; +} diff --git a/gtk/gtkstack.h b/gtk/gtkstack.h index 6379328041..658ef8bb6a 100644 --- a/gtk/gtkstack.h +++ b/gtk/gtkstack.h @@ -27,6 +27,7 @@ #endif #include +#include G_BEGIN_DECLS @@ -155,6 +156,10 @@ void gtk_stack_set_interpolate_size (GtkStack *stack, gboolean interpolate_size); GDK_AVAILABLE_IN_ALL gboolean gtk_stack_get_interpolate_size (GtkStack *stack); + +GDK_AVAILABLE_IN_ALL +GtkSelectionModel * gtk_stack_get_pages (GtkStack *stack); + G_END_DECLS #endif