Compare commits

...

25 Commits

Author SHA1 Message Date
Matthias Clasen
0b156637a5 gridview: Add tests for tile split
These tests are reusing the section model plumbing
of the listitemmanager tests, and just check that
splitting tiles according to a certain number of
columns works
2023-05-20 21:27:02 -04:00
Matthias Clasen
73696a8521 gridview: Export some functions privately
This will let us write tests for the tile
splitting code.
2023-05-20 21:27:02 -04:00
Matthias Clasen
b772b4600d gridview: Break out splitting code
I want to write some unit tests for the code
that splits tiles according to sections and
columns.
2023-05-20 21:27:02 -04:00
Matthias Clasen
7aa7d7be16 gridview: Rework size_allocate
Split things into multiple loops:
- gc
- Split tiles as needed
- Determine row heights
- Allocated remaining tiles
2023-05-20 21:27:02 -04:00
Matthias Clasen
8793ab183d gridview: Add a multi-row helper
Add a function that determines if a tile
spans multiple rows.
2023-05-20 21:27:02 -04:00
Matthias Clasen
b1353457d4 gridview: Drop an incorrect assertion
In the presence of sections, a multi-row
tile isn't guaranteed to be rectangular in
the sense that it contains MxN items, so
don't assert this.
2023-05-20 21:27:02 -04:00
Matthias Clasen
49b03b8a84 gridview: Check some invariants
Add assertions for the invariants we've
established in the previous commit.
2023-05-20 21:27:02 -04:00
Matthias Clasen
5cc40f1e5a gridview: Rework multirow tile handling
In the presence of sections, we can't really guarantee that big tiles
are rectangular in the sense that they contain MxN items, since sections
might introduce gaps that don't count for n_items.

But we can split off an incomplete first and last row to guarantee that
the first item is in the first column and the last item in the last one.
2023-05-20 21:27:02 -04:00
Matthias Clasen
b1723c1b0c gridview: Find the correct column for anchors
We use the new helper function to find the column after
estimating how many items from the beginning of the tile
we are.

Note that the offsetting inside the tile is not precise,
since section boundaries might introduce gaps. But once
we've settled on an item, we can determine its columns with
certainty.

The only cases when we still see mismatches is when n_columns
or has_sections changes between get_position_from_allocation
and size_allocate.
2023-05-20 21:27:02 -04:00
Matthias Clasen
bfa2e6725e gridview: Add some helpers
Add helper functions to get the section of
an item and to computes the column for an
item, based on sections and the current number
of columns.
2023-05-20 21:14:37 -04:00
Matthias Clasen
5620ffcbfd gridview: Add debugging for anchor positioning
Keep the anchor data from get_position_from_allocation
and warn in size_allocate if we place the anchor item
in a different column.
2023-05-20 21:14:37 -04:00
Matthias Clasen
d94fc2d4e2 gridview: Always set header and footer size
Update tile allocation for headers and foots in cases
where we go from having headers to not having headers.

Before this commit, turning sections off in testsections
would leave the global header and footer with some empty
space allocated.
2023-05-20 21:14:37 -04:00
Matthias Clasen
702ee8cb1e gridview: Drop footer special-casing
We already allocate all footer in the loop above,
no need to treat the last one specially anymore.
2023-05-20 21:14:37 -04:00
Matthias Clasen
0fe56ad497 gridview: Allocate headers properly
Allocate the right amount of space to headers and footers
to make sections break the grid in the expected way.
2023-05-20 21:14:37 -04:00
Matthias Clasen
4e18d469bc gridview: Allocate headers
For now, we make headers occupy the same space as a regular item.
This will be improved in future commits.
2023-05-20 21:14:37 -04:00
Matthias Clasen
b694aef7a6 testsections: Print out the sections
This helps for debugging some situations.
2023-05-20 21:14:37 -04:00
Matthias Clasen
631ee08d9a testsections: Warn on hscroll
Print out whenever the hadjustment value changes.
That makes it easier to catch situations where
vertical scrolling causes us to move horizontally
(ie jitter).

Note that this currently triggers even without
sections, so there's debugging work to be done.
2023-05-20 21:14:37 -04:00
Matthias Clasen
bbe9e9bcdd testsections: Capitalize the header properly 2023-05-20 21:14:37 -04:00
Matthias Clasen
ff46f050f5 testsections: Make sections toggleable
Add a check button, so sections can be turned
on and off. This helps debugging the transitions.
2023-05-20 21:14:37 -04:00
Matthias Clasen
9a66ec81bf testsections: Allow loading a word list
The code to load words from a list is copyied from
the words demo in gtk4-demo.
2023-05-20 20:51:43 -04:00
Matthias Clasen
e5f0e17378 testsections: A testbed for sections
Add a simple test client that lets us compare
and explore the section handling in listview
and gridview.
2023-05-20 20:51:43 -04:00
Matthias Clasen
c7d295dd9b gridview: Copy section plumbing
Add a header factory, and implement prepare_section and
create_header_widget.

Allocating header and footer tiles properly will be
left to future commits.
2023-05-20 20:51:43 -04:00
Matthias Clasen
2e30806d42 listbase: Export gtk_list_base_dump_tiles privately
This is so we call it from gtkgridview.c to debug things.
2023-05-20 20:51:43 -04:00
Matthias Clasen
56907ad66f listbase: Add headers and footers to tile debug
Use some shades of purple to show headers and
footers in the node dump for tiles.
2023-05-20 20:51:43 -04:00
Benjamin Otte
9cb94c1547 listbase: Add a debug keybinding
Ctrl-Shift-R (for rendernode, yay my creativity):
Creates a rendernode of the current tiles and dumps them into the
clipboard.
2023-05-20 20:51:43 -04:00
8 changed files with 962 additions and 77 deletions

View File

@@ -19,16 +19,18 @@
#include "config.h"
#include "gtkgridview.h"
#include "gtkgridviewprivate.h"
#include "gtkbitset.h"
#include "gtklistbaseprivate.h"
#include "gtklistheaderwidgetprivate.h"
#include "gtklistitemfactory.h"
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemwidgetprivate.h"
#include "gtkmultiselection.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtksectionmodel.h"
/* Maximum number of list items created by the gridview.
* For debugging, you can set this to G_MAXUINT to ensure
@@ -87,12 +89,19 @@ struct _GtkGridView
GtkListItemManager *item_manager;
GtkListItemFactory *factory;
GtkListItemFactory *header_factory;
guint min_columns;
guint max_columns;
gboolean single_click_activate;
/* set in size_allocate */
guint n_columns;
double column_width;
/* for debugging between get_position_from_allocation and size_allocate */
unsigned int anchor_position;
unsigned int anchor_column;
unsigned int anchor_n_columns;
gboolean anchor_has_sections;
};
struct _GtkGridViewClass
@@ -105,6 +114,7 @@ enum
PROP_0,
PROP_ENABLE_RUBBERBAND,
PROP_FACTORY,
PROP_HEADER_FACTORY,
PROP_MAX_COLUMNS,
PROP_MIN_COLUMNS,
PROP_MODEL,
@@ -257,6 +267,13 @@ gtk_grid_view_split (GtkListBase *base,
return split;
}
static void
gtk_grid_view_prepare_section (GtkListBase *base,
GtkListTile *tile,
guint position)
{
}
/* We define the listview as **inert** when the factory isn't used. */
static gboolean
gtk_grid_view_is_inert (GtkGridView *self)
@@ -269,7 +286,8 @@ gtk_grid_view_is_inert (GtkGridView *self)
static void
gtk_grid_view_update_factories_with (GtkGridView *self,
GtkListItemFactory *factory)
GtkListItemFactory *factory,
GtkListItemFactory *header_factory)
{
GtkListTile *tile;
@@ -277,8 +295,26 @@ gtk_grid_view_update_factories_with (GtkGridView *self,
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
switch (tile->type)
{
case GTK_LIST_TILE_ITEM:
if (tile->widget)
gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (tile->widget), factory);
break;
case GTK_LIST_TILE_HEADER:
if (tile->widget)
gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), header_factory);
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
case GTK_LIST_TILE_FOOTER:
case GTK_LIST_TILE_UNMATCHED_FOOTER:
case GTK_LIST_TILE_REMOVED:
g_assert (tile->widget == NULL);
break;
default:
g_assert_not_reached();
break;
}
}
}
@@ -286,13 +322,14 @@ static void
gtk_grid_view_update_factories (GtkGridView *self)
{
gtk_grid_view_update_factories_with (self,
gtk_grid_view_is_inert (self) ? NULL : self->factory);
gtk_grid_view_is_inert (self) ? NULL : self->factory,
gtk_grid_view_is_inert (self) ? NULL : self->header_factory);
}
static void
gtk_grid_view_clear_factories (GtkGridView *self)
{
gtk_grid_view_update_factories_with (self, NULL);
gtk_grid_view_update_factories_with (self, NULL, NULL);
}
static GtkListItemBase *
@@ -316,6 +353,20 @@ gtk_grid_view_create_list_widget (GtkListBase *base)
return GTK_LIST_ITEM_BASE (result);
}
static GtkListHeaderBase *
gtk_grid_view_create_header_widget (GtkListBase *base)
{
GtkGridView *self = GTK_GRID_VIEW (base);
GtkListItemFactory *factory;
if (gtk_grid_view_is_inert (self))
factory = NULL;
else
factory = self->header_factory;
return GTK_LIST_HEADER_BASE (gtk_list_header_widget_new (factory));
}
static gboolean
gtk_grid_view_get_allocation (GtkListBase *base,
guint pos,
@@ -384,6 +435,79 @@ gtk_grid_view_get_allocation (GtkListBase *base,
return TRUE;
}
/* Returns the section that position falls into
*/
static void
get_section_for_position (GtkListItemManager *items,
unsigned int position,
unsigned int *section_start,
unsigned int *section_end)
{
GListModel *model;
unsigned int start, end;
model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
if (!gtk_list_item_manager_get_has_sections (items))
{
start = 0;
end = g_list_model_get_n_items (model);
}
else
{
gtk_section_model_get_section (GTK_SECTION_MODEL (model), position, &start, &end);
}
if (section_start)
*section_start = start;
if (section_end)
*section_end = end;
}
/* Returns the column that the given item will fall in, taking
* sections into account. Note that this depends on whether
* we are currently showing sections, and on the number of
* columns that the grid is allocating.
*/
unsigned int
gtk_grid_view_get_column_for_position (GtkListItemManager *items,
unsigned int n_columns,
unsigned int position)
{
unsigned int start;
get_section_for_position (items, position, &start, NULL);
return (position - start) % n_columns;
}
/* Determine whether a tile is contained in a single row,
* or spans multiple rows.
*/
gboolean
gtk_grid_view_is_multirow_tile (GtkListItemManager *items,
unsigned int n_columns,
GtkListTile *tile)
{
unsigned int position;
unsigned int start, start2;
unsigned int col;
if (tile->n_items <= 1)
return FALSE;
position = gtk_list_tile_get_position (items, tile);
get_section_for_position (items, position, &start, NULL);
get_section_for_position (items, position + tile->n_items - 1, &start2, NULL);
if (start != start2)
return TRUE;
col = (position - start) % n_columns;
return col + tile->n_items - 1 > n_columns;
}
static gboolean
gtk_grid_view_get_position_from_allocation (GtkListBase *base,
int x,
@@ -394,6 +518,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
GtkGridView *self = GTK_GRID_VIEW (base);
GtkListTile *tile;
guint pos;
guint col;
tile = gtk_list_item_manager_get_nearest_tile (self->item_manager, x, y);
if (tile == NULL)
@@ -411,44 +536,43 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
}
pos = gtk_list_tile_get_position (self->item_manager, tile);
col = gtk_grid_view_get_column_for_position (self->item_manager, self->n_columns, pos);
if (tile->n_items > 1)
{
int xspacing, yspacing;
guint row_height;
guint row_index;
gtk_list_base_get_border_spacing (base, &xspacing, &yspacing);
/* offset in x direction */
pos += column_index (self, xspacing, MAX (tile->area.width - 1, x - tile->area.x));
if (area)
{
guint col = MIN (column_index (self, xspacing, x), self->n_columns - 1);
area->x = column_start (self, xspacing, col);
area->width = column_end (self, xspacing, col) - area->x;
}
pos += column_index (self, xspacing, MAX (tile->area.width - 1, x - tile->area.x)) - col;
/* offset in y direction */
if (tile->n_items > self->n_columns)
{
guint rows_in_tile = tile->n_items / self->n_columns;
guint row_height = (tile->area.height + yspacing) / rows_in_tile - yspacing;
guint row_index = MIN (tile->area.height - 1, y - tile->area.y) / (row_height + yspacing);
pos += self->n_columns * row_index;
if (area)
{
area->y = tile->area.y + row_index * (row_height + yspacing);
area->height = row_height;
}
row_height = (tile->area.height + yspacing) / rows_in_tile - yspacing;
row_index = MIN (tile->area.height - 1, y - tile->area.y) / (row_height + yspacing);
pos += self->n_columns * row_index;
}
else
{
if (area)
{
area->y = tile->area.y;
area->height = tile->area.height;
}
row_height = tile->area.height;
row_index = 0;
}
col = gtk_grid_view_get_column_for_position (self->item_manager, self->n_columns, pos);
if (area)
{
area->x = column_start (self, xspacing, col);
area->width = column_end (self, xspacing, col) - area->x;
area->y = tile->area.y + row_index * (row_height + yspacing);
area->height = row_height;
}
}
else
{
@@ -456,6 +580,11 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
*area = tile->area;
}
self->anchor_position = pos;
self->anchor_column = col;
self->anchor_n_columns = self->n_columns;
self->anchor_has_sections = gtk_list_item_manager_get_has_sections (self->item_manager);
*position = pos;
return TRUE;
@@ -632,6 +761,86 @@ gtk_grid_view_compute_n_columns (GtkGridView *self,
return n_columns;
}
void
gtk_grid_view_split_tiles_by_columns (GtkListItemManager *items,
guint n_columns)
{
GtkListTile *tile;
tile = gtk_list_item_manager_get_first (items);
while (tile != NULL)
{
/* Handle multirow tiles first */
if (tile->n_items > 1)
{
guint pos, col;
guint end, remaining;
pos = gtk_list_tile_get_position (items, tile);
col = gtk_grid_view_get_column_for_position (items, n_columns, pos);
get_section_for_position (items, pos, NULL, &end);
if (col > 0)
{
/* Determine if the first row needs to be split off */
remaining = MIN (n_columns - col, end - 1 - pos);
if (remaining > 0 && tile->n_items > remaining)
{
gtk_list_tile_split (items, tile, remaining);
tile = gtk_rb_tree_node_get_next (tile);
continue;
}
}
pos += tile->n_items - 1;
col = gtk_grid_view_get_column_for_position (items, n_columns, pos);
get_section_for_position (items, pos, NULL, &end);
if (col < n_columns - 1)
{
/* Determine if the last row needs to be split off */
remaining = MIN (n_columns - (col - 1), end - 1 - pos);
if (remaining > 0 && col + 1 < tile->n_items)
{
gtk_list_tile_split (items, tile, tile->n_items - (col + 1));
tile = gtk_rb_tree_node_get_next (tile);
tile = gtk_rb_tree_node_get_next (tile);
continue;
}
}
}
tile = gtk_rb_tree_node_get_next (tile);
}
#ifdef G_ENABLE_DEBUG
/* Verify some invariants:
* - there are no removed tiles left
* - multirow tiles start in column 0
* - non-multirow tiles are contained in one row
*/
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
g_assert (tile->type != GTK_LIST_TILE_REMOVED);
if (tile->n_items > 1)
{
unsigned int pos, col;
pos = gtk_list_tile_get_position (items, tile);
col = gtk_grid_view_get_column_for_position (items, n_columns, pos);
if (gtk_grid_view_is_multirow_tile (items, n_columns, tile))
g_assert (col == 0);
else
g_assert (col + tile->n_items - 1 <= n_columns);
}
}
#endif
}
static void
gtk_grid_view_measure_list (GtkWidget *widget,
int for_size,
@@ -670,7 +879,7 @@ gtk_grid_view_measure_list (GtkWidget *widget,
{
gtk_widget_measure (tile->widget,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
column_size,
gtk_list_tile_is_header (tile) ? for_size : column_size,
&child_min, &child_nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
row_height = MAX (row_height, child_min);
@@ -679,7 +888,10 @@ gtk_grid_view_measure_list (GtkWidget *widget,
measured = TRUE;
}
i += tile->n_items;
if (gtk_list_tile_is_footer (tile) || gtk_list_tile_is_header (tile))
i = n_columns;
else
i += tile->n_items;
if (i >= n_columns)
{
@@ -775,34 +987,34 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
self->column_width = ((orientation == GTK_ORIENTATION_VERTICAL ? width : height) + xspacing) / self->n_columns - xspacing;
self->column_width = MAX (self->column_width, col_min);
/* step 2: determine height of known rows */
/* step 2: split tiles as required */
gtk_grid_view_split_tiles_by_columns (self->item_manager, self->n_columns);
/* step 3: determine height of known rows */
heights = g_array_new (FALSE, FALSE, sizeof (int));
tile = gtk_list_item_manager_get_first (self->item_manager);
while (tile != NULL)
{
/* if it's a multirow tile, handle it here */
if (tile->n_items > 1 && tile->n_items >= self->n_columns)
if (gtk_grid_view_is_multirow_tile (self->item_manager, self->n_columns, tile))
{
if (tile->n_items % self->n_columns)
gtk_list_tile_split (self->item_manager, tile, tile->n_items / self->n_columns * self->n_columns);
tile = gtk_rb_tree_node_get_next (tile);
continue;
}
/* Not a multirow tile */
i = 0;
row_height = 0;
for (i = 0, start = tile;
i < self->n_columns && tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
g_assert (!gtk_grid_view_is_multirow_tile (self->item_manager, self->n_columns, tile));
if (tile->widget)
{
int min, nat, size;
gtk_widget_measure (tile->widget,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
self->column_width,
gtk_list_tile_is_header (tile) ? width : self->column_width,
&min, &nat, NULL, NULL);
if (scroll_policy == GTK_SCROLL_MINIMUM)
size = min;
@@ -812,32 +1024,59 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
g_array_append_val (heights, size);
row_height = MAX (row_height, size);
}
if (tile->n_items > self->n_columns - i)
gtk_list_tile_split (self->item_manager, tile, self->n_columns - i);
i += tile->n_items;
if (gtk_list_tile_is_footer (tile) || gtk_list_tile_is_header (tile))
i = self->n_columns;
else
i += tile->n_items;
}
if (row_height > 0)
{
for (i = 0;
start != tile;
start = gtk_rb_tree_node_get_next (start))
{
int n_columns;
int tile_height;
if (gtk_list_tile_is_footer (start))
{
n_columns = self->n_columns - i;
if (n_columns != 0 && n_columns != self->n_columns)
tile_height = row_height;
else
tile_height = 0;
}
else if (gtk_list_tile_is_header (start))
{
g_assert (i == 0);
n_columns = self->n_columns;
if (gtk_list_item_manager_get_has_sections (self->item_manager))
tile_height = row_height;
else
tile_height = 0;
}
else
{
n_columns = start->n_items;
tile_height = row_height;
}
gtk_list_tile_set_area_size (self->item_manager,
start,
column_end (self, xspacing, i + start->n_items - 1)
column_end (self, xspacing, i + n_columns - 1)
- column_start (self, xspacing, i),
row_height);
i += start->n_items;
tile_height);
i += n_columns;
}
g_assert (i <= self->n_columns);
}
}
/* step 3: determine height of rows with only unknown items */
/* step 4: determine height of rows with only unknown items */
unknown_row_height = gtk_grid_view_get_unknown_row_size (self, heights);
g_array_free (heights, TRUE);
/* step 4: determine height for remaining rows and set each row's position */
/* step 5: determine height for remaining rows and set each row's position */
y = 0;
i = 0;
for (tile = gtk_list_item_manager_get_first (self->item_manager);
@@ -848,55 +1087,71 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
tile,
column_start (self, xspacing, i),
y);
if (tile->n_items >= self->n_columns && tile->widget == NULL)
if (gtk_list_tile_is_footer (tile))
{
if (i > 0)
i = self->n_columns;
if (!gtk_list_item_manager_get_has_sections (self->item_manager) && i == 0)
gtk_list_tile_set_area_size (self->item_manager,
tile,
column_end (self, xspacing, self->n_columns - 1)
- column_start (self, xspacing, 0),
0);
}
else if (gtk_list_tile_is_header (tile))
{
g_assert (i == 0);
g_assert (tile->n_items % self->n_columns == 0);
gtk_list_tile_set_area_size (self->item_manager,
tile,
column_end (self, xspacing, self->n_columns - 1)
- column_start (self, xspacing, 0),
(unknown_row_height + yspacing) * (tile->n_items / self->n_columns) - yspacing);
y += tile->area.height + yspacing;
i = self->n_columns;
if (!gtk_list_item_manager_get_has_sections (self->item_manager))
gtk_list_tile_set_area_size (self->item_manager,
tile,
column_end (self, xspacing, self->n_columns - 1)
- column_start (self, xspacing, 0),
0);
}
else
{
if (tile->area.height == 0)
if (gtk_list_tile_get_position (self->item_manager, tile) == self->anchor_position &&
i != self->anchor_column)
{
g_print ("BAD: anchor column mismatch (anchor %u, column %u != %u, n_columns %u vs %u, sections %d vs %d)\n",
self->anchor_position,
self->anchor_column, i,
self->anchor_n_columns, self->n_columns,
self->anchor_has_sections, gtk_list_item_manager_get_has_sections (self->item_manager));
}
if (gtk_grid_view_is_multirow_tile (self->item_manager, self->n_columns, tile))
{
g_assert (i == 0);
gtk_list_tile_set_area_size (self->item_manager,
tile,
column_end (self, xspacing, self->n_columns - 1)
- column_start (self, xspacing, 0),
(unknown_row_height + yspacing) * (tile->n_items / self->n_columns) - yspacing);
y += tile->area.height + yspacing;
}
else
{
/* this case is for the last row - it may not be a full row so it won't
* be a multirow tile but it may have no widgets either */
gtk_list_tile_set_area_size (self->item_manager,
tile,
column_end (self, xspacing, i + tile->n_items - 1) - tile->area.x,
unknown_row_height);
i += tile->n_items;
}
i += tile->n_items;
}
if (i >= self->n_columns)
{
g_assert (i == self->n_columns);
y += tile->area.height + yspacing;
i = 0;
}
}
/* Add a filler tile for empty space in the bottom right */
if (i > 0)
{
GtkListTile *footer = gtk_list_item_manager_get_last (self->item_manager);
g_assert (gtk_list_tile_is_footer (footer));
tile = gtk_rb_tree_node_get_previous (footer);
gtk_list_tile_set_area_position (self->item_manager,
footer,
column_start (self, xspacing, i),
y);
gtk_list_tile_set_area_size (self->item_manager,
footer,
column_end (self, xspacing, self->n_columns - 1) - footer->area.x,
tile->area.height);
}
/* step 4: allocate the rest */
/* step 6: allocate the widgets */
gtk_list_base_allocate (GTK_LIST_BASE (self));
}
@@ -952,6 +1207,7 @@ gtk_grid_view_dispose (GObject *object)
self->item_manager = NULL;
g_clear_object (&self->factory);
g_clear_object (&self->header_factory);
G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object);
}
@@ -974,6 +1230,10 @@ gtk_grid_view_get_property (GObject *object,
g_value_set_object (value, self->factory);
break;
case PROP_HEADER_FACTORY:
g_value_set_object (value, self->header_factory);
break;
case PROP_MAX_COLUMNS:
g_value_set_uint (value, self->max_columns);
break;
@@ -1018,6 +1278,10 @@ gtk_grid_view_set_property (GObject *object,
gtk_grid_view_set_factory (self, g_value_get_object (value));
break;
case PROP_HEADER_FACTORY:
gtk_grid_view_set_header_factory (self, g_value_get_object (value));
break;
case PROP_MAX_COLUMNS:
gtk_grid_view_set_max_columns (self, g_value_get_uint (value));
break;
@@ -1071,6 +1335,8 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
list_base_class->split = gtk_grid_view_split;
list_base_class->create_list_widget = gtk_grid_view_create_list_widget;
list_base_class->prepare_section = gtk_grid_view_prepare_section;
list_base_class->create_header_widget = gtk_grid_view_create_header_widget;
list_base_class->get_allocation = gtk_grid_view_get_allocation;
list_base_class->get_items_in_rect = gtk_grid_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
@@ -1109,6 +1375,19 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkGridView:header-factory: (attributes org.gtk.Property.get=gtk_grid_view_get_header_factory org.gtk.Property.set=gtk_grid_view_set_header_factory)
*
* Factory for creating header widgets.
*
* Since: 4.12
*/
properties[PROP_HEADER_FACTORY] =
g_param_spec_object ("header-factory", NULL, NULL,
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkGridView:max-columns: (attributes org.gtk.Property.get=gtk_grid_view_get_max_columns org.gtk.Property.set=gtk_grid_view_set_max_columns)
*
@@ -1342,6 +1621,69 @@ gtk_grid_view_set_factory (GtkGridView *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_grid_view_get_header_factory: (attributes org.gtk.Method.get_property=header-factory)
* @self: a `GtkGridView`
*
* Gets the factory that's currently used to populate section headers.
*
* Returns: (nullable) (transfer none): The factory in use
*
* Since: 4.12
*/
GtkListItemFactory *
gtk_grid_view_get_header_factory (GtkGridView *self)
{
g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
return self->header_factory;
}
/**
* gtk_grid_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory)
* @self: a `GtkGridView`
* @factory: (nullable) (transfer none): the factory to use
*
* Sets the `GtkListItemFactory` to use for populating the
* [class@Gtk.ListHeader] objects used in section headers.
*
* If this factory is set to %NULL, the list will not show section headers.
*
* Since: 4.12
*/
void
gtk_grid_view_set_header_factory (GtkGridView *self,
GtkListItemFactory *factory)
{
gboolean had_sections;
g_return_if_fail (GTK_IS_GRID_VIEW (self));
g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
had_sections = gtk_list_item_manager_get_has_sections (self->item_manager);
if (!g_set_object (&self->header_factory, factory))
return;
gtk_list_item_manager_set_has_sections (self->item_manager, factory != NULL);
if (!gtk_grid_view_is_inert (self) &&
had_sections && gtk_list_item_manager_get_has_sections (self->item_manager))
{
GtkListTile *tile;
for (tile = gtk_list_item_manager_get_first (self->item_manager);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget && tile->type == GTK_LIST_TILE_HEADER)
gtk_list_header_widget_set_factory (GTK_LIST_HEADER_WIDGET (tile->widget), factory);
}
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]);
}
/**
* gtk_grid_view_get_max_columns: (attributes org.gtk.Method.get_property=max-columns)
* @self: a `GtkGridView`

View File

@@ -56,6 +56,14 @@ void gtk_grid_view_set_factory (GtkGridView
GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_grid_view_get_factory (GtkGridView *self);
GDK_AVAILABLE_IN_4_12
void gtk_grid_view_set_header_factory (GtkGridView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_grid_view_get_header_factory (GtkGridView *self);
GDK_AVAILABLE_IN_ALL
guint gtk_grid_view_get_min_columns (GtkGridView *self);
GDK_AVAILABLE_IN_ALL

40
gtk/gtkgridviewprivate.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright © 2023 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "gtk/gtktypes.h"
#include "gtk/gtkenums.h"
#include "gtk/gtkgridview.h"
#include "gtk/gtklistitemmanagerprivate.h"
G_BEGIN_DECLS
unsigned int gtk_grid_view_get_column_for_position (GtkListItemManager *items,
unsigned int n_columns,
unsigned int position);
gboolean gtk_grid_view_is_multirow_tile (GtkListItemManager *items,
unsigned int n_columns,
GtkListTile *tile);
void gtk_grid_view_split_tiles_by_columns (GtkListItemManager *items,
guint n_columns);
G_END_DECLS

View File

@@ -39,6 +39,8 @@
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include <gdk/gdkrgbaprivate.h>
/* Allow shadows to overdraw without immediately culling the widget at the viewport
* boundary.
* Choose this so that roughly 1 extra widget gets drawn on each side of the viewport,
@@ -1142,6 +1144,124 @@ gtk_list_base_move_cursor (GtkWidget *widget,
return TRUE;
}
GskRenderNode *
gtk_list_base_dump_tiles (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkSnapshot *snapshot;
GtkListTile *tile;
cairo_rectangle_int_t viewport;
guint i, focus, anchor, selected;
PangoLayout *layout;
char *s;
focus = gtk_list_base_get_focus_position (self);
anchor = gtk_list_base_get_anchor (self);
selected = gtk_list_item_tracker_get_position (priv->item_manager, priv->selected);
snapshot = gtk_snapshot_new ();
i = 0;
for (tile = gtk_list_item_manager_get_first (priv->item_manager);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
{
GdkRGBA color;
if (gtk_list_tile_is_header (tile))
color = GDK_RGBA("FF00FF");
else if (i == focus)
color = GDK_RGBA("00FF00");
else if (i == anchor)
color = GDK_RGBA("FFFF00");
else if (i == selected)
color = GDK_RGBA("0000FF");
else
color = GDK_RGBA("FFFFFF");
gtk_snapshot_append_color (snapshot,
&color,
&GRAPHENE_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
));
/* This should really look at the ListItem */
s = g_strdup_printf ("%u", i);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), s);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (tile->area.x + 2, tile->area.y + 2));
gtk_snapshot_append_layout (snapshot, layout, &GDK_RGBA("000000"));
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- tile->area.x - 2, - tile->area.y - 2));
g_object_unref (layout);
g_free (s);
}
else
{
GdkRGBA color;
if (gtk_list_tile_is_footer (tile))
color = GDK_RGBA("800080");
if (tile->n_items == 0)
color = GDK_RGBA("A07070");
else
color = GDK_RGBA("808080");
gtk_snapshot_append_color (snapshot,
&color,
&GRAPHENE_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
));
}
gtk_snapshot_append_border (snapshot,
&GSK_ROUNDED_RECT_INIT(
tile->area.x, tile->area.y,
tile->area.width, tile->area.height
),
(float[4]) { 1, 1, 1, 1 },
(GdkRGBA[4]) { GDK_RGBA("000000"), GDK_RGBA("000000"), GDK_RGBA("000000"), GDK_RGBA("000000") });
i += tile->n_items;
}
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
&viewport.y, NULL, &viewport.height);
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
&viewport.x, NULL, &viewport.width);
gtk_snapshot_append_color (snapshot,
&GDK_RGBA("0000F040"),
&GRAPHENE_RECT_INIT(
viewport.x, viewport.y,
viewport.width, viewport.height
));
return gtk_snapshot_free_to_node (snapshot);
}
static gboolean
gtk_list_base_copy_tiles_to_clipboard (GtkWidget *widget,
GVariant *args,
gpointer unused)
{
GtkListBase *self = GTK_LIST_BASE (widget);
GskRenderNode *node;
node = gtk_list_base_dump_tiles (self);
if (node == NULL)
return TRUE;
gdk_clipboard_set (gtk_widget_get_clipboard (widget),
GSK_TYPE_RENDER_NODE,
node);
gsk_render_node_unref (node);
return TRUE;
}
static void
gtk_list_base_add_move_binding (GtkWidgetClass *widget_class,
guint keyval,
@@ -1320,6 +1440,8 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_backslash, GDK_CONTROL_MASK, "list.unselect-all", NULL);
gtk_widget_class_add_binding (widget_class, GDK_KEY_R, GDK_CONTROL_MASK | GDK_SHIFT_MASK, gtk_list_base_copy_tiles_to_clipboard, NULL);
}
static gboolean

View File

@@ -95,3 +95,5 @@ GtkListTabBehavior gtk_list_base_get_tab_behavior (GtkListBase
void gtk_list_base_allocate (GtkListBase *self);
GskRenderNode *gtk_list_base_dump_tiles (GtkListBase *self);

View File

@@ -1,5 +1,6 @@
gtk_tests = [
# testname, optional extra sources
['testsections'],
['testfilelauncher'],
['input'],
['testpopup'],

317
tests/testsections.c Normal file
View File

@@ -0,0 +1,317 @@
#include <gtk/gtk.h>
static void
setup_item (GtkSignalListItemFactory *self,
GObject *object)
{
GtkListItem *list_item = GTK_LIST_ITEM (object);
GtkWidget *child = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (child), 0);
gtk_list_item_set_child (list_item, child);
}
static void
bind_item (GtkSignalListItemFactory *self,
GObject *object)
{
GtkListItem *list_item = GTK_LIST_ITEM (object);
GObject *item = gtk_list_item_get_item (list_item);
GtkWidget *child = gtk_list_item_get_child (list_item);
gtk_label_set_label (GTK_LABEL (child),
gtk_string_object_get_string (GTK_STRING_OBJECT (item)));
}
static void
setup_header (GtkSignalListItemFactory *self,
GObject *object)
{
GtkListHeader *header = GTK_LIST_HEADER (object);
GtkWidget *child = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (child), 0);
gtk_list_header_set_child (header, child);
}
static char *
get_first (GObject *this)
{
const char *s = gtk_string_object_get_string (GTK_STRING_OBJECT (this));
char buffer[6] = { 0, };
g_unichar_to_utf8 (g_unichar_toupper (g_utf8_get_char (s)), buffer);
return g_strdup (buffer);
}
static void
bind_header (GtkSignalListItemFactory *self,
GObject *object)
{
GtkListHeader *header = GTK_LIST_HEADER (object);
GObject *item = gtk_list_header_get_item (header);
GtkWidget *child = gtk_list_header_get_child (header);
PangoAttrList *attrs;
char *string;
string = get_first (item);
gtk_label_set_label (GTK_LABEL (child), string);
attrs = pango_attr_list_new ();
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
gtk_label_set_attributes (GTK_LABEL (child), attrs);
pango_attr_list_unref (attrs);
g_free (string);
}
static const char *strings[] = {
"Alpha", "Andromeda", "Anaphylaxis", "Anaheim", "Beer", "Branch", "Botulism", "Banana",
"Bee", "Crane", "Caldera", "Copper", "Crowd", "Dora", "Dolphin", "Dam", "Ding",
NULL,
};
gboolean done_reading = FALSE;
static gboolean
dump_sections (gpointer data)
{
GtkSectionModel *model = data;
if (!done_reading)
return G_SOURCE_CONTINUE;
for (unsigned int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++)
{
unsigned int s, e;
gtk_section_model_get_section (model, i, &s, &e);
g_print ("(%u %u)\n", s, e - 1);
i = e;
}
return G_SOURCE_REMOVE;
}
static void
read_lines_cb (GObject *object,
GAsyncResult *result,
gpointer data)
{
GBufferedInputStream *stream = G_BUFFERED_INPUT_STREAM (object);
GtkStringList *stringlist = data;
GError *error = NULL;
gsize size;
GPtrArray *lines;
gssize n_filled;
const char *buffer, *newline;
n_filled = g_buffered_input_stream_fill_finish (stream, result, &error);
if (n_filled < 0)
{
g_print ("Could not read data: %s\n", error->message);
g_clear_error (&error);
g_object_unref (stringlist);
return;
}
buffer = g_buffered_input_stream_peek_buffer (stream, &size);
if (n_filled == 0)
{
if (size)
gtk_string_list_take (stringlist, g_utf8_make_valid (buffer, size));
g_object_unref (stringlist);
done_reading = TRUE;
return;
}
lines = NULL;
while ((newline = memchr (buffer, '\n', size)))
{
if (newline > buffer)
{
if (lines == NULL)
lines = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (lines, g_utf8_make_valid (buffer, newline - buffer));
}
if (g_input_stream_skip (G_INPUT_STREAM (stream), newline - buffer + 1, NULL, &error) < 0)
{
g_clear_error (&error);
break;
}
buffer = g_buffered_input_stream_peek_buffer (stream, &size);
}
if (lines == NULL)
{
g_buffered_input_stream_set_buffer_size (stream, g_buffered_input_stream_get_buffer_size (stream) + 4096);
}
else
{
g_ptr_array_add (lines, NULL);
gtk_string_list_splice (stringlist, g_list_model_get_n_items (G_LIST_MODEL (stringlist)), 0, (const char **) lines->pdata);
g_ptr_array_free (lines, TRUE);
}
g_buffered_input_stream_fill_async (stream, -1, G_PRIORITY_HIGH_IDLE, NULL, read_lines_cb, data);
}
static void
file_is_open_cb (GObject *file,
GAsyncResult *result,
gpointer data)
{
GError *error = NULL;
GFileInputStream *file_stream;
GBufferedInputStream *stream;
file_stream = g_file_read_finish (G_FILE (file), result, &error);
if (file_stream == NULL)
{
g_print ("Could not open file: %s\n", error->message);
g_error_free (error);
g_object_unref (data);
return;
}
stream = G_BUFFERED_INPUT_STREAM (g_buffered_input_stream_new (G_INPUT_STREAM (file_stream)));
g_buffered_input_stream_fill_async (stream, -1, G_PRIORITY_HIGH_IDLE, NULL, read_lines_cb, data);
g_object_unref (stream);
}
static void
load_file (GtkStringList *list,
GFile *file)
{
gtk_string_list_splice (list, 0, g_list_model_get_n_items (G_LIST_MODEL (list)), NULL);
g_file_read_async (file, G_PRIORITY_HIGH_IDLE, NULL, file_is_open_cb, g_object_ref (list));
}
static void
toggle_cb (GtkCheckButton *check, GtkWidget *list)
{
GtkListItemFactory *header_factory = NULL;
if (gtk_check_button_get_active (check))
{
header_factory = gtk_signal_list_item_factory_new ();
g_signal_connect (header_factory, "setup", G_CALLBACK (setup_header), NULL);
g_signal_connect (header_factory, "bind", G_CALLBACK (bind_header), NULL);
}
if (GTK_IS_LIST_VIEW (list))
gtk_list_view_set_header_factory (GTK_LIST_VIEW (list), header_factory);
else
gtk_grid_view_set_header_factory (GTK_GRID_VIEW (list), header_factory);
g_clear_object (&header_factory);
}
static void
value_changed_cb (GtkAdjustment *adj, gpointer data)
{
g_print ("horizontal adjustment changed to %f\n", gtk_adjustment_get_value (adj));
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *sw;
GtkWidget *lv;
GtkWidget *gv;
GtkWidget *header;
GtkWidget *toggle;
GtkWidget *switcher;
GtkWidget *stack;
GtkListItemFactory *factory;
GtkExpression *expression;
GtkSortListModel *sortmodel;
GtkSelectionModel *selection;
GtkStringList *stringlist;
GtkAdjustment *adj;
stringlist = gtk_string_list_new (NULL);
if (argc > 1)
{
GFile *file = g_file_new_for_commandline_arg (argv[1]);
load_file (stringlist, file);
g_object_unref (file);
}
else
{
for (int i = 0; strings[i]; i++)
gtk_string_list_append (stringlist, strings[i]);
done_reading = TRUE;
}
gtk_init ();
window = gtk_window_new ();
gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
header = gtk_header_bar_new ();
gtk_window_set_titlebar (GTK_WINDOW (window), header);
toggle = gtk_check_button_new ();
gtk_widget_set_valign (toggle, GTK_ALIGN_CENTER);
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), toggle);
stack = gtk_stack_new ();
gtk_window_set_child (GTK_WINDOW (window), stack);
switcher = gtk_stack_switcher_new ();
gtk_header_bar_set_title_widget (GTK_HEADER_BAR (header), switcher);
gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (switcher), GTK_STACK (stack));
expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string");
sortmodel = gtk_sort_list_model_new (G_LIST_MODEL (stringlist),
GTK_SORTER (gtk_string_sorter_new (expression)));
expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, 0, NULL, (GCallback) get_first, NULL, NULL);
gtk_sort_list_model_set_section_sorter (sortmodel, GTK_SORTER (gtk_string_sorter_new (expression)));
selection = GTK_SELECTION_MODEL (gtk_no_selection_new (G_LIST_MODEL (sortmodel)));
sw = gtk_scrolled_window_new ();
gtk_stack_add_titled (GTK_STACK (stack), sw, "list", "List");
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
lv = gtk_list_view_new (g_object_ref (selection), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), lv);
g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_cb), lv);
sw = gtk_scrolled_window_new ();
gtk_stack_add_titled (GTK_STACK (stack), sw, "grid", "Grid");
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
gv = gtk_grid_view_new (g_object_ref (selection), factory);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gv);
g_signal_connect (toggle, "toggled", G_CALLBACK (toggle_cb), gv);
gtk_grid_view_set_min_columns (GTK_GRID_VIEW (gv), 5);
adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (sw));
g_signal_connect (adj, "value-changed", G_CALLBACK (value_changed_cb), NULL);
gtk_window_present (GTK_WINDOW (window));
g_timeout_add (500, dump_sections, selection);
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, FALSE);
g_object_unref (selection);
g_object_unref (stringlist);
return 0;
}

View File

@@ -20,6 +20,7 @@
#include <gtk/gtk.h>
#include "gtk/gtklistitemmanagerprivate.h"
#include "gtk/gtklistbaseprivate.h"
#include "gtk/gtkgridviewprivate.h"
static GListModel *
create_source_model (guint min_size, guint max_size)
@@ -374,7 +375,43 @@ print_changes_cb (GListModel *model,
}
static void
test_exhaustive (void)
check_column_tile_invariants (GtkListItemManager *items,
unsigned int n_columns)
{
GtkListTile *tile;
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
g_assert (tile->type != GTK_LIST_TILE_REMOVED);
if (tile->n_items > 1)
{
unsigned int pos, col;
pos = gtk_list_tile_get_position (items, tile);
col = gtk_grid_view_get_column_for_position (items, n_columns, pos);
if (gtk_grid_view_is_multirow_tile (items, n_columns, tile))
g_assert_true (col == 0);
else
g_assert_true (col + tile->n_items - 1 <= n_columns);
}
}
}
static void
check_grid_view (GtkListItemManager *items)
{
for (unsigned int n_columns = 1; n_columns < 10; n_columns++)
{
gtk_list_item_manager_gc_tiles (items);
gtk_grid_view_split_tiles_by_columns (items, n_columns);
check_column_tile_invariants (items, n_columns);
}
}
static void
test_exhaustive (gboolean grid)
{
GtkListItemTracker *trackers[N_TRACKERS];
GListStore *store;
@@ -484,7 +521,10 @@ test_exhaustive (void)
}
}
check_list_item_manager (items, trackers, N_TRACKERS);
if (grid)
check_grid_view (items);
else
check_list_item_manager (items, trackers, N_TRACKERS);
for (i = 0; i < N_TRACKERS; i++)
gtk_list_item_tracker_free (items, trackers[i]);
@@ -492,6 +532,18 @@ test_exhaustive (void)
gtk_window_destroy (GTK_WINDOW (widget));
}
static void
test_exhaustive_list (void)
{
test_exhaustive (FALSE);
}
static void
test_exhaustive_grid (void)
{
test_exhaustive (TRUE);
}
int
main (int argc, char *argv[])
{
@@ -499,7 +551,8 @@ main (int argc, char *argv[])
g_test_add_func ("/listitemmanager/create", test_create);
g_test_add_func ("/listitemmanager/create_with_items", test_create_with_items);
g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive);
g_test_add_func ("/listitemmanager/exhaustive", test_exhaustive_list);
g_test_add_func ("/gridview/split", test_exhaustive_grid);
return g_test_run ();
}