[gtk/wip/otte/listview: 41/158] listview: Only allocate necesary rows



commit 1741a61e2812e8d58c93f9cf1fd8f11003a43f51
Author: Benjamin Otte <otte redhat com>
Date:   Wed Sep 26 01:37:38 2018 +0200

    listview: Only allocate necesary rows
    
    This is the big one.
    
    The listview only allocates 200 rows around the visible row now.
    Everything else is kept in ListRow instances with row->widget == NULL.
    
    For rows without a widget, we assign the median height of the child
    widgets as the row's height and then do all calculations as if there
    were widgets that had requested that height (like setting adjustment
    values or reacting to adjustment value changes).
    
    When the view is scrolled, we bind the 200 rows to the new visible area,
    so that the part of the listview that can be seen is always allocated.

 gtk/gtklistview.c | 580 ++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 478 insertions(+), 102 deletions(-)
---
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 35866d47b7..7ef2529e3e 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -29,6 +29,12 @@
 #include "gtkscrollable.h"
 #include "gtkwidgetprivate.h"
 
+/* Maximum number of list items created by the listview.
+ * For debugging, you can set this to G_MAXUINT to ensure
+ * there's always a list item for every row.
+ */
+#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
+
 /**
  * SECTION:gtklistview
  * @title: GtkListView
@@ -54,21 +60,23 @@ struct _GtkListView
   int list_width;
 
   /* managing the visible region */
-  GtkWidget *anchor;
-  int anchor_align;
+  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 */
 };
 
 struct _ListRow
 {
   guint n_rows;
-  guint height;
+  guint height; /* per row */
   GtkWidget *widget;
 };
 
 struct _ListRowAugment
 {
   guint n_rows;
-  guint height;
+  guint height; /* total */
 };
 
 enum
@@ -88,6 +96,28 @@ G_DEFINE_TYPE_WITH_CODE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET,
 
 static GParamSpec *properties[N_PROPS] = { NULL, };
 
+static void G_GNUC_UNUSED
+dump (GtkListView *self)
+{
+  ListRow *row;
+  guint n_widgets, n_list_rows;
+
+  n_widgets = 0;
+  n_list_rows = 0;
+  g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
+  for (row = gtk_rb_tree_get_first (self->rows);
+       row;
+       row = gtk_rb_tree_node_get_next (row))
+    {
+      if (row->widget)
+        n_widgets++;
+      n_list_rows++;
+      g_print ("  %4u%s (%upx)\n", row->n_rows, row->widget ? " (widget)" : "", row->height);
+    }
+
+  g_print ("  => %u widgets in %u list rows\n", n_widgets, n_list_rows);
+}
+
 static void
 list_row_augment (GtkRbTree *tree,
                   gpointer   node_augment,
@@ -98,7 +128,7 @@ list_row_augment (GtkRbTree *tree,
   ListRow *row = node;
   ListRowAugment *aug = node_augment;
 
-  aug->height = row->height;
+  aug->height = row->height * row->n_rows;
   aug->n_rows = row->n_rows;
 
   if (left)
@@ -225,9 +255,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
           y -= aug->height;
         }
 
-      if (y < row->height)
+      if (y < row->height * row->n_rows)
         break;
-      y -= row->height;
+      y -= row->height * row->n_rows;
 
       row = gtk_rb_tree_node_get_right (row);
     }
@@ -242,15 +272,23 @@ static int
 list_row_get_y (GtkListView *self,
                 ListRow     *row)
 {
-  ListRow *parent;
-  int y = 0;
+  ListRow *parent, *left;
+  int y;
+
+  left = gtk_rb_tree_node_get_left (row);
+  if (left)
+    {
+      ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left);
+      y = aug->height;
+    }
+  else
+    y = 0; 
 
-  y = 0; 
   for (parent = gtk_rb_tree_node_get_parent (row);
        parent != NULL;
        parent = gtk_rb_tree_node_get_parent (row))
     {
-      ListRow *left = gtk_rb_tree_node_get_left (parent);
+      left = gtk_rb_tree_node_get_left (parent);
 
       if (left != row)
         {
@@ -259,7 +297,7 @@ list_row_get_y (GtkListView *self,
               ListRowAugment *aug = gtk_rb_tree_get_augment (self->rows, left);
               y += aug->height;
             }
-          y += parent->height;
+          y += parent->height * parent->n_rows;
         }
 
       row = parent;
@@ -282,25 +320,189 @@ gtk_list_view_get_list_height (GtkListView *self)
   return aug->height;
 }
 
+static gboolean
+gtk_list_view_merge_list_rows (GtkListView *self,
+                               ListRow     *first,
+                               ListRow     *second)
+{
+  if (first->widget || second->widget)
+    return FALSE;
+
+  first->n_rows += second->n_rows;
+  gtk_rb_tree_node_mark_dirty (first);
+  gtk_rb_tree_remove (self->rows, second);
+
+  return TRUE;
+}
+
+static void
+gtk_list_view_release_rows (GtkListView *self)
+{
+  ListRow *row, *prev, *next;
+  guint i;
+
+  row = gtk_rb_tree_get_first (self->rows);
+  i = 0;
+  while (i < self->anchor_start)
+    {
+      if (row->widget)
+        {
+          gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget);
+          row->widget = NULL;
+          i++;
+          prev = gtk_rb_tree_node_get_previous (row);
+          if (prev && gtk_list_view_merge_list_rows (self, prev, row))
+            row = prev;
+          next = gtk_rb_tree_node_get_next (row);
+          if (next && next->widget == NULL)
+            {
+              i += next->n_rows;
+              if (!gtk_list_view_merge_list_rows (self, next, row))
+                g_assert_not_reached ();
+              row = gtk_rb_tree_node_get_next (next);
+            }
+          else 
+            {
+              row = next;
+            }
+        }
+      else
+        {
+          i += row->n_rows;
+          row = gtk_rb_tree_node_get_next (row);
+        }
+    }
+
+  row = gtk_list_view_get_row (self, self->anchor_end, NULL);
+  if (row == NULL)
+    return;
+  
+  if (row->widget)
+    {
+      gtk_list_item_manager_release_list_item (self->item_manager, NULL, row->widget);
+      row->widget = NULL;
+      prev = gtk_rb_tree_node_get_previous (row);
+      if (prev && gtk_list_view_merge_list_rows (self, prev, row))
+        row = prev;
+    }
+
+  while ((next = gtk_rb_tree_node_get_next (row)))
+    {
+      if (next->widget)
+        {
+          gtk_list_item_manager_release_list_item (self->item_manager, NULL, next->widget);
+          next->widget = NULL;
+        }
+      gtk_list_view_merge_list_rows (self, row, next);
+    }
+}
+
+static void
+gtk_list_view_ensure_rows (GtkListView              *self,
+                           GtkListItemManagerChange *change,
+                           guint                     update_start)
+{
+  ListRow *row, *new_row;
+  guint i, offset;
+  GtkWidget *insert_before;
+
+  gtk_list_view_release_rows (self);
+
+  row = gtk_list_view_get_row (self, self->anchor_start, &offset);
+  if (offset > 0)
+    {
+      new_row = gtk_rb_tree_insert_before (self->rows, row);
+      new_row->n_rows = offset;
+      row->n_rows -= offset;
+      gtk_rb_tree_node_mark_dirty (row);
+    }
+
+  insert_before = gtk_widget_get_first_child (GTK_WIDGET (self));
+
+  for (i = self->anchor_start; i < self->anchor_end; i++)
+    {
+      if (row->n_rows > 1)
+        {
+          new_row = gtk_rb_tree_insert_before (self->rows, row);
+          new_row->n_rows = 1;
+          row->n_rows--;
+          gtk_rb_tree_node_mark_dirty (row);
+        }
+      else
+        {
+          new_row = row;
+          row = gtk_rb_tree_node_get_next (row);
+        }
+      if (new_row->widget == NULL)
+        {
+          if (change)
+            {
+              new_row->widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager,
+                                                                               change,
+                                                                               i,
+                                                                               insert_before);
+            }
+          if (new_row->widget == NULL)
+            {
+              new_row->widget = gtk_list_item_manager_acquire_list_item (self->item_manager,
+                                                                         i,
+                                                                         insert_before);
+            }
+        }
+      else
+        {
+          if (update_start <= i)
+            gtk_list_item_manager_update_list_item (self->item_manager, new_row->widget, i);
+          insert_before = gtk_widget_get_next_sibling (new_row->widget);
+        }
+    }
+}
+
+static void
+gtk_list_view_unset_anchor (GtkListView *self)
+{
+  self->anchor = NULL;
+  self->anchor_align = 0;
+  self->anchor_start = 0;
+  self->anchor_end = 0;
+}
+
 static void
-gtk_list_view_set_anchor (GtkListView *self,
-                          guint        position,
-                          double       align)
+gtk_list_view_set_anchor (GtkListView              *self,
+                          guint                     position,
+                          double                    align,
+                          GtkListItemManagerChange *change,
+                          guint                     update_start)
 {
   ListRow *row;
+  guint items_before, items_after, total_items, n_rows;
 
   g_assert (align >= 0.0 && align <= 1.0);
 
-  row = gtk_list_view_get_row (self, position, NULL);
-  if (row == NULL)
+  if (self->model)
+    n_rows = g_list_model_get_n_items (self->model);
+  else
+    n_rows = 0;
+  if (n_rows == 0)
     {
-      /* like, if the list is empty */
-      self->anchor = NULL;
-      self->anchor_align = 0.0;
+      gtk_list_view_unset_anchor (self);
       return;
     }
+  total_items = MIN (GTK_LIST_VIEW_MAX_LIST_ITEMS, n_rows);
+  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_rows - items_after) - items_before;
+  self->anchor_end = self->anchor_start + total_items;
+  g_assert (self->anchor_end <= n_rows);
+
+  gtk_list_view_ensure_rows (self, change, update_start);
 
+  row = gtk_list_view_get_row (self, position, NULL);
   self->anchor = row->widget;
+  g_assert (self->anchor);
   self->anchor_align = align;
 
   gtk_widget_queue_allocate (GTK_WIDGET (self));
@@ -318,11 +520,13 @@ gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment,
 
       row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy);
       if (row)
-        pos = list_row_get_position (self, row);
+        {
+          pos = list_row_get_position (self, row) + dy / row->height;
+        }
       else
         pos = 0;
 
-      gtk_list_view_set_anchor (self, pos, 0);
+      gtk_list_view_set_anchor (self, pos, 0, NULL, (guint) -1);
     }
   else
     { 
@@ -379,23 +583,38 @@ gtk_list_view_update_adjustments (GtkListView    *self,
                                      self);
 }
 
+static int
+compare_ints (gconstpointer first,
+               gconstpointer second)
+{
+  return *(int *) first - *(int *) second;
+}
+
+static guint
+gtk_list_view_get_unknown_row_height (GtkListView *self,
+                                      GArray      *heights)
+{
+  g_return_val_if_fail (heights->len > 0, 0);
+
+  /* return the median and hope rows are generally uniform with few outliers */
+  g_array_sort (heights, compare_ints);
+
+  return g_array_index (heights, int, heights->len / 2);
+}
+
 static void
-gtk_list_view_measure (GtkWidget      *widget,
-                       GtkOrientation  orientation,
-                       int             for_size,
-                       int            *minimum,
-                       int            *natural,
-                       int            *minimum_baseline,
-                       int            *natural_baseline)
+gtk_list_view_measure_across (GtkWidget      *widget,
+                              GtkOrientation  orientation,
+                              int             for_size,
+                              int            *minimum,
+                              int            *natural)
 {
   GtkListView *self = GTK_LIST_VIEW (widget);
   ListRow *row;
   int min, nat, child_min, child_nat;
-
   /* XXX: Figure out how to split a given height into per-row heights.
    * Good luck! */
-  if (orientation == GTK_ORIENTATION_HORIZONTAL)
-    for_size = -1;
+  for_size = -1;
 
   min = 0;
   nat = 0;
@@ -404,25 +623,87 @@ gtk_list_view_measure (GtkWidget      *widget,
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
     {
+      /* ignore unavailable rows */
+      if (row->widget == NULL)
+        continue;
+
       gtk_widget_measure (row->widget,
                           orientation, for_size,
                           &child_min, &child_nat, NULL, NULL);
-      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+      min = MAX (min, child_min);
+      nat = MAX (nat, child_nat);
+    }
+
+  *minimum = min;
+  *natural = nat;
+}
+
+static void
+gtk_list_view_measure_list (GtkWidget      *widget,
+                            GtkOrientation  orientation,
+                            int             for_size,
+                            int            *minimum,
+                            int            *natural)
+{
+  GtkListView *self = GTK_LIST_VIEW (widget);
+  ListRow *row;
+  int min, nat, child_min, child_nat;
+  GArray *min_heights, *nat_heights;
+  guint n_unknown;
+
+  min_heights = g_array_new (FALSE, FALSE, sizeof (int));
+  nat_heights = g_array_new (FALSE, FALSE, sizeof (int));
+  n_unknown = 0;
+  min = 0;
+  nat = 0;
+
+  for (row = gtk_rb_tree_get_first (self->rows);
+       row != NULL;
+       row = gtk_rb_tree_node_get_next (row))
+    {
+      if (row->widget)
         {
-          min = MAX (min, child_min);
-          nat = MAX (nat, child_nat);
+          gtk_widget_measure (row->widget,
+                              orientation, for_size,
+                              &child_min, &child_nat, NULL, NULL);
+          g_array_append_val (min_heights, child_min);
+          g_array_append_val (nat_heights, child_nat);
+          min += child_min;
+          nat += child_nat;
         }
       else
         {
-          min += child_nat;
-          nat = min;
+          n_unknown += row->n_rows;
         }
     }
 
+  if (n_unknown)
+    {
+      min += n_unknown * gtk_list_view_get_unknown_row_height (self, min_heights);
+      nat += n_unknown * gtk_list_view_get_unknown_row_height (self, nat_heights);
+    }
+  g_array_free (min_heights, TRUE);
+  g_array_free (nat_heights, TRUE);
+
   *minimum = min;
   *natural = nat;
 }
 
+static void
+gtk_list_view_measure (GtkWidget      *widget,
+                       GtkOrientation  orientation,
+                       int             for_size,
+                       int            *minimum,
+                       int            *natural,
+                       int            *minimum_baseline,
+                       int            *natural_baseline)
+{
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
+  else
+    gtk_list_view_measure_list (widget, orientation, for_size, minimum, natural);
+}
+
 static void
 gtk_list_view_size_allocate (GtkWidget *widget,
                              int        width,
@@ -432,8 +713,13 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   GtkListView *self = GTK_LIST_VIEW (widget);
   GtkAllocation child_allocation = { 0, 0, 0, 0 };
   ListRow *row;
+  GArray *heights;
   int min, nat, row_height;
 
+  /* step 0: exit early if list is empty */
+  if (gtk_rb_tree_get_root (self->rows) == NULL)
+    return;
+
   /* step 1: determine width of the list */
   gtk_widget_measure (widget, GTK_ORIENTATION_HORIZONTAL,
                       -1,
@@ -443,11 +729,16 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   else
     self->list_width = MAX (nat, width);
 
-  /* step 2: determine height of list */
+  /* step 2: determine height of known list items */
+  heights = g_array_new (FALSE, FALSE, sizeof (int));
+
   for (row = gtk_rb_tree_get_first (self->rows);
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
     {
+      if (row->widget == NULL)
+        continue;
+
       gtk_widget_measure (row->widget, GTK_ORIENTATION_VERTICAL,
                           self->list_width,
                           &min, &nat, NULL, NULL);
@@ -460,6 +751,25 @@ gtk_list_view_size_allocate (GtkWidget *widget,
           row->height = row_height;
           gtk_rb_tree_node_mark_dirty (row);
         }
+      g_array_append_val (heights, row_height);
+    }
+
+  /* step 3: determine height of unknown items */
+  row_height = gtk_list_view_get_unknown_row_height (self, heights);
+  g_array_free (heights, TRUE);
+
+  for (row = gtk_rb_tree_get_first (self->rows);
+       row != NULL;
+       row = gtk_rb_tree_node_get_next (row))
+    {
+      if (row->widget)
+        continue;
+
+      if (row->height != row_height)
+        {
+          row->height = row_height;
+          gtk_rb_tree_node_mark_dirty (row);
+        }
     }
 
   /* step 3: update the adjustments */
@@ -474,9 +784,13 @@ gtk_list_view_size_allocate (GtkWidget *widget,
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
     {
-      child_allocation.height = row->height;
-      gtk_widget_size_allocate (row->widget, &child_allocation, -1);
-      child_allocation.y += child_allocation.height;
+      if (row->widget)
+        {
+          child_allocation.height = row->height;
+          gtk_widget_size_allocate (row->widget, &child_allocation, -1);
+        }
+
+      child_allocation.y += row->height * row->n_rows;
     }
 }
 
@@ -504,79 +818,56 @@ gtk_list_view_remove_rows (GtkListView              *self,
                            guint                     n_rows)
 {
   ListRow *row;
-  guint i;
 
   if (n_rows == 0)
     return;
 
   row = gtk_list_view_get_row (self, position, NULL);
 
-  for (i = 0; i < n_rows; i++)
+  while (n_rows > 0)
     {
-      ListRow *next = gtk_rb_tree_node_get_next (row);
-      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;
+      if (row->n_rows > n_rows)
+        {
+          row->n_rows -= n_rows;
+          gtk_rb_tree_node_mark_dirty (row);
+          n_rows = 0;
+        }
+      else
+        {
+          ListRow *next = gtk_rb_tree_node_get_next (row);
+          if (row->widget)
+            gtk_list_item_manager_release_list_item (self->item_manager, change, row->widget);
+          row->widget = NULL;
+          n_rows -= row->n_rows;
+          gtk_rb_tree_remove (self->rows, row);
+          row = next;
+        }
     }
 
   gtk_widget_queue_resize (GTK_WIDGET (self));
 }
 
 static void
-gtk_list_view_add_rows (GtkListView              *self,
-                        GtkListItemManagerChange *change,
-                        guint                     position,
-                        guint                     n_rows)
+gtk_list_view_add_rows (GtkListView *self,
+                        guint        position,
+                        guint        n_rows)
 {  
   ListRow *row;
-  guint i;
+  guint offset;
 
   if (n_rows == 0)
     return;
 
-  row = gtk_list_view_get_row (self, position, NULL);
+  row = gtk_list_view_get_row (self, position, &offset);
 
-  for (i = 0; i < n_rows; i++)
-    {
-      ListRow *new_row;
-        
-      new_row = gtk_rb_tree_insert_before (self->rows, row);
-      new_row->n_rows = 1;
-      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);
-        }
-    }
+  if (row == NULL || row->widget)
+    row = gtk_rb_tree_insert_before (self->rows, row);
+  row->n_rows += n_rows;
+  gtk_rb_tree_node_mark_dirty (row);
 
   gtk_widget_queue_resize (GTK_WIDGET (self));
 }
 
-static void
-gtk_list_view_update_rows (GtkListView *self,
-                           guint        position)
-{
-  ListRow *row;
-
-  for (row = gtk_list_view_get_row (self, position, NULL);
-       row;
-       row = gtk_rb_tree_node_get_next (row))
-    {
-      gtk_list_item_manager_update_list_item (self->item_manager, row->widget, position);
-
-      position++;
-    }
-}
-
 static void
 gtk_list_view_model_items_changed_cb (GListModel  *model,
                                       guint        position,
@@ -589,17 +880,100 @@ gtk_list_view_model_items_changed_cb (GListModel  *model,
   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);
-  if (removed != added)
-    gtk_list_view_update_rows (self, position + added);
+  gtk_list_view_add_rows (self, position, added);
+
+  /* The anchor was removed, but it may just have moved to a different position */
+  if (self->anchor && gtk_list_item_manager_change_contains (change, self->anchor))
+    {
+      /* The anchor was removed, do a more expensive rebuild trying to find if
+       * the anchor maybe got readded somewhere else */
+      ListRow *row, *new_row;
+      GtkWidget *insert_before;
+      guint i, offset, anchor_pos;
+      
+      row = gtk_list_view_get_row (self, position, &offset);
+      for (new_row = row;
+           new_row && new_row->widget == NULL;
+           new_row = gtk_rb_tree_node_get_next (new_row))
+        ;
+      if (new_row)
+        insert_before = new_row->widget;
+      else
+        insert_before = NULL; /* we're at the end */
+
+      for (i = 0; i < added; i++)
+        {
+          GtkWidget *widget;
 
-  if (gtk_list_item_manager_change_contains (change, self->anchor))
+          widget = gtk_list_item_manager_try_reacquire_list_item (self->item_manager,
+                                                                  change,
+                                                                  position + i,
+                                                                  insert_before);
+          if (widget == NULL)
+            {
+              offset++;
+              continue;
+            }
+
+          if (offset > 0)
+            {
+              new_row = gtk_rb_tree_insert_before (self->rows, row);
+              new_row->n_rows = offset;
+              row->n_rows -= offset;
+              offset = 0;
+              gtk_rb_tree_node_mark_dirty (row);
+            }
+
+          if (row->n_rows == 1)
+            {
+              new_row = row;
+              row = gtk_rb_tree_node_get_next (row);
+            }
+          else
+            {
+              new_row = gtk_rb_tree_insert_before (self->rows, row);
+              new_row->n_rows = 1;
+              row->n_rows--;
+              gtk_rb_tree_node_mark_dirty (row);
+            }
+
+          new_row->widget = 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));
+
+          anchor_pos = position + (anchor_pos - position) * added / removed;
+        }
+      gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position);
+    }
+  else
     {
-      guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+      /* 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;
       
-      /* removed cannot be NULL or the anchor wouldn't have been removed */
-      anchor_pos = position + (anchor_pos - position) * added / removed;
-      gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align);
+      if (self->anchor)
+        {
+          anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor));
+
+          if (anchor_pos >= position)
+            anchor_pos += added - removed;
+        }
+      else
+        {
+          anchor_pos = 0;
+        }
+
+      gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align, change, position);
     }
 
   gtk_list_item_manager_end_change (self->item_manager, change);
@@ -617,6 +991,8 @@ gtk_list_view_clear_model (GtkListView *self)
                                         gtk_list_view_model_items_changed_cb,
                                         self);
   g_clear_object (&self->model);
+
+  gtk_list_view_unset_anchor (self);
 }
 
 static void
@@ -897,8 +1273,8 @@ gtk_list_view_set_model (GtkListView *self,
                         G_CALLBACK (gtk_list_view_model_items_changed_cb),
                         self);
 
-      gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model));
-      gtk_list_view_set_anchor (self, 0, 0);
+      gtk_list_view_add_rows (self, 0, g_list_model_get_n_items (model));
+      gtk_list_view_set_anchor (self, 0, 0, NULL, (guint) -1);
     }
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
@@ -927,7 +1303,7 @@ gtk_list_view_set_functions (GtkListView          *self,
   gtk_list_item_manager_set_factory (self->item_manager, factory);
   g_object_unref (factory);
 
-  gtk_list_view_add_rows (self, NULL, 0, n_items);
-  gtk_list_view_set_anchor (self, anchor, anchor_align);
+  gtk_list_view_add_rows (self, 0, n_items);
+  gtk_list_view_set_anchor (self, anchor, anchor_align, NULL, (guint) -1);
 }
 


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