diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 55c9f54e04..88f20c3835 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -550,6 +550,8 @@ gtk_list_box_set_header_func gtk_list_box_set_sort_func gtk_list_box_drag_highlight_row gtk_list_box_drag_unhighlight_row +GtkListBoxCreateWidgetFunc +gtk_list_box_bind_model gtk_list_box_row_new gtk_list_box_row_changed diff --git a/gtk/gtklistbox.c b/gtk/gtklistbox.c index a800f84d25..e358415e7d 100644 --- a/gtk/gtklistbox.c +++ b/gtk/gtklistbox.c @@ -99,6 +99,11 @@ typedef struct int n_visible_rows; gboolean in_widget; + + GListModel *bound_model; + GtkListBoxCreateWidgetFunc create_widget_func; + gpointer create_widget_func_data; + GDestroyNotify create_widget_func_data_destroy; } GtkListBoxPrivate; typedef struct @@ -260,6 +265,12 @@ static void gtk_list_box_update_row_styles (GtkListBox *box); static void gtk_list_box_update_row_style (GtkListBox *box, GtkListBoxRow *row); +static void gtk_list_box_bound_model_changed (GListModel *list, + guint position, + guint removed, + guint added, + gpointer user_data); + static GParamSpec *properties[LAST_PROPERTY] = { NULL, }; static guint signals[LAST_SIGNAL] = { 0 }; static GParamSpec *row_properties[LAST_ROW_PROPERTY] = { NULL, }; @@ -374,6 +385,15 @@ gtk_list_box_finalize (GObject *obj) g_sequence_free (priv->children); g_hash_table_unref (priv->header_hash); + if (priv->bound_model) + { + if (priv->create_widget_func_data_destroy) + priv->create_widget_func_data_destroy (priv->create_widget_func_data); + + g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_list_box_bound_model_changed, obj); + g_clear_object (&priv->bound_model); + } + G_OBJECT_CLASS (gtk_list_box_parent_class)->finalize (obj); } @@ -3561,3 +3581,95 @@ gtk_list_box_buildable_interface_init (GtkBuildableIface *iface) { iface->add_child = gtk_list_box_buildable_add_child; } + +static void +gtk_list_box_bound_model_changed (GListModel *list, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + GtkListBox *box = user_data; + GtkListBoxPrivate *priv = BOX_PRIV (user_data); + gint i; + + while (removed--) + { + GtkListBoxRow *row; + + row = gtk_list_box_get_row_at_index (box, position); + gtk_widget_destroy (GTK_WIDGET (row)); + } + + for (i = 0; i < added; i++) + { + GObject *item; + GtkWidget *widget; + + item = g_list_model_get_item (list, position + i); + widget = priv->create_widget_func (item, priv->create_widget_func_data); + gtk_widget_show_all (widget); + gtk_list_box_insert (box, widget, position + i); + + g_object_unref (item); + } +} + +/** + * gtk_list_box_bind_model: + * @box: a #GtkListBox + * @model: (allow-none): the #GListModel to be bound to @box + * @create_widget_func: a function that creates widgets for items + * @user_data: user data passed to @create_widget_func + * @user_data_free_func: function for freeing @user_data + * + * Binds @model to @box. + * + * If @box was already bound to a model, that previous binding is + * destroyed. + * + * The contents of @box are cleared and then filled with widgets that + * represent items from @model. @box is updated whenever @model changes. + * If @model is %NULL, @box is left empty. + * + * It is undefined to add or remove widgets directly (for example, with + * gtk_list_box_insert() or gtk_container_add()) while @box is bound to a + * model. + * + * Since: 3.16 + */ +void +gtk_list_box_bind_model (GtkListBox *box, + GListModel *model, + GtkListBoxCreateWidgetFunc create_widget_func, + gpointer user_data, + GDestroyNotify user_data_free_func) +{ + GtkListBoxPrivate *priv = BOX_PRIV (box); + + g_return_if_fail (GTK_IS_LIST_BOX (box)); + g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); + g_return_if_fail (model == NULL || create_widget_func != NULL); + + if (priv->bound_model) + { + if (priv->create_widget_func_data_destroy) + priv->create_widget_func_data_destroy (priv->create_widget_func_data); + + g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_list_box_bound_model_changed, box); + g_clear_object (&priv->bound_model); + } + + gtk_list_box_forall_internal (GTK_CONTAINER (box), FALSE, (GtkCallback) gtk_widget_destroy, NULL); + + if (model == NULL) + return; + + priv->bound_model = g_object_ref (model); + priv->create_widget_func = create_widget_func; + priv->create_widget_func_data = user_data; + priv->create_widget_func_data_destroy = user_data_free_func; + + g_signal_connect (priv->bound_model, "items-changed", G_CALLBACK (gtk_list_box_bound_model_changed), box); + gtk_list_box_bound_model_changed (model, 0, 0, g_list_model_get_n_items (model), box); +} diff --git a/gtk/gtklistbox.h b/gtk/gtklistbox.h index a4ee2ff705..ad6d851bc0 100644 --- a/gtk/gtklistbox.h +++ b/gtk/gtklistbox.h @@ -169,6 +169,20 @@ typedef void (*GtkListBoxUpdateHeaderFunc) (GtkListBoxRow *row, GtkListBoxRow *before, gpointer user_data); +/** + * GtkListBoxCreateWidgetFunc: + * @item: the item from the model for which to create a widget for + * + * Called for list boxes that are bound to a #GListModel with + * gtk_list_box_bind_model() for each item that gets added to the model. + * + * Returns: a #GtkWidget that represents @item + * + * Since: 3.16 + */ +typedef GtkWidget * (*GtkListBoxCreateWidgetFunc) (gpointer item, + gpointer user_data); + GDK_AVAILABLE_IN_3_10 GType gtk_list_box_row_get_type (void) G_GNUC_CONST; GDK_AVAILABLE_IN_3_10 @@ -286,6 +300,12 @@ GDK_AVAILABLE_IN_3_10 GtkWidget* gtk_list_box_new (void); +GDK_AVAILABLE_IN_3_16 +void gtk_list_box_bind_model (GtkListBox *box, + GListModel *model, + GtkListBoxCreateWidgetFunc create_widget_func, + gpointer user_data, + GDestroyNotify user_data_free_func); G_END_DECLS