[gtk/wip/otte/listview: 20/76] listview: Change change management



commit 33eb6217d84a08c8d20c79dd53fc5f4a01638cc1
Author: Benjamin Otte <otte redhat com>
Date:   Sat Sep 22 22:11:27 2018 +0200

    listview: Change change management
    
    Add a GtkListItemManagerChange object that tracks all removed list
    rows during an item-changed signal so they can be added back later.

 gtk/gtklistitemmanager.c        | 153 ++++++++++++++++++++++++++++++++++------
 gtk/gtklistitemmanagerprivate.h |  16 +++--
 gtk/gtklistview.c               |  53 +++++++++-----
 3 files changed, 180 insertions(+), 42 deletions(-)
---
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.
- *
- * 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.
+ *
+ * Queries the number of widgets currently handled by @self.
+ *
+ * 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 b6122989f6..4619ac7fd2 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,
@@ -829,7 +846,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]);
@@ -851,12 +868,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);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]