[gtk/emoji-grid: 2/5] gridview: Add support for sections
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/emoji-grid: 2/5] gridview: Add support for sections
- Date: Sat, 26 Feb 2022 02:22:21 +0000 (UTC)
commit a5ff901597757dba2ead52e44a930d1315c5359b
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]