[gtk/wip/otte/listview: 4/15] listitemmanager: Change the way listitems are handled




commit 49e416a0029b61f34e6ca66f88ee25d9eef132f0
Author: Benjamin Otte <otte redhat com>
Date:   Mon Nov 15 15:01:24 2021 +0100

    listitemmanager: Change the way listitems are handled
    
    Instead of immediately removing listitems when they are added/removed or
    their widgets become unnecessary, force a manual cleanup. Do this
    cleanup in size_allocate().
    
    This way, we can assign space to listitems during allocate and be sure
    it survives until the next call to size_allocate(), which allows
    tracking multiple changes to the listitems in a single frame, as the
    assigned space never collapses.
    
    It also simplifies gridview code, because it allows splitting listitems
    so that every listitem represents a rectangular region.
    
    Fixes #2971

 gtk/gtkgridview.c               | 562 +++++++++++++++++-----------------------
 gtk/gtklistbase.c               |   9 +-
 gtk/gtklistitemmanager.c        |  80 ++++--
 gtk/gtklistitemmanagerprivate.h |   6 +
 gtk/gtklistview.c               |  75 +++---
 5 files changed, 341 insertions(+), 391 deletions(-)
---
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index c6cefbb10e..cc00028434 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -107,13 +107,13 @@ struct _GtkGridViewClass
 struct _Cell
 {
   GtkListItemManagerItem parent;
-  guint size; /* total, only counting cells in first column */
+  GdkRectangle area;
 };
 
 struct _CellAugment
 {
   GtkListItemManagerItemAugment parent;
-  guint size; /* total, only counting first column */
+  GdkRectangle bounds;
 };
 
 enum
@@ -156,12 +156,12 @@ dump (GtkGridView *self)
       if (cell->parent.widget)
         n_widgets++;
       n_list_rows++;
-      n_items += cell->parent.n_items;
-      g_print ("%6u%6u %5ux%3u %s (%upx)\n",
+      g_print ("%6u%6u %5ux%3u %s (%d, %d, %d, %d)\n",
                cell->parent.n_items, n_items,
                n_items / (self->n_columns ? self->n_columns : self->min_columns),
                n_items % (self->n_columns ? self->n_columns : self->min_columns),
-               cell->parent.widget ? " (widget)" : "", cell->size);
+               cell->parent.widget ? " (widget)" : "", cell->area.x, cell->area.y, cell->area.width, 
cell->area.height);
+      n_items += cell->parent.n_items;
     }
 
   g_print ("  => %u widgets in %u list rows\n", n_widgets, n_list_rows);
@@ -179,139 +179,115 @@ cell_augment (GtkRbTree *tree,
 
   gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
 
-  aug->size = cell->size;
+  aug->bounds = cell->area;
 
   if (left)
     {
       CellAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
 
-      aug->size += left_aug->size;
+      gdk_rectangle_union (&aug->bounds, &left_aug->bounds, &aug->bounds);
     }
 
   if (right)
     {
       CellAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
 
-      aug->size += right_aug->size;
+      gdk_rectangle_union (&aug->bounds, &right_aug->bounds, &aug->bounds);
     }
 }
 
-/*<private>
- * gtk_grid_view_get_cell_at_y:
- * @self: a `GtkGridView`
- * @y: an offset in direction of @self's orientation
- * @position: (out caller-allocates) (optional): stores the position
- *   index of the returned row
- * @offset: (out caller-allocates) (optional): stores the offset
- *   in pixels between y and top of cell.
- * @offset: (out caller-allocates) (optional): stores the height
- *   of the cell
- *
- * Gets the Cell that occupies the leftmost position in the row at offset
- * @y into the primary direction.
- *
- * If y is larger than the height of all cells, %NULL will be returned.
- * In particular that means that for an empty grid, %NULL is returned
- * for any value.
- *
- * Returns: (nullable): The first cell at offset y
- **/
-static Cell *
-gtk_grid_view_get_cell_at_y (GtkGridView *self,
-                             int          y,
-                             guint       *position,
-                             int         *offset,
-                             int         *size)
+static gboolean
+cell_get_area (GtkGridView  *self,
+               Cell         *cell,
+               guint         pos,
+               GdkRectangle *area)
 {
-  Cell *cell, *tmp;
-  guint pos;
+  int x, y, n;
 
-  cell = gtk_list_item_manager_get_root (self->item_manager);
-  pos = 0;
+  g_assert (pos < cell->parent.n_items);
 
-  while (cell)
-    {
-      tmp = gtk_rb_tree_node_get_left (cell);
-      if (tmp)
-        {
-          CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
-          if (y < aug->size)
-            {
-              cell = tmp;
-              continue;
-            }
-          y -= aug->size;
-          pos += aug->parent.n_items;
-        }
+  if (cell->area.width == 0 || cell->area.height == 0)
+    return FALSE;
 
-      if (y < cell->size)
-        break;
-      y -= cell->size;
-      pos += cell->parent.n_items;
+  x = pos % self->n_columns;
+  y = pos / self->n_columns;
+  n = (cell->parent.n_items + self->n_columns - 1) / self->n_columns;
+  area->x = cell->area.x + x * cell->area.width / self->n_columns;
+  area->width = cell->area.width / MIN (cell->parent.n_items, self->n_columns);
+  area->y = cell->area.y + y * cell->area.height / n;
+  area->height = cell->area.height / n;
 
-      cell = gtk_rb_tree_node_get_right (cell);
-    }
+  return TRUE;
+}
 
-  if (cell == NULL)
-    {
-      if (position)
-        *position = 0;
-      if (offset)
-        *offset = 0;
-      if (size)
-        *size = 0;
-      return NULL;
-    }
+static Cell *
+cell_get_cell_at (GtkGridView *self,
+                  Cell        *cell,
+                  int          x,
+                  int          y,
+                  guint       *skip)
+{
+  CellAugment *aug;
+  Cell *result;
 
-  /* We know have the (range of) cell(s) that contains this offset.
-   * Now for the hard part of computing which index this actually is.
-   */
-  if (offset || position || size)
-    {
-      guint n_items = cell->parent.n_items;
-      guint no_widget_rows, skip;
+  aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
+  if (!gdk_rectangle_contains_point (&aug->bounds, x, y))
+    return NULL;
 
-      /* skip remaining items at end of row */
-      if (pos % self->n_columns)
-        {
-          skip = self->n_columns - pos % self->n_columns;
-          if (n_items <= skip)
-            {
-              g_warning ("ran out of items");
-              if (position)
-                *position = 0;
-              if (offset)
-                *offset = 0;
-              if (size)
-                *size = 0;
-              return NULL;
-            }
-          n_items -= skip;
-          pos += skip;
-        }
+  result = gtk_rb_tree_node_get_left (cell);
+  if (result)
+    result = cell_get_cell_at (self, result, x, y, skip);
+  if (result)
+    return result;
 
-      /* Skip all the rows this index doesn't go into */
-      no_widget_rows = (n_items - 1) / self->n_columns;
-      skip = MIN (y / self->unknown_row_height, no_widget_rows);
-      y -= skip * self->unknown_row_height;
-      pos += self->n_columns * skip;
+  result = gtk_rb_tree_node_get_right (cell);
+  if (result)
+    result = cell_get_cell_at (self, result, x, y, skip);
+  if (result)
+    return result;
 
-      if (position)
-        *position = pos;
-      if (offset)
-        *offset = y;
-      if (size)
-        {
-          if (skip < no_widget_rows)
-            *size = self->unknown_row_height;
-          else
-            *size = cell->size - no_widget_rows * self->unknown_row_height;
-        }
+  if (!gdk_rectangle_contains_point (&cell->area, x, y))
+    return NULL;
+
+  if (skip)
+    {
+      x = (x - cell->area.x) * (cell->parent.n_items % self->n_columns) / cell->area.width;
+      y = (y - cell->area.y) * ((cell->parent.n_items + self->n_columns - 1) / self->n_columns) / 
cell->area.height;
+      *skip = y * self->n_columns + x;
+      *skip = MIN (*skip, cell->parent.n_items - 1);
     }
 
   return cell;
 }
 
+/*<private>
+ * gtk_grid_view_get_cell_at:
+ * @self: a `GtkGridView`
+ * @x: an offset in direction opposite @self's orientation
+ * @y: an offset in direction of @self's orientation
+ * @skip: (out caller-allocates) (optional): stores the offset
+ *   position into the items of the returned cell
+ *
+ * Gets the Cell that occupies the position at (x, y).
+ *
+ * If no Cell is assigned to those coordinates, %NULL will be returned.
+ * In particular that means that for an empty grid, %NULL is returned
+ * for any value.
+ *
+ * Returns: (nullable): The cell at (x, y)
+ **/
+static Cell *
+gtk_grid_view_get_cell_at (GtkGridView *self,
+                           int          x,
+                           int          y,
+                           guint       *skip)
+{
+  return cell_get_cell_at (self,
+                           gtk_list_item_manager_get_root (self->item_manager),
+                           x, y,
+                           skip);
+}
+
 static gboolean
 gtk_grid_view_get_allocation_along (GtkListBase *base,
                                     guint        pos,
@@ -319,37 +295,12 @@ gtk_grid_view_get_allocation_along (GtkListBase *base,
                                     int         *size)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
-  Cell *cell, *tmp;
-  int y;
-
-  cell = gtk_list_item_manager_get_root (self->item_manager);
-  y = 0;
-  pos -= pos % self->n_columns;
-
-  while (cell)
-    {
-      tmp = gtk_rb_tree_node_get_left (cell);
-      if (tmp)
-        {
-          CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
-          if (pos < aug->parent.n_items)
-            {
-              cell = tmp;
-              continue;
-            }
-          pos -= aug->parent.n_items;
-          y += aug->size;
-        }
-
-      if (pos < cell->parent.n_items)
-        break;
-      y += cell->size;
-      pos -= cell->parent.n_items;
-
-      cell = gtk_rb_tree_node_get_right (cell);
-    }
+  Cell *cell;
+  GdkRectangle area;
+  guint skip;
 
-  if (cell == NULL)
+  cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
+  if (cell == NULL || !cell_get_area (self, cell, skip, &area))
     {
       if (offset)
         *offset = 0;
@@ -358,37 +309,10 @@ gtk_grid_view_get_allocation_along (GtkListBase *base,
       return FALSE;
     }
 
-  /* We know have the (range of) cell(s) that contains this offset.
-   * Now for the hard part of computing which index this actually is.
-   */
-  if (offset || size)
-    {
-      guint n_items = cell->parent.n_items;
-      guint skip;
-
-      /* skip remaining items at end of row */
-      if (pos % self->n_columns)
-        {
-          skip = pos % self->n_columns;
-          n_items -= skip;
-          pos -= skip;
-        }
-
-      /* Skip all the rows this index doesn't go into */
-      skip = pos / self->n_columns;
-      n_items -= skip * self->n_columns;
-      y += skip * self->unknown_row_height;
-
-      if (offset)
-        *offset = y;
-      if (size)
-        {
-          if (n_items > self->n_columns)
-            *size = self->unknown_row_height;
-          else
-            *size = cell->size - skip * self->unknown_row_height;
-        }
-    }
+  if (offset)
+    *offset = cell->area.y;
+  if (size)
+    *size = cell->area.height;
 
   return TRUE;
 }
@@ -400,15 +324,24 @@ gtk_grid_view_get_allocation_across (GtkListBase *base,
                                      int         *size)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
-  guint start;
+  Cell *cell;
+  GdkRectangle area;
+  guint skip;
 
-  pos %= self->n_columns;
-  start = ceil (self->column_width * pos);
+  cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
+  if (cell == NULL || !cell_get_area (self, cell, skip, &area))
+    {
+      if (offset)
+        *offset = 0;
+      if (size)
+        *size = 0;
+      return FALSE;
+    }
 
   if (offset)
-    *offset = start;
+    *offset = cell->area.x;
   if (size)
-    *size = ceil (self->column_width * (pos + 1)) - start;
+    *size = cell->area.width;
 
   return TRUE;
 }
@@ -421,37 +354,37 @@ gtk_grid_view_get_position_from_allocation (GtkListBase           *base,
                                             cairo_rectangle_int_t *area)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
-  int offset, size;
-  guint pos, n_items;
+  Cell *cell;
+  guint skip;
 
   if (across >= self->column_width * self->n_columns)
     return FALSE;
 
-  n_items = gtk_list_base_get_n_items (base);
-  if (!gtk_grid_view_get_cell_at_y (self,
-                                    along,
-                                    &pos,
-                                    &offset,
-                                    &size))
-    return FALSE;
-
-  pos += floor (across / self->column_width);
-
-  if (pos >= n_items)
+  cell = gtk_grid_view_get_cell_at (self,
+                                    across, along,
+                                    &skip);
+  if (cell != NULL)
     {
-      /* Ugh, we're in the last row and don't have enough items
-       * to fill the row.
-       * Do it the hard way then... */
-      pos = n_items - 1;
+      *position = skip + gtk_list_item_manager_get_item_position (self->item_manager, cell);
+    }
+  else
+    {
+      /* Assign the extra space at the last row to the last item
+       * (in case list.n_items() is not a multiple of the column count)
+       */
+      cell = gtk_list_item_manager_get_last (self->item_manager);
+      if (cell == NULL ||
+          along < cell->area.y || along > cell->area.y + cell->area.height ||
+          across < cell->area.x + cell->area.width || across >= ceil (self->column_width * self->n_columns))
+        return FALSE;
+      skip = cell->parent.n_items - 1;
+      *position = gtk_list_base_get_n_items (base) - 1;
     }
 
-  *position = pos;
   if (area)
     {
-      area->x = ceil (self->column_width * (pos % self->n_columns));
-      area->width = ceil (self->column_width * (1 + pos % self->n_columns)) - area->x;
-      area->y = along - offset;
-      area->height = size;
+      if (!cell_get_area (self, cell, skip, area))
+        memset (area, 0, sizeof (GdkRectangle));
     }
 
   return TRUE;
@@ -462,26 +395,19 @@ gtk_grid_view_get_items_in_rect (GtkListBase        *base,
                                  const GdkRectangle *rect)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
-  guint first_row, last_row, first_column, last_column, n_items;
+  guint start_pos, end_pos;
   GtkBitset *result;
 
   result = gtk_bitset_new_empty ();
 
-  n_items = gtk_list_base_get_n_items (base);
-  if (n_items == 0)
+  if (!gtk_grid_view_get_position_from_allocation (base, rect->x, rect->y, &start_pos, NULL) ||
+      !gtk_grid_view_get_position_from_allocation (base, rect->x + rect->width - 1, rect->y + rect->height - 
1, &end_pos, NULL))
     return result;
 
-  first_column = floor (rect->x / self->column_width);
-  last_column = floor ((rect->x + rect->width) / self->column_width);
-  if (!gtk_grid_view_get_cell_at_y (self, rect->y, &first_row, NULL, NULL))
-    first_row = rect->y < 0 ? 0 : n_items - 1;
-  if (!gtk_grid_view_get_cell_at_y (self, rect->y + rect->height, &last_row, NULL, NULL))
-    last_row = rect->y < 0 ? 0 : n_items - 1;
-
   gtk_bitset_add_rectangle (result,
-                            first_row + first_column,
-                            last_column - first_column + 1,
-                            (last_row - first_row) / self->n_columns + 1,
+                            start_pos,
+                            (end_pos - start_pos) % self->n_columns + 1,
+                            (end_pos - start_pos) / self->n_columns + 1,
                             self->n_columns);
 
   return result;
@@ -712,27 +638,31 @@ gtk_grid_view_measure (GtkWidget      *widget,
 }
 
 static void
-cell_set_size (Cell  *cell,
-               guint  size)
+cell_set_position (Cell *cell,
+                   int   x,
+                   int   y)
 {
-  if (cell->size == size)
+  if (cell->area.x == x &&
+      cell->area.y == y)
     return;
 
-  cell->size = size;
+  cell->area.x = x;
+  cell->area.y = y;
   gtk_rb_tree_node_mark_dirty (cell);
 }
 
-static int
-gtk_grid_view_compute_total_height (GtkGridView *self)
+static void
+cell_set_size (Cell *cell,
+               int   width,
+               int   height)
 {
-  Cell *cell;
-  CellAugment *aug;
+  if (cell->area.width == width &&
+      cell->area.height == height)
+    return;
 
-  cell = gtk_list_item_manager_get_root (self->item_manager);
-  if (cell == NULL)
-    return 0;
-  aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
-  return aug->size;
+  cell->area.width = width;
+  cell->area.height = height;
+  gtk_rb_tree_node_mark_dirty (cell);
 }
 
 static void
@@ -742,12 +672,11 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
                              int        baseline)
 {
   GtkGridView *self = GTK_GRID_VIEW (widget);
-  Cell *cell, *start;
+  Cell *cell, *row_cell;
   GArray *heights;
-  int min_row_height, row_height, col_min, col_nat;
+  int min_row_height, row_height, total_height, col_min, col_nat;
   GtkOrientation orientation, opposite_orientation;
   GtkScrollablePolicy scroll_policy;
-  gboolean known;
   int x, y;
   guint i;
 
@@ -756,11 +685,14 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
   opposite_orientation = OPPOSITE_ORIENTATION (orientation);
   min_row_height = ceil ((double) height / GTK_GRID_VIEW_MAX_VISIBLE_ROWS);
 
-  /* step 0: exit early if list is empty */
+  /* step 1: Clean up, so the items tracking deleted rows go away */
+  gtk_list_item_manager_gc (self->item_manager);
+
+  /* step 2: exit early if list is empty */
   if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
     return;
 
-  /* step 1: determine width of the list */
+  /* step 3: determine width of the list */
   gtk_grid_view_measure_column_size (self, &col_min, &col_nat);
   self->n_columns = gtk_grid_view_compute_n_columns (self, 
                                                      orientation == GTK_ORIENTATION_VERTICAL ? width : 
height,
@@ -768,146 +700,118 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
   self->column_width = (orientation == GTK_ORIENTATION_VERTICAL ? width : height) / self->n_columns;
   self->column_width = MAX (self->column_width, col_min);
 
-  /* step 2: determine height of known rows */
+  /* step 4: determine height of known rows */
   heights = g_array_new (FALSE, FALSE, sizeof (int));
+  total_height = 0;
 
-  i = 0;
-  row_height = 0;
-  start = NULL;
-  for (cell = gtk_list_item_manager_get_first (self->item_manager);
-       cell != NULL;
-       cell = gtk_rb_tree_node_get_next (cell))
+  cell = gtk_list_item_manager_get_first (self->item_manager);
+  while (cell)
     {
-      if (i == 0)
-        start = cell;
-      
-      if (cell->parent.widget)
+      if (cell->parent.n_items >= MAX (2, self->n_columns))
         {
-          int min, nat, size;
-          gtk_widget_measure (cell->parent.widget,
-                              gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
-                              self->column_width,
-                              &min, &nat, NULL, NULL);
-          if (scroll_policy == GTK_SCROLL_MINIMUM)
-            size = min;
-          else
-            size = nat;
-          size = MAX (size, min_row_height);
-          g_array_append_val (heights, size);
-          row_height = MAX (row_height, size);
+          int remainder = cell->parent.n_items % self->n_columns;
+
+          if (remainder > 0)
+            gtk_list_item_manager_split_item (self->item_manager, cell, cell->parent.n_items - remainder);
+          cell = gtk_rb_tree_node_get_next (cell);
+          continue;
         }
-      cell_set_size (cell, 0);
-      i += cell->parent.n_items;
 
-      if (i >= self->n_columns)
+      i = 0;
+      row_height = 0;
+      for (row_cell = cell;
+           row_cell && i < self->n_columns;
+           row_cell = gtk_rb_tree_node_get_next (row_cell))
         {
-          i %= self->n_columns;
+          if (row_cell->parent.widget)
+            {
+              int min, nat, size;
+              gtk_widget_measure (row_cell->parent.widget,
+                                  gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
+                                  self->column_width,
+                                  &min, &nat, NULL, NULL);
+              if (scroll_policy == GTK_SCROLL_MINIMUM)
+                size = min;
+              else
+                size = nat;
+              size = MAX (size, min_row_height);
+              g_array_append_val (heights, size);
+              row_height = MAX (row_height, size);
+            }
+          else if (row_cell->parent.n_items > self->n_columns - i)
+            {
+              gtk_list_item_manager_split_item (self->item_manager, row_cell, self->n_columns - i);
+            }
+          i += row_cell->parent.n_items;
+        }
 
-          cell_set_size (start, start->size + row_height);
-          start = cell;
-          row_height = 0;
+      for (i = 0;
+           cell != row_cell;
+           cell = gtk_rb_tree_node_get_next (cell))
+        {
+          cell_set_size (cell, ceil (self->column_width * (i + cell->parent.n_items)) - ceil 
(self->column_width * i), row_height);
+          i += cell->parent.n_items;
         }
+      total_height += row_height;
     }
-  if (i > 0)
-    cell_set_size (start, start->size + row_height);
 
-  /* step 3: determine height of rows with only unknown items */
+  /* step 5: determine height of rows with only unknown items and assign their size */
   self->unknown_row_height = gtk_grid_view_get_unknown_row_size (self, heights);
   g_array_free (heights, TRUE);
 
-  i = 0;
-  known = FALSE;
-  for (start = cell = gtk_list_item_manager_get_first (self->item_manager);
+  for (cell = gtk_list_item_manager_get_first (self->item_manager);
        cell != NULL;
        cell = gtk_rb_tree_node_get_next (cell))
     {
-      if (i == 0)
-        start = cell;
+      if (cell->parent.n_items < self->n_columns)
+        continue;
 
-      if (cell->parent.widget)
-        known = TRUE;
+      cell_set_size (cell,
+                     ceil (self->column_width * self->n_columns), 
+                     self->unknown_row_height * (cell->parent.n_items / self->n_columns));
+      total_height += self->unknown_row_height * (cell->parent.n_items / self->n_columns);
+    }
+
+  /* step 6: assign positions */
+  i = 0;
+  y = 0;
+  for (cell = gtk_list_item_manager_get_first (self->item_manager);
+       cell != NULL;
+       cell = gtk_rb_tree_node_get_next (cell))
+    {
+      cell_set_position (cell,
+                         ceil (self->column_width * i),
+                         y);
 
       i += cell->parent.n_items;
       if (i >= self->n_columns)
         {
-          if (!known)
-            cell_set_size (start, start->size + self->unknown_row_height);
-
-          i -= self->n_columns;
-          known = FALSE;
-
-          if (i >= self->n_columns)
-            {
-              cell_set_size (cell, cell->size + self->unknown_row_height * (i / self->n_columns));
-              i %= self->n_columns;
-            }
-          start = cell;
+          y += cell->area.height;
+          i = 0;
         }
     }
-  if (i > 0 && !known)
-    cell_set_size (start, start->size + self->unknown_row_height);
 
-  /* step 4: update the adjustments */
+  /* step 7: update the adjustments */
   gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
                                     self->column_width * self->n_columns,
-                                    gtk_grid_view_compute_total_height (self),
+                                    total_height,
                                     gtk_widget_get_size (widget, opposite_orientation),
                                     gtk_widget_get_size (widget, orientation),
                                     &x, &y);
 
-  /* step 5: run the size_allocate loop */
-  x = -x;
-  y = -y;
-  i = 0;
-  row_height = 0;
-
+  /* step 8: run the size_allocate loop */
   for (cell = gtk_list_item_manager_get_first (self->item_manager);
        cell != NULL;
        cell = gtk_rb_tree_node_get_next (cell))
     {
       if (cell->parent.widget)
         {
-          row_height += cell->size;
-
           gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
                                              cell->parent.widget,
-                                             x + ceil (self->column_width * i),
-                                             y,
-                                             ceil (self->column_width * (i + 1)) - ceil (self->column_width 
* i),
-                                             row_height);
-          i++;
-          if (i >= self->n_columns)
-            {
-              y += row_height;
-              i -= self->n_columns;
-              row_height = 0;
-            }
-        }
-      else
-        {
-          i += cell->parent.n_items;
-          /* skip remaining row if we didn't start one */
-          if (i > cell->parent.n_items && i >= self->n_columns)
-            {
-              i -= self->n_columns;
-              y += row_height;
-              row_height = 0;
-            }
-
-          row_height += cell->size;
-
-          /* skip rows that are completely contained by this cell */
-          if (i >= self->n_columns)
-            {
-              guint unknown_rows, unknown_height;
-
-              unknown_rows = i / self->n_columns;
-              unknown_height = unknown_rows * self->unknown_row_height;
-              row_height -= unknown_height;
-              y += unknown_height;
-              i %= self->n_columns;
-              g_assert (row_height >= 0);
-            }
+                                             cell->area.x - x,
+                                             cell->area.y - y,
+                                             cell->area.width,
+                                             cell->area.height);
         }
     }
 
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index e4013df05c..7547bafcca 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -122,6 +122,11 @@ static GParamSpec *properties[N_PROPS] = { NULL, };
  * last item will be returned for the whole width, even if there are empty
  * cells.
  *
+ * It is also possible for the area to be empty (ie have 0 width and height).
+ * This can happen when the widget has queued a resize and no current
+ * allocation information is available for the position or when the position
+ * has been newly added to the model.
+ *
  * Returns: %TRUE on success or %FALSE if no position occupies the given offset.
  **/
 static guint
@@ -1524,8 +1529,8 @@ gtk_list_base_start_rubberband (GtkListBase *self,
 
   priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager);
   gtk_list_item_tracker_set_position (priv->item_manager, priv->rubberband->start_tracker, pos, 0, 0);
-  priv->rubberband->start_align_across = (double) (list_x - item_area.x) / item_area.width;
-  priv->rubberband->start_align_along = (double) (list_y - item_area.y) / item_area.height;
+  priv->rubberband->start_align_across = item_area.width ? (double) (list_x - item_area.x) / item_area.width 
: 0.5;
+  priv->rubberband->start_align_along = item_area.height ? (double) (list_y - item_area.y) / 
item_area.height : 0.5;
 
   priv->rubberband->pointer_x = x;
   priv->rubberband->pointer_y = y;
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index 1a0ff55161..f099440f03 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -145,6 +145,12 @@ gtk_list_item_manager_get_first (GtkListItemManager *self)
   return gtk_rb_tree_get_first (self->items);
 }
 
+gpointer
+gtk_list_item_manager_get_last (GtkListItemManager *self)
+{
+  return gtk_rb_tree_get_last (self->items);
+}
+
 gpointer
 gtk_list_item_manager_get_root (GtkListItemManager *self)
 {
@@ -375,7 +381,8 @@ gtk_list_item_manager_remove_items (GtkListItemManager *self,
             gtk_list_item_manager_release_list_item (self, change, item->widget);
           item->widget = NULL;
           n_items -= item->n_items;
-          gtk_rb_tree_remove (self->items, item);
+          item->n_items = 0;
+          gtk_rb_tree_node_mark_dirty (item);
           item = next;
         }
     }
@@ -404,6 +411,25 @@ gtk_list_item_manager_add_items (GtkListItemManager *self,
   gtk_widget_queue_resize (GTK_WIDGET (self->widget));
 }
 
+gpointer
+gtk_list_item_manager_split_item (GtkListItemManager *self,
+                                  gpointer            itemp,
+                                  guint               size)
+{
+  GtkListItemManagerItem *item = itemp;
+  GtkListItemManagerItem *new_item;
+
+  g_assert (size > 0 && size < item->n_items);
+
+  new_item = gtk_rb_tree_insert_after (self->items, item);
+  new_item->n_items = item->n_items - size;
+  gtk_rb_tree_node_mark_dirty (new_item);
+  item->n_items = size;
+  gtk_rb_tree_node_mark_dirty (item);
+
+  return new_item;
+}
+
 static gboolean
 gtk_list_item_manager_merge_list_items (GtkListItemManager     *self,
                                         GtkListItemManagerItem *first,
@@ -419,11 +445,36 @@ gtk_list_item_manager_merge_list_items (GtkListItemManager     *self,
   return TRUE;
 }
 
+void
+gtk_list_item_manager_gc (GtkListItemManager *self)
+{
+  GtkListItemManagerItem *item, *next;
+
+  item = gtk_rb_tree_get_first (self->items);
+
+  while (item)
+    {
+      next = gtk_rb_tree_node_get_next (item);
+
+      if (item->n_items == 0)
+        {
+          gtk_rb_tree_remove (self->items, item);
+          item = next;
+          continue;
+        }
+
+      if (next && gtk_list_item_manager_merge_list_items (self, item, next))
+        continue;
+
+      item = next;
+    }
+}
+
 static void
 gtk_list_item_manager_release_items (GtkListItemManager *self,
                                      GQueue             *released)
 {
-  GtkListItemManagerItem *item, *prev, *next;
+  GtkListItemManagerItem *item;
   guint position, i, n_items, query_n_items;
   gboolean tracked;
 
@@ -448,28 +499,9 @@ gtk_list_item_manager_release_items (GtkListItemManager *self,
             {
               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
-            {
-              i += item->n_items;
-              item = gtk_rb_tree_node_get_next (item);
             }
+          i += item->n_items;
+          item = gtk_rb_tree_node_get_next (item);
         }
       position += query_n_items;
     }
@@ -522,6 +554,8 @@ gtk_list_item_manager_ensure_items (GtkListItemManager *self,
       for (i = 0; i < query_n_items; i++)
         {
           g_assert (item != NULL);
+          while (item->n_items == 0)
+            item = gtk_rb_tree_node_get_next (item);
           if (item->n_items > 1)
             {
               new_item = gtk_rb_tree_insert_before (self->items, item);
diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h
index d2b4bbc433..096c1adbb2 100644
--- a/gtk/gtklistitemmanagerprivate.h
+++ b/gtk/gtklistitemmanagerprivate.h
@@ -73,6 +73,7 @@ void                    gtk_list_item_manager_augment_node      (GtkRbTree
                                                                  gpointer                right);
 gpointer                gtk_list_item_manager_get_root          (GtkListItemManager     *self);
 gpointer                gtk_list_item_manager_get_first         (GtkListItemManager     *self);
+gpointer                gtk_list_item_manager_get_last          (GtkListItemManager     *self);
 gpointer                gtk_list_item_manager_get_nth           (GtkListItemManager     *self,
                                                                  guint                   position,
                                                                  guint                  *offset);
@@ -81,6 +82,11 @@ guint                   gtk_list_item_manager_get_item_position (GtkListItemMana
 gpointer                gtk_list_item_manager_get_item_augment  (GtkListItemManager     *self,
                                                                  gpointer                item);
 
+gpointer                gtk_list_item_manager_split_item        (GtkListItemManager     *self,
+                                                                 gpointer                item,
+                                                                 guint                   size);
+void                    gtk_list_item_manager_gc                (GtkListItemManager     *self);
+
 void                    gtk_list_item_manager_set_factory       (GtkListItemManager     *self,
                                                                  GtkListItemFactory     *factory);
 GtkListItemFactory *    gtk_list_item_manager_get_factory       (GtkListItemManager     *self);
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 1868bdc36e..842d1b7eb4 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -213,7 +213,7 @@ list_row_augment (GtkRbTree *tree,
 
   gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
 
-  aug->height = row->height * row->parent.n_items;
+  aug->height = row->height;
 
   if (left)
     {
@@ -253,9 +253,9 @@ gtk_list_view_get_row_at_y (GtkListView *self,
           y -= aug->height;
         }
 
-      if (y < row->height * row->parent.n_items)
+      if (y < row->height)
         break;
-      y -= row->height * row->parent.n_items;
+      y -= row->height;
 
       row = gtk_rb_tree_node_get_right (row);
     }
@@ -295,7 +295,7 @@ list_row_get_y (GtkListView *self,
               ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
               y += aug->height;
             }
-          y += parent->height * parent->parent.n_items;
+          y += parent->height;
         }
 
       row = parent;
@@ -304,20 +304,6 @@ list_row_get_y (GtkListView *self,
   return y ;
 }
 
-static int
-gtk_list_view_get_list_height (GtkListView *self)
-{
-  ListRow *row;
-  ListRowAugment *aug;
-
-  row = gtk_list_item_manager_get_root (self->item_manager);
-  if (row == NULL)
-    return 0;
-
-  aug = gtk_list_item_manager_get_item_augment (self->item_manager, row);
-  return aug->height;
-}
-
 static gboolean
 gtk_list_view_get_allocation_along (GtkListBase *base,
                                     guint        pos,
@@ -340,12 +326,13 @@ gtk_list_view_get_allocation_along (GtkListBase *base,
     }
 
   y = list_row_get_y (self, row);
-  y += skip * row->height;
+  g_assert (row->parent.n_items > 0);
+  y += skip * row->height / row->parent.n_items;
 
   if (offset)
     *offset = y;
   if (size)
-    *size = row->height;
+    *size = row->height / row->parent.n_items;
 
   return TRUE;
 }
@@ -420,7 +407,7 @@ gtk_list_view_get_position_from_allocation (GtkListBase           *base,
 {
   GtkListView *self = GTK_LIST_VIEW (base);
   ListRow *row;
-  int remaining;
+  int remaining, one_item_height;
 
   if (across >= self->list_width)
     return FALSE;
@@ -430,15 +417,24 @@ gtk_list_view_get_position_from_allocation (GtkListBase           *base,
     return FALSE;
 
   *pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
-  g_assert (remaining < row->height * row->parent.n_items);
-  *pos += remaining / row->height;
+  if (row->parent.n_items == 0)
+    {
+      row = gtk_list_item_manager_get_nth (self->item_manager, *pos, NULL);
+      remaining = 0;
+      one_item_height = 1;
+    }
+  else
+    one_item_height = row->height / row->parent.n_items;
+
+  g_assert (remaining < row->height);
+  *pos += remaining / one_item_height;
 
   if (area)
     {
       area->x = 0;
       area->width = self->list_width;
-      area->y = along - remaining % row->height;
-      area->height = row->height;
+      area->y = along - remaining % one_item_height;
+      area->height = one_item_height;
     }
 
   return TRUE;
@@ -584,7 +580,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   GtkListView *self = GTK_LIST_VIEW (widget);
   ListRow *row;
   GArray *heights;
-  int min, nat, row_height;
+  int min, nat, row_height, total_height;
   int x, y;
   GtkOrientation orientation, opposite_orientation;
   GtkScrollablePolicy scroll_policy, opposite_scroll_policy;
@@ -594,11 +590,14 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
   opposite_scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), opposite_orientation);
 
-  /* step 0: exit early if list is empty */
+  /* step 1: Clean up, so the items tracking deleted rows go away */
+  gtk_list_item_manager_gc (self->item_manager);
+
+  /* step 2: exit early if list is empty */
   if (gtk_list_item_manager_get_root (self->item_manager) == NULL)
     return;
 
-  /* step 1: determine width of the list */
+  /* step 3: determine width of the list */
   gtk_widget_measure (widget, opposite_orientation,
                       -1,
                       &min, &nat, NULL, NULL);
@@ -608,8 +607,9 @@ gtk_list_view_size_allocate (GtkWidget *widget,
   else
     self->list_width = MAX (nat, self->list_width);
 
-  /* step 2: determine height of known list items */
+  /* step 4: determine height of known list items */
   heights = g_array_new (FALSE, FALSE, sizeof (int));
+  total_height = 0;
 
   for (row = gtk_list_item_manager_get_first (self->item_manager);
        row != NULL;
@@ -630,10 +630,11 @@ gtk_list_view_size_allocate (GtkWidget *widget,
           row->height = row_height;
           gtk_rb_tree_node_mark_dirty (row);
         }
+      total_height += row->height;
       g_array_append_val (heights, row_height);
     }
 
-  /* step 3: determine height of unknown items */
+  /* step 5: determine height of unknown items */
   row_height = gtk_list_view_get_unknown_row_height (self, heights);
   g_array_free (heights, TRUE);
 
@@ -644,25 +645,25 @@ gtk_list_view_size_allocate (GtkWidget *widget,
       if (row->parent.widget)
         continue;
 
-      if (row->height != row_height)
+      if (row->height != row->parent.n_items * row_height)
         {
-          row->height = row_height;
+          row->height = row->parent.n_items * row_height;
           gtk_rb_tree_node_mark_dirty (row);
         }
+      total_height += row->height;
     }
 
-  /* step 3: update the adjustments */
+  /* step 6: update the adjustments */
   gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
                                     self->list_width,
-                                    gtk_list_view_get_list_height (self),
+                                    total_height,
                                     gtk_widget_get_size (widget, opposite_orientation),
                                     gtk_widget_get_size (widget, orientation),
                                     &x, &y);
   x = -x;
   y = -y;
 
-  /* step 4: actually allocate the widgets */
-
+  /* step 7: actually allocate the widgets */
   for (row = gtk_list_item_manager_get_first (self->item_manager);
        row != NULL;
        row = gtk_rb_tree_node_get_next (row))
@@ -677,7 +678,7 @@ gtk_list_view_size_allocate (GtkWidget *widget,
                                              row->height);
         }
 
-      y += row->height * row->parent.n_items;
+      y += row->height;
     }
 
   gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));


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