From 9f47bfe193b326abf3c2046274dba7e6e27da824 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 27 May 2023 17:26:39 -0400 Subject: [PATCH 1/8] sortlistmodel: Fix handling of section sort keys When the section sorter changes, we need to update the keys, otherwise the sorter will continue to report the old sections. This code is currently a bit suboptimal, since the creation of sort keys and section sort keys are muddled together. Fixes: #5854 --- gtk/gtksortlistmodel.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index 50add242f0..becfec012e 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -912,6 +912,12 @@ gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, } } + if (self->section_sorter) + { + gtk_sort_keys_unref (self->section_sort_keys); + self->section_sort_keys = gtk_sorter_get_keys (self->section_sorter); + } + if (gtk_sort_list_model_start_sorting (self, NULL)) pos = n_items = 0; else From aeba1e08e8fd78a4dd0994da774ca67337bcfb64 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 06:30:40 -0400 Subject: [PATCH 2/8] sortlistmodel: Emit sections-changed When a new section sorter is set, potentially all sections have changed. So emit sections-changed for all items. Tests included. --- gtk/gtksortlistmodel.c | 3 + testsuite/gtk/sortlistmodel.c | 138 ++++++++++++++++++++++++---------- 2 files changed, 103 insertions(+), 38 deletions(-) diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index becfec012e..8d05d30407 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -1246,6 +1246,9 @@ gtk_sort_list_model_set_section_sorter (GtkSortListModel *self, g_set_object (&self->section_sorter, sorter); gtk_sort_list_model_ensure_real_sorter (self); + if (self->n_items > 0) + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), 0, self->n_items); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_SORTER]); } diff --git a/testsuite/gtk/sortlistmodel.c b/testsuite/gtk/sortlistmodel.c index a9a4dc97f4..d3e6e558db 100644 --- a/testsuite/gtk/sortlistmodel.c +++ b/testsuite/gtk/sortlistmodel.c @@ -52,6 +52,41 @@ model_to_string (GListModel *model) return g_string_free (string, FALSE); } +static char * +section_model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i, s, e; + + if (!GTK_IS_SECTION_MODEL (model)) + return model_to_string (model); + + i = 0; + while (i < g_list_model_get_n_items (model)) + { + gtk_section_model_get_section (GTK_SECTION_MODEL (model), i, &s, &e); + g_assert (s == i); + + if (i > 0) + g_string_append (string, " "); + + g_string_append (string, "["); + + for (; i < e; i++) + { + if (i > s) + g_string_append (string, " "); + + g_string_append_printf (string, "%u", get (model, i)); + } + + g_string_append (string, "]"); + i = e; + } + + return g_string_free (string, FALSE); +} + static void splice (GListStore *store, guint pos, @@ -115,6 +150,14 @@ insert (GListStore *store, g_free (s); \ }G_STMT_END +#define assert_section_model(model, expected) G_STMT_START{ \ + char *s = section_model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + #define assert_changes(model, expected) G_STMT_START{ \ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ if (!g_str_equal (changes->str, expected)) \ @@ -176,6 +219,20 @@ items_changed (GListModel *model, } } +static void +sections_changed (GListModel *model, + guint position, + guint n_items, + GString *changes) +{ + g_assert_true (n_items != 0); + + if (changes->len) + g_string_append (changes, ", "); + + g_string_append_printf (changes, "s%u:%u", position, n_items); +} + static void notify_n_items (GObject *object, GParamSpec *pspec, @@ -236,6 +293,7 @@ new_model (gpointer model) changes = g_string_new (""); g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + g_signal_connect (result, "sections-changed", G_CALLBACK (sections_changed), changes); g_signal_connect (result, "notify::n-items", G_CALLBACK (notify_n_items), changes); return result; @@ -571,55 +629,59 @@ test_add_remove_item (void) } static int -sort_func (gconstpointer p1, - gconstpointer p2, - gpointer data) +by_n (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); + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + unsigned int n = GPOINTER_TO_UINT (data); - /* compare just the first byte */ - return (int)(s1[0]) - (int)(s2[0]); + n1 = n1 / n; + n2 = n2 / n; + + if (n1 < n2) + return -1; + else if (n1 > n2) + return 1; + else + return 0; } static void test_sections (void) { - GtkStringList *list; - const char *strings[] = { - "aaa", - "aab", - "abc", - "bbb", - "bq1", - "bq2", - "cc", - "cx", - NULL - }; + GListStore *store; + GtkSortListModel *model; 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); + store = new_store ((guint[]) { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }); + model = new_model (store); + assert_model (model, "1 2 3 4 5 6 7 8 9 10"); + assert_section_model (model, "[1 2 3 4 5 6 7 8 9 10]"); + assert_changes (model, ""); - 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_assert_true (gtk_sort_list_model_get_section_sorter (model) == NULL); - g_object_unref (sorted); + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (3), NULL)); + gtk_sort_list_model_set_section_sorter (model, sorter); + g_assert_true (gtk_sort_list_model_get_section_sorter (model) == sorter); + g_object_unref (sorter); + + assert_changes (model, "s0:10"); + assert_section_model (model, "[1 2] [3 4 5] [6 7 8] [9 10]"); + + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (5), NULL)); + g_assert_false (gtk_sort_list_model_get_section_sorter (model) == sorter); + gtk_sort_list_model_set_section_sorter (model, sorter); + g_assert_true (gtk_sort_list_model_get_section_sorter (model) == sorter); + g_object_unref (sorter); + + assert_changes (model, "s0:10"); + assert_section_model (model, "[1 2 3 4] [5 6 7 8 9] [10]"); + + g_object_unref (store); + g_object_unref (model); } int From 8f3d3ca58745f4abf4270aea2ccb553bbdbf2a44 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 06:24:34 -0400 Subject: [PATCH 3/8] slicelistmodel: Pass through sections Implement GtkSectionModel in the obvious way. Tests included. --- gtk/gtkslicelistmodel.c | 72 +++++++++++++++- testsuite/gtk/slicelistmodel.c | 145 +++++++++++++++++++++++++++++++-- 2 files changed, 206 insertions(+), 11 deletions(-) diff --git a/gtk/gtkslicelistmodel.c b/gtk/gtkslicelistmodel.c index 232dbe5aac..98bcee025d 100644 --- a/gtk/gtkslicelistmodel.c +++ b/gtk/gtkslicelistmodel.c @@ -20,6 +20,7 @@ #include "config.h" #include "gtkslicelistmodel.h" +#include "gtksectionmodelprivate.h" #include "gtkprivate.h" @@ -31,6 +32,8 @@ * This is useful when implementing paging by setting the size to the number * of elements per page and updating the offset whenever a different page is * opened. + * + * `GtkSliceListModel` passes through sections from the underlying model. */ #define DEFAULT_SIZE 10 @@ -52,8 +55,6 @@ struct _GtkSliceListModel GListModel *model; guint offset; guint size; - - guint n_items; }; struct _GtkSliceListModelClass @@ -111,8 +112,69 @@ gtk_slice_list_model_model_init (GListModelInterface *iface) iface->get_item = gtk_slice_list_model_get_item; } +static void +gtk_slice_list_model_get_section (GtkSectionModel *model, + guint position, + guint *start, + guint *end) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (model); + unsigned int n_items; + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + if (position >= n_items) + { + *start = n_items; + *end = G_MAXUINT; + } + else + { + gtk_list_model_get_section (self->model, position + self->offset, start, end); + + *start = MAX (*start, self->offset) - self->offset; + *end = MIN (*end - self->offset, n_items); + } +} + +static void +gtk_slice_list_model_sections_changed_cb (GtkSectionModel *model, + unsigned int position, + unsigned int n_items, + gpointer user_data) +{ + GtkSliceListModel *self = GTK_SLICE_LIST_MODEL (user_data); + unsigned int start = position; + unsigned int end = position + n_items; + unsigned int size; + + if (end <= self->offset) + return; + + size = g_list_model_get_n_items (G_LIST_MODEL (self)); + + end = MIN (end - self->offset, size); + + if (start <= self->offset) + start = 0; + else + start = start - self->offset; + + if (start >= size) + return; + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), start, end - start); +} + +static void +gtk_slice_list_model_section_model_init (GtkSectionModelInterface *iface) +{ + iface->get_section = gtk_slice_list_model_get_section; +} + G_DEFINE_TYPE_WITH_CODE (GtkSliceListModel, gtk_slice_list_model, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_slice_list_model_model_init)) + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_slice_list_model_model_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SECTION_MODEL, gtk_slice_list_model_section_model_init)) + static void gtk_slice_list_model_items_changed_cb (GListModel *model, @@ -238,6 +300,7 @@ gtk_slice_list_model_clear_model (GtkSliceListModel *self) if (self->model == NULL) return; + g_signal_handlers_disconnect_by_func (self->model, gtk_slice_list_model_sections_changed_cb, self); g_signal_handlers_disconnect_by_func (self->model, gtk_slice_list_model_items_changed_cb, self); g_clear_object (&self->model); } @@ -387,6 +450,9 @@ gtk_slice_list_model_set_model (GtkSliceListModel *self, self->model = g_object_ref (model); g_signal_connect (model, "items-changed", G_CALLBACK (gtk_slice_list_model_items_changed_cb), self); added = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (GTK_IS_SECTION_MODEL (model)) + g_signal_connect (model, "sections-changed", G_CALLBACK (gtk_slice_list_model_sections_changed_cb), self); } else { diff --git a/testsuite/gtk/slicelistmodel.c b/testsuite/gtk/slicelistmodel.c index e86abd69e7..680f699eda 100644 --- a/testsuite/gtk/slicelistmodel.c +++ b/testsuite/gtk/slicelistmodel.c @@ -51,6 +51,41 @@ model_to_string (GListModel *model) return g_string_free (string, FALSE); } +static char * +section_model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i, s, e; + + if (!GTK_IS_SECTION_MODEL (model)) + return model_to_string (model); + + i = 0; + while (i < g_list_model_get_n_items (model)) + { + gtk_section_model_get_section (GTK_SECTION_MODEL (model), i, &s, &e); + g_assert (s == i); + + if (i > 0) + g_string_append (string, " "); + + g_string_append (string, "["); + + for (; i < e; i++) + { + if (i > s) + g_string_append (string, " "); + + g_string_append_printf (string, "%u", get (model, i)); + } + + g_string_append (string, "]"); + i = e; + } + + return g_string_free (string, FALSE); +} + static GListStore * new_store (guint start, guint end, @@ -116,6 +151,14 @@ insert (GListStore *store, g_free (s); \ }G_STMT_END +#define assert_section_model(model, expected) G_STMT_START{ \ + char *s = section_model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + #define assert_changes(model, expected) G_STMT_START{ \ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ if (!g_str_equal (changes->str, expected)) \ @@ -174,6 +217,20 @@ items_changed (GListModel *model, } } +static void +sections_changed (GListModel *model, + guint position, + guint n_items, + GString *changes) +{ + g_assert_true (n_items != 0); + + if (changes->len) + g_string_append (changes, ", "); + + g_string_append_printf (changes, "s%u:%u", position, n_items); +} + static void notify_n_items (GObject *object, GParamSpec *pspec, @@ -194,18 +251,19 @@ free_changes (gpointer data) } static GtkSliceListModel * -new_model (GListStore *store, guint offset, guint size) +new_model (GListModel *store, guint offset, guint size) { GtkSliceListModel *result; GString *changes; if (store) g_object_ref (store); - result = gtk_slice_list_model_new (G_LIST_MODEL (store), offset, size); + result = gtk_slice_list_model_new (store, offset, size); changes = g_string_new (""); - g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_object_set_qdata_full (G_OBJECT (result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + g_signal_connect (result, "sections-changed", G_CALLBACK (sections_changed), changes); g_signal_connect (result, "notify::n-items", G_CALLBACK (notify_n_items), changes); return result; @@ -230,7 +288,7 @@ test_create (void) GListStore *store; store = new_store (1, 5, 2); - slice = new_model (store, 0, 10); + slice = new_model (G_LIST_MODEL (store), 0, 10); assert_model (slice, "1 3 5"); assert_changes (slice, ""); @@ -273,7 +331,7 @@ test_set_slice (void) GListStore *store; store = new_store (1, 7, 2); - slice = new_model (store, 0, 3); + slice = new_model (G_LIST_MODEL (store), 0, 3); assert_model (slice, "1 3 5"); assert_changes (slice, ""); @@ -302,7 +360,7 @@ test_changes (void) GListStore *store; store = new_store (1, 20, 1); - slice = new_model (store, 10, 5); + slice = new_model (G_LIST_MODEL (store), 10, 5); assert_model (slice, "11 12 13 14 15"); assert_changes (slice, ""); @@ -349,7 +407,7 @@ test_bug_added_equals_removed (void) GListStore *store; store = new_store (1, 10, 1); - slice = new_model (store, 0, 10); + slice = new_model (G_LIST_MODEL (store), 0, 10); assert_model (slice, "1 2 3 4 5 6 7 8 9 10"); assert_changes (slice, ""); @@ -368,7 +426,7 @@ test_bug_skip_amount (void) GListStore *store; store = new_store (1, 5, 1); - slice = new_model (store, 2, 2); + slice = new_model (G_LIST_MODEL (store), 2, 2); assert_model (slice, "3 4"); assert_changes (slice, ""); @@ -380,6 +438,76 @@ test_bug_skip_amount (void) g_object_unref (slice); } +static int +by_n (gconstpointer p1, + gconstpointer p2, + gpointer data) +{ + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + unsigned int n = GPOINTER_TO_UINT (data); + + n1 = n1 / n; + n2 = n2 / n; + + if (n1 < n2) + return -1; + else if (n1 > n2) + return 1; + else + return 0; +} + +static int +compare (gconstpointer first, + gconstpointer second, + gpointer unused) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark)) + - GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark)); +} + +static void +test_sections (void) +{ + GListStore *store; + GtkSortListModel *sorted; + GtkSliceListModel *slice; + GtkSorter *sorter; + + store = new_store (1, 10, 1); + sorted = gtk_sort_list_model_new (G_LIST_MODEL (store), + GTK_SORTER (gtk_custom_sorter_new (compare, NULL, NULL))); + slice = new_model (G_LIST_MODEL (sorted), 0, 10); + assert_model (slice, "1 2 3 4 5 6 7 8 9 10"); + assert_section_model (slice, "[1 2 3 4 5 6 7 8 9 10]"); + assert_changes (slice, ""); + + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (3), NULL)); + gtk_sort_list_model_set_section_sorter (sorted, sorter); + g_object_unref (sorter); + + assert_section_model (slice, "[1 2] [3 4 5] [6 7 8] [9 10]"); + assert_changes (slice, "s0:10"); + + gtk_slice_list_model_set_size (slice, 5); + + assert_section_model (slice, "[1 2] [3 4 5]"); + assert_changes (slice, "5-5*"); + + gtk_slice_list_model_set_offset (slice, 1); + assert_section_model (slice, "[2] [3 4 5] [6]"); + assert_changes (slice, "0-5+5"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 0, 3); + assert_changes (slice, "s0:2"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 5, 3); + assert_changes (slice, "s4:1"); + + g_object_unref (slice); +} + int main (int argc, char *argv[]) { @@ -396,6 +524,7 @@ main (int argc, char *argv[]) g_test_add_func ("/slicelistmodel/changes", test_changes); g_test_add_func ("/slicelistmodel/bug/added_equals_removed", test_bug_added_equals_removed); g_test_add_func ("/slicelistmodel/bug/skip_amount", test_bug_skip_amount); + g_test_add_func ("/slicelistmodel/sections", test_sections); return g_test_run (); } From ba8d4902b5a71ef5a593dd48d0af1e5de4c14911 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 09:13:00 -0400 Subject: [PATCH 4/8] filterlistmodel: Pass through sections-changed If our underlying model emits sections-changed, we need to pass it on. Add a test for this too. --- gtk/gtkfilterlistmodel.c | 36 +++++++++++++++++++++++++++++++++ testsuite/gtk/filterlistmodel.c | 16 +++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/gtk/gtkfilterlistmodel.c b/gtk/gtkfilterlistmodel.c index a41b19fa6e..840115162d 100644 --- a/gtk/gtkfilterlistmodel.c +++ b/gtk/gtkfilterlistmodel.c @@ -190,6 +190,39 @@ gtk_filter_list_model_get_section (GtkSectionModel *model, *out_end = *out_start + gtk_bitset_get_size_in_range (self->matches, start, end - 1); } +static void +gtk_filter_list_model_sections_changed_cb (GtkSectionModel *model, + unsigned int position, + unsigned int n_items, + gpointer user_data) +{ + GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (user_data); + unsigned int start, end; + + switch (self->strictness) + { + case GTK_FILTER_MATCH_NONE: + return; + + case GTK_FILTER_MATCH_ALL: + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), position, n_items); + break; + + case GTK_FILTER_MATCH_SOME: + if (position > 0) + start = gtk_bitset_get_size_in_range (self->matches, 0, position - 1); + else + start = 0; + end = gtk_bitset_get_size_in_range (self->matches, 0, position + n_items - 1); + if (end - start > 0) + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), start, end - start); + break; + + default: + g_assert_not_reached (); + } +} + static void gtk_filter_list_model_section_model_init (GtkSectionModelInterface *iface) { @@ -465,6 +498,7 @@ gtk_filter_list_model_clear_model (GtkFilterListModel *self) gtk_filter_list_model_stop_filtering (self); g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self); + g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_sections_changed_cb, self); g_clear_object (&self->model); if (self->matches) gtk_bitset_remove_all (self->matches); @@ -829,6 +863,8 @@ gtk_filter_list_model_set_model (GtkFilterListModel *self, { self->model = g_object_ref (model); g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self); + if (GTK_IS_SECTION_MODEL (model)) + g_signal_connect (model, "sections-changed", G_CALLBACK (gtk_filter_list_model_sections_changed_cb), self); if (removed == 0) { self->strictness = GTK_FILTER_MATCH_NONE; diff --git a/testsuite/gtk/filterlistmodel.c b/testsuite/gtk/filterlistmodel.c index 9d48910313..22a49ab1bc 100644 --- a/testsuite/gtk/filterlistmodel.c +++ b/testsuite/gtk/filterlistmodel.c @@ -478,6 +478,17 @@ filter_func (gpointer item, return s[0] == s[1]; } +static void +sections_changed (GtkSectionModel *model, + unsigned int start, + unsigned int end, + gpointer user_data) +{ + gboolean *got_it = user_data; + + *got_it = TRUE; +} + static void test_sections (void) { @@ -499,6 +510,7 @@ test_sections (void) guint s, e; GtkFilterListModel *filtered; GtkFilter *filter; + gboolean got_it = FALSE; list = gtk_string_list_new (strings); sorter = GTK_SORTER (gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"))); @@ -541,6 +553,10 @@ test_sections (void) g_assert_cmpint (s, ==, 3); g_assert_cmpint (e, ==, 4); + g_signal_connect (filtered, "sections-changed", G_CALLBACK (sections_changed), &got_it); + gtk_sort_list_model_set_section_sorter (GTK_SORT_LIST_MODEL (sorted), NULL); + g_assert_true (got_it); + g_object_unref (filtered); g_object_unref (sorted); } From 8825917140d5245c1885fe29b441c34e36f5f83b Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 11:10:49 -0400 Subject: [PATCH 5/8] noselection: Pass through sections-changed If our underlying model emits sections-changed, we need to pass it on. Add a test for this too. --- gtk/gtknoselection.c | 21 +++++- testsuite/gtk/noselection.c | 137 +++++++++++++++++++++++++++++++++--- 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/gtk/gtknoselection.c b/gtk/gtknoselection.c index 20bff24df2..872ee2eaad 100644 --- a/gtk/gtknoselection.c +++ b/gtk/gtknoselection.c @@ -33,6 +33,8 @@ * * This model is meant to be used as a simple wrapper around a `GListModel` * when a `GtkSelectionModel` is required. + * + * `GtkNoSelection` passes through sections from the underlying model. */ struct _GtkNoSelection { @@ -152,15 +154,29 @@ gtk_no_selection_items_changed_cb (GListModel *model, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]); } +static void +gtk_no_selection_sections_changed_cb (GtkSectionModel *model, + unsigned int position, + unsigned int n_items, + gpointer user_data) +{ + GtkNoSelection *self = GTK_NO_SELECTION (user_data); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), position, n_items); +} + static void gtk_no_selection_clear_model (GtkNoSelection *self) { if (self->model == NULL) return; - g_signal_handlers_disconnect_by_func (self->model, + g_signal_handlers_disconnect_by_func (self->model, gtk_no_selection_items_changed_cb, self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_no_selection_sections_changed_cb, + self); g_clear_object (&self->model); } @@ -345,6 +361,9 @@ gtk_no_selection_set_model (GtkNoSelection *self, self->model = g_object_ref (model); g_signal_connect (self->model, "items-changed", G_CALLBACK (gtk_no_selection_items_changed_cb), self); + if (GTK_IS_SECTION_MODEL (self->model)) + g_signal_connect (self->model, "sections-changed", + G_CALLBACK (gtk_no_selection_sections_changed_cb), self); n_items_after = g_list_model_get_n_items (self->model); } else diff --git a/testsuite/gtk/noselection.c b/testsuite/gtk/noselection.c index 59e0526035..ab71d87b89 100644 --- a/testsuite/gtk/noselection.c +++ b/testsuite/gtk/noselection.c @@ -52,6 +52,42 @@ model_to_string (GListModel *model) return g_string_free (string, FALSE); } +static char * +section_model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i, s, e, n; + + if (!GTK_IS_SECTION_MODEL (model)) + return model_to_string (model); + + n = g_list_model_get_n_items (model); + + i = 0; + while (i < n) + { + gtk_section_model_get_section (GTK_SECTION_MODEL (model), i, &s, &e); + + if (i > 0) + g_string_append (string, " "); + + g_string_append (string, "["); + + for (; i < e; i++) + { + if (i > s) + g_string_append (string, " "); + + g_string_append_printf (string, "%u", get (model, i)); + } + + g_string_append (string, "]"); + i = e; + } + + return g_string_free (string, FALSE); +} + static char * selection_to_string (GListModel *model) { @@ -136,6 +172,14 @@ insert (GListStore *store, g_free (s); \ }G_STMT_END +#define assert_section_model(model, expected) G_STMT_START{ \ + char *s = section_model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + #define ignore_changes(model) G_STMT_START{ \ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ g_string_set_size (changes, 0); \ @@ -220,6 +264,20 @@ items_changed (GListModel *model, } } +static void +sections_changed (GListModel *model, + guint position, + guint n_items, + GString *changes) +{ + g_assert_true (n_items != 0); + + if (changes->len) + g_string_append (changes, ", "); + + g_string_append_printf (changes, "s%u:%u", position, n_items); +} + static void notify_n_items (GObject *object, GParamSpec *pspec, @@ -252,16 +310,17 @@ free_changes (gpointer data) } static GtkSelectionModel * -new_model (GListStore *store, gboolean autoselect, gboolean can_unselect) +new_model (GListModel *store, gboolean autoselect, gboolean can_unselect) { GtkSelectionModel *result; GString *changes; - result = GTK_SELECTION_MODEL (gtk_no_selection_new (g_object_ref (G_LIST_MODEL (store)))); + result = GTK_SELECTION_MODEL (gtk_no_selection_new (g_object_ref (store))); changes = g_string_new (""); - g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_object_set_qdata_full (G_OBJECT (result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + g_signal_connect (result, "sections-changed", G_CALLBACK (sections_changed), changes); g_signal_connect (result, "notify::n-items", G_CALLBACK (notify_n_items), changes); changes = g_string_new (""); @@ -284,7 +343,7 @@ test_create (void) } store = new_store (1, 5, 2); - selection = new_model (store, FALSE, FALSE); + selection = new_model (G_LIST_MODEL (store), FALSE, FALSE); assert_model (selection, "1 3 5"); assert_changes (selection, ""); @@ -325,7 +384,7 @@ test_changes (void) } store = new_store (1, 5, 1); - selection = new_model (store, FALSE, FALSE); + selection = new_model (G_LIST_MODEL (store), FALSE, FALSE); assert_model (selection, "1 2 3 4 5"); assert_changes (selection, ""); assert_selection (selection, ""); @@ -367,7 +426,7 @@ test_selection (void) } store = new_store (1, 5, 1); - selection = new_model (store, TRUE, FALSE); + selection = new_model (G_LIST_MODEL (store), TRUE, FALSE); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -444,7 +503,7 @@ test_query_range (void) GListStore *store; store = new_store (1, 5, 1); - selection = new_model (store, TRUE, TRUE); + selection = new_model (G_LIST_MODEL (store), TRUE, TRUE); check_get_selection (selection); gtk_selection_model_unselect_item (selection, 0); @@ -472,7 +531,7 @@ test_set_model (void) store = new_store (1, 5, 1); m1 = G_LIST_MODEL (store); m2 = G_LIST_MODEL (gtk_slice_list_model_new (g_object_ref (m1), 0, 3)); - selection = new_model (store, TRUE, TRUE); + selection = new_model (G_LIST_MODEL (store), TRUE, TRUE); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -531,6 +590,67 @@ test_empty (void) g_object_unref (selection); } +static int +by_n (gconstpointer p1, + gconstpointer p2, + gpointer data) +{ + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + unsigned int n = GPOINTER_TO_UINT (data); + + n1 = n1 / n; + n2 = n2 / n; + + if (n1 < n2) + return -1; + else if (n1 > n2) + return 1; + else + return 0; +} + +static int +compare (gconstpointer first, + gconstpointer second, + gpointer unused) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark)) + - GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark)); +} + +static void +test_sections (void) +{ + GListStore *store; + GtkSortListModel *sorted; + GtkSelectionModel *selection; + GtkSorter *sorter; + + store = new_store (1, 10, 1); + sorted = gtk_sort_list_model_new (G_LIST_MODEL (store), + GTK_SORTER (gtk_custom_sorter_new (compare, NULL, NULL))); + selection = new_model (G_LIST_MODEL (sorted), TRUE, TRUE); + assert_model (selection, "1 2 3 4 5 6 7 8 9 10"); + assert_section_model (selection, "[1 2 3 4 5 6 7 8 9 10]"); + assert_changes (selection, ""); + + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (3), NULL)); + gtk_sort_list_model_set_section_sorter (sorted, sorter); + g_object_unref (sorter); + + assert_section_model (selection, "[1 2] [3 4 5] [6 7 8] [9 10]"); + assert_changes (selection, "s0:10"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 0, 3); + assert_changes (selection, "s0:3"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 5, 3); + assert_changes (selection, "s5:3"); + + g_object_unref (selection); +} + int main (int argc, char *argv[]) { @@ -548,6 +668,7 @@ main (int argc, char *argv[]) g_test_add_func ("/noselection/changes", test_changes); g_test_add_func ("/noselection/set-model", test_set_model); g_test_add_func ("/noselection/empty", test_empty); + g_test_add_func ("/noselection/sections", test_sections); return g_test_run (); } From a1352a88ffc53d8417ba295bfbcd5f84e412e02f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 11:17:54 -0400 Subject: [PATCH 6/8] singleselection: Pass through sections-changed If our underlying model emits sections-changed, we need to pass it on. Add a test for this too. --- gtk/gtksingleselection.c | 21 ++++- testsuite/gtk/singleselection.c | 145 +++++++++++++++++++++++++++++--- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/gtk/gtksingleselection.c b/gtk/gtksingleselection.c index 07f0c7eaee..e4a19176a9 100644 --- a/gtk/gtksingleselection.c +++ b/gtk/gtksingleselection.c @@ -300,15 +300,29 @@ gtk_single_selection_items_changed_cb (GListModel *model, g_object_thaw_notify (G_OBJECT (self)); } +static void +gtk_single_selection_sections_changed_cb (GtkSectionModel *model, + unsigned int position, + unsigned int n_items, + gpointer user_data) +{ + GtkSingleSelection *self = GTK_SINGLE_SELECTION (user_data); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), position, n_items); +} + static void gtk_single_selection_clear_model (GtkSingleSelection *self) { if (self->model == NULL) return; - g_signal_handlers_disconnect_by_func (self->model, + g_signal_handlers_disconnect_by_func (self->model, gtk_single_selection_items_changed_cb, self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_single_selection_sections_changed_cb, + self); g_clear_object (&self->model); } @@ -558,7 +572,7 @@ gtk_single_selection_set_model (GtkSingleSelection *self, return; g_object_freeze_notify (G_OBJECT (self)); - + n_items_before = self->model ? g_list_model_get_n_items (self->model) : 0; gtk_single_selection_clear_model (self); @@ -567,6 +581,9 @@ gtk_single_selection_set_model (GtkSingleSelection *self, self->model = g_object_ref (model); g_signal_connect (self->model, "items-changed", G_CALLBACK (gtk_single_selection_items_changed_cb), self); + if (GTK_IS_SECTION_MODEL (self->model)) + g_signal_connect (self->model, "sections-changed", + G_CALLBACK (gtk_single_selection_sections_changed_cb), self); gtk_single_selection_items_changed_cb (self->model, 0, n_items_before, diff --git a/testsuite/gtk/singleselection.c b/testsuite/gtk/singleselection.c index faad2d681d..1952da879e 100644 --- a/testsuite/gtk/singleselection.c +++ b/testsuite/gtk/singleselection.c @@ -52,6 +52,42 @@ model_to_string (GListModel *model) return g_string_free (string, FALSE); } +static char * +section_model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i, s, e, n; + + if (!GTK_IS_SECTION_MODEL (model)) + return model_to_string (model); + + n = g_list_model_get_n_items (model); + + i = 0; + while (i < n) + { + gtk_section_model_get_section (GTK_SECTION_MODEL (model), i, &s, &e); + + if (i > 0) + g_string_append (string, " "); + + g_string_append (string, "["); + + for (; i < e; i++) + { + if (i > s) + g_string_append (string, " "); + + g_string_append_printf (string, "%u", get (model, i)); + } + + g_string_append (string, "]"); + i = e; + } + + return g_string_free (string, FALSE); +} + static char * selection_to_string (GListModel *model) { @@ -136,6 +172,14 @@ insert (GListStore *store, g_free (s); \ }G_STMT_END +#define assert_section_model(model, expected) G_STMT_START{ \ + char *s = section_model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + #define ignore_changes(model) G_STMT_START{ \ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ g_string_set_size (changes, 0); \ @@ -220,6 +264,20 @@ items_changed (GListModel *model, } } +static void +sections_changed (GListModel *model, + guint position, + guint n_items, + GString *changes) +{ + g_assert_true (n_items != 0); + + if (changes->len) + g_string_append (changes, ", "); + + g_string_append_printf (changes, "s%u:%u", position, n_items); +} + static void notify_n_items (GObject *object, GParamSpec *pspec, @@ -252,12 +310,12 @@ free_changes (gpointer data) } static GtkSelectionModel * -new_model (GListStore *store, gboolean autoselect, gboolean can_unselect) +new_model (GListModel *store, gboolean autoselect, gboolean can_unselect) { GtkSelectionModel *result; GString *changes; - result = GTK_SELECTION_MODEL (gtk_single_selection_new (g_object_ref (G_LIST_MODEL (store)))); + result = GTK_SELECTION_MODEL (gtk_single_selection_new (g_object_ref (store))); /* We want to return an empty selection unless autoselect is true, * so undo the initial selection due to autoselect defaulting to TRUE. @@ -271,8 +329,9 @@ new_model (GListStore *store, gboolean autoselect, gboolean can_unselect) gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (result), can_unselect); changes = g_string_new (""); - g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_object_set_qdata_full (G_OBJECT (result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + g_signal_connect (result, "sections-changed", G_CALLBACK (sections_changed), changes); g_signal_connect (result, "notify::n-items", G_CALLBACK (notify_n_items), changes); changes = g_string_new (""); @@ -296,7 +355,7 @@ test_create (void) } store = new_store (1, 5, 2); - selection = new_model (store, FALSE, FALSE); + selection = new_model (G_LIST_MODEL (store), FALSE, FALSE); g_assert_false (gtk_single_selection_get_autoselect (GTK_SINGLE_SELECTION (selection))); assert_model (selection, "1 3 5"); @@ -344,7 +403,7 @@ test_changes (void) } store = new_store (1, 5, 1); - selection = new_model (store, FALSE, FALSE); + selection = new_model (G_LIST_MODEL (store), FALSE, FALSE); assert_model (selection, "1 2 3 4 5"); assert_changes (selection, ""); assert_selection (selection, ""); @@ -386,7 +445,7 @@ test_selection (void) } store = new_store (1, 5, 1); - selection = new_model (store, TRUE, FALSE); + selection = new_model (G_LIST_MODEL (store), TRUE, FALSE); assert_selection (selection, "1"); assert_selection_changes (selection, ""); @@ -442,7 +501,7 @@ test_autoselect (void) } store = new_empty_store (); - selection = new_model (store, TRUE, FALSE); + selection = new_model (G_LIST_MODEL (store), TRUE, FALSE); assert_model (selection, ""); assert_changes (selection, ""); assert_selection (selection, ""); @@ -513,7 +572,7 @@ test_autoselect_toggle (void) } store = new_store (1, 1, 1); - selection = new_model (store, TRUE, TRUE); + selection = new_model (G_LIST_MODEL (store), TRUE, TRUE); assert_model (selection, "1"); assert_changes (selection, ""); assert_selection (selection, "1"); @@ -555,7 +614,7 @@ test_can_unselect (void) } store = new_store (1, 5, 1); - selection = new_model (store, TRUE, FALSE); + selection = new_model (G_LIST_MODEL (store), TRUE, FALSE); assert_selection (selection, "1"); assert_selection_changes (selection, ""); @@ -600,7 +659,7 @@ test_persistence (void) } store = new_store (1, 5, 1); - selection = new_model (store, TRUE, FALSE); + selection = new_model (G_LIST_MODEL (store), TRUE, FALSE); assert_selection (selection, "1"); assert_selection_changes (selection, ""); g_assert_true (gtk_selection_model_is_selected (selection, 0)); @@ -653,7 +712,7 @@ test_query_range (void) GListStore *store; store = new_store (1, 5, 1); - selection = new_model (store, TRUE, TRUE); + selection = new_model (G_LIST_MODEL (store), TRUE, TRUE); check_get_selection (selection); gtk_selection_model_unselect_item (selection, 0); @@ -681,7 +740,7 @@ test_set_model (void) store = new_store (1, 5, 1); m1 = G_LIST_MODEL (store); m2 = G_LIST_MODEL (gtk_slice_list_model_new (g_object_ref (m1), 0, 3)); - selection = new_model (store, TRUE, TRUE); + selection = new_model (G_LIST_MODEL (store), TRUE, TRUE); assert_selection (selection, "1"); assert_selection_changes (selection, ""); @@ -743,6 +802,67 @@ test_empty (void) g_object_unref (selection); } +static int +by_n (gconstpointer p1, + gconstpointer p2, + gpointer data) +{ + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + unsigned int n = GPOINTER_TO_UINT (data); + + n1 = n1 / n; + n2 = n2 / n; + + if (n1 < n2) + return -1; + else if (n1 > n2) + return 1; + else + return 0; +} + +static int +compare (gconstpointer first, + gconstpointer second, + gpointer unused) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark)) + - GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark)); +} + +static void +test_sections (void) +{ + GListStore *store; + GtkSortListModel *sorted; + GtkSelectionModel *selection; + GtkSorter *sorter; + + store = new_store (1, 10, 1); + sorted = gtk_sort_list_model_new (G_LIST_MODEL (store), + GTK_SORTER (gtk_custom_sorter_new (compare, NULL, NULL))); + selection = new_model (G_LIST_MODEL (sorted), TRUE, TRUE); + assert_model (selection, "1 2 3 4 5 6 7 8 9 10"); + assert_section_model (selection, "[1 2 3 4 5 6 7 8 9 10]"); + assert_changes (selection, ""); + + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (3), NULL)); + gtk_sort_list_model_set_section_sorter (sorted, sorter); + g_object_unref (sorter); + + assert_section_model (selection, "[1 2] [3 4 5] [6 7 8] [9 10]"); + assert_changes (selection, "s0:10"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 0, 3); + assert_changes (selection, "s0:3"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 5, 3); + assert_changes (selection, "s5:3"); + + g_object_unref (selection); +} + int main (int argc, char *argv[]) { @@ -764,6 +884,7 @@ main (int argc, char *argv[]) g_test_add_func ("/singleselection/changes", test_changes); g_test_add_func ("/singleselection/set-model", test_set_model); g_test_add_func ("/singleselection/empty", test_empty); + g_test_add_func ("/singleselection/sections", test_sections); return g_test_run (); } From 9c1049e710515e1505bd83783bd342b3ea63b6a4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 23 May 2023 11:24:02 -0400 Subject: [PATCH 7/8] multiselection: Pass through sections-changed If our underlying model emits sections-changed, we need to pass it on. Add a test for this too. --- gtk/gtkmultiselection.c | 17 ++++ testsuite/gtk/multiselection.c | 145 ++++++++++++++++++++++++++++++--- 2 files changed, 150 insertions(+), 12 deletions(-) diff --git a/gtk/gtkmultiselection.c b/gtk/gtkmultiselection.c index f549541baa..7e54d647b3 100644 --- a/gtk/gtkmultiselection.c +++ b/gtk/gtkmultiselection.c @@ -291,6 +291,17 @@ gtk_multi_selection_items_changed_cb (GListModel *model, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]); } +static void +gtk_multi_selection_sections_changed_cb (GtkSectionModel *model, + unsigned int position, + unsigned int n_items, + gpointer user_data) +{ + GtkMultiSelection *self = GTK_MULTI_SELECTION (user_data); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), position, n_items); +} + static void gtk_multi_selection_clear_model (GtkMultiSelection *self) { @@ -300,6 +311,9 @@ gtk_multi_selection_clear_model (GtkMultiSelection *self) g_signal_handlers_disconnect_by_func (self->model, gtk_multi_selection_items_changed_cb, self); + g_signal_handlers_disconnect_by_func (self->model, + gtk_multi_selection_sections_changed_cb, + self); g_clear_object (&self->model); } @@ -490,6 +504,9 @@ gtk_multi_selection_set_model (GtkMultiSelection *self, "items-changed", G_CALLBACK (gtk_multi_selection_items_changed_cb), self); + if (GTK_IS_SECTION_MODEL (self->model)) + g_signal_connect (self->model, "sections-changed", + G_CALLBACK (gtk_multi_selection_sections_changed_cb), self); gtk_multi_selection_items_changed_cb (self->model, 0, n_items_before, diff --git a/testsuite/gtk/multiselection.c b/testsuite/gtk/multiselection.c index f6fffcb814..d709969376 100644 --- a/testsuite/gtk/multiselection.c +++ b/testsuite/gtk/multiselection.c @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2019, Red Hat, Inc. * Authors: Matthias Clasen * @@ -52,6 +52,42 @@ model_to_string (GListModel *model) return g_string_free (string, FALSE); } +static char * +section_model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + guint i, s, e, n; + + if (!GTK_IS_SECTION_MODEL (model)) + return model_to_string (model); + + n = g_list_model_get_n_items (model); + + i = 0; + while (i < n) + { + gtk_section_model_get_section (GTK_SECTION_MODEL (model), i, &s, &e); + + if (i > 0) + g_string_append (string, " "); + + g_string_append (string, "["); + + for (; i < e; i++) + { + if (i > s) + g_string_append (string, " "); + + g_string_append_printf (string, "%u", get (model, i)); + } + + g_string_append (string, "]"); + i = e; + } + + return g_string_free (string, FALSE); +} + static char * selection_to_string (GListModel *model) { @@ -140,6 +176,14 @@ insert (GListStore *store, g_free (s); \ }G_STMT_END +#define assert_section_model(model, expected) G_STMT_START{ \ + char *s = section_model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + #define ignore_changes(model) G_STMT_START{ \ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ g_string_set_size (changes, 0); \ @@ -224,6 +268,20 @@ items_changed (GListModel *model, } } +static void +sections_changed (GListModel *model, + guint position, + guint n_items, + GString *changes) +{ + g_assert_true (n_items != 0); + + if (changes->len) + g_string_append (changes, ", "); + + g_string_append_printf (changes, "s%u:%u", position, n_items); +} + static void notify_n_items (GObject *object, GParamSpec *pspec, @@ -256,16 +314,17 @@ free_changes (gpointer data) } static GtkSelectionModel * -new_model (GListStore *store) +new_model (GListModel *store) { GtkSelectionModel *result; GString *changes; - result = GTK_SELECTION_MODEL (gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (store)))); + result = GTK_SELECTION_MODEL (gtk_multi_selection_new (g_object_ref (store))); changes = g_string_new (""); - g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_object_set_qdata_full (G_OBJECT (result), changes_quark, changes, free_changes); g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + g_signal_connect (result, "sections-changed", G_CALLBACK (sections_changed), changes); g_signal_connect (result, "notify::n-items", G_CALLBACK (notify_n_items), changes); changes = g_string_new (""); @@ -299,7 +358,7 @@ test_create (void) guint start, end; store = new_store (1, 5, 2); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_model (selection, "1 3 5"); assert_changes (selection, ""); @@ -340,7 +399,7 @@ test_changes (void) gboolean ret; store = new_store (1, 5, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_model (selection, "1 2 3 4 5"); assert_changes (selection, ""); assert_selection (selection, ""); @@ -387,7 +446,7 @@ test_selection (void) gboolean ret; store = new_store (1, 5, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -442,7 +501,7 @@ test_select_range (void) gboolean ret; store = new_store (1, 5, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -477,7 +536,7 @@ test_readd (void) store = new_store (1, 5, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_model (selection, "1 2 3 4 5"); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -506,7 +565,7 @@ test_set_selection (void) store = new_store (1, 10, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_model (selection, "1 2 3 4 5 6 7 8 9 10"); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -547,7 +606,7 @@ test_selection_filter (void) gboolean ret; store = new_store (1, 5, 1); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -660,7 +719,7 @@ test_set_model (void) store = new_store (1, 5, 1); m1 = G_LIST_MODEL (store); m2 = G_LIST_MODEL (gtk_slice_list_model_new (g_object_ref (m1), 0, 3)); - selection = new_model (store); + selection = new_model (G_LIST_MODEL (store)); assert_selection (selection, ""); assert_selection_changes (selection, ""); @@ -742,6 +801,67 @@ test_empty_filter (void) g_object_unref (selection); } +static int +by_n (gconstpointer p1, + gconstpointer p2, + gpointer data) +{ + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + unsigned int n = GPOINTER_TO_UINT (data); + + n1 = n1 / n; + n2 = n2 / n; + + if (n1 < n2) + return -1; + else if (n1 > n2) + return 1; + else + return 0; +} + +static int +compare (gconstpointer first, + gconstpointer second, + gpointer unused) +{ + return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark)) + - GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark)); +} + +static void +test_sections (void) +{ + GListStore *store; + GtkSortListModel *sorted; + GtkSelectionModel *selection; + GtkSorter *sorter; + + store = new_store (1, 10, 1); + sorted = gtk_sort_list_model_new (G_LIST_MODEL (store), + GTK_SORTER (gtk_custom_sorter_new (compare, NULL, NULL))); + selection = new_model (G_LIST_MODEL (sorted)); + assert_model (selection, "1 2 3 4 5 6 7 8 9 10"); + assert_section_model (selection, "[1 2 3 4 5 6 7 8 9 10]"); + assert_changes (selection, ""); + + sorter = GTK_SORTER (gtk_custom_sorter_new (by_n, GUINT_TO_POINTER (3), NULL)); + gtk_sort_list_model_set_section_sorter (sorted, sorter); + g_object_unref (sorter); + + assert_section_model (selection, "[1 2] [3 4 5] [6 7 8] [9 10]"); + assert_changes (selection, "s0:10"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 0, 3); + assert_changes (selection, "s0:3"); + + gtk_section_model_sections_changed (GTK_SECTION_MODEL (sorted), 5, 3); + assert_changes (selection, "s5:3"); + + g_object_unref (selection); +} + int main (int argc, char *argv[]) { @@ -764,6 +884,7 @@ main (int argc, char *argv[]) g_test_add_func ("/multiselection/set-model", test_set_model); g_test_add_func ("/multiselection/empty", test_empty); g_test_add_func ("/multiselection/selection-filter/empty", test_empty_filter); + g_test_add_func ("/multiselection/sections", test_sections); return g_test_run (); } From 5c1c22156c5cb1276d7593ccbda9c51ec8ccfebd Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 27 May 2023 19:40:20 -0400 Subject: [PATCH 8/8] sortlistmodel: Optimize signals When we emit items-changed due to a section sorter change, don't also emit sections-changed. Instead make the items-changed signal cover the whole range. Tests included. --- gtk/gtksortlistmodel.c | 40 ++++++++++++++++++++++++----------- testsuite/gtk/sortlistmodel.c | 23 ++++++++++++++++++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/gtk/gtksortlistmodel.c b/gtk/gtksortlistmodel.c index 8d05d30407..61ca5a8c64 100644 --- a/gtk/gtksortlistmodel.c +++ b/gtk/gtksortlistmodel.c @@ -873,9 +873,10 @@ gtk_sort_list_model_get_property (GObject *object, } static void -gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, - int change, - GtkSortListModel *self) +gtk_sort_list_model_sorter_changed (GtkSorter *sorter, + int change, + GtkSortListModel *self, + gboolean sections_changed) { guint pos, n_items; @@ -928,8 +929,25 @@ gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, gtk_sort_list_model_clear_items (self, &pos, &n_items); } - if (n_items > 0) - g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items); + if (sections_changed && self->n_items > 0) + { + if (n_items > 0) + g_list_model_items_changed (G_LIST_MODEL (self), 0, self->n_items, self->n_items); + else + gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), 0, self->n_items); + } + else if (n_items > 0) + { + g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items); + } +} + +static void +gtk_sort_list_model_sorter_changed_cb (GtkSorter *sorter, + int change, + GtkSortListModel *self) +{ + gtk_sort_list_model_sorter_changed (sorter, change, self, FALSE); } static void @@ -955,7 +973,8 @@ gtk_sort_list_model_clear_real_sorter (GtkSortListModel *self) } static void -gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self) +gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self, + gboolean sections_changed) { if (self->sorter) { @@ -980,7 +999,7 @@ gtk_sort_list_model_ensure_real_sorter (GtkSortListModel *self) 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); + gtk_sort_list_model_sorter_changed (self->real_sorter, GTK_SORTER_CHANGE_DIFFERENT, self, sections_changed); } static void @@ -1202,7 +1221,7 @@ gtk_sort_list_model_set_sorter (GtkSortListModel *self, gtk_sort_list_model_clear_real_sorter (self); g_set_object (&self->sorter, sorter); - gtk_sort_list_model_ensure_real_sorter (self); + gtk_sort_list_model_ensure_real_sorter (self, FALSE); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]); } @@ -1244,10 +1263,7 @@ gtk_sort_list_model_set_section_sorter (GtkSortListModel *self, gtk_sort_list_model_clear_real_sorter (self); g_set_object (&self->section_sorter, sorter); - gtk_sort_list_model_ensure_real_sorter (self); - - if (self->n_items > 0) - gtk_section_model_sections_changed (GTK_SECTION_MODEL (self), 0, self->n_items); + gtk_sort_list_model_ensure_real_sorter (self, TRUE); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SECTION_SORTER]); } diff --git a/testsuite/gtk/sortlistmodel.c b/testsuite/gtk/sortlistmodel.c index d3e6e558db..b0b85117ed 100644 --- a/testsuite/gtk/sortlistmodel.c +++ b/testsuite/gtk/sortlistmodel.c @@ -648,6 +648,22 @@ by_n (gconstpointer p1, return 0; } +static int +weird (gconstpointer p1, + gconstpointer p2, + gpointer data) +{ + guint n1 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p1), number_quark)); + guint n2 = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (p2), number_quark)); + + if (n1 == 5 && n2 != 5) + return -1; + else if (n1 != 5 && n2 == 5) + return 1; + + return by_n (p1, p2, data); +} + static void test_sections (void) { @@ -680,6 +696,13 @@ test_sections (void) assert_changes (model, "s0:10"); assert_section_model (model, "[1 2 3 4] [5 6 7 8 9] [10]"); + sorter = GTK_SORTER (gtk_custom_sorter_new (weird, GUINT_TO_POINTER (5), NULL)); + gtk_sort_list_model_set_section_sorter (model, sorter); + g_object_unref (sorter); + + assert_changes (model, "0-10+10"); + assert_section_model (model, "[5] [1 2 3 4] [6 7 8 9] [10]"); + g_object_unref (store); g_object_unref (model); }