[gtk/wip/otte/listview: 3/4] gridview: Add support for sections




commit 76ab29e0b3dd7662d9d4b1fc75829d6c8f468d6f
Author: Benjamin Otte <otte redhat com>
Date:   Wed Feb 23 03:52:09 2022 +0100

    gridview: Add support for sections

 gtk/gtkgridview.c        | 979 ++++++++++++++++++++++++++++++++---------------
 gtk/gtkgridview.h        |   7 +
 gtk/gtklistitemmanager.c |   9 +-
 3 files changed, 696 insertions(+), 299 deletions(-)
---
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index 9cae58289b..85b027c315 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -95,7 +95,6 @@ struct _GtkGridView
   guint max_columns;
   /* set in size_allocate */
   guint n_columns;
-  int unknown_row_height;
   double column_width;
 };
 
@@ -107,24 +106,52 @@ struct _GtkGridViewClass
 struct _Cell
 {
   GtkListItemManagerItem parent;
-  GdkRectangle area;
+  enum {
+    /* A newly created cell that hasn't yet been allocated */
+    UNKNOWN,
+    /* A section header.
+     * A 0-size cell, the section header widget is in the next
+     * cell (sorry, the list item manager needs that).
+     */
+    SECTION,
+    /* One or more cells in the same row.
+     * May contain a widget, but don't have to.
+     */
+    CELL,
+    /* One or more rows spanning all columns.
+     * Does not contain a widget.
+     */
+    BLOCK,
+    /* The bottom right part of the last row.
+     * A 0-size cell that contains the bottom right area of
+     * a grid where the number of items is not a whole multiple
+     * of the columns
+     */
+    FILLER
+  } cell_type;
+  int height;
+  int col_start;
+  int col_end;
 };
 
 struct _CellAugment
 {
   GtkListItemManagerItemAugment parent;
-  GdkRectangle bounds;
+  int height;
+  int col_start;
+  int col_end;
 };
 
 enum
 {
   PROP_0,
+  PROP_ENABLE_RUBBERBAND,
   PROP_FACTORY,
   PROP_MAX_COLUMNS,
   PROP_MIN_COLUMNS,
   PROP_MODEL,
+  PROP_SECTION_FACTORY,
   PROP_SINGLE_CLICK_ACTIVATE,
-  PROP_ENABLE_RUBBERBAND,
 
   N_PROPS
 };
@@ -145,6 +172,86 @@ static guint signals[LAST_SIGNAL] = { 0 };
 #include "gtkscrollable.h"
 #include "gtksnapshot.h"
 
+static inline int
+column_x (GtkGridView *self,
+          guint        column)
+{
+  return ceil (column * self->column_width);
+}
+
+static inline guint
+column_from_x (GtkGridView *self,
+               double       x)
+{
+  return floor (x / self->column_width);
+}
+
+static inline int
+cell_x (GtkGridView *self,
+        Cell        *cell)
+{
+  return column_x (self, cell->col_start);
+}
+
+static inline int
+cell_width (GtkGridView *self,
+            Cell        *cell)
+{
+  return column_x (self, cell->col_end) - column_x (self, cell->col_start);
+}
+
+static inline int
+cell_y (GtkGridView *self,
+        Cell        *cell)
+{
+  CellAugment *left_aug;
+  Cell *prev, *left;
+  int y;
+
+  left = gtk_rb_tree_node_get_left (cell);
+  if (left != NULL)
+    {
+      left_aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
+      y = left_aug->height;
+      if (cell->col_start != 0)
+        y -= cell->height;
+    }
+  else
+    {
+      y = 0;
+    }
+
+  prev = cell;
+  for (cell = gtk_rb_tree_node_get_parent (cell);
+       cell != NULL;
+       cell = gtk_rb_tree_node_get_parent (cell))
+    {
+      left = gtk_rb_tree_node_get_left (cell);
+      if (left != prev)
+        {
+          if (left != NULL)
+            {
+              left_aug = gtk_list_item_manager_get_item_augment (self->item_manager, left);
+              y += left_aug->height;
+            }
+          if (cell->col_start == 0)
+            y += cell->height;
+          if (cell->col_end != self->n_columns)
+            y -= cell->height;
+        }
+      prev = cell;
+    }
+
+  return y;
+}
+
+static inline int
+cell_height (GtkGridView *self,
+             Cell        *cell)
+{
+  return cell->height;
+}
+
 static void G_GNUC_UNUSED
 dump (GtkGridView   *self,
       const GdkRGBA *bg)
@@ -157,6 +264,7 @@ dump (GtkGridView   *self,
   GdkRGBA black = GDK_RGBA("000000");
   float scale = 8.0;
   GtkAdjustment *hadj, *vadj;
+  int y;
 
   hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self));
   vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
@@ -174,19 +282,62 @@ dump (GtkGridView   *self,
                                                   gtk_adjustment_get_upper (hadj),
                                                   gtk_adjustment_get_upper (vadj)));
 
+  y = 0;
   for (cell = gtk_list_item_manager_get_first (self->item_manager);
        cell;
        cell = gtk_rb_tree_node_get_next (cell))
     {
-      GskRoundedRect r = GSK_ROUNDED_RECT_INIT (cell->area.x, cell->area.y, cell->area.width, 
cell->area.height);
+      GskRoundedRect r = GSK_ROUNDED_RECT_INIT (cell_x (self, cell), y, cell_width (self, cell), cell_height 
(self, cell));
 
-      if (cell->parent.widget)
-        gtk_snapshot_append_color (snapshot, &GDK_RGBA("FFFFFF"), &r.bounds);
-
-      gtk_snapshot_append_border (snapshot,
-                                  &r,
-                                  (float[4]) { scale, scale, scale, scale },
-                                  (GdkRGBA[4]) { black, black, black, black });
+      switch (cell->cell_type)
+        {
+        case UNKNOWN:
+          break;
+
+        case CELL:
+          if (cell->parent.widget)
+            gtk_snapshot_append_color (snapshot, &GDK_RGBA("FFFFFF"), &r.bounds);
+
+          gtk_snapshot_append_border (snapshot,
+                                      &r,
+                                      (float[4]) { scale, scale, scale, scale },
+                                      (GdkRGBA[4]) { black, black, black, black });
+          if (cell->col_end == self->n_columns)
+            y += cell_height (self, cell);
+          break;
+
+        case SECTION:
+          gtk_snapshot_append_color (snapshot, &GDK_RGBA("FFFFFF"), &r.bounds);
+          gtk_snapshot_append_border (snapshot,
+                                      &r,
+                                      (float[4]) { scale, scale, scale, scale },
+                                      (GdkRGBA[4]) { black, black, black, black });
+          y += cell_height (self, cell);
+          break;
+
+        case FILLER:
+          gtk_snapshot_push_repeat (snapshot, &r.bounds, &GRAPHENE_RECT_INIT (0, 0, 2 * scale, 2 * scale));
+          gtk_snapshot_append_color (snapshot, &black, &GRAPHENE_RECT_INIT (0, 0, scale, scale));
+          gtk_snapshot_pop (snapshot);
+          gtk_snapshot_append_border (snapshot,
+                                      &r,
+                                      (float[4]) { scale, scale, scale, scale },
+                                      (GdkRGBA[4]) { black, black, black, black });
+          y += cell_height (self, cell);
+          break;
+
+        case BLOCK:
+          gtk_snapshot_append_border (snapshot,
+                                      &r,
+                                      (float[4]) { scale, scale, scale, scale },
+                                      (GdkRGBA[4]) { black, black, black, black });
+          y += cell_height (self, cell);
+          break;
+
+        default:
+          g_assert_not_reached ();
+          break;
+        }
     }
 
   gtk_snapshot_append_color (snapshot,
@@ -221,113 +372,95 @@ cell_augment (GtkRbTree *tree,
 
   gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
 
-  aug->bounds = cell->area;
-
   if (left)
     {
       CellAugment *left_aug = gtk_rb_tree_get_augment (tree, left);
 
-      gdk_rectangle_union (&aug->bounds, &left_aug->bounds, &aug->bounds);
+      aug->height = left_aug->height;
+      aug->col_start = left_aug->col_start;
+      if (cell->col_start == 0)
+        aug->height += cell->height;
+    }
+  else
+    {
+      aug->height = cell->height;
+      aug->col_start = cell->col_start;
     }
 
   if (right)
     {
       CellAugment *right_aug = gtk_rb_tree_get_augment (tree, right);
 
-      gdk_rectangle_union (&aug->bounds, &right_aug->bounds, &aug->bounds);
-    }
-}
-
-static gboolean
-cell_get_area (GtkGridView  *self,
-               Cell         *cell,
-               guint         pos,
-               GdkRectangle *area)
-{
-  int x, y, n;
-
-  g_assert (pos < cell->parent.n_items);
+      aug->height += right_aug->height;
+      aug->col_end = right_aug->col_end;
 
-  if (cell->area.width == 0 || cell->area.height == 0)
-    return FALSE;
-
-  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;
-
-  return TRUE;
+      if (right_aug->col_start != 0)
+        aug->height -= cell->height;
+    }
+  else
+    {
+      aug->col_end = cell->col_end;
+    }
 }
 
 static Cell *
-cell_get_cell_at (GtkGridView *self,
-                  Cell        *cell,
-                  int          x,
-                  int          y,
-                  guint       *skip)
+get_cell_at (GtkGridView *self,
+             guint        col,
+             int          y,
+             guint       *pos_cell,
+             int         *y_cell)
 {
-  CellAugment *aug;
-  Cell *result;
+  Cell *cell, *tmp;
+  guint pos;
+  int y_org;
 
-  aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
-  if (!gdk_rectangle_contains_point (&aug->bounds, x, y))
-    return NULL;
+  cell = gtk_list_item_manager_get_root (self->item_manager);
+  y_org = y;
+  pos = 0;
 
-  result = gtk_rb_tree_node_get_left (cell);
-  if (result)
-    result = cell_get_cell_at (self, result, x, y, skip);
-  if (result)
-    return result;
+  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->height - cell->height ||
+              (y < aug->height && col < aug->col_end))
+            {
+              cell = tmp;
+              continue;
+            }
+          if (cell->col_start == 0)
+            y -= aug->height;
+          else
+            y -= aug->height - cell->height;
+          pos += aug->parent.n_items;
+        }
 
-  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 (y < cell->height && col >= cell->col_start && col < cell->col_end)
+        break;
+      if (cell->col_end == self->n_columns)
+        y -= cell->height;
+      pos += cell->parent.n_items;
 
-  if (!gdk_rectangle_contains_point (&cell->area, x, y))
-    return NULL;
+      cell = gtk_rb_tree_node_get_right (cell);
+    }
 
-  if (skip)
+  if (cell == NULL)
     {
-      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);
+      if (pos_cell)
+        *pos_cell = 0;
+      if (y_cell)
+        *y_cell = 0;
+      return NULL;
     }
 
-  return cell;
-}
+  if (pos_cell)
+    *pos_cell = pos;
+  if (y_cell)
+    *y_cell = y_org - y;
 
-/*<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);
+  return cell;
 }
 
 static gboolean
@@ -338,24 +471,31 @@ gtk_grid_view_get_allocation_along (GtkListBase *base,
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
   Cell *cell;
-  GdkRectangle area;
-  guint skip;
+  guint remaining;
+
+  cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &remaining);
+  if (cell == NULL)
+    return FALSE;
 
-  cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
-  if (cell == NULL || !cell_get_area (self, cell, skip, &area))
+  /* only multi-row cell, remaining items are irrelevant for other cells */
+  if (cell->cell_type == BLOCK)
     {
+      int row_height = cell_height (self, cell) / self->n_columns;
+
+      remaining /= self->n_columns;
+
       if (offset)
-        *offset = 0;
+        *offset = cell_y (self, cell) + remaining * row_height;
       if (size)
-        *size = 0;
-      return FALSE;
+        *size = row_height;
+    }
+  else
+    {
+      if (offset)
+        *offset = cell_y (self, cell);
+      if (size)
+        *size = cell_height (self, cell);
     }
-
-  if (offset)
-    *offset = cell->area.y;
-  if (size)
-    *size = cell->area.height;
-
   return TRUE;
 }
 
@@ -367,66 +507,98 @@ gtk_grid_view_get_allocation_across (GtkListBase *base,
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
   Cell *cell;
-  GdkRectangle area;
-  guint skip;
+  guint remaining;
 
-  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;
-    }
+  cell = gtk_list_item_manager_get_nth (self->item_manager, pos, &remaining);
+  if (cell == NULL)
+    return FALSE;
 
+  remaining %= self->n_columns;
   if (offset)
-    *offset = cell->area.x;
+    *offset = column_x (self, cell->col_start + remaining);
   if (size)
-    *size = cell->area.width;
-
+    *size = column_x (self, cell->col_start + remaining + 1) - column_x (self, cell->col_start + remaining);
   return TRUE;
 }
 
 static gboolean
 gtk_grid_view_get_position_from_allocation (GtkListBase           *base,
-                                            int                    across,
-                                            int                    along,
+                                            int                    x,
+                                            int                    y,
                                             guint                 *position,
                                             cairo_rectangle_int_t *area)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
   Cell *cell;
-  guint skip;
+  int y_offset;
+  guint pos, col;
 
-  if (across >= self->column_width * self->n_columns)
+  if (x < 0 || y < 0)
     return FALSE;
 
-  cell = gtk_grid_view_get_cell_at (self,
-                                    across, along,
-                                    &skip);
-  if (cell != NULL)
-    {
-      *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;
-    }
+  col = column_from_x (self, x);
+  if (col >= self->n_columns)
+    return FALSE;
+
+  cell = get_cell_at (self,
+                      col,
+                      y,
+                      &pos,
+                      &y_offset);
+  if (cell == NULL)
+    return FALSE;
 
-  if (area)
+  while (TRUE)
     {
-      if (!cell_get_area (self, cell, skip, area))
-        memset (area, 0, sizeof (GdkRectangle));
+      switch (cell->cell_type)
+        {
+          case CELL:
+            *position = pos + col - cell->col_start;
+            if (area)
+              {
+                area->x = column_x (self, col);
+                area->width = column_x (self, col + 1) - area->x;
+                area->y = y_offset;
+                area->height = cell_height (self, cell);
+              }
+            return TRUE;
+
+          case FILLER:
+            cell = gtk_rb_tree_node_get_previous (cell);
+            pos -= cell->parent.n_items;
+            col = cell->col_start - 1;
+            break;
+
+          case SECTION:
+            cell = gtk_rb_tree_node_get_next (cell);
+            while (cell->col_end < col)
+              {
+                cell = gtk_rb_tree_node_get_next (cell);
+                pos += cell->parent.n_items;
+              }
+            break;
+
+          case BLOCK:
+            {
+              int row_height = cell->height / (cell->parent.n_items / self->n_columns);
+              int row = (y - y_offset) / row_height;
+              *position = pos + row * self->n_columns + col;
+              if (area)
+                {
+                  area->x = column_x (self, col);
+                  area->width = column_x (self, col + 1) - area->x;
+                  area->y = y_offset + row * row_height;
+                  area->height = row_height;
+                }
+              return TRUE;
+            }
+            break;
+
+          case UNKNOWN:
+          default:
+            g_assert_not_reached ();
+            break;
+        }
     }
 
   return TRUE;
@@ -437,20 +609,27 @@ gtk_grid_view_get_items_in_rect (GtkListBase        *base,
                                  const GdkRectangle *rect)
 {
   GtkGridView *self = GTK_GRID_VIEW (base);
-  guint start_pos, end_pos;
+  Cell *first_cell, *last_cell;
+  guint first_col, last_col;
+  guint first_pos, last_pos;
+  int first_y, last_y;
   GtkBitset *result;
 
   result = gtk_bitset_new_empty ();
 
-  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))
+  first_col = rect->x < 0 ? 0 : column_from_x (self, rect->x);
+  last_col = column_from_x (self, rect->x + rect->width);
+  last_col = MIN (last_col, self->n_columns - 1);
+
+  first_cell = get_cell_at (self, first_col, rect->y, &first_pos, &first_y);
+  if (first_cell == NULL)
+    return result;
+  last_cell = get_cell_at (self, last_col, rect->y + rect->height, &last_pos, &last_y);
+  if (last_cell == NULL)
     return result;
 
-  gtk_bitset_add_rectangle (result,
-                            start_pos,
-                            (end_pos - start_pos) % self->n_columns + 1,
-                            (end_pos - start_pos) / self->n_columns + 1,
-                            self->n_columns);
+  /* XXX: this is wrong, but can be fixed when the rest works. */
+  gtk_bitset_add_range_closed (result, first_pos, last_pos);
 
   return result;
 }
@@ -517,33 +696,46 @@ gtk_grid_view_get_unknown_row_size (GtkGridView *self,
 static void
 gtk_grid_view_measure_column_size (GtkGridView *self,
                                    int         *minimum,
-                                   int         *natural)
+                                   int         *natural,
+                                   int         *minimum_section,
+                                   int         *natural_section)
 {
   GtkOrientation opposite;
   Cell *cell;
-  int min, nat, child_min, child_nat;
+  int min, nat, min_sec, nat_sec, child_min, child_nat;
 
   min = 0;
   nat = 0;
+  min_sec = 0;
+  nat_sec = 0;
   opposite = gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self));
 
   for (cell = gtk_list_item_manager_get_first (self->item_manager);
        cell != NULL;
        cell = gtk_rb_tree_node_get_next (cell))
     {
-      /* ignore unavailable cells */
-      if (cell->parent.widget == NULL)
-        continue;
-
-      gtk_widget_measure (cell->parent.widget,
-                          opposite, -1,
-                          &child_min, &child_nat, NULL, NULL);
-      min = MAX (min, child_min);
-      nat = MAX (nat, child_nat);
+      if (cell->parent.section_header)
+        {
+          gtk_widget_measure (cell->parent.widget,
+                              opposite, -1,
+                              &child_min, &child_nat, NULL, NULL);
+          min_sec = MAX (min_sec, child_min);
+          nat_sec = MAX (nat_sec, child_nat);
+        }
+      if (cell->parent.widget)
+        {
+          gtk_widget_measure (cell->parent.widget,
+                              opposite, -1,
+                              &child_min, &child_nat, NULL, NULL);
+          min = MAX (min, child_min);
+          nat = MAX (nat, child_nat);
+        }
     }
 
   *minimum = min;
   *natural = nat;
+  *minimum_section = min_sec;
+  *natural_section = nat_sec;
 }
 
 static void
@@ -553,11 +745,14 @@ gtk_grid_view_measure_across (GtkWidget *widget,
                               int       *natural)
 {
   GtkGridView *self = GTK_GRID_VIEW (widget);
+  int min_section, nat_section;
 
-  gtk_grid_view_measure_column_size (self, minimum, natural);
+  gtk_grid_view_measure_column_size (self, minimum, natural, &min_section, &nat_section);
 
   *minimum *= self->min_columns;
   *natural *= self->max_columns;
+  *minimum = MAX (*minimum, min_section);
+  *natural = MAX (*natural, nat_section);
 }
 
 static guint
@@ -589,7 +784,7 @@ gtk_grid_view_measure_list (GtkWidget *widget,
   GtkGridView *self = GTK_GRID_VIEW (widget);
   GtkScrollablePolicy scroll_policy;
   Cell *cell;
-  int height, row_height, child_min, child_nat, column_size, col_min, col_nat;
+  int height, row_height, child_min, child_nat, column_size, col_min, col_nat, sec_min, sec_nat;
   gboolean measured;
   GArray *heights;
   guint n_unknown, n_columns;
@@ -600,8 +795,9 @@ gtk_grid_view_measure_list (GtkWidget *widget,
   n_unknown = 0;
   height = 0;
 
-  gtk_grid_view_measure_column_size (self, &col_min, &col_nat);
+  gtk_grid_view_measure_column_size (self, &col_min, &col_nat, &sec_min, &sec_nat);
   for_size = MAX (for_size, col_min * (int) self->min_columns);
+  for_size = MAX (for_size, sec_min);
   n_columns = gtk_grid_view_compute_n_columns (self, for_size, col_min, col_nat);
   column_size = for_size / n_columns;
 
@@ -679,31 +875,20 @@ gtk_grid_view_measure (GtkWidget      *widget,
     gtk_grid_view_measure_across (widget, for_size, minimum, natural);
 }
 
-static void
-cell_set_position (Cell *cell,
-                   int   x,
-                   int   y)
-{
-  if (cell->area.x == x &&
-      cell->area.y == y)
-    return;
-
-  cell->area.x = x;
-  cell->area.y = y;
-  gtk_rb_tree_node_mark_dirty (cell);
-}
-
 static void
 cell_set_size (Cell *cell,
-               int   width,
-               int   height)
+               int   height,
+               int   col_start,
+               int   col_end)
 {
-  if (cell->area.width == width &&
-      cell->area.height == height)
+  if (cell->height == height &&
+      cell->col_start == col_start &&
+      cell->col_end == col_end)
     return;
 
-  cell->area.width = width;
-  cell->area.height = height;
+  cell->height = height;
+  cell->col_start = col_start;
+  cell->col_end = col_end;
   gtk_rb_tree_node_mark_dirty (cell);
 }
 
@@ -714,9 +899,9 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
                              int        baseline)
 {
   GtkGridView *self = GTK_GRID_VIEW (widget);
-  Cell *cell, *row_cell;
+  Cell *cell, *tmp;
   GArray *heights;
-  int min_row_height, row_height, total_height, col_min, col_nat;
+  int min_row_height, row_height, total_height, total_width, col_min, col_nat, sec_min, sec_nat;
   GtkOrientation orientation, opposite_orientation;
   GtkScrollablePolicy scroll_policy;
   int x, y;
@@ -729,133 +914,265 @@ 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 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 3: determine width of the list */
-  gtk_grid_view_measure_column_size (self, &col_min, &col_nat);
+  /* step 1: determine width of the list */
+  gtk_grid_view_measure_column_size (self, &col_min, &col_nat, &sec_min, &sec_nat);
+  total_width = orientation == GTK_ORIENTATION_VERTICAL ? width : height;
+  total_width = MAX (total_width, sec_min);
   self->n_columns = gtk_grid_view_compute_n_columns (self, 
-                                                     orientation == GTK_ORIENTATION_VERTICAL ? width : 
height,
+                                                     total_width,
                                                      col_min, col_nat);
-  self->column_width = (orientation == GTK_ORIENTATION_VERTICAL ? width : height) / self->n_columns;
-  self->column_width = MAX (self->column_width, col_min);
+  self->column_width = (double) total_width / self->n_columns;
+  if (col_min > self->column_width)
+    {
+      self->column_width = col_min;
+      total_width = col_min * self->n_columns;
+    }
 
-  /* step 4: determine height of known rows */
-  heights = g_array_new (FALSE, FALSE, sizeof (int));
-  total_height = 0;
+  /* step 2: exit early if list is empty */
+  if (total_width == 0)
+    {
+      gtk_list_item_manager_gc (self->item_manager);
+      return;
+    }
 
-  cell = gtk_list_item_manager_get_first (self->item_manager);
-  while (cell)
+  /* step 3: Clean up, so we have the right items in the right order with the right types in the list */
+  gtk_list_item_manager_gc (self->item_manager);
+  i = 0;
+  for (cell = gtk_list_item_manager_get_first (self->item_manager);
+       cell != NULL;
+       cell = gtk_rb_tree_node_get_next (cell))
     {
-      if (cell->parent.n_items >= MAX (2, self->n_columns))
+      if (cell->parent.section_header)
         {
-          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;
+          if (i > 0)
+            {
+              cell->cell_type = FILLER;
+              cell = gtk_list_item_manager_split_item (self->item_manager, cell, 0);
+              i = 0;
+            }
+          cell->cell_type = SECTION;
+          cell = gtk_list_item_manager_split_item (self->item_manager, cell, 0);
         }
-
-      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))
+      if (cell->parent.n_items + i > self->n_columns)
         {
-          if (row_cell->parent.widget)
+          if (i > 0)
             {
-              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);
+              cell->cell_type = CELL;
+              cell = gtk_list_item_manager_split_item (self->item_manager, cell, self->n_columns - i);
+              i = 0;
             }
-          else if (row_cell->parent.n_items > self->n_columns - i)
+          if (cell->parent.n_items >= self->n_columns)
             {
-              gtk_list_item_manager_split_item (self->item_manager, row_cell, self->n_columns - i);
+              int remainder = cell->parent.n_items % self->n_columns;
+              cell->cell_type = BLOCK;
+              if (remainder)
+                {
+                  cell = gtk_list_item_manager_split_item (self->item_manager, cell, cell->parent.n_items - 
remainder);
+                  cell->cell_type = CELL;
+                }
+            }
+          else
+            {
+              cell->cell_type = CELL;
             }
-          i += row_cell->parent.n_items;
         }
-
-      for (i = 0;
-           cell != row_cell;
-           cell = gtk_rb_tree_node_get_next (cell))
+      else
         {
-          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;
+          cell->cell_type = CELL;
         }
-      total_height += row_height;
+      i = (i + cell->parent.n_items) % self->n_columns;
+    }
+  if (i > 0)
+    {
+       cell = gtk_list_item_manager_get_last (self->item_manager);
+       cell = gtk_list_item_manager_split_item (self->item_manager, cell, cell->parent.n_items);
+       cell->cell_type = FILLER;
     }
 
-  /* 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);
+  /* step 4: determine height of known rows */
+  heights = g_array_new (FALSE, FALSE, sizeof (int));
+  total_height = 0;
 
-  for (cell = gtk_list_item_manager_get_first (self->item_manager);
-       cell != NULL;
-       cell = gtk_rb_tree_node_get_next (cell))
+  i = 0;
+  row_height = 0;
+  cell = gtk_list_item_manager_get_first (self->item_manager);
+  while (cell != NULL)
     {
-      if (cell->parent.n_items < self->n_columns)
-        continue;
+      switch (cell->cell_type)
+        {
+        case CELL:
+          tmp = cell;
+          row_height = 0;
+          for (i = 0;
+               i < self->n_columns;
+               cell = gtk_rb_tree_node_get_next (cell))
+            {
+              i += cell->parent.n_items;
+              if (cell->cell_type == FILLER)
+                {
+                  i = self->n_columns;
+                }
+              else if (cell->parent.widget)
+                {
+                  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);
+                }
+            }
+          if (row_height > 0)
+            {
+              total_height += row_height;
+              for (i = 0;
+                   tmp != cell;
+                   tmp = gtk_rb_tree_node_get_next (tmp))
+                {
+                  if (tmp->cell_type == FILLER)
+                    cell_set_size (tmp, row_height, i, self->n_columns);
+                  else
+                    cell_set_size (tmp, row_height, i, i + tmp->parent.n_items);
+                  i += tmp->parent.n_items;
+                }
+            }
+          break;
 
-      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);
+        case BLOCK:
+          cell = gtk_rb_tree_node_get_next (cell);
+          break;
+
+        case SECTION:
+          {
+            int min, nat;
+            cell = gtk_rb_tree_node_get_next (cell);
+            gtk_widget_measure (cell->parent.widget,
+                                gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
+                                total_width,
+                                &min, &nat, NULL, NULL);
+            if (scroll_policy == GTK_SCROLL_MINIMUM)
+              row_height = min;
+            else
+              row_height = nat;
+            total_height += row_height;
+            cell_set_size (cell, row_height, 0, self->n_columns);
+          }
+          break;
+
+        case FILLER:
+        case UNKNOWN:
+        default:
+          g_assert_not_reached ();
+          break;
+        }
     }
 
-  /* step 6: assign positions */
+  /* step 5: determine height of rows with only unknown items and assign their size */
+  row_height = gtk_grid_view_get_unknown_row_size (self, heights);
+  g_array_free (heights, TRUE);
   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 = gtk_list_item_manager_get_first (self->item_manager);
+  while (cell != NULL)
     {
-      cell_set_position (cell,
-                         ceil (self->column_width * i),
-                         y);
-
-      i += cell->parent.n_items;
-      if (i >= self->n_columns)
+      switch (cell->cell_type)
         {
-          y += cell->area.height;
-          i = 0;
+        case CELL:
+          tmp = gtk_rb_tree_node_get_next (cell);
+          if (cell->parent.widget == NULL &&
+              tmp->cell_type == FILLER)
+            {
+              /* Special case where we don't have assigned a height,
+               * becuase it's the last row in a block with filler */
+              cell_set_size (cell, row_height, 0, cell->parent.n_items);
+              cell_set_size (tmp, row_height, cell->parent.n_items, self->n_columns);
+              total_height += row_height;
+              cell = gtk_rb_tree_node_get_next (tmp);
+            }
+          else
+            {
+              /* skip the row */
+              for (cell = gtk_rb_tree_node_get_next (cell);
+                   cell && cell->col_start != 0;
+                   cell = gtk_rb_tree_node_get_next (cell))
+                {}
+            }
+          break;
+        
+        case BLOCK:
+          i = cell->parent.n_items / self->n_columns;
+          cell_set_size (cell, i * row_height, 0, self->n_columns);
+          total_height += row_height * i;
+          cell = gtk_rb_tree_node_get_next (cell);
+          break;
+
+        case SECTION:
+          cell = gtk_rb_tree_node_get_next (cell);
+          break;
+
+        case FILLER:
+        case UNKNOWN:
+        default:
+          g_assert_not_reached ();
+          break;
         }
     }
 
   /* step 7: update the adjustments */
   gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
-                                    self->column_width * self->n_columns,
+                                    total_width,
                                     total_height,
                                     gtk_widget_get_size (widget, opposite_orientation),
                                     gtk_widget_get_size (widget, orientation),
                                     &x, &y);
 
   /* step 8: run the size_allocate loop */
+  y = -y;
   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)
+      switch (cell->cell_type)
         {
+        case CELL:
+          if (cell->parent.widget)
+            {
+              gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
+                                                 cell->parent.widget,
+                                                 cell_x (self, cell) - x,
+                                                 y,
+                                                 cell_width (self, cell),
+                                                 cell_height (self, cell));
+            }
+          if (cell->col_end == self->n_columns)
+            y += cell->height;
+          break;
+
+        case SECTION:
+          tmp = gtk_rb_tree_node_get_next (cell);
           gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
-                                             cell->parent.widget,
-                                             cell->area.x - x,
-                                             cell->area.y - y,
-                                             cell->area.width,
-                                             cell->area.height);
+                                             tmp->parent.section_header,
+                                             cell_x (self, cell) - x,
+                                             y,
+                                             cell_width (self, cell),
+                                             cell_height (self, cell));
+          y += cell->height;
+          break;
+
+        case BLOCK:
+        case FILLER:
+          y += cell->height;
+          break;
+
+        case UNKNOWN:
+        default:
+          g_assert_not_reached ();
+          break;
         }
     }
 
@@ -884,6 +1201,10 @@ gtk_grid_view_get_property (GObject    *object,
 
   switch (property_id)
     {
+    case PROP_ENABLE_RUBBERBAND:
+      g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
+      break;
+
     case PROP_FACTORY:
       g_value_set_object (value, gtk_list_item_manager_get_factory (self->item_manager));
       break;
@@ -900,12 +1221,12 @@ gtk_grid_view_get_property (GObject    *object,
       g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
       break;
 
-    case PROP_SINGLE_CLICK_ACTIVATE:
-      g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
+    case PROP_SECTION_FACTORY:
+      g_value_set_object (value, gtk_list_item_manager_get_section_factory (self->item_manager));
       break;
 
-    case PROP_ENABLE_RUBBERBAND:
-      g_value_set_boolean (value, gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
+    case PROP_SINGLE_CLICK_ACTIVATE:
+      g_value_set_boolean (value, gtk_list_item_manager_get_single_click_activate (self->item_manager));
       break;
 
     default:
@@ -924,6 +1245,10 @@ gtk_grid_view_set_property (GObject      *object,
 
   switch (property_id)
     {
+    case PROP_ENABLE_RUBBERBAND:
+      gtk_grid_view_set_enable_rubberband (self, g_value_get_boolean (value));
+      break;
+
     case PROP_FACTORY:
       gtk_grid_view_set_factory (self, g_value_get_object (value));
       break;
@@ -940,12 +1265,12 @@ gtk_grid_view_set_property (GObject      *object,
       gtk_grid_view_set_model (self, g_value_get_object (value));
       break;
 
-    case PROP_SINGLE_CLICK_ACTIVATE:
-      gtk_grid_view_set_single_click_activate (self, g_value_get_boolean (value));
+    case PROP_SECTION_FACTORY:
+      gtk_grid_view_set_section_factory (self, g_value_get_object (value));
       break;
 
-    case PROP_ENABLE_RUBBERBAND:
-      gtk_grid_view_set_enable_rubberband (self, g_value_get_boolean (value));
+    case PROP_SINGLE_CLICK_ACTIVATE:
+      gtk_grid_view_set_single_click_activate (self, g_value_get_boolean (value));
       break;
 
     default:
@@ -998,6 +1323,18 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
   gobject_class->get_property = gtk_grid_view_get_property;
   gobject_class->set_property = gtk_grid_view_set_property;
 
+  /**
+   * GtkGridView:enable-rubberband: (attributes org.gtk.Property.get=gtk_grid_view_get_enable_rubberband 
org.gtk.Property.set=gtk_grid_view_set_enable_rubberband)
+   *
+   * Allow rubberband selection.
+   */
+  properties[PROP_ENABLE_RUBBERBAND] =
+    g_param_spec_boolean ("enable-rubberband",
+                          P_("Enable rubberband selection"),
+                          P_("Allow selecting items by dragging with the mouse"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * GtkGridView:factory: (attributes org.gtk.Property.get=gtk_grid_view_get_factory 
org.gtk.Property.set=gtk_grid_view_set_factory)
    *
@@ -1050,6 +1387,20 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
                          GTK_TYPE_SELECTION_MODEL,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
+  /**
+   * GtkGridView:section-factory: (attributes org.gtk.Property.get=gtk_grid_view_get_section_factory 
org.gtk.Property.set=gtk_grid_view_set_section_factory)
+   *
+   * Factory for populating section headers.
+   *
+   * Since: 4.8
+   */
+  properties[PROP_SECTION_FACTORY] =
+    g_param_spec_object ("section-factory",
+                         P_("Section factory"),
+                         P_("Factory for populating secion headers"),
+                         GTK_TYPE_LIST_ITEM_FACTORY,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
   /**
    * GtkGridView:single-click-activate: (attributes 
org.gtk.Property.get=gtk_grid_view_get_single_click_activate 
org.gtk.Property.set=gtk_grid_view_set_single_click_activate)
    *
@@ -1062,18 +1413,6 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
                           FALSE,
                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
-  /**
-   * GtkGridView:enable-rubberband: (attributes org.gtk.Property.get=gtk_grid_view_get_enable_rubberband 
org.gtk.Property.set=gtk_grid_view_set_enable_rubberband)
-   *
-   * Allow rubberband selection.
-   */
-  properties[PROP_ENABLE_RUBBERBAND] =
-    g_param_spec_boolean ("enable-rubberband",
-                          P_("Enable rubberband selection"),
-                          P_("Allow selecting items by dragging with the mouse"),
-                          FALSE,
-                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 
   /**
@@ -1251,6 +1590,50 @@ gtk_grid_view_set_factory (GtkGridView        *self,
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
 }
 
+/**
+ * gtk_grid_view_get_section_factory: (attributes org.gtk.Method.get_property=section-factory)
+ * @self: a `GtkListView`
+ *
+ * Gets the factory that's currently used to populate section headers.
+ *
+ * Returns: (nullable) (transfer none): The factory in use
+ *
+ * Since: 4.8
+ */
+GtkListItemFactory *
+gtk_grid_view_get_section_factory (GtkGridView *self)
+{
+  g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
+
+  return gtk_list_item_manager_get_section_factory (self->item_manager);
+}
+
+/**
+ * gtk_grid_view_set_section_factory: (attributes org.gtk.Method.set_property=section-factory)
+ * @self: a `GtkListView`
+ * @factory: (nullable) (transfer none): the factory to use
+ *
+ * Sets the `GtkListItemFactory` to use for populating section headers.
+ *
+ * If this factory is set to %NULL, the grid will not use section.
+ *
+ * Since: 4.8
+ */
+void
+gtk_grid_view_set_section_factory (GtkGridView        *self,
+                                   GtkListItemFactory *factory)
+{
+  g_return_if_fail (GTK_IS_GRID_VIEW (self));
+  g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
+
+  if (factory == gtk_list_item_manager_get_section_factory (self->item_manager))
+    return;
+
+  gtk_list_item_manager_set_section_factory (self->item_manager, factory);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_FACTORY]);
+}
+
 /**
  * gtk_grid_view_get_max_columns: (attributes org.gtk.Method.get_property=max-columns)
  * @self: a `GtkGridView`
diff --git a/gtk/gtkgridview.h b/gtk/gtkgridview.h
index 80b0e72bbd..19ba924d58 100644
--- a/gtk/gtkgridview.h
+++ b/gtk/gtkgridview.h
@@ -57,6 +57,13 @@ void            gtk_grid_view_set_factory                       (GtkGridView
 GDK_AVAILABLE_IN_ALL
 GtkListItemFactory *
                 gtk_grid_view_get_factory                       (GtkGridView            *self);
+GDK_AVAILABLE_IN_4_8
+void            gtk_grid_view_set_section_factory               (GtkGridView            *self,
+                                                                 GtkListItemFactory     *factory);
+GDK_AVAILABLE_IN_4_8
+GtkListItemFactory *
+                gtk_grid_view_get_section_factory               (GtkGridView            *self);
+
 GDK_AVAILABLE_IN_ALL
 guint           gtk_grid_view_get_min_columns                   (GtkGridView            *self);
 GDK_AVAILABLE_IN_ALL
diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c
index b41fb51505..203462a406 100644
--- a/gtk/gtklistitemmanager.c
+++ b/gtk/gtklistitemmanager.c
@@ -639,10 +639,17 @@ gtk_list_item_manager_split_item (GtkListItemManager *self,
   GtkListItemManagerItem *item = itemp;
   GtkListItemManagerItem *new_item;
 
-  g_assert (size > 0 && size < item->n_items);
+  g_assert (size <= item->n_items);
 
   new_item = gtk_rb_tree_insert_after (self->items, item);
   new_item->n_items = item->n_items - size;
+  if (size == 0)
+    {
+      new_item->widget = item->widget;
+      item->widget = NULL;
+      new_item->section_header = item->section_header;
+      item->section_header = NULL;
+    }
   gtk_rb_tree_node_mark_dirty (new_item);
   item->n_items = size;
   gtk_rb_tree_node_mark_dirty (item);


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