Compare commits

...

36 Commits

Author SHA1 Message Date
Matthias Clasen
7bde123457 Typo fix 2023-04-12 16:28:12 +02:00
Matthias Clasen
e45116e69e Typo fix 2023-04-12 16:26:21 +02:00
Matthias Clasen
7b03bd6d14 Typo fix 2023-04-12 16:26:13 +02:00
Matthias Clasen
169b58b148 gtk-demo: Add columview header demo
This does not work yet.
2023-04-12 16:22:37 +02:00
Matthias Clasen
2d16ffe326 columnview: Add a header factory 2023-04-12 15:57:10 +02:00
Benjamin Otte
290eda6243 gtk-demo: Change the settings demo
Now it displays a long list of all settings with sections and allows
filtering the list to quickly search keys.

We might want to keep the old example, too, maybe?
2023-04-12 14:36:50 +02:00
Benjamin Otte
f0335c71b3 theme: Add some random CSS for section headers 2023-04-12 14:36:50 +02:00
Benjamin Otte
2f31fe06a0 listitemmanager: Create header items for sections
Now, finally, listitemmanager has all the necessary support to make
section headers work.
2023-04-12 14:36:50 +02:00
Benjamin Otte
eb79498f62 listview: GtkListHeader and gtk_list_view_set_header_factory()
Adds infrastructure for handling factories for list headers.

For now, listitemmanager doesn't instantiate them, so they aren't used.
2023-04-12 14:36:50 +02:00
Benjamin Otte
34106bdda8 listitemmanager: Add GtkListItemChange
... for tracking widgets during changes.

This just pulls all the different disjointed parts into one struct with
a sensible API.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c474e0ac92 listitemmanager: Add GtkListHeaderBase and vfuncs
This is plumbing for section handling.

The vfuncs don't do anything, this is just the infrastructure.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c93eaa49b5 listitembase: exit early
When update() is called but no values are changed, exit early.
2023-04-11 15:59:43 +02:00
Benjamin Otte
c25d0bbac3 listitemmanager: Make sections configurable
Add a gtk_list_item_manager_set_has_sections() (default: FALSE) that
requires explicit turning on for the listitemmanager to gain section
support.
2023-04-11 15:59:43 +02:00
Benjamin Otte
3353a0836b listitemmanager: Add/remove sections as needed
When ensuring widgets, ensure that their section is known. This will
be relevant when we use section widgets.

Also ensure that sections that don't cover any widget get destroyed.
2023-04-11 15:59:43 +02:00
Benjamin Otte
29d7c42fee testsuite: assert widgets are in matched sections
All widgets that we display should have their sections known, so assert
that that is the case.
2023-04-11 15:59:43 +02:00
Benjamin Otte
176be81ede testsuite: Add a print function for the listitemmanager
This is very useful when debugging.
2023-04-11 15:59:43 +02:00
Benjamin Otte
95effedead listitemmanager: Add gtk_list_tile_get_next_skip()
... and gtk_list_tile_get_previous_skip() and use them.

Allows skipping over REMOVED and FILLER tiles which makes the code a lot
more readable.
2023-04-11 15:59:43 +02:00
Benjamin Otte
89cfc182a2 listitemmanager: Add gtk_list_tile_set_type() 2023-04-11 15:59:43 +02:00
Benjamin Otte
c51d82f444 testsuite: Add verbose output to listitemmanager test 2023-04-11 15:59:43 +02:00
Benjamin Otte
cd2c64aff9 listitemmanager: Properly handle sections during add/remove
We don't insert sections ourselves yet, but we handle the existing one
when items get added or removed.
2023-04-11 15:59:43 +02:00
Benjamin Otte
5f6796b83f listitemmanager: Augment header/footer info 2023-04-11 15:59:43 +02:00
Benjamin Otte
77f3cd00a9 listitemmanager: Add section tiles
For now, we just have a HEADER at the start and a FOOTER at the end.
That's hard enough to get right.
2023-04-11 15:59:43 +02:00
Benjamin Otte
a1f41485f8 testsuite: Improve listitemmanager test
It now has trackers!
2023-04-11 15:59:43 +02:00
Benjamin Otte
5afda9e732 listitemmanager: Add tile types
We have a FILLER and a REMOVED type now.

Also makes gc() more sensitive to types.
2023-04-11 15:59:43 +02:00
Benjamin Otte
d181fb11a1 listitemmanager: Add a type to tiles
There's only one type for now, but hey, you have to start somewhere.
2023-04-11 15:59:43 +02:00
Benjamin Otte
37340cbebe testsuite: Add tests for sections to filterlistmodel 2023-04-11 15:59:43 +02:00
Matthias Clasen
38cf1e1895 Add a test for filterlistmodel sections 2023-04-11 15:59:43 +02:00
Matthias Clasen
d93e276f3f filterlistmodel: Support sections
Propagate sections from the child model to
the filter model.
2023-04-11 15:59:43 +02:00
Benjamin Otte
778321cb7a sortlistmodel: add a fast path for get_section() 2023-04-11 15:59:43 +02:00
Benjamin Otte
2259109e32 testsuite: Add section tests to sortlistmodel test 2023-04-11 15:59:43 +02:00
Matthias Clasen
5ceb4c0815 Add a test for sortlistmodel sections 2023-04-11 15:59:43 +02:00
Benjamin Otte
3960098e72 sortlistmodel: Implement GtkSectionModel
The get_section() implementation is a slow and steady implementation
that has to be careful to not screw up when an incremental sort is only
partially sorted.
2023-04-11 15:59:29 +02:00
Benjamin Otte
849df30b50 flattenlistmodel: Implement GtkSectionModel
Each child model is reported as one section.
2023-04-11 13:37:32 +02:00
Benjamin Otte
f886f8e677 Implement GtkSectionModel for all selection models 2023-04-11 13:37:32 +02:00
Benjamin Otte
74c55de53d Add GtkSectionModel
Prototyping the interface to be used for sections in listview, so people
can review and play with it.
2023-04-11 13:37:32 +02:00
Benjamin Otte
65a631a561 testsuite: Add a listitemmanager test 2023-04-11 13:35:50 +02:00
40 changed files with 3962 additions and 706 deletions

View File

@@ -29,6 +29,7 @@ struct _SettingsKey
enum {
PROP_0,
PROP_NAME,
PROP_SETTINGS,
PROP_SUMMARY,
PROP_DESCRIPTION,
PROP_VALUE,
@@ -89,6 +90,10 @@ settings_key_get_property (GObject *object,
}
break;
case PROP_SETTINGS:
g_value_set_object (value, self->settings);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -118,6 +123,8 @@ settings_key_class_init (SettingsKeyClass *klass)
g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_NAME] =
g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_SETTINGS] =
g_param_spec_object ("settings", NULL, NULL, G_TYPE_SETTINGS, G_PARAM_READABLE);
properties[PROP_SUMMARY] =
g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READABLE);
properties[PROP_VALUE] =
@@ -147,6 +154,23 @@ settings_key_new (GSettings *settings,
return result;
}
static char *
settings_key_get_search_string (SettingsKey *self)
{
char *schema, *result;
g_object_get (self->settings, "schema-id", &schema, NULL);
result = g_strconcat (g_settings_schema_key_get_name (self->key), " ",
g_settings_schema_key_get_summary (self->key), " ",
schema,
NULL);
g_free (schema);
return result;
}
static void
item_value_changed (GtkEditableLabel *label,
GParamSpec *pspec,
@@ -203,28 +227,16 @@ strvcmp (gconstpointer p1,
return strcmp (*s1, *s2);
}
static GtkFilter *current_filter;
static gboolean
transform_settings_to_keys (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer data)
static gpointer
map_settings_to_keys (gpointer item,
gpointer unused)
{
GtkTreeListRow *treelistrow;
GSettings *settings;
GSettings *settings = item;
GSettingsSchema *schema;
GListStore *store;
GtkSortListModel *sort_model;
GtkFilterListModel *filter_model;
GtkFilter *filter;
char **keys;
guint i;
treelistrow = g_value_get_object (from_value);
if (treelistrow == NULL)
return TRUE;
settings = gtk_tree_list_row_get_item (treelistrow);
g_object_get (settings, "settings-schema", &schema, NULL);
store = g_list_store_new (SETTINGS_TYPE_KEY);
@@ -244,16 +256,7 @@ transform_settings_to_keys (GBinding *binding,
g_settings_schema_unref (schema);
g_object_unref (settings);
sort_model = gtk_sort_list_model_new (G_LIST_MODEL (store),
g_object_ref (gtk_column_view_get_sorter (GTK_COLUMN_VIEW (data))));
filter = GTK_FILTER (gtk_string_filter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name")));
g_set_object (&current_filter, filter);
filter_model = gtk_filter_list_model_new (G_LIST_MODEL (sort_model), filter);
g_value_take_object (to_value, gtk_no_selection_new (G_LIST_MODEL (filter_model)));
return TRUE;
return store;
}
static GListModel *
@@ -310,24 +313,11 @@ search_enabled (GtkSearchEntry *entry)
gtk_editable_set_text (GTK_EDITABLE (entry), "");
}
static void
search_changed (GtkSearchEntry *entry,
gpointer data)
{
const char *text = gtk_editable_get_text (GTK_EDITABLE (entry));
if (current_filter)
gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), text);
}
static void
stop_search (GtkSearchEntry *entry,
gpointer data)
{
gtk_editable_set_text (GTK_EDITABLE (entry), "");
if (current_filter)
gtk_string_filter_set_search (GTK_STRING_FILTER (current_filter), "");
}
static GtkWidget *window = NULL;
@@ -337,94 +327,53 @@ do_listview_settings (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *listview, *columnview;
GtkListView *listview;
GListModel *model;
GtkTreeListModel *treemodel;
GtkSingleSelection *selection;
GtkNoSelection *selection;
GtkBuilderScope *scope;
GtkBuilder *builder;
GtkColumnViewColumn *name_column;
GtkColumnViewColumn *type_column;
GtkColumnViewColumn *default_column;
GtkColumnViewColumn *summary_column;
GtkColumnViewColumn *description_column;
GtkSorter *sorter;
GActionGroup *actions;
GAction *action;
GError *error = NULL;
GtkFilter *filter;
g_type_ensure (SETTINGS_TYPE_KEY);
scope = gtk_builder_cscope_new ();
gtk_builder_cscope_add_callback (scope, search_enabled);
gtk_builder_cscope_add_callback (scope, search_changed);
gtk_builder_cscope_add_callback (scope, stop_search);
gtk_builder_cscope_add_callback (scope, settings_key_get_search_string);
gtk_builder_cscope_add_callback (scope, item_value_changed);
builder = gtk_builder_new ();
gtk_builder_set_scope (builder, scope);
g_object_unref (scope);
gtk_builder_add_from_resource (builder, "/listview_settings/listview_settings.ui", NULL);
gtk_builder_add_from_resource (builder, "/listview_settings/listview_settings.ui", &error);
g_assert_no_error (error);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
listview = GTK_WIDGET (gtk_builder_get_object (builder, "listview"));
columnview = GTK_WIDGET (gtk_builder_get_object (builder, "columnview"));
type_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "type_column"));
default_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "default_column"));
summary_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "summary_column"));
description_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "description_column"));
actions = G_ACTION_GROUP (g_simple_action_group_new ());
action = G_ACTION (g_property_action_new ("show-type", type_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-default", default_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-summary", summary_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
action = G_ACTION (g_property_action_new ("show-description", description_column, "visible"));
g_action_map_add_action (G_ACTION_MAP (actions), action);
g_object_unref (action);
gtk_widget_insert_action_group (columnview, "columnview", actions);
g_object_unref (actions);
listview = GTK_LIST_VIEW (gtk_builder_get_object (builder, "listview"));
filter = GTK_FILTER (gtk_builder_get_object (builder, "filter"));
model = create_settings_model (NULL, NULL);
treemodel = gtk_tree_list_model_new (model,
FALSE,
TRUE,
TRUE,
create_settings_model,
NULL,
NULL);
selection = gtk_single_selection_new (G_LIST_MODEL (treemodel));
g_object_bind_property_full (selection, "selected-item",
columnview, "model",
G_BINDING_SYNC_CREATE,
transform_settings_to_keys,
NULL,
columnview, NULL);
model = G_LIST_MODEL (gtk_map_list_model_new (G_LIST_MODEL (treemodel), map_settings_to_keys, NULL, NULL));
model = G_LIST_MODEL (gtk_flatten_list_model_new (model));
model = G_LIST_MODEL (gtk_filter_list_model_new (model, g_object_ref (filter)));
selection = gtk_no_selection_new (model);
gtk_list_view_set_model (GTK_LIST_VIEW (listview), GTK_SELECTION_MODEL (selection));
g_object_unref (selection);
name_column = GTK_COLUMN_VIEW_COLUMN (gtk_builder_get_object (builder, "name_column"));
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "name")));
gtk_column_view_column_set_sorter (name_column, sorter);
g_object_unref (sorter);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (SETTINGS_TYPE_KEY, NULL, "type")));
gtk_column_view_column_set_sorter (type_column, sorter);
g_object_unref (sorter);
g_object_unref (builder);
}

View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkStringFilter" id="filter">
<property name="expression">
<closure type="gchararray" function="settings_key_get_search_string" />
</property>
<property name="search" bind-source="entry" bind-property="text" />
</object>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Settings</property>
<property name="default-width">640</property>
@@ -14,15 +20,26 @@
</object>
</child>
<child>
<object class="GtkPaned">
<property name="position">300</property>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional"/>
<signal name="notify::search-mode-enabled" handler="search_enabled" object="entry"/>
<child>
<object class="GtkSearchEntry" id="entry">
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="GtkListView" id="listview">
<property name="tab-behavior">item</property>
<property name="vexpand">1</property>
<style>
<class name="navigation-sidebar"/>
<class name="rich-list"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
@@ -31,20 +48,71 @@
<interface>
<template class="GtkListItem">
<property name="child">
<object class="GtkTreeExpander" id="expander">
<binding name="list-row">
<lookup name="item">GtkListItem</lookup>
</binding>
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="schema" type="GSettings">
<lookup name="item">expander</lookup>
<object class="GtkBox">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
<child>
<object class="GtkLabel">
<style>
<class name="dim-label"/>
</style>
<property name="xalign">0</property>
<property name="ellipsize">end</property>
<binding name="label">
<lookup name="summary" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</child>
<child>
<object class="GtkEntry">
<property name="hexpand">1</property>
<property name="halign">end</property>
<binding name="text">
<lookup name="value" type="SettingsKey">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
<signal name="notify::label" handler="item_value_changed"/>
</object>
</property>
</child>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
<property name="header-factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListHeader">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="schema" type="GSettings">
<lookup name="settings" type="SettingsKey">
<lookup name="item">GtkListHeader</lookup>
</lookup>
</lookup>
</binding>
</object>
</property>
</template>
@@ -56,232 +124,7 @@
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar">
<property name="search-mode-enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional"/>
<signal name="notify::search-mode-enabled" handler="search_enabled" object="entry"/>
<child>
<object class="GtkSearchEntry" id="entry">
<signal name="search-changed" handler="search_changed"/>
<signal name="stop-search" handler="stop_search"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child>
<object class="GtkColumnView" id="columnview">
<property name="tab-behavior">cell</property>
<style>
<class name="data-table"/>
</style>
<child>
<object class="GtkColumnViewColumn" id="name_column">
<property name="title">Name</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="name" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn">
<property name="title">Value</property>
<property name="resizable">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkEditableLabel">
<binding name="text">
<lookup name="value" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
<signal name="notify::label" handler="item_value_changed"/>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="type_column">
<property name="title">Type</property>
<property name="resizable">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="type" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="default_column">
<property name="title">Default</property>
<property name="resizable">1</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<binding name="label">
<lookup name="default-value" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="summary_column">
<property name="title">Summary</property>
<property name="resizable">1</property>
<property name="visible">0</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="wrap">1</property>
<binding name="label">
<lookup name="summary" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="description_column">
<property name="title">Description</property>
<property name="resizable">1</property>
<property name="visible">0</property>
<property name="expand">1</property>
<property name="header-menu">header_menu</property>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="bytes"><![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkColumnViewCell">
<property name="child">
<object class="GtkLabel">
<property name="xalign">0</property>
<property name="wrap">1</property>
<binding name="label">
<lookup name="description" type="SettingsKey">
<lookup name="item">GtkColumnViewCell</lookup>
</lookup>
</binding>
</object>
</property>
</template>
</interface>
]]></property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<menu id="header_menu">
<section>
<item>
<attribute name="label" translatable="yes">Type</attribute>
<attribute name="action">columnview.show-type</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Default value</attribute>
<attribute name="action">columnview.show-default</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Summary</attribute>
<attribute name="action">columnview.show-summary</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Description</attribute>
<attribute name="action">columnview.show-description</attribute>
</item>
</section>
</menu>
</interface>

View File

@@ -63,6 +63,19 @@ ucd_item_get_name (UcdItem *item)
return item->name;
}
static int
compare_script (gconstpointer a, gconstpointer b, gpointer data)
{
const UcdItem *ai = a;
const UcdItem *bi = b;
int s1, s2;
s1 = (int) g_unichar_get_script (ai->codepoint);
s2 = (int) g_unichar_get_script (bi->codepoint);
return s2 - s1;
}
static GListModel *
ucd_model_new (void)
{
@@ -72,6 +85,7 @@ ucd_model_new (void)
GListStore *store;
guint u;
char *name;
GtkSortListModel *sortmodel;
bytes = g_resources_lookup_data ("/listview_ucd_data/ucdnames.data", 0, NULL);
v = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(us)"), bytes, TRUE));
@@ -93,7 +107,10 @@ ucd_model_new (void)
g_variant_unref (v);
g_bytes_unref (bytes);
return G_LIST_MODEL (store);
sortmodel = gtk_sort_list_model_new (G_LIST_MODEL (store), NULL);
gtk_sort_list_model_set_section_sorter (sortmodel, GTK_SORTER (gtk_custom_sorter_new (compare_script, NULL, NULL)));
return G_LIST_MODEL (sortmodel);
}
static void
@@ -223,6 +240,16 @@ bind_combining_class (GtkSignalListItemFactory *factory,
gtk_label_set_label (GTK_LABEL (label), get_combining_class_name (g_unichar_combining_class (codepoint)));
}
static void
setup_header (GtkSignalListItemFactory *factory,
GObject *listitem)
{
GtkWidget *label;
label = gtk_label_new ("");
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_list_header_set_child (GTK_LIST_HEADER (listitem), label);
}
static void
bind_script (GtkSignalListItemFactory *factory,
GObject *listitem)
@@ -232,8 +259,8 @@ bind_script (GtkSignalListItemFactory *factory,
gunichar codepoint;
GUnicodeScript script;
label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
label = gtk_list_header_get_child (GTK_LIST_HEADER (listitem));
item = gtk_list_header_get_item (GTK_LIST_HEADER (listitem));
codepoint = ucd_item_get_codepoint (UCD_ITEM (item));
script = g_unichar_get_script (codepoint);
@@ -325,12 +352,10 @@ create_ucd_view (GtkWidget *label)
g_object_unref (column);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", G_CALLBACK (setup_label), NULL);
g_signal_connect (factory, "setup", G_CALLBACK (setup_header), NULL);
g_signal_connect (factory, "bind", G_CALLBACK (bind_script), NULL);
column = gtk_column_view_column_new ("Script", factory);
gtk_column_view_column_set_resizable (column, TRUE);
gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
g_object_unref (column);
gtk_column_view_set_header_factory (GTK_COLUMN_VIEW (cv), factory);
g_object_unref (factory);
return cv;
}

View File

@@ -174,6 +174,7 @@
#include <gtk/gtklistbase.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistheader.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtklistitemfactory.h>
#include <gtk/deprecated/gtkliststore.h>
@@ -225,6 +226,7 @@
#include <gtk/gtkscrolledwindow.h>
#include <gtk/gtksearchbar.h>
#include <gtk/gtksearchentry.h>
#include <gtk/gtksectionmodel.h>
#include <gtk/gtkselectionfiltermodel.h>
#include <gtk/gtkselectionmodel.h>
#include <gtk/gtkseparator.h>

View File

@@ -220,6 +220,7 @@ enum
PROP_COLUMNS,
PROP_ENABLE_RUBBERBAND,
PROP_HADJUSTMENT,
PROP_HEADER_FACTORY,
PROP_HSCROLL_POLICY,
PROP_MODEL,
PROP_REORDERABLE,
@@ -628,6 +629,10 @@ gtk_column_view_get_property (GObject *object,
g_value_set_object (value, self->hadjustment);
break;
case PROP_HEADER_FACTORY:
g_value_set_object (value, gtk_column_view_get_header_factory (self));
break;
case PROP_HSCROLL_POLICY:
g_value_set_enum (value, gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)));
break;
@@ -711,6 +716,10 @@ gtk_column_view_set_property (GObject *object,
}
break;
case PROP_HEADER_FACTORY:
gtk_column_view_set_header_factory (self, g_value_get_object (value));
break;
case PROP_HSCROLL_POLICY:
if (gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)) != g_value_get_enum (value))
{
@@ -825,6 +834,18 @@ gtk_column_view_class_init (GtkColumnViewClass *klass)
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkColumnView:header-factory: (attributes org.gtk.Property.get=gtk_column_view_get_header_factory org.gtk.Property.set=gtk_column_view_set_header_factory)
*
* The factory used for creating section headers.
*
* 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);
/**
* GtkColumnView:model: (attributes org.gtk.Property.get=gtk_column_view_get_model org.gtk.Property.set=gtk_column_view_set_model)
*
@@ -2018,6 +2039,49 @@ gtk_column_view_get_enable_rubberband (GtkColumnView *self)
return gtk_list_view_get_enable_rubberband (self->listview);
}
/**
* gtk_column_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory)
* @self: a `GtkColumnView`
* @factory: (nullable): The header factory
*
* Sets the factory used for creating seciton headers.
*
* If this factory is not set - which is the default - then no headers will be used.
*
* Since: 4.12
*/
void
gtk_column_view_set_header_factory (GtkColumnView *self,
GtkListItemFactory *factory)
{
g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
if (factory == gtk_list_view_get_header_factory (self->listview))
return;
gtk_list_view_set_header_factory (self->listview, factory);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]);
}
/**
* gtk_column_view_get_header_factory: (attributes org.gtk.Method.get_property=header-factory)
* @self: a `GtkColumnView`
*
* Gets the factory set via [method@Gtk.ColumnView.set_header_factory].
*
* Returns: (nullable) (transfer none): The factory
*
* Since: 4.12
*/
GtkListItemFactory *
gtk_column_view_get_header_factory (GtkColumnView *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
return gtk_list_view_get_header_factory (self->listview);
}
/**
* gtk_column_view_set_row_factory: (attributes org.gtk.Method.set_property=row-factory)
* @self: a `GtkColumnView`
@@ -2060,7 +2124,7 @@ gtk_column_view_set_row_factory (GtkColumnView *self,
GtkListItemFactory *
gtk_column_view_get_row_factory (GtkColumnView *self)
{
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
return gtk_list_view_get_factory (self->listview);
}

View File

@@ -122,5 +122,12 @@ GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_column_view_get_row_factory (GtkColumnView *self);
GDK_AVAILABLE_IN_4_12
void gtk_column_view_set_header_factory (GtkColumnView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_column_view_get_header_factory (GtkColumnView *self);
G_END_DECLS

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtkprivate.h"
#include "gtksectionmodelprivate.h"
/**
* GtkFilterListModel:
@@ -135,8 +136,67 @@ gtk_filter_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_filter_list_model_get_item;
}
static void
gtk_filter_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (model);
guint n_items;
guint pos, start, end;
switch (self->strictness)
{
case GTK_FILTER_MATCH_NONE:
*out_start = 0;
*out_end = G_MAXUINT;
return;
case GTK_FILTER_MATCH_ALL:
gtk_list_model_get_section (self->model, position, out_start, out_end);
return;
case GTK_FILTER_MATCH_SOME:
n_items = gtk_bitset_get_size (self->matches);
if (position >= n_items)
{
*out_start = n_items;
*out_end = G_MAXUINT;
return;
}
if (!GTK_IS_SECTION_MODEL (self->model))
{
*out_start = 0;
*out_end = n_items;
return;
}
break;
default:
g_assert_not_reached ();
}
/* if we get here, we have a section model, and are MATCH_SOME */
pos = gtk_bitset_get_nth (self->matches, position);
gtk_section_model_get_section (GTK_SECTION_MODEL (self->model), pos, &start, &end);
if (start == 0)
*out_start = 0;
else
*out_start = gtk_bitset_get_size_in_range (self->matches, 0, start - 1);
*out_end = *out_start + gtk_bitset_get_size_in_range (self->matches, start, end - 1);
}
static void
gtk_filter_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_filter_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_filter_list_model_section_model_init))
static gboolean
gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
@@ -164,7 +224,7 @@ gtk_filter_list_model_run_filter (GtkFilterListModel *self,
gboolean more;
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
if (self->pending == NULL)
return;
@@ -355,7 +415,7 @@ gtk_filter_list_model_set_property (GObject *object,
}
}
static void
static void
gtk_filter_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
@@ -503,7 +563,7 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
case GTK_FILTER_MATCH_SOME:
{
GtkBitset *old, *pending;
if (self->matches == NULL)
{
if (self->strictness == GTK_FILTER_MATCH_ALL)

View File

@@ -21,8 +21,8 @@
#include "gtkflattenlistmodel.h"
#include "gtksectionmodel.h"
#include "gtkrbtreeprivate.h"
#include "gtkprivate.h"
/**
* GtkFlattenListModel:
@@ -200,8 +200,39 @@ gtk_flatten_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_flatten_list_model_get_item;
}
static void
gtk_flatten_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkFlattenListModel *self = GTK_FLATTEN_LIST_MODEL (model);
FlattenNode *node;
guint model_pos;
node = gtk_flatten_list_model_get_nth (self->items, position, &model_pos);
if (node == NULL)
{
*out_start = gtk_flatten_list_model_get_n_items (G_LIST_MODEL (self));
*out_end = G_MAXUINT;
return;
}
*out_start = position - model_pos;
*out_end = position - model_pos + g_list_model_get_n_items (node->model);
}
static void
gtk_flatten_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_flatten_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkFlattenListModel, gtk_flatten_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_flatten_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_flatten_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_flatten_list_model_section_model_init))
static void
gtk_flatten_list_model_items_changed_cb (GListModel *model,
@@ -433,7 +464,7 @@ gtk_flatten_list_model_class_init (GtkFlattenListModelClass *class)
properties[PROP_MODEL] =
g_param_spec_object ("model", NULL, NULL,
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkFlattenListModel:n-items:

View File

@@ -886,7 +886,7 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
{
GtkListTile *filler;
tile = gtk_list_item_manager_get_last (self->item_manager);
filler = gtk_list_tile_split (self->item_manager, tile, tile->n_items);
filler = gtk_list_tile_append_filler (self->item_manager, tile);
gtk_list_tile_set_area_position (self->item_manager,
filler,
column_start (self, xspacing, i),

View File

@@ -1954,11 +1954,25 @@ gtk_list_base_split_func (GtkWidget *widget,
}
static GtkListItemBase *
gtk_list_base_create_widget_func (GtkWidget *widget)
gtk_list_base_create_list_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_list_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_prepare_section_func (GtkWidget *widget,
GtkListTile *tile,
guint pos)
{
GTK_LIST_BASE_GET_CLASS (widget)->prepare_section (GTK_LIST_BASE (widget), tile, pos);
}
static GtkListHeaderBase *
gtk_list_base_create_header_widget_func (GtkWidget *widget)
{
return GTK_LIST_BASE_GET_CLASS (widget)->create_header_widget (GTK_LIST_BASE (widget));
}
static void
gtk_list_base_init_real (GtkListBase *self,
GtkListBaseClass *g_class)
@@ -1968,7 +1982,9 @@ gtk_list_base_init_real (GtkListBase *self,
priv->item_manager = gtk_list_item_manager_new (GTK_WIDGET (self),
gtk_list_base_split_func,
gtk_list_base_create_widget_func);
gtk_list_base_create_list_widget_func,
gtk_list_base_prepare_section_func,
gtk_list_base_create_header_widget_func);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
priv->anchor_side_along = GTK_PACK_START;
priv->anchor_side_across = GTK_PACK_START;

View File

@@ -37,6 +37,10 @@ struct _GtkListBaseClass
GtkListTile *tile,
guint n_items);
GtkListItemBase * (* create_list_widget) (GtkListBase *self);
void (* prepare_section) (GtkListBase *self,
GtkListTile *tile,
guint position);
GtkListHeaderBase * (* create_header_widget) (GtkListBase *self);
gboolean (* get_allocation) (GtkListBase *self,
guint pos,

381
gtk/gtklistheader.c Normal file
View File

@@ -0,0 +1,381 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderprivate.h"
/**
* GtkListHeader:
*
* `GtkListHeader` is used by list widgets to represent the headers they
* display.
*
* The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via
* their factory, but provide a different set of properties suitable for
* managing the header instead of individual items.
*
* Since: 4.12
*/
enum
{
PROP_0,
PROP_CHILD,
PROP_END,
PROP_ITEM,
PROP_N_ITEMS,
PROP_START,
N_PROPS
};
G_DEFINE_TYPE (GtkListHeader, gtk_list_header, G_TYPE_OBJECT)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_list_header_dispose (GObject *object)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
g_assert (self->owner == NULL); /* would hold a reference */
g_clear_object (&self->child);
G_OBJECT_CLASS (gtk_list_header_parent_class)->dispose (object);
}
static void
gtk_list_header_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
switch (property_id)
{
case PROP_CHILD:
g_value_set_object (value, self->child);
break;
case PROP_END:
if (self->owner)
g_value_set_uint (value, gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)));
else
g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
break;
case PROP_ITEM:
if (self->owner)
g_value_set_object (value, gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner)));
break;
case PROP_N_ITEMS:
g_value_set_uint (value, gtk_list_header_get_n_items (self));
break;
case PROP_START:
if (self->owner)
g_value_set_uint (value, gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner)));
else
g_value_set_uint (value, GTK_INVALID_LIST_POSITION);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListHeader *self = GTK_LIST_HEADER (object);
switch (property_id)
{
case PROP_CHILD:
gtk_list_header_set_child (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_class_init (GtkListHeaderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_list_header_dispose;
gobject_class->get_property = gtk_list_header_get_property;
gobject_class->set_property = gtk_list_header_set_property;
/**
* GtkListHeader:child: (attributes org.gtk.Property.get=gtk_list_header_get_child org.gtk.Property.set=gtk_list_header_set_child)
*
* Widget used for display.
*
* Since: 4.12
*/
properties[PROP_CHILD] =
g_param_spec_object ("child", NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:end: (attributes org.gtk.Property.get=gtk_list_header_get_end)
*
* The first position no longer part of this section.
*
* Since: 4.12
*/
properties[PROP_END] =
g_param_spec_uint ("end", NULL, NULL,
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:item: (attributes org.gtk.Property.get=gtk_list_header_get_item)
*
* The item at the start of the section.
*
* Since: 4.12
*/
properties[PROP_ITEM] =
g_param_spec_object ("item", NULL, NULL,
G_TYPE_OBJECT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:n-items: (attributes org.gtk.Property.get=gtk_list_header_get_n_items)
*
* Number of items in this section.
*
* Since: 4.12
*/
properties[PROP_N_ITEMS] =
g_param_spec_uint ("n-items", NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListHeader:start: (attributes org.gtk.Property.get=gtk_list_header_get_start)
*
* First position of items in this section.
*
* Since: 4.12
*/
properties[PROP_START] =
g_param_spec_uint ("start", NULL, NULL,
0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gtk_list_header_init (GtkListHeader *self)
{
}
GtkListHeader *
gtk_list_header_new (void)
{
return g_object_new (GTK_TYPE_LIST_HEADER, NULL);
}
void
gtk_list_header_do_notify (GtkListHeader *list_header,
gboolean notify_item,
gboolean notify_start,
gboolean notify_end,
gboolean notify_n_items)
{
GObject *object = G_OBJECT (list_header);
if (notify_item)
g_object_notify_by_pspec (object, properties[PROP_ITEM]);
if (notify_start)
g_object_notify_by_pspec (object, properties[PROP_START]);
if (notify_end)
g_object_notify_by_pspec (object, properties[PROP_END]);
if (notify_n_items)
g_object_notify_by_pspec (object, properties[PROP_N_ITEMS]);
}
/**
* gtk_list_header_get_item: (attributes org.gtk.Method.get_property=item)
* @self: a `GtkListHeader`
*
* Gets the model item at the start of the section.
* This is the item that occupies the list model at position
* [property@Gtk.ListHeader:start].
*
* If @self is unbound, this function returns %NULL.
*
* Returns: (nullable) (transfer none) (type GObject): The item displayed
*
* Since: 4.12
**/
gpointer
gtk_list_header_get_item (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
if (self->owner)
return gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self->owner));
else
return NULL;
}
/**
* gtk_list_header_get_child: (attributes org.gtk.Method.get_property=child)
* @self: a `GtkListHeader`
*
* Gets the child previously set via gtk_list_header_set_child() or
* %NULL if none was set.
*
* Returns: (transfer none) (nullable): The child
*
* Since: 4.12
*/
GtkWidget *
gtk_list_header_get_child (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), NULL);
return self->child;
}
/**
* gtk_list_header_set_child: (attributes org.gtk.Method.set_property=child)
* @self: a `GtkListHeader`
* @child: (nullable): The list item's child or %NULL to unset
*
* Sets the child to be used for this listitem.
*
* This function is typically called by applications when
* setting up a header so that the widget can be reused when
* binding it multiple times.
*
* Since: 4.12
*/
void
gtk_list_header_set_child (GtkListHeader *self,
GtkWidget *child)
{
g_return_if_fail (GTK_IS_LIST_HEADER (self));
g_return_if_fail (child == NULL || gtk_widget_get_parent (child) == NULL);
if (self->child == child)
return;
g_clear_object (&self->child);
if (child)
{
g_object_ref_sink (child);
self->child = child;
}
if (self->owner)
gtk_list_header_widget_set_child (self->owner, child);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
}
/**
* gtk_list_header_get_start: (attributes org.gtk.Method.get_property=start)
* @self: a `GtkListHeader`
*
* Gets the start position in the model of the section that @self is
* currently the header for.
*
* If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
*
* Returns: The start position of the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_start (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
else
return GTK_INVALID_LIST_POSITION;
}
/**
* gtk_list_header_get_end: (attributes org.gtk.Method.get_property=end)
* @self: a `GtkListHeader`
*
* Gets the end position in the model of the section that @self is
* currently the header for.
*
* If @self is unbound, %GTK_INVALID_LIST_POSITION is returned.
*
* Returns: The end position of the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_end (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner));
else
return GTK_INVALID_LIST_POSITION;
}
/**
* gtk_list_header_get_n_items: (attributes org.gtk.Method.get_property=n-items)
* @self: a `GtkListHeader`
*
* Gets the the number of items in the section.
*
* If @self is unbound, 0 is returned.
*
* Returns: The number of items in the section
*
* Since: 4.12
*/
guint
gtk_list_header_get_n_items (GtkListHeader *self)
{
g_return_val_if_fail (GTK_IS_LIST_HEADER (self), GTK_INVALID_LIST_POSITION);
if (self->owner)
return gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self->owner)) -
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self->owner));
else
return 0;
}

50
gtk/gtklistheader.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER (gtk_list_header_get_type ())
GDK_AVAILABLE_IN_4_12
GDK_DECLARE_INTERNAL_TYPE (GtkListHeader, gtk_list_header, GTK, LIST_HEADER, GObject)
GDK_AVAILABLE_IN_4_12
gpointer gtk_list_header_get_item (GtkListHeader *self);
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_start (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_end (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
guint gtk_list_header_get_n_items (GtkListHeader *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_12
void gtk_list_header_set_child (GtkListHeader *self,
GtkWidget *child);
GDK_AVAILABLE_IN_4_12
GtkWidget * gtk_list_header_get_child (GtkListHeader *self);
G_END_DECLS

112
gtk/gtklistheaderbase.c Normal file
View File

@@ -0,0 +1,112 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderbaseprivate.h"
typedef struct _GtkListHeaderBasePrivate GtkListHeaderBasePrivate;
struct _GtkListHeaderBasePrivate
{
GObject *item;
guint start;
guint end;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderBase, gtk_list_header_base, GTK_TYPE_WIDGET)
static void
gtk_list_header_base_default_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_set_object (&priv->item, item);
priv->start = start;
priv->end = end;
}
static void
gtk_list_header_base_dispose (GObject *object)
{
GtkListHeaderBase *self = GTK_LIST_HEADER_BASE (object);
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
g_clear_object (&priv->item);
G_OBJECT_CLASS (gtk_list_header_base_parent_class)->dispose (object);
}
static void
gtk_list_header_base_class_init (GtkListHeaderBaseClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->update = gtk_list_header_base_default_update;
gobject_class->dispose = gtk_list_header_base_dispose;
}
static void
gtk_list_header_base_init (GtkListHeaderBase *self)
{
}
void
gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
if (priv->item == item &&
priv->start == start &&
priv->end == end)
return;
GTK_LIST_HEADER_BASE_GET_CLASS (self)->update (self, item, start, end);
}
guint
gtk_list_header_base_get_start (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->start;
}
guint
gtk_list_header_base_get_end (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->end;
}
gpointer
gtk_list_header_base_get_item (GtkListHeaderBase *self)
{
GtkListHeaderBasePrivate *priv = gtk_list_header_base_get_instance_private (self);
return priv->item;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtkwidget.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER_BASE (gtk_list_header_base_get_type ())
#define GTK_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBase))
#define GTK_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
#define GTK_IS_LIST_HEADER_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_IS_LIST_HEADER_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_BASE))
#define GTK_LIST_HEADER_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_BASE, GtkListHeaderBaseClass))
typedef struct _GtkListHeaderBase GtkListHeaderBase;
typedef struct _GtkListHeaderBaseClass GtkListHeaderBaseClass;
struct _GtkListHeaderBase
{
GtkWidget parent_instance;
};
struct _GtkListHeaderBaseClass
{
GtkWidgetClass parent_class;
void (* update) (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
};
GType gtk_list_header_base_get_type (void) G_GNUC_CONST;
void gtk_list_header_base_update (GtkListHeaderBase *self,
gpointer item,
guint start,
guint end);
guint gtk_list_header_base_get_start (GtkListHeaderBase *self);
guint gtk_list_header_base_get_end (GtkListHeaderBase *self);
gpointer gtk_list_header_base_get_item (GtkListHeaderBase *self);
G_END_DECLS

View File

@@ -0,0 +1,52 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtklistheader.h"
#include "gtklistheaderwidgetprivate.h"
G_BEGIN_DECLS
struct _GtkListHeader
{
GObject parent_instance;
GtkListHeaderWidget *owner; /* has a reference */
GtkWidget *child;
};
struct _GtkListHeaderClass
{
GObjectClass parent_class;
};
GtkListHeader * gtk_list_header_new (void);
void gtk_list_header_do_notify (GtkListHeader *list_header,
gboolean notify_item,
gboolean notify_start,
gboolean notify_end,
gboolean notify_n_items);
G_END_DECLS

295
gtk/gtklistheaderwidget.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtklistheaderwidgetprivate.h"
#include "gtkbinlayout.h"
#include "gtklistheaderprivate.h"
#include "gtklistitemfactoryprivate.h"
#include "gtklistbaseprivate.h"
#include "gtkwidget.h"
typedef struct _GtkListHeaderWidgetPrivate GtkListHeaderWidgetPrivate;
struct _GtkListHeaderWidgetPrivate
{
GtkListItemFactory *factory;
GtkListHeader *header;
};
enum {
PROP_0,
PROP_FACTORY,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkListHeaderWidget, gtk_list_header_widget, GTK_TYPE_LIST_HEADER_BASE)
static GParamSpec *properties[N_PROPS] = { NULL, };
static void
gtk_list_header_widget_setup_func (gpointer object,
gpointer data)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header = object;
priv->header = header;
header->owner = self;
gtk_list_header_widget_set_child (self, header->child);
gtk_list_header_do_notify (header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
}
static void
gtk_list_header_widget_setup_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header;
header = gtk_list_header_new ();
gtk_list_item_factory_setup (priv->factory,
G_OBJECT (header),
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_widget_setup_func,
self);
g_assert (priv->header == header);
}
static void
gtk_list_header_widget_teardown_func (gpointer object,
gpointer data)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (data);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeader *header = object;
header->owner = NULL;
priv->header = NULL;
gtk_list_header_widget_set_child (self, NULL);
gtk_list_header_do_notify (header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)) != GTK_INVALID_LIST_POSITION,
gtk_list_header_base_get_start (GTK_LIST_HEADER_BASE (self)) != gtk_list_header_base_get_end (GTK_LIST_HEADER_BASE (self)));
}
static void
gtk_list_header_widget_teardown_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
gpointer header = priv->header;
gtk_list_item_factory_teardown (priv->factory,
header,
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
gtk_list_header_widget_teardown_func,
self);
g_assert (priv->header == NULL);
g_object_unref (header);
}
typedef struct {
GtkListHeaderWidget *widget;
gpointer item;
guint start;
guint end;
} GtkListHeaderWidgetUpdate;
static void
gtk_list_header_widget_update_func (gpointer object,
gpointer data)
{
GtkListHeaderWidgetUpdate *update = data;
GtkListHeaderWidget *self = update->widget;
GtkListHeaderBase *base = GTK_LIST_HEADER_BASE (self);
/* Track notify manually instead of freeze/thaw_notify for performance reasons. */
gboolean notify_item, notify_start, notify_end, notify_n_items;
/* FIXME: It's kinda evil to notify external objects from here... */
notify_item = gtk_list_header_base_get_item (base) != update->item;
notify_start = gtk_list_header_base_get_start (base) != update->start;
notify_end = gtk_list_header_base_get_end (base) != update->end;
notify_n_items = gtk_list_header_base_get_end (base) - gtk_list_header_base_get_start (base) != update->end - update->start;
GTK_LIST_HEADER_BASE_CLASS (gtk_list_header_widget_parent_class)->update (base,
update->item,
update->start,
update->end);
if (object)
gtk_list_header_do_notify (object, notify_item, notify_start, notify_end, notify_n_items);
}
static void
gtk_list_header_widget_update (GtkListHeaderBase *base,
gpointer item,
guint start,
guint end)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (base);
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
GtkListHeaderWidgetUpdate update = { self, item, start, end };
if (priv->header)
{
gtk_list_item_factory_update (priv->factory,
G_OBJECT (priv->header),
gtk_list_header_base_get_item (GTK_LIST_HEADER_BASE (self)) != NULL,
item != NULL,
gtk_list_header_widget_update_func,
&update);
}
else
{
gtk_list_header_widget_update_func (NULL, &update);
}
}
static void
gtk_list_header_widget_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
switch (property_id)
{
case PROP_FACTORY:
gtk_list_header_widget_set_factory (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_list_header_widget_clear_factory (GtkListHeaderWidget *self)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
if (priv->factory == NULL)
return;
if (priv->header)
gtk_list_header_widget_teardown_factory (self);
g_clear_object (&priv->factory);
}
static void
gtk_list_header_widget_dispose (GObject *object)
{
GtkListHeaderWidget *self = GTK_LIST_HEADER_WIDGET (object);
gtk_list_header_widget_clear_factory (self);
G_OBJECT_CLASS (gtk_list_header_widget_parent_class)->dispose (object);
}
static void
gtk_list_header_widget_class_init (GtkListHeaderWidgetClass *klass)
{
GtkListHeaderBaseClass *base_class = GTK_LIST_HEADER_BASE_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
base_class->update = gtk_list_header_widget_update;
gobject_class->set_property = gtk_list_header_widget_set_property;
gobject_class->dispose = gtk_list_header_widget_dispose;
properties[PROP_FACTORY] =
g_param_spec_object ("factory", NULL, NULL,
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
gtk_widget_class_set_css_name (widget_class, I_("header"));
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_ROW_HEADER);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
}
static void
gtk_list_header_widget_init (GtkListHeaderWidget *self)
{
}
void
gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
GtkListItemFactory *factory)
{
GtkListHeaderWidgetPrivate *priv = gtk_list_header_widget_get_instance_private (self);
if (priv->factory == factory)
return;
gtk_list_header_widget_clear_factory (self);
if (factory)
{
priv->factory = g_object_ref (factory);
gtk_list_header_widget_setup_factory (self);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
GtkWidget *
gtk_list_header_widget_new (GtkListItemFactory *factory)
{
return g_object_new (GTK_TYPE_LIST_HEADER_WIDGET,
"factory", factory,
NULL);
}
void
gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
GtkWidget *child)
{
GtkWidget *cur_child = gtk_widget_get_first_child (GTK_WIDGET (self));
if (cur_child == child)
return;
g_clear_pointer (&cur_child, gtk_widget_unparent);
if (child)
gtk_widget_set_parent (child, GTK_WIDGET (self));
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include "gtklistheaderbaseprivate.h"
#include "gtklistitemfactory.h"
G_BEGIN_DECLS
#define GTK_TYPE_LIST_HEADER_WIDGET (gtk_list_header_widget_get_type ())
#define GTK_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidget))
#define GTK_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
#define GTK_IS_LIST_HEADER_WIDGET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_LIST_HEADER_WIDGET))
#define GTK_IS_LIST_HEADER_WIDGET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_LIST_HEADER_WIDGET))
#define GTK_LIST_HEADER_WIDGET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_LIST_HEADER_WIDGET, GtkListHeaderWidgetClass))
typedef struct _GtkListHeaderWidget GtkListHeaderWidget;
typedef struct _GtkListHeaderWidgetClass GtkListHeaderWidgetClass;
struct _GtkListHeaderWidget
{
GtkListHeaderBase parent_instance;
};
struct _GtkListHeaderWidgetClass
{
GtkListHeaderBaseClass parent_class;
};
GType gtk_list_header_widget_get_type (void) G_GNUC_CONST;
GtkWidget * gtk_list_header_widget_new (GtkListItemFactory *factory);
void gtk_list_header_widget_set_factory (GtkListHeaderWidget *self,
GtkListItemFactory *factory);
GtkListItemFactory * gtk_list_header_widget_get_factory (GtkListHeaderWidget *self);
void gtk_list_header_widget_set_child (GtkListHeaderWidget *self,
GtkWidget *child);
G_END_DECLS

View File

@@ -81,6 +81,11 @@ gtk_list_item_base_update (GtkListItemBase *self,
GtkListItemBasePrivate *priv = gtk_list_item_base_get_instance_private (self);
gboolean was_selected;
if (priv->position == position &&
priv->item == item &&
priv->selected == selected)
return;
was_selected = priv->selected;
GTK_LIST_ITEM_BASE_GET_CLASS (self)->update (self, position, item, selected);

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@
#include "gtk/gtkenums.h"
#include "gtk/gtklistitembaseprivate.h"
#include "gtk/gtklistheaderbaseprivate.h"
#include "gtk/gtklistitemfactory.h"
#include "gtk/gtkrbtreeprivate.h"
#include "gtk/gtkselectionmodel.h"
@@ -43,8 +44,20 @@ typedef struct _GtkListTile GtkListTile;
typedef struct _GtkListTileAugment GtkListTileAugment;
typedef struct _GtkListItemTracker GtkListItemTracker;
typedef enum
{
GTK_LIST_TILE_ITEM,
GTK_LIST_TILE_HEADER,
GTK_LIST_TILE_FOOTER,
GTK_LIST_TILE_UNMATCHED_HEADER,
GTK_LIST_TILE_UNMATCHED_FOOTER,
GTK_LIST_TILE_FILLER,
GTK_LIST_TILE_REMOVED,
} GtkListTileType;
struct _GtkListTile
{
GtkListTileType type;
GtkWidget *widget;
guint n_items;
/* area occupied by tile. May be empty if tile has no allcoation */
@@ -54,6 +67,10 @@ struct _GtkListTile
struct _GtkListTileAugment
{
guint n_items;
guint has_header :1;
guint has_footer :1;
/* union of all areas of tile and children */
cairo_rectangle_int_t area;
};
@@ -63,7 +80,9 @@ GType gtk_list_item_manager_get_type (void) G_GNUC_CO
GtkListItemManager * gtk_list_item_manager_new (GtkWidget *widget,
GtkListTile * (* split_func) (GtkWidget *, GtkListTile *, guint),
GtkListItemBase * (* create_widget) (GtkWidget *));
GtkListItemBase * (* create_widget) (GtkWidget *),
void (* prepare_section) (GtkWidget *, GtkListTile *, guint),
GtkListHeaderBase * (* create_header_widget) (GtkWidget *));
void gtk_list_item_manager_get_tile_bounds (GtkListItemManager *self,
GdkRectangle *out_bounds);
@@ -97,12 +116,17 @@ void gtk_list_tile_set_area_size (GtkListItemMana
GtkListTile * gtk_list_tile_split (GtkListItemManager *self,
GtkListTile *tile,
guint n_items);
GtkListTile * gtk_list_tile_append_filler (GtkListItemManager *self,
GtkListTile *previous);
GtkListTile * gtk_list_tile_gc (GtkListItemManager *self,
GtkListTile *tile);
void gtk_list_item_manager_set_model (GtkListItemManager *self,
GtkSelectionModel *model);
GtkSelectionModel * gtk_list_item_manager_get_model (GtkListItemManager *self);
void gtk_list_item_manager_set_has_sections (GtkListItemManager *self,
gboolean has_sections);
gboolean gtk_list_item_manager_get_has_sections (GtkListItemManager *self);
GtkListItemTracker * gtk_list_item_tracker_new (GtkListItemManager *self);
void gtk_list_item_tracker_free (GtkListItemManager *self,

View File

@@ -23,6 +23,7 @@
#include "gtkbitset.h"
#include "gtklistbaseprivate.h"
#include "gtklistheaderwidgetprivate.h"
#include "gtklistitemmanagerprivate.h"
#include "gtklistitemwidgetprivate.h"
#include "gtkmultiselection.h"
@@ -145,6 +146,7 @@ enum
PROP_0,
PROP_ENABLE_RUBBERBAND,
PROP_FACTORY,
PROP_HEADER_FACTORY,
PROP_MODEL,
PROP_SHOW_SEPARATORS,
PROP_SINGLE_CLICK_ACTIVATE,
@@ -163,29 +165,6 @@ G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE)
static GParamSpec *properties[N_PROPS] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
static void G_GNUC_UNUSED
dump (GtkListView *self)
{
GtkListTile *tile;
guint n_widgets, n_list_rows;
n_widgets = 0;
n_list_rows = 0;
//g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
for (tile = gtk_list_item_manager_get_first (self->item_manager);
tile;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
n_widgets++;
n_list_rows++;
g_print (" %4u%s %d,%d,%d,%d\n", tile->n_items, tile->widget ? " (widget)" : "",
tile->area.x, tile->area.y, tile->area.width, tile->area.height);
}
g_print (" => %u widgets in %u list rows\n", n_widgets, n_list_rows);
}
static GtkListTile *
gtk_list_view_split (GtkListBase *base,
GtkListTile *tile,
@@ -215,6 +194,13 @@ gtk_list_view_split (GtkListBase *base,
return new_tile;
}
static void
gtk_list_view_prepare_section (GtkListBase *base,
GtkListTile *tile,
guint position)
{
}
/* We define the listview as **inert** when the factory isn't used. */
static gboolean
gtk_list_view_is_inert (GtkListView *self)
@@ -228,7 +214,8 @@ gtk_list_view_is_inert (GtkListView *self)
static void
gtk_list_view_update_factories_with (GtkListView *self,
GtkListItemFactory *factory)
GtkListItemFactory *factory,
GtkListItemFactory *header_factory)
{
GtkListTile *tile;
@@ -236,8 +223,27 @@ gtk_list_view_update_factories_with (GtkListView *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_FILLER:
case GTK_LIST_TILE_REMOVED:
g_assert (tile->widget == NULL);
break;
default:
g_assert_not_reached();
break;
}
}
}
@@ -245,13 +251,14 @@ static void
gtk_list_view_update_factories (GtkListView *self)
{
gtk_list_view_update_factories_with (self,
gtk_list_view_is_inert (self) ? NULL : self->factory);
gtk_list_view_is_inert (self) ? NULL : self->factory,
gtk_list_view_is_inert (self) ? NULL : self->header_factory);
}
static void
gtk_list_view_clear_factories (GtkListView *self)
{
gtk_list_view_update_factories_with (self, NULL);
gtk_list_view_update_factories_with (self, NULL, NULL);
}
static GtkListItemBase *
@@ -275,6 +282,20 @@ gtk_list_view_create_list_widget (GtkListBase *base)
return GTK_LIST_ITEM_BASE (result);
}
static GtkListHeaderBase *
gtk_list_view_create_header_widget (GtkListBase *base)
{
GtkListView *self = GTK_LIST_VIEW (base);
GtkListItemFactory *factory;
if (gtk_list_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_list_view_get_allocation (GtkListBase *base,
guint pos,
@@ -527,8 +548,11 @@ gtk_list_view_measure_list (GtkWidget *widget,
gtk_widget_measure (tile->widget,
orientation, for_size,
&child_min, &child_nat, NULL, NULL);
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
if (tile->type == GTK_LIST_TILE_ITEM)
{
g_array_append_val (min_heights, child_min);
g_array_append_val (nat_heights, child_nat);
}
min += child_min;
nat += child_nat;
}
@@ -622,7 +646,8 @@ gtk_list_view_size_allocate (GtkWidget *widget,
else
row_height = nat;
gtk_list_tile_set_area_size (self->item_manager, tile, list_width, row_height);
g_array_append_val (heights, row_height);
if (tile->type == GTK_LIST_TILE_ITEM)
g_array_append_val (heights, row_height);
}
/* step 3: determine height of unknown items and set the positions */
@@ -723,6 +748,10 @@ gtk_list_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_MODEL:
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
@@ -763,6 +792,10 @@ gtk_list_view_set_property (GObject *object,
gtk_list_view_set_factory (self, g_value_get_object (value));
break;
case PROP_HEADER_FACTORY:
gtk_list_view_set_header_factory (self, g_value_get_object (value));
break;
case PROP_MODEL:
gtk_list_view_set_model (self, g_value_get_object (value));
break;
@@ -812,6 +845,8 @@ gtk_list_view_class_init (GtkListViewClass *klass)
list_base_class->split = gtk_list_view_split;
list_base_class->create_list_widget = gtk_list_view_create_list_widget;
list_base_class->prepare_section = gtk_list_view_prepare_section;
list_base_class->create_header_widget = gtk_list_view_create_header_widget;
list_base_class->get_allocation = gtk_list_view_get_allocation;
list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
@@ -849,6 +884,18 @@ gtk_list_view_class_init (GtkListViewClass *klass)
GTK_TYPE_LIST_ITEM_FACTORY,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GtkListView:header-factory: (attributes org.gtk.Property.get=gtk_list_view_get_header_factory org.gtk.Property.set=gtk_list_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);
/**
* GtkListView:model: (attributes org.gtk.Property.get=gtk_list_view_get_model org.gtk.Property.set=gtk_list_view_set_model)
*
@@ -1070,6 +1117,68 @@ gtk_list_view_set_factory (GtkListView *self,
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}
/**
* gtk_list_view_get_header_factory: (attributes org.gtk.Method.get_property=header-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.12
*/
GtkListItemFactory *
gtk_list_view_get_header_factory (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return self->header_factory;
}
/**
* gtk_list_view_set_header_factory: (attributes org.gtk.Method.set_property=header-factory)
* @self: a `GtkListView`
* @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_list_view_set_header_factory (GtkListView *self,
GtkListItemFactory *factory)
{
gboolean had_sections;
g_return_if_fail (GTK_IS_LIST_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 (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_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
* @self: a `GtkListView`
@@ -1139,7 +1248,7 @@ gtk_list_view_set_single_click_activate (GtkListView *self,
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
if (tile->widget)
if (tile->widget && tile->type == GTK_LIST_TILE_ITEM)
gtk_list_factory_widget_set_single_click_activate (GTK_LIST_FACTORY_WIDGET (tile->widget), single_click_activate);
}

View File

@@ -57,6 +57,13 @@ GDK_AVAILABLE_IN_ALL
GtkListItemFactory *
gtk_list_view_get_factory (GtkListView *self);
GDK_AVAILABLE_IN_4_12
void gtk_list_view_set_header_factory (GtkListView *self,
GtkListItemFactory *factory);
GDK_AVAILABLE_IN_4_12
GtkListItemFactory *
gtk_list_view_get_header_factory (GtkListView *self);
GDK_AVAILABLE_IN_ALL
void gtk_list_view_set_show_separators (GtkListView *self,
gboolean show_separators);

View File

@@ -30,6 +30,7 @@ struct _GtkListView
GtkListItemManager *item_manager;
GtkListItemFactory *factory;
GtkListItemFactory *header_factory;
gboolean show_separators;
gboolean single_click_activate;
};

View File

@@ -22,6 +22,7 @@
#include "gtkmultiselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -94,6 +95,23 @@ gtk_multi_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_multi_selection_get_item;
}
static void
gtk_multi_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkMultiSelection *self = GTK_MULTI_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_multi_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_multi_selection_get_section;
}
static gboolean
gtk_multi_selection_is_selected (GtkSelectionModel *model,
guint position)
@@ -205,6 +223,8 @@ gtk_multi_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkMultiSelection, gtk_multi_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_multi_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_multi_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_multi_selection_selection_model_init))

View File

@@ -22,6 +22,7 @@
#include "gtknoselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -92,6 +93,23 @@ gtk_no_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_no_selection_get_item;
}
static void
gtk_no_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkNoSelection *self = GTK_NO_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_no_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_no_selection_get_section;
}
static gboolean
gtk_no_selection_is_selected (GtkSelectionModel *model,
guint position)
@@ -117,6 +135,8 @@ gtk_no_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkNoSelection, gtk_no_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_no_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_no_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_no_selection_selection_model_init))

225
gtk/gtksectionmodel.c Normal file
View File

@@ -0,0 +1,225 @@
/*
* Copyright © 2022 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksectionmodelprivate.h"
#include "gtkmarshalers.h"
/**
* GtkSectionModel:
*
* `GtkSectionModel` is an interface that adds support for section to list models.
*
* This support is then used by widgets using list models to be able to group their
* items into sections.
*
* Many GTK list models support sections inherently, or they pass through the sections
* of a model they are wrapping.
*
* A `GtkSectionModel` groups successive items into so-called sections. List widgets
* like `GtkListView` then allow displaying section headers for these sections.
*
* When the section groupings of a model changes, the model will emit the
* [signal@Gtk.SectionModel::sections-changed] signal by calling the
* [method@Gtk.SectionModel.sections_changed] function. All sections in the given range
* now need to be queried again.
* The [signal@Gio.ListModel::items-changed] signal has the same effect, all sections in
* that range are invalidated, too.
*
* Since: 4.12
*/
G_DEFINE_INTERFACE (GtkSectionModel, gtk_section_model, G_TYPE_LIST_MODEL)
enum {
SECTIONS_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
gtk_section_model_default_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end)
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (position >= n_items)
{
*out_start = n_items;
*out_end = G_MAXUINT;
}
*out_start = 0;
*out_end = n_items;
}
static void
gtk_section_model_default_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_section_model_default_get_section;
/**
* GtkSectionModel::sections-changed
* @model: a `GtkSectionModel`
* @position: The first item that may have changed
* @n_items: number of items with changes
*
* Emitted when the start-of-section state of some of the items in @model changes.
*
* Note that this signal does not specify the new section state of the
* items, they need to be queried manually. It is also not necessary for
* a model to change the section state of any of the items in the section
* model, though it would be rather useless to emit such a signal.
*
* The [signal@Gio.ListModel::items-changed] implies the effect of the
* [signal@Gtk.SectionModel::sections-changed] signal for all the items
* it covers.
*
* Since: 4.12
*/
signals[SECTIONS_CHANGED] =
g_signal_new ("sections-changed",
GTK_TYPE_SECTION_MODEL,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
_gtk_marshal_VOID__UINT_UINT,
G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
g_signal_set_va_marshaller (signals[SECTIONS_CHANGED],
GTK_TYPE_SECTION_MODEL,
_gtk_marshal_VOID__UINT_UINTv);
}
/**
* gtk_section_model_get_section:
* @self: a `GtkSectionModel`
* @position: the position of the item to query
* @out_start: (out caller-allocates): the position of the first
* item in the section
* @out_end: (out caller-allocates): the position of the first
* item not part of the section anymore.
*
* Query the section that covers the given position. The number of
* items in the section can be computed by `out_end - out_start`.
*
* If the position is larger than the number of items, a single
* range from n_items to G_MAXUINT will be returned.
*
* Since: 4.12
*/
void
gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end)
{
GtkSectionModelInterface *iface;
g_return_if_fail (GTK_IS_SECTION_MODEL (self));
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
iface = GTK_SECTION_MODEL_GET_IFACE (self);
iface->get_section (self, position, out_start, out_end);
g_warn_if_fail (*out_start < *out_end);
}
/* A version of gtk_section_model_get_section() that handles NULL
* (treats it as the empty list) and any GListModel (treats it as
* a single section).
**/
void
gtk_list_model_get_section (GListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
g_return_if_fail (out_start != NULL);
g_return_if_fail (out_end != NULL);
if (self == NULL)
{
*out_start = 0;
*out_end = G_MAXUINT;
return;
}
g_return_if_fail (G_IS_LIST_MODEL (self));
if (!GTK_IS_SECTION_MODEL (self))
{
guint n_items = g_list_model_get_n_items (self);
if (position < n_items)
{
*out_start = 0;
*out_end = G_MAXUINT;
}
else
{
*out_start = n_items;
*out_end = G_MAXUINT;
}
return;
}
gtk_section_model_get_section (GTK_SECTION_MODEL (self), position, out_start, out_end);
}
/**
* gtk_section_model_section_changed:
* @self: a `GtkSectionModel`
* @position: the first changed item
* @n_items: the number of changed items
*
* This function emits the [signal@Gtk.SectionModel::section-changed]
* signal to notify about changes to sections. It must cover all
* positions that used to be a section start or that are now a section
* start. It does not have to cover all positions for which the section
* has changed.
*
* The [signal@Gio.ListModel::items-changed] implies the effect of the
* [signal@Gtk.SectionModel::section-changed] signal for all the items
* it covers.
*
* It is recommended that when changes to the items cause section changes
* in a larger range, that the larger range is included in the emission
* of the [signal@Gio.ListModel::items-changed] instead of emitting
* two signals.
*
* Since: 4.12
*/
void
gtk_section_model_sections_changed (GtkSectionModel *self,
guint position,
guint n_items)
{
g_return_if_fail (GTK_IS_SECTION_MODEL (self));
g_return_if_fail (n_items > 0);
g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (self)));
g_signal_emit (self, signals[SECTIONS_CHANGED], 0, position, n_items);
}

72
gtk/gtksectionmodel.h Normal file
View File

@@ -0,0 +1,72 @@
/*
* Copyright © 2022 Benjamin Otte
*
* 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/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_SECTION_MODEL (gtk_section_model_get_type ())
GDK_AVAILABLE_IN_4_12
G_DECLARE_INTERFACE (GtkSectionModel, gtk_section_model, GTK, SECTION_MODEL, GListModel)
/**
* GtkSectionModelInterface:
* @get_section: Return the section that covers the given position. If
* the position is outside the number of items, returns a single range from
* n_items to G_MAXUINT
*
* The list of virtual functions for the `GtkSectionModel` interface.
* No function must be implemented, but unless `GtkSectionModel::get_section()`
* is implemented, the whole model will just be a single section.
*
* Since: 4.12
*/
struct _GtkSectionModelInterface
{
/*< private >*/
GTypeInterface g_iface;
/*< public >*/
void (* get_section) (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
};
GDK_AVAILABLE_IN_4_12
void gtk_section_model_get_section (GtkSectionModel *self,
guint position,
guint *out_start,
guint *out_end);
/* for implementations only */
GDK_AVAILABLE_IN_4_12
void gtk_section_model_sections_changed (GtkSectionModel *self,
guint position,
guint n_items);
G_END_DECLS

View File

@@ -0,0 +1,14 @@
#pragma once
#include "gtksectionmodel.h"
G_BEGIN_DECLS
void gtk_list_model_get_section (GListModel *self,
guint position,
guint *out_start,
guint *out_end);
G_END_DECLS

View File

@@ -22,6 +22,7 @@
#include "gtksingleselection.h"
#include "gtkbitset.h"
#include "gtksectionmodelprivate.h"
#include "gtkselectionmodel.h"
/**
@@ -103,6 +104,23 @@ gtk_single_selection_list_model_init (GListModelInterface *iface)
iface->get_item = gtk_single_selection_get_item;
}
static void
gtk_single_selection_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
gtk_list_model_get_section (self->model, position, out_start, out_end);
}
static void
gtk_single_selection_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_single_selection_get_section;
}
static gboolean
gtk_single_selection_is_selected (GtkSelectionModel *model,
guint position)
@@ -167,6 +185,8 @@ gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
gtk_single_selection_list_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL,
gtk_single_selection_section_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
gtk_single_selection_selection_model_init))

View File

@@ -22,7 +22,9 @@
#include "gtksortlistmodel.h"
#include "gtkbitset.h"
#include "gtkmultisorter.h"
#include "gtkprivate.h"
#include "gtksectionmodel.h"
#include "gtksorterprivate.h"
#include "timsort/gtktimsortprivate.h"
@@ -73,6 +75,13 @@
* If you run into performance issues with `GtkSortListModel`,
* it is strongly recommended that you write your own sorting list
* model.
*
* `GtkSortListModel` allows sorting the items into sections. It
* implements `GtkSectionModel` and when [property@Gtk.SortListModel:section-sorter]
* is set, it will sort all items with that sorter and items comparing
* equal with it will be put into the same section.
* The [property@Gtk.SortListModel:sorter] will then be used to sort items
* inside their sections.
*/
enum {
@@ -82,6 +91,7 @@ enum {
PROP_MODEL,
PROP_N_ITEMS,
PROP_PENDING,
PROP_SECTION_SORTER,
PROP_SORTER,
NUM_PROPERTIES
};
@@ -92,6 +102,8 @@ struct _GtkSortListModel
GListModel *model;
GtkSorter *sorter;
GtkSorter *section_sorter;
GtkSorter *real_sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
@@ -99,6 +111,7 @@ struct _GtkSortListModel
guint n_items;
GtkSortKeys *sort_keys;
GtkSortKeys *section_sort_keys; /* we assume they are compatible with the sort keys because they're the first element */
gsize key_size;
gpointer keys;
GtkBitset *missing_keys;
@@ -174,8 +187,159 @@ gtk_sort_list_model_model_init (GListModelInterface *iface)
iface->get_item = gtk_sort_list_model_get_item;
}
static void
gtk_sort_list_model_ensure_key (GtkSortListModel *self,
guint pos)
{
gpointer item;
if (!gtk_bitset_contains (self->missing_keys, pos))
return;
item = g_list_model_get_item (self->model, pos);
gtk_sort_keys_init_key (self->sort_keys, item, key_from_pos (self, pos));
g_object_unref (item);
gtk_bitset_remove (self->missing_keys, pos);
}
static void
gtk_sort_list_model_get_section_unsorted (GtkSortListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
gpointer *pos, *start, *end;
pos = &self->positions[position];
gtk_sort_list_model_ensure_key (self, pos_from_key (self, *pos));
for (start = pos;
start > self->positions;
start--)
{
gtk_sort_list_model_ensure_key (self, pos_from_key (self, start[-1]));
if (gtk_sort_keys_compare (self->section_sort_keys, start[-1], *pos) != GTK_ORDERING_EQUAL)
break;
}
for (end = pos + 1;
end < &self->positions[self->n_items];
end++)
{
gtk_sort_list_model_ensure_key (self, pos_from_key (self, *end));
if (gtk_sort_keys_compare (self->section_sort_keys, *end, *pos) != GTK_ORDERING_EQUAL)
break;
}
*out_start = start - self->positions;
*out_end = end - self->positions;
}
static void
gtk_sort_list_model_get_section_sorted (GtkSortListModel *self,
guint position,
guint *out_start,
guint *out_end)
{
gpointer *pos;
guint step, min, max, mid;
pos = &self->positions[position];
max = position;
step = 1;
while (max > 0)
{
min = max - MIN (max, step);
step *= 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[min], *pos) == GTK_ORDERING_EQUAL)
{
max = min;
continue;
}
/* now min is different, max is equal, bsearch where that changes */
while (max - min > 1)
{
mid = (max + min) / 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
max = mid;
else
min = mid;
}
break;
}
*out_start = max;
min = position;
step = 1;
while (min < self->n_items - 1)
{
max = min + MIN (self->n_items - 1 - min, step);
step *= 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[max], *pos) == GTK_ORDERING_EQUAL)
{
min = max;
continue;
}
/* now min is equal, max is different, bsearch where that changes */
while (max - min > 1)
{
mid = (max + min) / 2;
if (gtk_sort_keys_compare (self->section_sort_keys, self->positions[mid], *pos) == GTK_ORDERING_EQUAL)
min = mid;
else
max = mid;
}
break;
}
*out_end = min + 1;
}
static void
gtk_sort_list_model_get_section (GtkSectionModel *model,
guint position,
guint *out_start,
guint *out_end)
{
GtkSortListModel *self = GTK_SORT_LIST_MODEL (model);
if (position >= self->n_items)
{
*out_start = self->n_items;
*out_end = G_MAXUINT;
return;
}
if (self->section_sort_keys == NULL)
{
*out_start = 0;
*out_end = self->n_items;
return;
}
/* When the list is not sorted:
* - keys may not exist yet
* - equal items may not be adjacent
* So add a slow path that can deal with that, but is O(N).
* The fast path is O(log N) and will be used for I guess
* 99% of cases.
*/
if (self->sort_cb)
gtk_sort_list_model_get_section_unsorted (self, position, out_start, out_end);
else
gtk_sort_list_model_get_section_sorted (self, position, out_start, out_end);
}
static void
gtk_sort_list_model_section_model_init (GtkSectionModelInterface *iface)
{
iface->get_section = gtk_sort_list_model_get_section;
}
G_DEFINE_TYPE_WITH_CODE (GtkSortListModel, gtk_sort_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init))
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sort_list_model_model_init)
G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_sort_list_model_section_model_init))
static gboolean
gtk_sort_list_model_is_sorting (GtkSortListModel *self)
@@ -379,6 +543,7 @@ gtk_sort_list_model_clear_keys (GtkSortListModel *self)
g_clear_pointer (&self->missing_keys, gtk_bitset_unref);
g_clear_pointer (&self->keys, g_free);
g_clear_pointer (&self->sort_keys, gtk_sort_keys_unref);
g_clear_pointer (&self->section_sort_keys, gtk_sort_keys_unref);
self->key_size = 0;
}
@@ -426,9 +591,9 @@ gtk_sort_list_model_clear_items (GtkSortListModel *self,
static gboolean
gtk_sort_list_model_should_sort (GtkSortListModel *self)
{
return self->sorter != NULL &&
return self->real_sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
gtk_sorter_get_order (self->real_sorter) != GTK_SORTER_ORDER_NONE;
}
static void
@@ -436,9 +601,12 @@ gtk_sort_list_model_create_keys (GtkSortListModel *self)
{
g_assert (self->keys == NULL);
g_assert (self->sort_keys == NULL);
g_assert (self->section_sort_keys == NULL);
g_assert (self->key_size == 0);
self->sort_keys = gtk_sorter_get_keys (self->sorter);
self->sort_keys = gtk_sorter_get_keys (self->real_sorter);
if (self->section_sorter)
self->section_sort_keys = gtk_sorter_get_keys (self->section_sorter);
self->key_size = gtk_sort_keys_get_key_size (self->sort_keys);
self->keys = g_malloc_n (self->n_items, self->key_size);
self->missing_keys = gtk_bitset_new_range (0, self->n_items);
@@ -646,6 +814,10 @@ gtk_sort_list_model_set_property (GObject *object,
gtk_sort_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SECTION_SORTER:
gtk_sort_list_model_set_section_sorter (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sort_list_model_set_sorter (self, g_value_get_object (value));
break;
@@ -686,6 +858,10 @@ gtk_sort_list_model_get_property (GObject *object,
g_value_set_uint (value, gtk_sort_list_model_get_pending (self));
break;
case PROP_SECTION_SORTER:
g_value_set_object (value, self->section_sorter);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
@@ -763,13 +939,42 @@ gtk_sort_list_model_clear_model (GtkSortListModel *self)
}
static void
gtk_sort_list_model_clear_sorter (GtkSortListModel *self)
gtk_sort_list_model_clear_real_sorter (GtkSortListModel *self)
{
if (self->sorter == NULL)
if (self->real_sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sort_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
g_signal_handlers_disconnect_by_func (self->real_sorter, gtk_sort_list_model_sorter_changed_cb, self);
g_clear_object (&self->real_sorter);
}
static void
gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self)
{
if (self->sorter)
{
if (self->section_sorter)
{
GtkMultiSorter *multi;
multi = gtk_multi_sorter_new ();
self->real_sorter = GTK_SORTER (multi);
gtk_multi_sorter_append (multi, g_object_ref (self->section_sorter));
gtk_multi_sorter_append (multi, g_object_ref (self->sorter));
}
else
self->real_sorter = g_object_ref (self->sorter);
}
else
{
if (self->section_sorter)
self->real_sorter = g_object_ref (self->section_sorter);
}
if (self->real_sorter)
g_signal_connect (self->real_sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
gtk_sort_list_model_sorter_changed_cb (self->real_sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
static void
@@ -778,7 +983,9 @@ gtk_sort_list_model_dispose (GObject *object)
GtkSortListModel *self = GTK_SORT_LIST_MODEL (object);
gtk_sort_list_model_clear_model (self);
gtk_sort_list_model_clear_sorter (self);
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->section_sorter);
g_clear_object (&self->sorter);
G_OBJECT_CLASS (gtk_sort_list_model_parent_class)->dispose (object);
};
@@ -846,6 +1053,18 @@ gtk_sort_list_model_class_init (GtkSortListModelClass *class)
0, G_MAXUINT, 0,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSortListModel:section-sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_section_sorter org.gtk.Property.set=gtk_sort_list_model_set_section_sorter)
*
* The section sorter for this model, if one is set.
*
* Since: 4.12
*/
properties[PROP_SECTION_SORTER] =
g_param_spec_object ("section-sorter", NULL, NULL,
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSortListModel:sorter: (attributes org.gtk.Property.get=gtk_sort_list_model_get_sorter org.gtk.Property.set=gtk_sort_list_model_set_sorter)
*
@@ -972,15 +1191,16 @@ gtk_sort_list_model_set_sorter (GtkSortListModel *self,
g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sort_list_model_clear_sorter (self);
if (self->sorter == sorter)
return;
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->sorter);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sort_list_model_sorter_changed_cb), self);
}
self->sorter = g_object_ref (sorter);
gtk_sort_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
gtk_sort_list_model_ensure_real_sorter (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
@@ -1001,6 +1221,55 @@ gtk_sort_list_model_get_sorter (GtkSortListModel *self)
return self->sorter;
}
/**
* gtk_sort_list_model_set_section_sorter: (attributes org.gtk.Method.set_property=section-sorter)
* @self: a `GtkSortListModel`
* @sorter: (nullable): the `GtkSorter` to sort @model with
*
* Sets a new section sorter on @self.
*
* Since: 4.12
*/
void
gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SORT_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
if (self->section_sorter == sorter)
return;
gtk_sort_list_model_clear_real_sorter (self);
g_clear_object (&self->section_sorter);
if (sorter)
self->section_sorter = g_object_ref (sorter);
gtk_sort_list_model_ensure_real_sorter (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_SORTER]);
}
/**
* gtk_sort_list_model_get_section_sorter: (attributes org.gtk.Method.get_property=section-sorter)
* @self: a `GtkSortListModel`
*
* Gets the section sorter that is used to sort items of @self into
* sections.
*
* Returns: (nullable) (transfer none): the sorter of #self
*
* Since: 4.12
*/
GtkSorter *
gtk_sort_list_model_get_section_sorter (GtkSortListModel *self)
{
g_return_val_if_fail (GTK_IS_SORT_LIST_MODEL (self), NULL);
return self->section_sorter;
}
/**
* gtk_sort_list_model_set_incremental: (attributes org.gtk.Method.set_property=incremental)
* @self: a `GtkSortListModel`

View File

@@ -45,6 +45,12 @@ void gtk_sort_list_model_set_sorter (GtkSortListMode
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sort_list_model_get_sorter (GtkSortListModel *self);
GDK_AVAILABLE_IN_4_12
void gtk_sort_list_model_set_section_sorter (GtkSortListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_4_12
GtkSorter * gtk_sort_list_model_get_section_sorter (GtkSortListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sort_list_model_set_model (GtkSortListModel *self,
GListModel *model);

View File

@@ -271,6 +271,9 @@ gtk_public_sources = files([
'gtklinkbutton.c',
'gtklistbox.c',
'gtklistfactorywidget.c',
'gtklistheader.c',
'gtklistheaderbase.c',
'gtklistheaderwidget.c',
'gtklistitem.c',
'gtklistitembase.c',
'gtklistitemfactory.c',
@@ -333,6 +336,7 @@ gtk_public_sources = files([
'gtkscrolledwindow.c',
'gtksearchbar.c',
'gtksearchentry.c',
'gtksectionmodel.c',
'gtkselectionfiltermodel.c',
'gtkselectionmodel.c',
'gtkseparator.c',
@@ -518,6 +522,7 @@ gtk_public_headers = files([
'gtklinkbutton.h',
'gtklistbase.h',
'gtklistbox.h',
'gtklistheader.h',
'gtklistitem.h',
'gtklistitemfactory.h',
'gtklistview.h',
@@ -565,6 +570,7 @@ gtk_public_headers = files([
'gtkscrolledwindow.h',
'gtksearchbar.h',
'gtksearchentry.h',
'gtksectionmodel.h',
'gtkselectionfiltermodel.h',
'gtkselectionmodel.h',
'gtkseparator.h',

View File

@@ -3371,7 +3371,7 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
.rich-list { /* rich lists usually containing other widgets than just labels/text */
& > row {
& > row, & > header {
padding: 8px 12px;
min-height: 32px; /* should be tall even when only containing a label */
@@ -3379,6 +3379,14 @@ columnview row:not(:selected) cell editablelabel.editing text selection {
border-spacing: 12px;
}
}
& > header {
@extend %osd;
background-color: $osd_bg_color;
border-bottom: 1px solid $borders-color;
border-top: 1px solid $borders-color;
font-weight: bold;
}
}
/********************************************************

View File

@@ -43,6 +43,23 @@
} \
}G_STMT_END
#define assert_sections_equal(model1, model2) G_STMT_START{ \
guint _i, _n, _start1, _end1, _start2, _end2; \
g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (model1)), ==, g_list_model_get_n_items (G_LIST_MODEL (model2))); \
_n = g_list_model_get_n_items (G_LIST_MODEL (model1)); \
for (_i = 0; _i < _n; _i = _end1) \
{ \
gtk_section_model_get_section (model1, _i, &_start1, &_end1); \
gtk_section_model_get_section (model2, _i, &_start2, &_end2); \
g_assert_cmpint (_start1, <, _end1); \
g_assert_cmpint (_start2, <, _end2); \
g_assert_cmpint (_start1, ==, _start2); \
g_assert_cmpint (_end1, ==, _end2); \
g_assert_cmpint (_i, ==, _start1); \
g_assert_cmpint (_end1, <=, _n); \
} \
}G_STMT_END
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@@ -469,6 +486,7 @@ test_model_changes (gconstpointer model_id)
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (flatten1), G_LIST_MODEL (model2));
assert_sections_equal (GTK_SECTION_MODEL (flatten1), GTK_SECTION_MODEL (model2));
}
}

View File

@@ -323,7 +323,7 @@ test_change_filter (void)
{
GtkFilterListModel *filter;
GtkFilter *custom;
filter = new_model (10, is_not_near, GUINT_TO_POINTER (5));
assert_model (filter, "1 2 8 9 10");
assert_changes (filter, "");
@@ -457,6 +457,94 @@ test_add_remove_item (void)
g_object_unref (filter);
}
static int
sort_func (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
/* compare just the first byte */
return (int)(s1[0]) - (int)(s2[0]);
}
static gboolean
filter_func (gpointer item,
gpointer data)
{
const char *s = gtk_string_object_get_string ((GtkStringObject *)item);
return s[0] == s[1];
}
static void
test_sections (void)
{
GtkStringList *list;
const char *strings[] = {
"aaa",
"aab",
"abc",
"bbb",
"bq1",
"bq2",
"cc",
"cx",
NULL
};
GtkSorter *sorter;
GtkSortListModel *sorted;
GtkSorter *section_sorter;
guint s, e;
GtkFilterListModel *filtered;
GtkFilter *filter;
list = gtk_string_list_new (strings);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
g_object_unref (section_sorter);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 6);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
g_assert_cmpint (s, ==, 6);
g_assert_cmpint (e, ==, 8);
filtered = gtk_filter_list_model_new (NULL, NULL);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, G_MAXUINT);
gtk_filter_list_model_set_model (filtered, G_LIST_MODEL (sorted));
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
filter = GTK_FILTER (gtk_custom_filter_new (filter_func, NULL, NULL));
gtk_filter_list_model_set_filter (filtered, filter);
g_object_unref (filter);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 2);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 2, &s, &e);
g_assert_cmpint (s, ==, 2);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (filtered), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 4);
g_object_unref (filtered);
g_object_unref (sorted);
}
int
main (int argc, char *argv[])
{
@@ -472,6 +560,7 @@ main (int argc, char *argv[])
g_test_add_func ("/filterlistmodel/incremental", test_incremental);
g_test_add_func ("/filterlistmodel/empty", test_empty);
g_test_add_func ("/filterlistmodel/add_remove_item", test_add_remove_item);
g_test_add_func ("/filterlistmodel/sections", test_sections);
return g_test_run ();
}

View File

@@ -0,0 +1,497 @@
/*
* Copyright © 2023 Benjamin Otte
*
* 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 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/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
#include "gtk/gtklistitemmanagerprivate.h"
#include "gtk/gtklistbaseprivate.h"
static GListModel *
create_source_model (guint min_size, guint max_size)
{
GtkStringList *list;
guint i, size;
size = g_test_rand_int_range (min_size, max_size + 1);
list = gtk_string_list_new (NULL);
for (i = 0; i < size; i++)
gtk_string_list_append (list, g_test_rand_bit () ? "A" : "B");
return G_LIST_MODEL (list);
}
void
print_list_item_manager_tiles (GtkListItemManager *items)
{
GString *string;
GtkListTile *tile;
string = g_string_new ("");
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_ITEM:
if (tile->widget)
g_string_append_c (string, 'W');
else if (tile->n_items == 1)
g_string_append_c (string, 'x');
else
g_string_append_printf (string, "%u,", tile->n_items);
break;
case GTK_LIST_TILE_HEADER:
g_string_append_c (string, '[');
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_string_append_c (string, '(');
break;
case GTK_LIST_TILE_FOOTER:
g_string_append_c (string, ']');
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_string_append_c (string, ')');
break;
case GTK_LIST_TILE_FILLER:
g_string_append_c (string, '_');
break;
case GTK_LIST_TILE_REMOVED:
g_string_append_c (string, '.');
break;
default:
g_assert_not_reached ();
break;
}
}
g_print ("%s\n", string->str);
g_string_free (string, TRUE);
}
static void
check_list_item_manager (GtkListItemManager *items,
GtkListItemTracker **trackers,
gsize n_trackers)
{
GListModel *model = G_LIST_MODEL (gtk_list_item_manager_get_model (items));
GtkListTile *tile;
guint n_items = 0;
guint i;
gboolean has_sections;
enum {
NO_SECTION,
MATCHED_SECTION,
UNMATCHED_SECTION
} section_state = NO_SECTION;
has_sections = gtk_list_item_manager_get_has_sections (items);
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
GObject *item = g_list_model_get_item (model, n_items);
if (has_sections)
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
else
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmphex (GPOINTER_TO_SIZE (item), ==, GPOINTER_TO_SIZE (gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (tile->widget))));
g_object_unref (item);
g_assert_cmpint (n_items, ==, gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (tile->widget)));
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
/* We don't add fillers */
g_assert_not_reached ();
break;
case GTK_LIST_TILE_REMOVED:
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
break;
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
for (tile = gtk_list_tile_gc (items, gtk_list_item_manager_get_first (items));
tile != NULL;
tile = gtk_list_tile_gc (items, gtk_rb_tree_node_get_next (tile)))
;
n_items = 0;
for (tile = gtk_list_item_manager_get_first (items);
tile != NULL;
tile = gtk_rb_tree_node_get_next (tile))
{
switch (tile->type)
{
case GTK_LIST_TILE_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_true (tile->widget);
section_state = MATCHED_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_HEADER:
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = UNMATCHED_SECTION;
break;
case GTK_LIST_TILE_FOOTER:
g_assert_cmpint (section_state, ==, MATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_true (has_sections);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_UNMATCHED_FOOTER:
g_assert_cmpint (section_state, ==, UNMATCHED_SECTION);
g_assert_cmpint (tile->n_items, ==, 0);
g_assert_false (tile->widget);
section_state = NO_SECTION;
break;
case GTK_LIST_TILE_ITEM:
g_assert_cmpint (section_state, !=, NO_SECTION);
if (tile->widget)
{
g_assert_cmpint (tile->n_items, ==, 1);
}
if (tile->n_items)
n_items += tile->n_items;
break;
case GTK_LIST_TILE_FILLER:
case GTK_LIST_TILE_REMOVED:
default:
g_assert_not_reached ();
break;
}
}
g_assert_cmpint (section_state, ==, NO_SECTION);
g_assert_cmpint (n_items, ==, g_list_model_get_n_items (model));
for (i = 0; i < n_trackers; i++)
{
guint pos, offset;
pos = gtk_list_item_tracker_get_position (items, trackers[i]);
if (pos == GTK_INVALID_LIST_POSITION)
continue;
tile = gtk_list_item_manager_get_nth (items, pos, &offset);
g_assert_cmpint (tile->n_items, ==, 1);
g_assert_cmpint (offset, ==, 0);
g_assert_true (tile->widget);
}
}
static GtkListTile *
split_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
GtkListItemManager *items = g_object_get_data (G_OBJECT (widget), "the-items");
return gtk_list_tile_split (items, tile, n_items);
}
static void
prepare_simple (GtkWidget *widget,
GtkListTile *tile,
guint n_items)
{
}
static GtkListItemBase *
create_simple_item (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_ITEM_BASE, NULL);
}
static GtkListHeaderBase *
create_simple_header (GtkWidget *widget)
{
return g_object_new (GTK_TYPE_LIST_HEADER_BASE, NULL);
}
static void
test_create (void)
{
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
gtk_window_destroy (GTK_WINDOW (widget));
}
static void
test_create_with_items (void)
{
GListModel *source;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
source = create_source_model (1, 50);
selection = gtk_no_selection_new (G_LIST_MODEL (source));
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
check_list_item_manager (items, NULL, 0);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
#define N_TRACKERS 3
#define N_WIDGETS_PER_TRACKER 10
#define N_RUNS 500
static void
print_changes_cb (GListModel *model,
guint position,
guint removed,
guint added,
gpointer unused)
{
if (!g_test_verbose ())
return;
if (removed == 0)
g_test_message ("%u/%u: adding %u items", position, g_list_model_get_n_items (model), added);
else if (added == 0)
g_test_message ("%u/%u: removing %u items", position, g_list_model_get_n_items (model), removed);
else
g_test_message ("%u/%u: removing %u and adding %u items", position, g_list_model_get_n_items (model), removed, added);
}
static void
test_exhaustive (void)
{
GtkListItemTracker *trackers[N_TRACKERS];
GListStore *store;
GtkFlattenListModel *flatten;
GtkNoSelection *selection;
GtkListItemManager *items;
GtkWidget *widget;
gsize i;
widget = gtk_window_new ();
items = gtk_list_item_manager_new (widget,
split_simple,
create_simple_item,
prepare_simple,
create_simple_header);
for (i = 0; i < N_TRACKERS; i++)
trackers[i] = gtk_list_item_tracker_new (items);
g_object_set_data_full (G_OBJECT (widget), "the-items", items, g_object_unref);
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
selection = gtk_no_selection_new (G_LIST_MODEL (flatten));
g_signal_connect (selection, "items-changed", G_CALLBACK (print_changes_cb), NULL);
gtk_list_item_manager_set_model (items, GTK_SELECTION_MODEL (selection));
for (i = 0; i < N_RUNS; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position, n_items;
if (g_test_verbose ())
print_list_item_manager_tiles (items);
switch (g_test_rand_int_range (0, 6))
{
case 0:
if (g_test_verbose ())
g_test_message ("GC and checking");
check_list_item_manager (items, trackers, N_TRACKERS);
break;
case 1:
/* remove a model */
remove = TRUE;
break;
case 2:
/* add a model */
add = TRUE;
break;
case 3:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
case 4:
n_items = g_list_model_get_n_items (G_LIST_MODEL (selection));
if (n_items > 0)
{
guint tracker_id = g_test_rand_int_range (0, N_TRACKERS);
guint pos = g_test_rand_int_range (0, n_items);
guint n_before = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
guint n_after = g_test_rand_int_range (0, N_WIDGETS_PER_TRACKER / 2);
if (g_test_verbose ())
g_test_message ("setting tracker %u to %u -%u + %u", tracker_id, pos, n_before, n_after);
gtk_list_item_tracker_set_position (items,
trackers [tracker_id],
pos,
n_before, n_after);
}
break;
case 5:
{
gboolean has_sections = g_test_rand_bit ();
if (g_test_verbose ())
g_test_message ("Setting has_sections to %s", has_sections ? "true" : "false");
gtk_list_item_manager_set_has_sections (items, has_sections);
}
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the filters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
}
check_list_item_manager (items, trackers, N_TRACKERS);
for (i = 0; i < N_TRACKERS; i++)
gtk_list_item_tracker_free (items, trackers[i]);
g_object_unref (selection);
gtk_window_destroy (GTK_WINDOW (widget));
}
int
main (int argc, char *argv[])
{
gtk_test_init (&argc, &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);
return g_test_run ();
}

View File

@@ -129,6 +129,7 @@ internal_tests = [
{ 'name': 'texthistory' },
{ 'name': 'fnmatch' },
{ 'name': 'a11y' },
{ 'name': 'listitemmanager' },
]
is_debug = get_option('buildtype').startswith('debug')

View File

@@ -36,6 +36,8 @@
if (o1 != o2) \
{ \
char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
g_print ("%s\n", model_to_string (model1)); \
g_print ("%s\n", model_to_string (model2)); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
g_free (_s); \
} \
@@ -45,6 +47,19 @@
} \
}G_STMT_END
#define assert_model_sections(model) G_STMT_START{ \
guint _i, _start, _end; \
_start = 0; \
_end = 0; \
for (_i = 0; _i < G_MAXUINT; _i = _end) \
{ \
gtk_section_model_get_section (GTK_SECTION_MODEL (model), _i, &_start, &_end); \
\
g_assert_cmpint (_start, ==, _i); \
g_assert_cmpint (_end, >, _i); \
} \
}G_STMT_END
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
@@ -287,7 +302,7 @@ create_sorter (gsize id)
/* match all As, Bs and nothing */
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
if (id == 1)
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
return sorter;
default:
@@ -463,6 +478,258 @@ test_stability (gconstpointer model_id)
g_object_unref (flatten);
}
static gboolean
string_is_lowercase (GtkStringObject *o)
{
return g_ascii_islower (*gtk_string_object_get_string (o));
}
/* Run:
* source => section-sorter
* source => sorter
* and set a section sorter on the section sorter that is a subsort of
* the real sorter.
*
* And then randomly add/remove sources and change the sorters and
* see if the two sorters stay identical
*/
static void
test_section_sorters (gconstpointer model_id)
{
GListStore *store;
GtkFlattenListModel *flatten;
GtkSortListModel *sort1, *sort2;
GtkSorter *sorter;
gsize i;
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
sort1 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
sort2 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
for (i = 0; i < 500; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position;
switch (g_test_rand_int_range (0, 4))
{
case 0:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_section_sorter (sort1, sorter);
gtk_sort_list_model_set_sorter (sort1, NULL);
gtk_sort_list_model_set_sorter (sort2, sorter);
g_clear_object (&sorter);
break;
case 1:
/* use a section sorter that is a more generic version of the sorter */
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), FALSE);
gtk_sort_list_model_set_sorter (sort1, sorter);
gtk_sort_list_model_set_sorter (sort2, sorter);
g_clear_object (&sorter);
sorter = GTK_SORTER (gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_BOOLEAN, NULL, 0, NULL, G_CALLBACK (string_is_lowercase), NULL, NULL)));
gtk_sort_list_model_set_section_sorter (sort1, sorter);
g_clear_object (&sorter);
break;
case 2:
/* remove a model */
remove = TRUE;
break;
case 3:
/* add a model */
add = TRUE;
break;
case 4:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the sorters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
if (g_test_rand_bit ())
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
}
if (g_test_rand_bit ())
assert_model_sections (G_LIST_MODEL (sort1));
}
g_object_unref (sort2);
g_object_unref (sort1);
g_object_unref (flatten);
}
/* Run:
* source => sorter
* And then randomly add/remove sources and change the sorters and
* see if the invariants for sections keep correct.
*/
static void
test_sections (gconstpointer model_id)
{
GListStore *store;
GtkFlattenListModel *flatten;
GtkSortListModel *sort;
GtkSorter *sorter;
gsize i;
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
sort = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (flatten), NULL);
for (i = 0; i < 500; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position;
switch (g_test_rand_int_range (0, 4))
{
case 0:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_sorter (sort, sorter);
g_clear_object (&sorter);
break;
case 1:
/* set the same sorter, once as section sorter, once as sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_section_sorter (sort, sorter);
g_clear_object (&sorter);
break;
case 2:
/* remove a model */
remove = TRUE;
break;
case 3:
/* add a model */
add = TRUE;
break;
case 4:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the sorters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
if (g_test_rand_bit ())
ensure_updated ();
if (g_test_rand_bit ())
{
guint start, end, pos, n, sec_start, sec_end;
gpointer prev_item, item;
n = g_list_model_get_n_items (G_LIST_MODEL (sort));
sorter = gtk_sort_list_model_get_section_sorter (sort);
start = end = 0;
prev_item = item = NULL;
for (pos = 0; pos < n; pos++)
{
gtk_section_model_get_section (GTK_SECTION_MODEL (sort), pos, &sec_start, &sec_end);
prev_item = item;
item = g_list_model_get_item (G_LIST_MODEL (sort), pos);
if (end <= pos)
{
g_assert_cmpint (pos, ==, end);
/* there should be a new section */
g_assert_cmpint (sec_start, ==, end);
g_assert_cmpint (sec_end, >, sec_start);
g_assert_cmpint (sec_end, <=, n);
start = sec_start;
end = sec_end;
if (prev_item)
{
g_assert_nonnull (sorter);
g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), !=, GTK_ORDERING_EQUAL);
}
}
else
{
/* the old section keeps on going */
g_assert_cmpint (sec_start, ==, start);
g_assert_cmpint (sec_end, ==, end);
if (prev_item && sorter)
g_assert_cmpint (gtk_sorter_compare (sorter, prev_item, item), ==, GTK_ORDERING_EQUAL);
}
g_clear_object (&prev_item);
}
g_clear_object (&item);
/* for good measure, check the error condition */
if (n < G_MAXINT32)
{
gtk_section_model_get_section (GTK_SECTION_MODEL (sort), g_test_rand_int_range (n, G_MAXINT32), &sec_start, &sec_end);
g_assert_cmpint (sec_start, ==, n);
g_assert_cmpint (sec_end, ==, G_MAXUINT);
}
sorter = NULL;
}
}
g_object_unref (sort);
g_object_unref (flatten);
}
static void
add_test_for_all_models (const char *name,
GTestDataFunc test_func)
@@ -488,6 +755,8 @@ main (int argc, char *argv[])
add_test_for_all_models ("two-sorters", test_two_sorters);
add_test_for_all_models ("stability", test_stability);
add_test_for_all_models ("section-sorters", test_section_sorters);
add_test_for_all_models ("sections", test_sections);
return g_test_run ();
}

View File

@@ -258,7 +258,7 @@ test_create (void)
{
GtkSortListModel *sort;
GListStore *store;
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@@ -280,7 +280,7 @@ test_set_model (void)
{
GtkSortListModel *sort;
GListStore *store;
sort = new_model (NULL);
assert_model (sort, "");
assert_changes (sort, "");
@@ -319,7 +319,7 @@ test_set_sorter (void)
GtkSortListModel *sort;
GtkSorter *sorter;
GListStore *store;
store = new_store ((guint[]) { 4, 8, 2, 6, 10, 0 });
sort = new_model (store);
assert_model (sort, "2 4 6 8 10");
@@ -350,7 +350,7 @@ test_add_items (void)
{
GtkSortListModel *sort;
GListStore *store;
/* add beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 50, 0 });
sort = new_model (store);
@@ -390,7 +390,7 @@ test_remove_items (void)
{
GtkSortListModel *sort;
GListStore *store;
/* remove beginning */
store = new_store ((guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
sort = new_model (store);
@@ -570,6 +570,58 @@ test_add_remove_item (void)
g_object_unref (sort);
}
static int
sort_func (gconstpointer p1,
gconstpointer p2,
gpointer data)
{
const char *s1 = gtk_string_object_get_string ((GtkStringObject *)p1);
const char *s2 = gtk_string_object_get_string ((GtkStringObject *)p2);
/* compare just the first byte */
return (int)(s1[0]) - (int)(s2[0]);
}
static void
test_sections (void)
{
GtkStringList *list;
const char *strings[] = {
"aaa",
"aab",
"abc",
"bbb",
"bq1",
"bq2",
"cc",
"cx",
NULL
};
GtkSorter *sorter;
GtkSortListModel *sorted;
GtkSorter *section_sorter;
guint s, e;
list = gtk_string_list_new (strings);
sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string")));
sorted = gtk_sort_list_model_new (G_LIST_MODEL (list), sorter);
section_sorter = GTK_SORTER (gtk_custom_sorter_new (sort_func, NULL, NULL));
gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), section_sorter);
g_object_unref (section_sorter);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 0, &s, &e);
g_assert_cmpint (s, ==, 0);
g_assert_cmpint (e, ==, 3);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 3, &s, &e);
g_assert_cmpint (s, ==, 3);
g_assert_cmpint (e, ==, 6);
gtk_section_model_get_section (GTK_SECTION_MODEL (sorted), 6, &s, &e);
g_assert_cmpint (s, ==, 6);
g_assert_cmpint (e, ==, 8);
g_object_unref (sorted);
}
int
main (int argc, char *argv[])
{
@@ -589,6 +641,7 @@ main (int argc, char *argv[])
g_test_add_func ("/sortlistmodel/incremental/remove", test_incremental_remove);
g_test_add_func ("/sortlistmodel/oob-access", test_out_of_bounds_access);
g_test_add_func ("/sortlistmodel/add-remove-item", test_add_remove_item);
g_test_add_func ("/sortlistmodel/sections", test_sections);
return g_test_run ();
}