[gtk/wip/otte/listview: 4/15] listitemmanager: Change the way listitems are handled
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 4/15] listitemmanager: Change the way listitems are handled
- Date: Fri, 18 Feb 2022 02:22:13 +0000 (UTC)
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]