/* bz-dynamic-list-view.c * * Copyright 2025 Adam Masciola * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "bz-dynamic-list-view.h" #include "bz-marshalers.h" G_DEFINE_ENUM_TYPE ( BzDynamicListViewKind, bz_dynamic_list_view_kind, G_DEFINE_ENUM_VALUE (BZ_DYNAMIC_LIST_VIEW_KIND_HBOX, "h-box"), G_DEFINE_ENUM_VALUE (BZ_DYNAMIC_LIST_VIEW_KIND_VBOX, "v-box"), G_DEFINE_ENUM_VALUE (BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX, "list-box"), G_DEFINE_ENUM_VALUE (BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX, "flow-box"), G_DEFINE_ENUM_VALUE (BZ_DYNAMIC_LIST_VIEW_KIND_CAROUSEL, "carousel")) struct _BzDynamicListView { AdwBin parent_instance; GListModel *model; gboolean scroll; BzDynamicListViewKind noscroll_kind; GType child_type; char *child_prop; char *object_prop; guint max_children_per_line; guint row_spacing; guint column_spacing; char *child_type_string; GtkScrolledWindow *scrolled_window; GPtrArray *box_children; }; G_DEFINE_FINAL_TYPE (BzDynamicListView, bz_dynamic_list_view, ADW_TYPE_BIN); enum { PROP_0, PROP_MODEL, PROP_SCROLL, PROP_NOSCROLL_KIND, PROP_CHILD_TYPE, PROP_CHILD_PROP, PROP_OBJECT_PROP, PROP_MAX_CHILDREN_PER_LINE, PROP_VADJUSTMENT, PROP_ROW_SPACING, PROP_COLUMN_SPACING, LAST_PROP }; static GParamSpec *props[LAST_PROP] = { 0 }; enum { SIGNAL_BIND_WIDGET, SIGNAL_UNBIND_WIDGET, LAST_SIGNAL, }; static guint signals[LAST_SIGNAL]; static void refresh (BzDynamicListView *self); static void list_item_factory_setup (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory); static void list_item_factory_teardown (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory); static void list_item_factory_bind (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory); static void list_item_factory_unbind (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory); static GtkWidget * create_child_widget (GObject *object, BzDynamicListView *self); static void items_changed (GListModel *model, guint position, guint removed, guint added, BzDynamicListView *self); static void bz_dynamic_list_view_dispose (GObject *object) { BzDynamicListView *self = BZ_DYNAMIC_LIST_VIEW (object); if (self->model != NULL) g_signal_handlers_disconnect_by_func (self->model, items_changed, self); g_clear_object (&self->model); g_clear_pointer (&self->child_prop, g_free); g_clear_pointer (&self->object_prop, g_free); g_clear_pointer (&self->child_type_string, g_free); g_clear_pointer (&self->box_children, g_ptr_array_unref); G_OBJECT_CLASS (bz_dynamic_list_view_parent_class)->dispose (object); } static void bz_dynamic_list_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BzDynamicListView *self = BZ_DYNAMIC_LIST_VIEW (object); switch (prop_id) { case PROP_MODEL: g_value_set_object (value, bz_dynamic_list_view_get_model (self)); break; case PROP_SCROLL: g_value_set_boolean (value, bz_dynamic_list_view_get_scroll (self)); break; case PROP_NOSCROLL_KIND: g_value_set_enum (value, bz_dynamic_list_view_get_noscroll_kind (self)); break; case PROP_CHILD_TYPE: g_value_set_string (value, bz_dynamic_list_view_get_child_type (self)); break; case PROP_CHILD_PROP: g_value_set_string (value, bz_dynamic_list_view_get_child_prop (self)); break; case PROP_OBJECT_PROP: g_value_set_string (value, bz_dynamic_list_view_get_object_prop (self)); break; case PROP_MAX_CHILDREN_PER_LINE: g_value_set_uint (value, bz_dynamic_list_view_get_max_children_per_line (self)); break; case PROP_VADJUSTMENT: g_value_set_object (value, bz_dynamic_list_view_get_vadjustment (self)); break; case PROP_ROW_SPACING: g_value_set_uint (value, bz_dynamic_list_view_get_row_spacing (self)); break; case PROP_COLUMN_SPACING: g_value_set_uint (value, bz_dynamic_list_view_get_column_spacing (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bz_dynamic_list_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BzDynamicListView *self = BZ_DYNAMIC_LIST_VIEW (object); switch (prop_id) { case PROP_MODEL: bz_dynamic_list_view_set_model (self, g_value_get_object (value)); break; case PROP_SCROLL: bz_dynamic_list_view_set_scroll (self, g_value_get_boolean (value)); break; case PROP_NOSCROLL_KIND: bz_dynamic_list_view_set_noscroll_kind (self, g_value_get_enum (value)); break; case PROP_CHILD_TYPE: bz_dynamic_list_view_set_child_type (self, g_value_get_string (value)); break; case PROP_CHILD_PROP: bz_dynamic_list_view_set_child_prop (self, g_value_get_string (value)); break; case PROP_OBJECT_PROP: bz_dynamic_list_view_set_object_prop (self, g_value_get_string (value)); break; case PROP_MAX_CHILDREN_PER_LINE: bz_dynamic_list_view_set_max_children_per_line (self, g_value_get_uint (value)); break; case PROP_ROW_SPACING: bz_dynamic_list_view_set_row_spacing (self, g_value_get_uint (value)); break; case PROP_COLUMN_SPACING: bz_dynamic_list_view_set_column_spacing (self, g_value_get_uint (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void bz_dynamic_list_view_class_init (BzDynamicListViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = bz_dynamic_list_view_set_property; object_class->get_property = bz_dynamic_list_view_get_property; object_class->dispose = bz_dynamic_list_view_dispose; props[PROP_MODEL] = g_param_spec_object ( "model", NULL, NULL, G_TYPE_LIST_MODEL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_SCROLL] = g_param_spec_boolean ( "scroll", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_NOSCROLL_KIND] = g_param_spec_enum ( "noscroll-kind", NULL, NULL, BZ_TYPE_DYNAMIC_LIST_VIEW_KIND, BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_MAX_CHILDREN_PER_LINE] = g_param_spec_uint ( "max-children-per-line", NULL, NULL, 1, G_MAXUINT, 4, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_ROW_SPACING] = g_param_spec_uint ( "row-spacing", NULL, NULL, 0, G_MAXUINT, 11, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_COLUMN_SPACING] = g_param_spec_uint ( "column-spacing", NULL, NULL, 0, G_MAXUINT, 11, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_CHILD_TYPE] = g_param_spec_string ( "child-type", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_CHILD_PROP] = g_param_spec_string ( "child-prop", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_OBJECT_PROP] = g_param_spec_string ( "object-prop", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); props[PROP_VADJUSTMENT] = g_param_spec_object ( "vadjustment", NULL, NULL, GTK_TYPE_ADJUSTMENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); signals[SIGNAL_BIND_WIDGET] = g_signal_new ( "bind-widget", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, bz_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); g_signal_set_va_marshaller ( signals[SIGNAL_BIND_WIDGET], G_TYPE_FROM_CLASS (klass), bz_marshal_VOID__OBJECT_OBJECTv); signals[SIGNAL_UNBIND_WIDGET] = g_signal_new ( "unbind-widget", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, bz_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); g_signal_set_va_marshaller ( signals[SIGNAL_UNBIND_WIDGET], G_TYPE_FROM_CLASS (klass), bz_marshal_VOID__OBJECT_OBJECTv); } static void bz_dynamic_list_view_init (BzDynamicListView *self) { self->child_type = G_TYPE_INVALID; self->noscroll_kind = BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX; self->max_children_per_line = 4; self->row_spacing = 11; self->column_spacing = 11; self->box_children = g_ptr_array_new (); } BzDynamicListView * bz_dynamic_list_view_new (void) { return g_object_new (BZ_TYPE_DYNAMIC_LIST_VIEW, NULL); } GListModel * bz_dynamic_list_view_get_model (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), NULL); return self->model; } gboolean bz_dynamic_list_view_get_scroll (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), FALSE); return self->scroll; } BzDynamicListViewKind bz_dynamic_list_view_get_noscroll_kind (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), FALSE); return self->noscroll_kind; } const char * bz_dynamic_list_view_get_child_type (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), NULL); return self->child_type_string; } const char * bz_dynamic_list_view_get_child_prop (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), NULL); return self->child_prop; } const char * bz_dynamic_list_view_get_object_prop (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), NULL); return self->object_prop; } guint bz_dynamic_list_view_get_max_children_per_line (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), 4); return self->max_children_per_line; } GtkAdjustment * bz_dynamic_list_view_get_vadjustment (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), NULL); if (self->scrolled_window == NULL) return NULL; return gtk_scrolled_window_get_vadjustment (self->scrolled_window); } void bz_dynamic_list_view_set_model (BzDynamicListView *self, GListModel *model) { g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); if (self->model != NULL) g_signal_handlers_disconnect_by_func (self->model, items_changed, self); g_clear_object (&self->model); if (model != NULL) self->model = g_object_ref (model); refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MODEL]); } void bz_dynamic_list_view_set_scroll (BzDynamicListView *self, gboolean scroll) { g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); self->scroll = scroll; refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SCROLL]); } void bz_dynamic_list_view_set_noscroll_kind (BzDynamicListView *self, BzDynamicListViewKind noscroll_kind) { g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); g_return_if_fail (noscroll_kind >= 0 && noscroll_kind < BZ_DYNAMIC_LIST_VIEW_N_KINDS); self->noscroll_kind = noscroll_kind; refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NOSCROLL_KIND]); } void bz_dynamic_list_view_set_child_type (BzDynamicListView *self, const char *child_type) { GType type = G_TYPE_INVALID; g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); if (child_type != NULL) { type = g_type_from_name (child_type); g_return_if_fail (g_type_is_a (type, GTK_TYPE_WIDGET)); } g_clear_pointer (&self->child_type_string, g_free); self->child_type = type; if (child_type != NULL) self->child_type_string = g_strdup (child_type); refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TYPE]); } void bz_dynamic_list_view_set_child_prop (BzDynamicListView *self, const char *child_prop) { g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); g_clear_pointer (&self->child_prop, g_free); if (child_prop != NULL) self->child_prop = g_strdup (child_prop); refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_PROP]); } void bz_dynamic_list_view_set_object_prop (BzDynamicListView *self, const char *object_prop) { g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); g_clear_pointer (&self->object_prop, g_free); if (object_prop != NULL) self->object_prop = g_strdup (object_prop); refresh (self); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_OBJECT_PROP]); } void bz_dynamic_list_view_set_max_children_per_line (BzDynamicListView *self, guint max_children) { GtkWidget *child; g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); g_return_if_fail (max_children > 0); self->max_children_per_line = max_children; child = adw_bin_get_child (ADW_BIN (self)); if (child != NULL && self->noscroll_kind == BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX && !self->scroll) { gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (child), max_children); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MAX_CHILDREN_PER_LINE]); } guint bz_dynamic_list_view_get_row_spacing (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), 5); return self->row_spacing; } guint bz_dynamic_list_view_get_column_spacing (BzDynamicListView *self) { g_return_val_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self), 5); return self->column_spacing; } void bz_dynamic_list_view_set_row_spacing (BzDynamicListView *self, guint row_spacing) { GtkWidget *child; g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); self->row_spacing = row_spacing; child = adw_bin_get_child (ADW_BIN (self)); if (child != NULL && self->noscroll_kind == BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX && !self->scroll) { gtk_flow_box_set_row_spacing (GTK_FLOW_BOX (child), row_spacing); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ROW_SPACING]); } void bz_dynamic_list_view_set_column_spacing (BzDynamicListView *self, guint column_spacing) { GtkWidget *child; g_return_if_fail (BZ_IS_DYNAMIC_LIST_VIEW (self)); self->column_spacing = column_spacing; child = adw_bin_get_child (ADW_BIN (self)); if (child != NULL && self->noscroll_kind == BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX && !self->scroll) { gtk_flow_box_set_column_spacing (GTK_FLOW_BOX (child), column_spacing); } g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COLUMN_SPACING]); } static void refresh (BzDynamicListView *self) { if (self->model != NULL) g_signal_handlers_disconnect_by_func (self->model, items_changed, self); self->scrolled_window = NULL; g_ptr_array_set_size (self->box_children, 0); adw_bin_set_child (ADW_BIN (self), NULL); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VADJUSTMENT]); if (self->model == NULL || self->child_prop == NULL || self->child_type == G_TYPE_INVALID) return; if (self->scroll) { GtkWidget *window = NULL; GtkNoSelection *selection = NULL; GtkListItemFactory *factory = NULL; GtkWidget *view = NULL; window = gtk_scrolled_window_new (); selection = gtk_no_selection_new (g_object_ref (self->model)); factory = gtk_signal_list_item_factory_new (); view = gtk_list_view_new (GTK_SELECTION_MODEL (selection), factory); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_widget_add_css_class (view, "navigation-sidebar"); gtk_list_view_set_single_click_activate (GTK_LIST_VIEW (view), TRUE); g_signal_connect_swapped (factory, "setup", G_CALLBACK (list_item_factory_setup), self); g_signal_connect_swapped (factory, "teardown", G_CALLBACK (list_item_factory_teardown), self); g_signal_connect_swapped (factory, "bind", G_CALLBACK (list_item_factory_bind), self); g_signal_connect_swapped (factory, "unbind", G_CALLBACK (list_item_factory_unbind), self); gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (window), view); self->scrolled_window = GTK_SCROLLED_WINDOW (window); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VADJUSTMENT]); adw_bin_set_child (ADW_BIN (self), window); } else { switch (self->noscroll_kind) { case BZ_DYNAMIC_LIST_VIEW_KIND_HBOX: case BZ_DYNAMIC_LIST_VIEW_KIND_VBOX: { GtkWidget *widget = NULL; if (self->noscroll_kind == BZ_DYNAMIC_LIST_VIEW_KIND_VBOX) widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); else widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); g_signal_connect ( self->model, "items-changed", G_CALLBACK (items_changed), self); adw_bin_set_child (ADW_BIN (self), widget); items_changed (self->model, 0, 0, g_list_model_get_n_items (self->model), self); } break; case BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX: { GtkWidget *widget = NULL; widget = gtk_list_box_new (); gtk_list_box_set_selection_mode ( GTK_LIST_BOX (widget), GTK_SELECTION_NONE); gtk_list_box_bind_model ( GTK_LIST_BOX (widget), self->model, (GtkListBoxCreateWidgetFunc) create_child_widget, self, NULL); adw_bin_set_child (ADW_BIN (self), widget); } break; case BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX: { GtkWidget *widget = NULL; widget = gtk_flow_box_new (); gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (widget), TRUE); gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (widget), self->max_children_per_line); gtk_flow_box_set_row_spacing (GTK_FLOW_BOX (widget), self->row_spacing); gtk_flow_box_set_column_spacing (GTK_FLOW_BOX (widget), self->column_spacing); gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (widget), GTK_SELECTION_NONE); gtk_flow_box_bind_model ( GTK_FLOW_BOX (widget), self->model, (GtkFlowBoxCreateWidgetFunc) create_child_widget, self, NULL); adw_bin_set_child (ADW_BIN (self), widget); } break; case BZ_DYNAMIC_LIST_VIEW_KIND_CAROUSEL: { GtkWidget *widget = NULL; widget = adw_carousel_new (); adw_carousel_set_allow_scroll_wheel (ADW_CAROUSEL (widget), FALSE); g_signal_connect ( self->model, "items-changed", G_CALLBACK (items_changed), self); adw_bin_set_child (ADW_BIN (self), widget); items_changed (self->model, 0, 0, g_list_model_get_n_items (self->model), self); } break; case BZ_DYNAMIC_LIST_VIEW_N_KINDS: default: g_assert_not_reached (); } } } static void list_item_factory_setup (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory) { GtkWidget *child = NULL; child = g_object_new (self->child_type, NULL); gtk_list_item_set_child (item, child); } static void list_item_factory_teardown (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory) { gtk_list_item_set_child (item, NULL); } static void list_item_factory_bind (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory) { GObject *object = NULL; GtkWidget *child = NULL; object = gtk_list_item_get_item (item); child = gtk_list_item_get_child (item); gtk_list_item_set_focusable (item, FALSE); gtk_list_item_set_selectable (item, FALSE); gtk_list_item_set_activatable (item, FALSE); if (self->object_prop != NULL) { GBinding *binding = NULL; binding = g_object_bind_property ( object, self->object_prop, child, self->child_prop, G_BINDING_SYNC_CREATE); g_object_set_data_full ( G_OBJECT (item), "binding", binding, g_object_unref); } else g_object_set (child, self->child_prop, object, NULL); g_signal_emit (self, signals[SIGNAL_BIND_WIDGET], 0, child, object); } static void list_item_factory_unbind (BzDynamicListView *self, GtkListItem *item, GtkSignalListItemFactory *factory) { GtkWidget *child = NULL; GBinding *binding = NULL; g_autoptr (GObject) object = NULL; child = gtk_list_item_get_child (item); binding = g_object_steal_data (G_OBJECT (item), "binding"); if (binding != NULL) { object = g_binding_dup_source (binding); g_binding_unbind (binding); g_object_unref (binding); } else { g_object_get (child, self->child_prop, &object, NULL); g_object_set (child, self->child_prop, NULL, NULL); } g_signal_emit (self, signals[SIGNAL_UNBIND_WIDGET], 0, child, object); } static GtkWidget * create_child_widget (GObject *object, BzDynamicListView *self) { GtkWidget *widget = NULL; widget = g_object_new (self->child_type, NULL); if (self->object_prop != NULL) g_object_bind_property ( object, self->object_prop, widget, self->child_prop, G_BINDING_SYNC_CREATE); else g_object_set (widget, self->child_prop, object, NULL); gtk_widget_set_receives_default (widget, TRUE); g_signal_emit (self, signals[SIGNAL_BIND_WIDGET], 0, widget, object); switch (self->noscroll_kind) { case BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX: { GtkWidget *child = NULL; child = gtk_list_box_row_new (); gtk_widget_add_css_class (GTK_WIDGET (child), "disable-adw-flow-box-styling"); gtk_widget_set_focusable (GTK_WIDGET (child), FALSE); gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (child), FALSE); gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (child), widget); return child; } case BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX: { GtkWidget *child = NULL; child = gtk_flow_box_child_new (); gtk_widget_add_css_class (GTK_WIDGET (child), "disable-adw-flow-box-styling"); gtk_widget_set_focusable (GTK_WIDGET (child), FALSE); gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (child), widget); return child; } case BZ_DYNAMIC_LIST_VIEW_KIND_HBOX: case BZ_DYNAMIC_LIST_VIEW_KIND_VBOX: case BZ_DYNAMIC_LIST_VIEW_KIND_CAROUSEL: case BZ_DYNAMIC_LIST_VIEW_N_KINDS: default: return widget; } } static void items_changed (GListModel *model, guint position, guint removed, guint added, BzDynamicListView *self) { GtkWidget *bin_child = NULL; bin_child = adw_bin_get_child (ADW_BIN (self)); for (guint i = 0; i < removed; i++) { switch (self->noscroll_kind) { case BZ_DYNAMIC_LIST_VIEW_KIND_HBOX: case BZ_DYNAMIC_LIST_VIEW_KIND_VBOX: { GtkWidget *child = NULL; child = g_ptr_array_index (self->box_children, position); gtk_box_remove (GTK_BOX (bin_child), g_steal_pointer (&child)); g_ptr_array_remove_index (self->box_children, position); } break; case BZ_DYNAMIC_LIST_VIEW_KIND_CAROUSEL: adw_carousel_remove ( ADW_CAROUSEL (bin_child), adw_carousel_get_nth_page (ADW_CAROUSEL (bin_child), position)); break; case BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX: case BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX: case BZ_DYNAMIC_LIST_VIEW_N_KINDS: default: g_assert_not_reached (); } } for (guint i = 0; i < added; i++) { g_autoptr (GObject) object = NULL; GtkWidget *widget = NULL; object = g_list_model_get_item (model, position + i); widget = create_child_widget (object, self); switch (self->noscroll_kind) { case BZ_DYNAMIC_LIST_VIEW_KIND_HBOX: case BZ_DYNAMIC_LIST_VIEW_KIND_VBOX: { if (position + i < self->box_children->len) { GtkWidget *sibling = NULL; sibling = g_ptr_array_index (self->box_children, position + i); gtk_box_insert_child_after (GTK_BOX (bin_child), widget, sibling); } else gtk_box_append (GTK_BOX (bin_child), widget); } break; case BZ_DYNAMIC_LIST_VIEW_KIND_CAROUSEL: adw_carousel_insert (ADW_CAROUSEL (bin_child), widget, position + i); break; case BZ_DYNAMIC_LIST_VIEW_KIND_LIST_BOX: case BZ_DYNAMIC_LIST_VIEW_KIND_FLOW_BOX: case BZ_DYNAMIC_LIST_VIEW_N_KINDS: default: g_assert_not_reached (); } } } /* End of bz-dynamic-list-view.c */