[gtk/wip/otte/listview: 54/155] listitemmanager: Add trackers



commit cc5172b06f27e0b1a4777c1dd67266cdb60ba508
Author: Benjamin Otte <otte redhat com>
Date:   Wed Feb 27 08:45:28 2019 +0100

    listitemmanager: Add trackers
    
    ... and replace the anchor tracking with a tracker.
    
    Trackers track an item through the list across changes and ensure that
    this item (and potentially siblings before/after it) are always backed
    by a GtkListItem and that if the item gets removed a replacement gets
    chosen.
    
    This is now used for tracking the anchor but can also be used to add
    trackers for the cursor later.

 gtk/gtklistitemmanager.c        | 588 ++++++++++++++++++++++++++--------------
 gtk/gtklistitemmanagerprivate.h |  16 +-
 gtk/gtklistview.c               |  38 ++-
 gtk/gtkselectionmodel.h         |  12 +
 4 files changed, 436 insertions(+), 218 deletions(-)
---
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index ac922cc7094..35a894ed51f 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -35,12 +35,7 @@ struct _GtkListItemManager
   GtkListItemFactory *factory;
 
   GtkRbTree *items;
-
-  /* managing the visible region */
-  GtkWidget *anchor; /* may be NULL if list is empty */
-  int anchor_align; /* what to align the anchor to */
-  guint anchor_start; /* start of region we allocate row widgets for */
-  guint anchor_end; /* end of same region - first position to not have a widget */
+  GSList *trackers;
 };
 
 struct _GtkListItemManagerClass
@@ -48,6 +43,14 @@ struct _GtkListItemManagerClass
   GObjectClass parent_class;
 };
 
+struct _GtkListItemTracker
+{
+  guint position;
+  GtkListItem *widget;
+  guint n_before;
+  guint n_after;
+};
+
 static GtkWidget *      gtk_list_item_manager_acquire_list_item (GtkListItemManager     *self,
                                                                  guint                   position,
                                                                  GtkWidget              *prev_sibling);
@@ -239,6 +242,104 @@ gtk_list_item_manager_get_item_augment (GtkListItemManager *self,
   return gtk_rb_tree_get_augment (self->items, item);
 }
 
+static void
+gtk_list_item_tracker_unset_position (GtkListItemManager *self,
+                                      GtkListItemTracker *tracker)
+{
+  tracker->widget = NULL;
+  tracker->position = GTK_INVALID_LIST_POSITION;
+}
+
+static gboolean
+gtk_list_item_tracker_query_range (GtkListItemManager *self,
+                                   GtkListItemTracker *tracker,
+                                   guint               n_items,
+                                   guint              *out_start,
+                                   guint              *out_n_items)
+{
+  /* We can't look at tracker->widget here because we might not
+   * have set it yet.
+   */
+  if (tracker->position == GTK_INVALID_LIST_POSITION)
+    return FALSE;
+
+  /* This is magic I made up that is meant to be both
+   * correct and doesn't overflow when start and/or end are close to 0 or
+   * close to max.
+   * But beware, I didn't test it.
+   */
+  *out_n_items = tracker->n_before + tracker->n_after + 1;
+  *out_n_items = MIN (*out_n_items, n_items);
+
+  *out_start = MAX (tracker->position, tracker->n_before) - tracker->n_before;
+  *out_start = MIN (*out_start, n_items - *out_n_items);
+
+  return TRUE;
+}
+
+static void
+gtk_list_item_query_tracked_range (GtkListItemManager *self,
+                                   guint               n_items,
+                                   guint               position,
+                                   guint              *out_n_items,
+                                   gboolean           *out_tracked)
+{
+  GSList *l;
+  guint tracker_start, tracker_n_items;
+
+  g_assert (position < n_items);
+
+  *out_tracked = FALSE;
+  *out_n_items = n_items - position;
+
+  /* step 1: Check if position is tracked */
+
+  for (l = self->trackers; l; l = l->next)
+    {
+      if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items))
+        continue;
+
+      if (tracker_start > position)
+        {
+          *out_n_items = MIN (*out_n_items, tracker_start - position);
+        }
+      else if (tracker_start + tracker_n_items <= position)
+        {
+          /* do nothing */
+        }
+      else
+        {
+          *out_tracked = TRUE;
+          *out_n_items = tracker_start + tracker_n_items - position;
+          break;
+        }
+    }
+
+  /* If nothing's tracked, we're done */
+  if (!*out_tracked)
+    return;
+
+  /* step 2: make the tracked range as large as possible
+   * NB: This is O(N_TRACKERS^2), but the number of trackers should be <5 */
+restart:
+  for (l = self->trackers; l; l = l->next)
+    {
+      if (!gtk_list_item_tracker_query_range (self, l->data, n_items, &tracker_start, &tracker_n_items))
+        continue;
+
+      if (tracker_start + tracker_n_items <= position + *out_n_items)
+        continue;
+      if (tracker_start > position + *out_n_items)
+        continue;
+
+      if (*out_n_items + position < tracker_start + tracker_n_items)
+        {
+          *out_n_items = tracker_start + tracker_n_items - position;
+          goto restart;
+        }
+    }
+}
+
 static void
 gtk_list_item_manager_remove_items (GtkListItemManager *self,
                                     GHashTable         *change,
@@ -296,15 +397,6 @@ gtk_list_item_manager_add_items (GtkListItemManager *self,
   gtk_widget_queue_resize (GTK_WIDGET (self->widget));
 }
 
-static void
-gtk_list_item_manager_unset_anchor (GtkListItemManager *self)
-{
-  self->anchor = NULL;
-  self->anchor_align = 0;
-  self->anchor_start = 0;
-  self->anchor_end = 0;
-}
-
 static gboolean
 gtk_list_item_manager_merge_list_items (GtkListItemManager     *self,
                                         GtkListItemManagerItem *first,
@@ -325,61 +417,53 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
                                      GQueue             *released)
 {
   GtkListItemManagerItem *item, *prev, *next;
-  guint i;
+  guint position, i, n_items, query_n_items;
+  gboolean tracked;
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+  position = 0;
 
-  item = gtk_rb_tree_get_first (self->items);
-  i = 0;
-  while (i < self->anchor_start)
+  while (position < n_items)
     {
-      if (item->widget)
+      gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked);
+      if (tracked)
         {
-          g_queue_push_tail (released, item->widget);
-          item->widget = NULL;
-          i++;
-          prev = gtk_rb_tree_node_get_previous (item);
-          if (prev && gtk_list_item_manager_merge_list_items (self, prev, item))
-            item = prev;
-          next = gtk_rb_tree_node_get_next (item);
-          if (next && next->widget == NULL)
+          position += query_n_items;
+          continue;
+        }
+
+      item = gtk_list_item_manager_get_nth (self, position, &i);
+      i = position - i;
+      while (i < position + query_n_items)
+        {
+          if (item->widget)
             {
-              i += next->n_items;
-              if (!gtk_list_item_manager_merge_list_items (self, next, item))
-                g_assert_not_reached ();
-              item = gtk_rb_tree_node_get_next (next);
+              g_queue_push_tail (released, item->widget);
+              item->widget = NULL;
+              i++;
+              prev = gtk_rb_tree_node_get_previous (item);
+              if (prev && gtk_list_item_manager_merge_list_items (self, prev, item))
+                item = prev;
+              next = gtk_rb_tree_node_get_next (item);
+              if (next && next->widget == NULL)
+                {
+                  i += next->n_items;
+                  if (!gtk_list_item_manager_merge_list_items (self, next, item))
+                    g_assert_not_reached ();
+                  item = gtk_rb_tree_node_get_next (next);
+                }
+              else 
+                {
+                  item = next;
+                }
             }
-          else 
+          else
             {
-              item = next;
+              i += item->n_items;
+              item = gtk_rb_tree_node_get_next (item);
             }
         }
-      else
-        {
-          i += item->n_items;
-          item = gtk_rb_tree_node_get_next (item);
-        }
-    }
-
-  item = gtk_list_item_manager_get_nth (self, self->anchor_end, NULL);
-  if (item == NULL)
-    return;
-  
-  if (item->widget)
-    {
-      g_queue_push_tail (released, item->widget);
-      item->widget = NULL;
-      prev = gtk_rb_tree_node_get_previous (item);
-      if (prev && gtk_list_item_manager_merge_list_items (self, prev, item))
-        item = prev;
-    }
-
-  while ((next = gtk_rb_tree_node_get_next (item)))
-    {
-      if (next->widget)
-        {
-          g_queue_push_tail (released, next->widget);
-          next->widget = NULL;
-        }
-      gtk_list_item_manager_merge_list_items (self, item, next);
+      position += query_n_items;
     }
 }
 
@@ -389,117 +473,98 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
                                     guint               update_start)
 {
   GtkListItemManagerItem *item, *new_item;
-  guint i, offset;
   GtkWidget *widget, *insert_after;
+  guint position, i, n_items, query_n_items, offset;
   GQueue released = G_QUEUE_INIT;
+  gboolean tracked;
+
+  if (self->model == NULL)
+    return;
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+  position = 0;
 
   gtk_list_item_manager_release_items (self, &released);
 
-  item = gtk_list_item_manager_get_nth (self, self->anchor_start, &offset);
-  if (offset > 0)
+  while (position < n_items)
     {
-      new_item = gtk_rb_tree_insert_before (self->items, item);
-      new_item->n_items = offset;
-      item->n_items -= offset;
-      gtk_rb_tree_node_mark_dirty (item);
-    }
+      gtk_list_item_query_tracked_range (self, n_items, position, &query_n_items, &tracked);
+      if (!tracked)
+        {
+          position += query_n_items;
+          continue;
+        }
 
-  insert_after = NULL;
+      item = gtk_list_item_manager_get_nth (self, position, &offset);
+      for (new_item = item;
+           new_item && new_item->widget == NULL;
+           new_item = gtk_rb_tree_node_get_previous (new_item))
+         { /* do nothing */ }
+      insert_after = new_item ? new_item->widget : NULL;
 
-  for (i = self->anchor_start; i < self->anchor_end; i++)
-    {
-      if (item->n_items > 1)
+      if (offset > 0)
         {
           new_item = gtk_rb_tree_insert_before (self->items, item);
-          new_item->n_items = 1;
-          item->n_items--;
+          new_item->n_items = offset;
+          item->n_items -= offset;
           gtk_rb_tree_node_mark_dirty (item);
         }
-      else
-        {
-          new_item = item;
-          item = gtk_rb_tree_node_get_next (item);
-        }
-      if (new_item->widget == NULL)
+
+      for (i = 0; i < query_n_items; i++)
         {
-          if (change)
+          if (item->n_items > 1)
+            {
+              new_item = gtk_rb_tree_insert_before (self->items, item);
+              new_item->n_items = 1;
+              item->n_items--;
+              gtk_rb_tree_node_mark_dirty (item);
+            }
+          else
             {
-              new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self,
-                                                                                change,
-                                                                                i,
-                                                                                insert_after);
+              new_item = item;
+              item = gtk_rb_tree_node_get_next (item);
             }
           if (new_item->widget == NULL)
             {
-              new_item->widget = g_queue_pop_head (&released);
-              if (new_item->widget)
+              if (change)
                 {
-                  gtk_list_item_manager_move_list_item (self,
-                                                        new_item->widget,
-                                                        i,
-                                                        insert_after);
+                  new_item->widget = gtk_list_item_manager_try_reacquire_list_item (self,
+                                                                                    change,
+                                                                                    position + i,
+                                                                                    insert_after);
                 }
-              else
+              if (new_item->widget == NULL)
                 {
-                  new_item->widget = gtk_list_item_manager_acquire_list_item (self,
-                                                                              i,
-                                                                              insert_after);
+                  new_item->widget = g_queue_pop_head (&released);
+                  if (new_item->widget)
+                    {
+                      gtk_list_item_manager_move_list_item (self,
+                                                            new_item->widget,
+                                                            position + i,
+                                                            insert_after);
+                    }
+                  else
+                    {
+                      new_item->widget = gtk_list_item_manager_acquire_list_item (self,
+                                                                                  position + i,
+                                                                                  insert_after);
+                    }
                 }
             }
+          else
+            {
+              if (update_start <= position + i)
+                gtk_list_item_manager_update_list_item (self, new_item->widget, position + i);
+            }
+          insert_after = new_item->widget;
         }
-      else
-        {
-          if (update_start <= i)
-            gtk_list_item_manager_update_list_item (self, new_item->widget, i);
-        }
-      insert_after = new_item->widget;
+      position += query_n_items;
     }
 
   while ((widget = g_queue_pop_head (&released)))
     gtk_list_item_manager_release_list_item (self, NULL, widget);
 }
 
-void
-gtk_list_item_manager_set_anchor (GtkListItemManager *self,
-                                  guint               position,
-                                  double              align,
-                                  GHashTable         *change,
-                                  guint               update_start)
-{
-  GtkListItemManagerItem *item;
-  guint items_before, items_after, total_items, n_items;
-
-  g_assert (align >= 0.0 && align <= 1.0);
-
-  if (self->model)
-    n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
-  else
-    n_items = 0;
-  if (n_items == 0)
-    {
-      gtk_list_item_manager_unset_anchor (self);
-      return;
-    }
-  total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_items);
-  if (align < 0.5)
-    items_before = ceil (total_items * align);
-  else
-    items_before = floor (total_items * align);
-  items_after = total_items - items_before;
-  self->anchor_start = CLAMP (position, items_before, n_items - items_after) - items_before;
-  self->anchor_end = self->anchor_start + total_items;
-  g_assert (self->anchor_end <= n_items);
-
-  gtk_list_item_manager_ensure_items (self, change, update_start);
-
-  item = gtk_list_item_manager_get_nth (self, position, NULL);
-  self->anchor = item->widget;
-  g_assert (self->anchor);
-  self->anchor_align = align;
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self->widget));
-}
-
 static void
 gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
                                               guint               position,
@@ -510,20 +575,34 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
   GHashTable *change;
   GHashTableIter iter;
   gpointer list_item;
+  GSList *l;
+  guint n_items;
 
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
   change = g_hash_table_new (g_direct_hash, g_direct_equal);
 
   gtk_list_item_manager_remove_items (self, change, position, removed);
   gtk_list_item_manager_add_items (self, position, added);
 
-  /* The anchor was removed, but it may just have moved to a different position */
-  if (self->anchor && g_hash_table_lookup (change, gtk_list_item_get_item (GTK_LIST_ITEM (self->anchor))) == 
self->anchor)
+  /* Check if any tracked item was removed */
+  for (l = self->trackers; l; l = l->next)
+    {
+      GtkListItemTracker *tracker = l->data;
+
+      if (tracker->widget == NULL)
+        continue;
+
+      if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
+        break;
+    }
+
+  /* At least one tracked item was removed, do a more expensive rebuild
+   * trying to find where it moved */
+  if (l)
     {
-      /* The anchor was removed, do a more expensive rebuild trying to find if
-       * the anchor maybe got readded somewhere else */
       GtkListItemManagerItem *item, *new_item;
       GtkWidget *insert_after;
-      guint i, offset, anchor_pos;
+      guint i, offset;
       
       item = gtk_list_item_manager_get_nth (self, position, &offset);
       for (new_item = item ? gtk_rb_tree_node_get_previous (item) : gtk_rb_tree_get_last (self->items);
@@ -573,50 +652,76 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
 
           new_item->widget = widget;
           insert_after = widget;
-
-          if (widget == self->anchor)
-            {
-              anchor_pos = position + i;
-              break;
-            }
         }
+    }
 
-      if (i == added)
-        {
-          /* The anchor wasn't readded. Guess a good anchor position */
-          anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+  /* Update tracker positions if necessary, they need to have correct
+   * positions for gtk_list_item_manager_ensure_items().
+   * We don't update the items, they will be updated by ensure_items()
+   * and then we can update them. */
+  for (l = self->trackers; l; l = l->next)
+    {
+      GtkListItemTracker *tracker = l->data;
 
-          anchor_pos = position + (anchor_pos - position) * added / removed;
-          if (anchor_pos >= g_list_model_get_n_items (G_LIST_MODEL (self->model)) &&
-              anchor_pos > 0)
-            anchor_pos--;
+      if (tracker->position == GTK_INVALID_LIST_POSITION)
+        {
+          /* if the list is no longer empty, set the tracker to a valid position. */
+          if (n_items > 0 && n_items == added && removed == 0)
+            tracker->position = 0;
         }
-      gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position);
-    }
-  else
-    {
-      /* The anchor is still where it was.
-       * We just may need to update its position and check that its surrounding widgets
-       * exist (they might be new ones). */
-      guint anchor_pos;
-      
-      if (self->anchor)
+      else if (tracker->position >= position + removed)
         {
-          anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
-
-          if (anchor_pos >= position)
-            anchor_pos += added - removed;
+          tracker->position += added - removed;
+        }
+      else if (tracker->position >= position)
+        {
+          if (g_hash_table_lookup (change, gtk_list_item_get_item (tracker->widget)))
+            {
+              /* The item is gone. Guess a good new position */
+              tracker->position = position + (tracker->position - position) * added / removed;
+              if (tracker->position >= n_items)
+                {
+                  if (n_items == 0)
+                    tracker->position = GTK_INVALID_LIST_POSITION;
+                  else
+                    tracker->position--;
+                }
+              tracker->widget = NULL;
+            }
+          else
+            {
+              /* item was put in its right place in the expensive loop above,
+               * and we updated its position while at it. So grab it from there.
+               */
+              tracker->position = gtk_list_item_get_position (tracker->widget);
+            }
         }
       else
         {
-          anchor_pos = 0;
+          /* nothing changed for items before position */
         }
-
-      gtk_list_item_manager_set_anchor (self, anchor_pos, self->anchor_align, change, position);
     }
 
-  g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
-  g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
+  gtk_list_item_manager_ensure_items (self, change, position + added);
+
+  /* final loop through the trackers: Grab the missing widgets.
+   * For items that had been removed and a new position was set, grab
+   * their item now that we ensured it exists.
+   */
+  for (l = self->trackers; l; l = l->next)
+    {
+      GtkListItemTracker *tracker = l->data;
+      GtkListItemManagerItem *item;
+
+      if (tracker->widget != NULL || 
+          tracker->position == GTK_INVALID_LIST_POSITION)
+        continue;
+
+      item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
+      g_assert (item != NULL);
+      g_assert (item->widget);
+      tracker->widget = GTK_LIST_ITEM (item->widget);
+    }
 
   g_hash_table_iter_init (&iter, change);
   while (g_hash_table_iter_next (&iter, NULL, &list_item))
@@ -625,6 +730,8 @@ gtk_list_item_manager_model_items_changed_cb (GListModel         *model,
     }
 
   g_hash_table_unref (change);
+
+  gtk_widget_queue_resize (self->widget);
 }
 
 static void
@@ -658,30 +765,19 @@ gtk_list_item_manager_model_selection_changed_cb (GListModel         *model,
     }
 }
 
-guint
-gtk_list_item_manager_get_anchor (GtkListItemManager *self,
-                                  double             *align)
-{
-  guint anchor_pos;
-
-  if (self->anchor)
-    anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
-  else
-    anchor_pos = 0;
-
-  if (align)
-    *align = self->anchor_align;
-
-  return anchor_pos;
-}
-
 static void
 gtk_list_item_manager_clear_model (GtkListItemManager *self)
 {
+  GSList *l;
+
   if (self->model == NULL)
     return;
 
   gtk_list_item_manager_remove_items (self, NULL, 0, g_list_model_get_n_items (G_LIST_MODEL (self->model)));
+  for (l = self->trackers; l; l = l->next)
+    {
+      gtk_list_item_tracker_unset_position (self, l->data);
+    }
 
   g_signal_handlers_disconnect_by_func (self->model,
                                         gtk_list_item_manager_model_selection_changed_cb,
@@ -690,8 +786,6 @@ gtk_list_item_manager_clear_model (GtkListItemManager *self)
                                         gtk_list_item_manager_model_items_changed_cb,
                                         self);
   g_clear_object (&self->model);
-
-  gtk_list_item_manager_unset_anchor (self);
 }
 
 static void
@@ -725,8 +819,8 @@ void
 gtk_list_item_manager_set_factory (GtkListItemManager *self,
                                    GtkListItemFactory *factory)
 {
-  guint n_items, anchor;
-  double anchor_align;
+  guint n_items;
+  GSList *l;
 
   g_return_if_fail (GTK_IS_LIST_ITEM_MANAGER (self));
   g_return_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory));
@@ -735,13 +829,26 @@ gtk_list_item_manager_set_factory (GtkListItemManager *self,
     return;
 
   n_items = self->model ? g_list_model_get_n_items (G_LIST_MODEL (self->model)) : 0;
-  anchor = gtk_list_item_manager_get_anchor (self, &anchor_align);
   gtk_list_item_manager_remove_items (self, NULL, 0, n_items);
 
   g_set_object (&self->factory, factory);
 
   gtk_list_item_manager_add_items (self, 0, n_items);
-  gtk_list_item_manager_set_anchor (self, anchor, anchor_align, NULL, (guint) -1);
+
+  gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
+
+  for (l = self->trackers; l; l = l->next)
+    {
+      GtkListItemTracker *tracker = l->data;
+      GtkListItemManagerItem *item;
+
+      if (tracker->widget == NULL)
+        continue;
+
+      item = gtk_list_item_manager_get_nth (self, tracker->position, NULL);
+      g_assert (item);
+      tracker->widget = GTK_LIST_ITEM (item->widget);
+    }
 }
 
 GtkListItemFactory *
@@ -778,8 +885,6 @@ gtk_list_item_manager_set_model (GtkListItemManager *self,
                         self);
 
       gtk_list_item_manager_add_items (self, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));
-
-      gtk_list_item_manager_set_anchor (self, 0, 0, NULL, (guint) -1);
     }
 }
 
@@ -961,3 +1066,72 @@ gtk_list_item_manager_release_list_item (GtkListItemManager *self,
   gtk_widget_unparent (item);
 }
 
+GtkListItemTracker *
+gtk_list_item_tracker_new (GtkListItemManager *self)
+{
+  GtkListItemTracker *tracker;
+
+  g_return_val_if_fail (GTK_IS_LIST_ITEM_MANAGER (self), NULL);
+
+  tracker = g_slice_new0 (GtkListItemTracker);
+
+  tracker->position = GTK_INVALID_LIST_POSITION;
+
+  self->trackers = g_slist_prepend (self->trackers, tracker);
+
+  return tracker;
+}
+
+void
+gtk_list_item_tracker_free (GtkListItemManager *self,
+                            GtkListItemTracker *tracker)
+{
+  gtk_list_item_tracker_unset_position (self, tracker);
+
+  self->trackers = g_slist_remove (self->trackers, tracker);
+
+  g_slice_free (GtkListItemTracker, tracker);
+
+  gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
+
+  gtk_widget_queue_resize (self->widget);
+}
+
+void
+gtk_list_item_tracker_set_position (GtkListItemManager *self,
+                                    GtkListItemTracker *tracker,
+                                    guint               position,
+                                    guint               n_before,
+                                    guint               n_after)
+{
+  GtkListItemManagerItem *item;
+  guint n_items;
+
+  gtk_list_item_tracker_unset_position (self, tracker);
+
+  if (self->model == NULL)
+    return;
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
+  if (position >= n_items)
+    position = n_items - 1; /* for n_items == 0 this underflows to GTK_INVALID_LIST_POSITION */
+
+  tracker->position = position;
+  tracker->n_before = n_before;
+  tracker->n_after = n_after;
+
+  gtk_list_item_manager_ensure_items (self, NULL, G_MAXUINT);
+
+  item = gtk_list_item_manager_get_nth (self, position, NULL);
+  if (item)
+    tracker->widget = GTK_LIST_ITEM (item->widget);
+
+  gtk_widget_queue_resize (self->widget);
+}
+
+guint
+gtk_list_item_tracker_get_position (GtkListItemManager *self,
+                                    GtkListItemTracker *tracker)
+{
+  return tracker->position;
+}
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index 9a506c7573f..c83fc7d92f6 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -40,6 +40,7 @@ typedef struct _GtkListItemManager GtkListItemManager;
 typedef struct _GtkListItemManagerClass GtkListItemManagerClass;
 typedef struct _GtkListItemManagerItem GtkListItemManagerItem; /* sorry */
 typedef struct _GtkListItemManagerItemAugment GtkListItemManagerItemAugment;
+typedef struct _GtkListItemTracker GtkListItemTracker;
 
 struct _GtkListItemManagerItem
 {
@@ -86,13 +87,16 @@ GtkSelectionModel *     gtk_list_item_manager_get_model         (GtkListItemMana
 
 guint                   gtk_list_item_manager_get_size          (GtkListItemManager     *self);
 
-void                    gtk_list_item_manager_set_anchor        (GtkListItemManager     *self,
+GtkListItemTracker *    gtk_list_item_tracker_new               (GtkListItemManager     *self);
+void                    gtk_list_item_tracker_free              (GtkListItemManager     *self,
+                                                                 GtkListItemTracker     *tracker);
+void                    gtk_list_item_tracker_set_position      (GtkListItemManager     *self,
+                                                                 GtkListItemTracker     *tracker,
                                                                  guint                   position,
-                                                                 double                  align,
-                                                                 GHashTable             *change,
-                                                                 guint                   update_start);
-guint                   gtk_list_item_manager_get_anchor        (GtkListItemManager     *self,
-                                                                 double                 *align);
+                                                                 guint                   n_before,
+                                                                 guint                   n_after);
+guint                   gtk_list_item_tracker_get_position      (GtkListItemManager     *self,
+                                                                 GtkListItemTracker     *tracker);
 
 
 G_END_DECLS
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index e91b1b7b62c..f2363d96616 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -37,6 +37,9 @@
  */
 #define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
 
+/* Extra items to keep above + below every tracker */
+#define GTK_LIST_VIEW_EXTRA_ITEMS 2
+
 /**
  * SECTION:gtklistview
  * @title: GtkListView
@@ -59,6 +62,9 @@ struct _GtkListView
   GtkScrollablePolicy scroll_policy[2];
 
   int list_width;
+
+  GtkListItemTracker *anchor;
+  double anchor_align;
 };
 
 struct _ListRow
@@ -229,6 +235,23 @@ gtk_list_view_get_list_height (GtkListView *self)
   return aug->height;
 }
 
+static void
+gtk_list_view_set_anchor (GtkListView *self,
+                          guint        position,
+                          double       align)
+{
+  gtk_list_item_tracker_set_position (self->item_manager,
+                                      self->anchor,
+                                      position,
+                                      GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align,
+                                      GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 - 
GTK_LIST_VIEW_MAX_LIST_ITEMS * align);
+  if (self->anchor_align != align)
+    {
+      self->anchor_align = align;
+      gtk_widget_queue_allocate (GTK_WIDGET (self));
+    }
+}
+
 static void
 gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
                                            GtkListView   *self)
@@ -247,7 +270,7 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
       else
         pos = 0;
 
-      gtk_list_item_manager_set_anchor (self->item_manager, pos, 0, NULL, (guint) -1);
+      gtk_list_view_set_anchor (self, pos, 0);
     }
   
   gtk_widget_queue_allocate (GTK_WIDGET (self));
@@ -272,18 +295,17 @@ gtk_list_view_update_adjustments (GtkListView    *self,
     {
       ListRow *row;
       guint anchor;
-      double anchor_align;
 
       page_size = gtk_widget_get_height (GTK_WIDGET (self));
       upper = gtk_list_view_get_list_height (self);
 
-      anchor = gtk_list_item_manager_get_anchor (self->item_manager, &anchor_align);
+      anchor = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
       row = gtk_list_item_manager_get_nth (self->item_manager, anchor, NULL);
       if (row)
         value = list_row_get_y (self, row);
       else
         value = 0;
-      value -= anchor_align * (page_size - (row ? row->height : 0));
+      value -= self->anchor_align * (page_size - (row ? row->height : 0));
     }
   upper = MAX (upper, page_size);
 
@@ -536,6 +558,11 @@ gtk_list_view_dispose (GObject *object)
   gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL);
   gtk_list_view_clear_adjustment (self, GTK_ORIENTATION_VERTICAL);
 
+  if (self->anchor)
+    {
+      gtk_list_item_tracker_free (self->item_manager, self->anchor);
+      self->anchor = NULL;
+    }
   g_clear_object (&self->item_manager);
 
   G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
@@ -766,6 +793,7 @@ static void
 gtk_list_view_init (GtkListView *self)
 {
   self->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self), ListRow, ListRowAugment, 
list_row_augment);
+  self->anchor = gtk_list_item_tracker_new (self->item_manager);
 
   self->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
   self->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
@@ -839,6 +867,7 @@ gtk_list_view_set_model (GtkListView *self,
         selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
 
       gtk_list_item_manager_set_model (self->item_manager, selection_model);
+      gtk_list_view_set_anchor (self, 0, 0.0);
 
       g_object_unref (selection_model);
     }
@@ -847,7 +876,6 @@ gtk_list_view_set_model (GtkListView *self,
       gtk_list_item_manager_set_model (self->item_manager, NULL);
     }
 
-
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
 }
 
diff --git a/gtk/gtkselectionmodel.h b/gtk/gtkselectionmodel.h
index 9e8de6a66b6..c982a918ad0 100644
--- a/gtk/gtkselectionmodel.h
+++ b/gtk/gtkselectionmodel.h
@@ -33,6 +33,18 @@ G_BEGIN_DECLS
 GDK_AVAILABLE_IN_ALL
 G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel)
 
+/**
+ * GTK_INVALID_LIST_POSITION:
+ *
+ * The value used to refer to a guaranteed invalid position in a #GListModel. This
+ * value may be returned from some functions, others may accept it as input.
+ * Its interpretion may differ for different functions.
+ *
+ * Refer to each function's documentation for if this value is allowed and what it
+ * does.
+ */
+#define GTK_INVALID_LIST_POSITION (G_MAXUINT)
+
 /**
  * GtkSelectionModelInterface:
  * @is_selected: Return if the item at the given position is selected.


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