diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index 2f2a1d2db9..a88502efbb 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -35,6 +35,11 @@ struct _GtkListItemManagerClass GObjectClass parent_class; }; +struct _GtkListItemManagerChange +{ + GHashTable *items; +}; + G_DEFINE_TYPE (GtkListItemManager, gtk_list_item_manager, G_TYPE_OBJECT) static void @@ -122,30 +127,81 @@ gtk_list_item_manager_get_model (GtkListItemManager *self) return self->model; } +#if 0 /* - * gtk_list_item_manager_model_changed: + * gtk_list_item_manager_get_size: * @self: a #GtkListItemManager - * @position: the position at which the model changed - * @removed: the number of items removed - * @added: the number of items added * - * This function must be called by the owning @widget at the - * appropriate time. - * The manager does not connect to GListModel::items-changed itself - * but relies on its widget calling this function. + * Queries the number of widgets currently handled by @self. * - * This function should be called after @widget has released all - * #GListItems it intends to delete in response to the @removed rows - * but before it starts creating new ones for the @added rows. + * This includes both widgets that have been acquired and + * those currently waiting to be used again. + * + * Returns: Number of widgets handled by @self + **/ +guint +gtk_list_item_manager_get_size (GtkListItemManager *self) +{ + return g_hash_table_size (self->pool); +} +#endif + +/* + * gtk_list_item_manager_begin_change: + * @self: a #GtkListItemManager + * + * Begins a change operation in response to a model's items-changed + * signal. + * During an ongoing change operation, list items will not be discarded + * when released but will be kept around in anticipation of them being + * added back in a different posiion later. + * + * Once it is known that no more list items will be reused, + * gtk_list_item_manager_end_change() should be called. This should happen + * as early as possible, so the list items held for the change can be + * reqcquired. + * + * Returns: The object to use for this change + **/ +GtkListItemManagerChange * +gtk_list_item_manager_begin_change (GtkListItemManager *self) +{ + GtkListItemManagerChange *change; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + + change = g_slice_new (GtkListItemManagerChange); + change->items = g_hash_table_new (g_direct_hash, g_direct_equal); + + return change; +} + +/* + * gtk_list_item_manager_end_change: + * @self: a #GtkListItemManager + * @change: a change + * + * Ends a change operation begun with gtk_list_item_manager_begin_change() + * and releases all list items still cached. **/ void -gtk_list_item_manager_model_changed (GtkListItemManager *self, - guint position, - guint removed, - guint added) +gtk_list_item_manager_end_change (GtkListItemManager *self, + GtkListItemManagerChange *change) { + GHashTableIter iter; + gpointer list_item; + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); - g_return_if_fail (self->model != NULL); + g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); + + g_hash_table_iter_init (&iter, change->items); + while (g_hash_table_iter_next (&iter, NULL, &list_item)) + { + gtk_list_item_manager_release_list_item (self, NULL, list_item); + } + + g_hash_table_unref (change->items); + g_slice_free (GtkListItemManagerChange, change); } /* @@ -178,6 +234,7 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); result = gtk_list_item_factory_create (self->factory); + item = g_list_model_get_item (self->model, position); gtk_list_item_factory_bind (self->factory, result, item); g_object_unref (item); @@ -186,9 +243,55 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, return GTK_WIDGET (result); } +/** + * gtk_list_item_manager_try_acquire_list_item_from_change: + * @self: a #GtkListItemManager + * @position: the row in the model to create a list item for + * @next_sibling: the widget this widget should be inserted before or %NULL + * if none + * + * Like gtk_list_item_manager_acquire_list_item(), but only tries to acquire list + * items from those previously released as part of @change. + * If no matching list item is found, %NULL is returned and the caller should use + * gtk_list_item_manager_acquire_list_item(). + * + * Returns: (nullable): a properly setup widget to use in @position or %NULL if + * no item for reuse existed + **/ +GtkWidget * +gtk_list_item_manager_try_reacquire_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + GtkWidget *next_sibling) +{ + GtkListItem *result; + gpointer item; + + g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL); + g_return_val_if_fail (next_sibling == NULL || GTK_IS_WIDGET (next_sibling), NULL); + + /* XXX: can we avoid temporarily allocating items on failure? */ + item = g_list_model_get_item (self->model, position); + if (g_hash_table_steal_extended (change->items, item, NULL, (gpointer *) &result)) + { + gtk_widget_insert_before (GTK_WIDGET (result), self->widget, next_sibling); + /* XXX: Should we let the listview do this? */ + gtk_widget_queue_resize (GTK_WIDGET (result)); + } + else + { + result = NULL; + } + g_object_unref (item); + + return GTK_WIDGET (result); +} + /* * gtk_list_item_manager_release_list_item: * @self: a #GtkListItemManager + * @change: (allow-none): The change associated with this release or + * %NULL if this is a final removal * @item: an item previously acquired with * gtk_list_item_manager_acquire_list_item() * @@ -196,11 +299,21 @@ gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, * gtk_list_item_manager_acquire_list_item() and is no longer in use. **/ void -gtk_list_item_manager_release_list_item (GtkListItemManager *self, - GtkWidget *item) +gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, + GtkWidget *item) { g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self)); g_return_if_fail (GTK_IS_LIST_ITEM (item)); + if (change != NULL) + { + if (g_hash_table_insert (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (item)), item)) + return; + + g_warning ("FIXME: Handle the same item multiple times in the list.\nLars says this totally should not happen, but here we are."); + } + + gtk_list_item_factory_unbind (self->factory, GTK_LIST_ITEM (item)); gtk_widget_unparent (item); } diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index 84f574576e..2af8e8fae7 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -36,6 +36,7 @@ G_BEGIN_DECLS typedef struct _GtkListItemManager GtkListItemManager; typedef struct _GtkListItemManagerClass GtkListItemManagerClass; +typedef struct _GtkListItemManagerChange GtkListItemManagerChange; GType gtk_list_item_manager_get_type (void) G_GNUC_CONST; @@ -48,15 +49,22 @@ void gtk_list_item_manager_set_model (GtkListItemMana GListModel *model); GListModel * gtk_list_item_manager_get_model (GtkListItemManager *self); +guint gtk_list_item_manager_get_size (GtkListItemManager *self); -void gtk_list_item_manager_model_changed (GtkListItemManager *self, - guint position, - guint removed, - guint added); +GtkListItemManagerChange * + gtk_list_item_manager_begin_change (GtkListItemManager *self); +void gtk_list_item_manager_end_change (GtkListItemManager *self, + GtkListItemManagerChange *change); GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling); +GtkWidget * gtk_list_item_manager_try_reacquire_list_item + (GtkListItemManager *self, + GtkListItemManagerChange *change, + guint position, + GtkWidget *next_sibling); void gtk_list_item_manager_release_list_item (GtkListItemManager *self, + GtkListItemManagerChange *change, GtkWidget *widget); G_END_DECLS diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index c9d0e0ac29..f5c36e55fc 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -426,9 +426,10 @@ gtk_list_view_size_allocate (GtkWidget *widget, } static void -gtk_list_view_remove_rows (GtkListView *self, - guint position, - guint n_rows) +gtk_list_view_remove_rows (GtkListView *self, + GtkListItemManagerChange *change, + guint position, + guint n_rows) { ListRow *row; guint i, n_remaining; @@ -454,7 +455,7 @@ gtk_list_view_remove_rows (GtkListView *self, for (i = 0; i < n_rows; i++) { ListRow *next = gtk_rb_tree_node_get_next (row); - gtk_list_item_manager_release_list_item (self->item_manager, row->widget); + gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget); row->widget = NULL; gtk_rb_tree_remove (self->rows, row); row = next; @@ -464,10 +465,11 @@ gtk_list_view_remove_rows (GtkListView *self, } static void -gtk_list_view_add_rows (GtkListView *self, - guint position, - guint n_rows) -{ +gtk_list_view_add_rows (GtkListView *self, + GtkListItemManagerChange *change, + guint position, + guint n_rows) +{ ListRow *row; guint i, n_total; @@ -489,9 +491,19 @@ gtk_list_view_add_rows (GtkListView *self, new_row = gtk_rb_tree_insert_before (self->rows, row); new_row->n_rows = 1; - new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, - position + i, - row ? row->widget : NULL); + if (change) + { + new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager, + change, + position + i, + row ? row->widget : NULL); + } + if (new_row->widget == NULL) + { + new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager, + position + i, + row ? row->widget : NULL); + } } gtk_widget_queue_resize (GTK_WIDGET (self)); @@ -504,9 +516,14 @@ gtk_list_view_model_items_changed_cb (GListModel *model, guint added, GtkListView *self) { - gtk_list_view_remove_rows (self, position, removed); - gtk_list_item_manager_model_changed (self->item_manager, position, removed, added); - gtk_list_view_add_rows (self, position, added); + GtkListItemManagerChange *change; + + change = gtk_list_item_manager_begin_change (self->item_manager); + + gtk_list_view_remove_rows (self, change, position, removed); + gtk_list_view_add_rows (self, change, position, added); + + gtk_list_item_manager_end_change (self->item_manager, change); } static void @@ -515,7 +532,7 @@ gtk_list_view_clear_model (GtkListView *self) if (self->model == NULL) return; - gtk_list_view_remove_rows (self, 0, g_list_model_get_n_items (self->model)); + gtk_list_view_remove_rows (self, NULL, 0, g_list_model_get_n_items (self->model)); g_signal_handlers_disconnect_by_func (self->model, gtk_list_view_model_items_changed_cb, @@ -830,7 +847,7 @@ gtk_list_view_set_model (GtkListView *self, G_CALLBACK (gtk_list_view_model_items_changed_cb), self); - gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model)); + gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model)); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -852,12 +869,12 @@ gtk_list_view_set_functions (GtkListView *self, g_return_if_fail (user_data != NULL || user_destroy == NULL); n_items = self->model ? g_list_model_get_n_items (self->model) : 0; - gtk_list_view_remove_rows (self, 0, n_items); + gtk_list_view_remove_rows (self, NULL, 0, n_items); factory = gtk_list_item_factory_new (create_func, bind_func, user_data, user_destroy); gtk_list_item_manager_set_factory (self->item_manager, factory); g_object_unref (factory); - gtk_list_view_add_rows (self, 0, n_items); + gtk_list_view_add_rows (self, NULL, 0, n_items); }