1 | /* GtkSorter tests. |
2 | * |
3 | * Copyright (C) 2019, Red Hat, Inc. |
4 | * Authors: Benjamin Otte <otte@gnome.org> |
5 | * Matthias Clasen <mclasen@redhat.com> |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include <locale.h> |
22 | |
23 | #include <gtk/gtk.h> |
24 | |
25 | static GQuark number_quark; |
26 | |
27 | static guint |
28 | get_number (GObject *object) |
29 | { |
30 | return GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); |
31 | } |
32 | |
33 | static guint |
34 | get (GListModel *model, |
35 | guint position) |
36 | { |
37 | GObject *object = g_list_model_get_item (list: model, position); |
38 | guint number; |
39 | g_assert_nonnull (object); |
40 | number = get_number (object); |
41 | g_object_unref (object); |
42 | return number; |
43 | } |
44 | |
45 | static char * |
46 | get_string (gpointer object) |
47 | { |
48 | return g_strdup_printf (format: "%u" , GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark))); |
49 | } |
50 | |
51 | static guint |
52 | get_number_mod_5 (GObject *object) |
53 | { |
54 | return get_number (object) % 5; |
55 | } |
56 | |
57 | static void |
58 | append_digit (GString *s, |
59 | guint digit) |
60 | { |
61 | static const char *names[10] = { NULL, "one" , "two" , "three" , "four" , "five" , "six" , "seven" , "eight" , "nine" }; |
62 | |
63 | if (digit == 0) |
64 | return; |
65 | |
66 | g_assert_cmpint (digit, <, 10); |
67 | |
68 | if (s->len) |
69 | g_string_append_c (s, ' '); |
70 | g_string_append (string: s, val: names[digit]); |
71 | } |
72 | |
73 | static void |
74 | append_below_thousand (GString *s, |
75 | guint n) |
76 | { |
77 | if (n >= 100) |
78 | { |
79 | append_digit (s, digit: n / 100); |
80 | g_string_append (string: s, val: " hundred" ); |
81 | n %= 100; |
82 | } |
83 | |
84 | if (n >= 20) |
85 | { |
86 | const char *names[10] = { NULL, NULL, "twenty" , "thirty" , "forty" , "fifty" , "sixty" , "seventy" , "eighty" , "ninety" }; |
87 | if (s->len) |
88 | g_string_append_c (s, ' '); |
89 | g_string_append (string: s, val: names [n / 10]); |
90 | n %= 10; |
91 | } |
92 | |
93 | if (n >= 10) |
94 | { |
95 | const char *names[10] = { "ten" , "eleven" , "twelve" , "thirteen" , "fourteen" , |
96 | "fifteen" , "sixteen" , "seventeen" , "eighteen" , "nineteen" }; |
97 | if (s->len) |
98 | g_string_append_c (s, ' '); |
99 | g_string_append (string: s, val: names [n - 10]); |
100 | } |
101 | else |
102 | { |
103 | append_digit (s, digit: n); |
104 | } |
105 | } |
106 | |
107 | static char * |
108 | get_spelled_out (gpointer object) |
109 | { |
110 | guint n = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark)); |
111 | GString *s; |
112 | |
113 | g_assert_cmpint (n, <, 1000000); |
114 | |
115 | if (n == 0) |
116 | return g_strdup (str: "Zero" ); |
117 | |
118 | s = g_string_new (NULL); |
119 | |
120 | if (n >= 1000) |
121 | { |
122 | append_below_thousand (s, n: n / 1000); |
123 | g_string_append (string: s, val: " thousand" ); |
124 | n %= 1000; |
125 | } |
126 | |
127 | append_below_thousand (s, n); |
128 | |
129 | /* Capitalize first letter so we can do case-sensitive sorting */ |
130 | s->str[0] = g_ascii_toupper (c: s->str[0]); |
131 | |
132 | return g_string_free (string: s, FALSE); |
133 | } |
134 | |
135 | static char * |
136 | model_to_string (GListModel *model) |
137 | { |
138 | GString *string = g_string_new (NULL); |
139 | guint i; |
140 | |
141 | for (i = 0; i < g_list_model_get_n_items (list: model); i++) |
142 | { |
143 | if (i > 0) |
144 | g_string_append (string, val: " " ); |
145 | g_string_append_printf (string, format: "%u" , get (model, position: i)); |
146 | } |
147 | |
148 | return g_string_free (string, FALSE); |
149 | } |
150 | |
151 | static GListStore * |
152 | new_store (guint start, |
153 | guint end, |
154 | guint step); |
155 | |
156 | static void |
157 | add (GListStore *store, |
158 | guint number) |
159 | { |
160 | GObject *object; |
161 | |
162 | /* 0 cannot be differentiated from NULL, so don't use it */ |
163 | g_assert_cmpint (number, !=, 0); |
164 | |
165 | object = g_object_new (G_TYPE_OBJECT, NULL); |
166 | g_object_set_qdata (object, quark: number_quark, GUINT_TO_POINTER (number)); |
167 | g_list_store_append (store, item: object); |
168 | g_object_unref (object); |
169 | } |
170 | |
171 | #define assert_model(model, expected) G_STMT_START{ \ |
172 | char *s = model_to_string (G_LIST_MODEL (model)); \ |
173 | if (!g_str_equal (s, expected)) \ |
174 | g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ |
175 | #model " == " #expected, s, "==", expected); \ |
176 | g_free (s); \ |
177 | }G_STMT_END |
178 | |
179 | #define assert_not_model(model, expected) G_STMT_START{ \ |
180 | char *s = model_to_string (G_LIST_MODEL (model)); \ |
181 | if (g_str_equal (s, expected)) \ |
182 | g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ |
183 | #model " != " #expected, s, "!=", expected); \ |
184 | g_free (s); \ |
185 | }G_STMT_END |
186 | |
187 | /* This could be faster by foreach()ing through the models and comparing |
188 | * the item pointers */ |
189 | #define assert_model_equal(model1, model2) G_STMT_START{\ |
190 | char *s1 = model_to_string (G_LIST_MODEL (model1)); \ |
191 | char *s2 = model_to_string (G_LIST_MODEL (model2)); \ |
192 | if (!g_str_equal (s1, s2)) \ |
193 | g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ |
194 | #model1 " != " #model2, s1, "==", s2); \ |
195 | g_free (s2); \ |
196 | g_free (s1); \ |
197 | }G_STMT_END |
198 | |
199 | static GListStore * |
200 | new_empty_store (void) |
201 | { |
202 | return g_list_store_new (G_TYPE_OBJECT); |
203 | } |
204 | |
205 | static GListStore * |
206 | new_store (guint start, |
207 | guint end, |
208 | guint step) |
209 | { |
210 | GListStore *store = new_empty_store (); |
211 | guint i; |
212 | |
213 | for (i = start; i <= end; i += step) |
214 | add (store, number: i); |
215 | |
216 | return store; |
217 | } |
218 | |
219 | static GListStore * |
220 | fisher_yates_shuffle (GListStore *store) |
221 | { |
222 | int i, n; |
223 | gboolean shuffled = FALSE; |
224 | |
225 | while (!shuffled) |
226 | { |
227 | n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: store)); |
228 | for (i = 0; i < n; i++) |
229 | { |
230 | int pos = g_random_int_range (begin: 0, end: n - i); |
231 | GObject *item; |
232 | |
233 | item = g_list_model_get_item (list: G_LIST_MODEL (ptr: store), position: pos); |
234 | g_list_store_remove (store, position: pos); |
235 | g_list_store_append (store, item); |
236 | g_object_unref (object: item); |
237 | shuffled |= pos != 0; |
238 | } |
239 | } |
240 | |
241 | return store; |
242 | } |
243 | |
244 | static GtkSortListModel * |
245 | new_model (guint size, |
246 | GtkSorter *sorter) |
247 | { |
248 | GtkSortListModel *result; |
249 | |
250 | if (sorter) |
251 | g_object_ref (sorter); |
252 | result = gtk_sort_list_model_new (model: G_LIST_MODEL (ptr: fisher_yates_shuffle (store: new_store (start: 1, end: size, step: 1))), sorter); |
253 | |
254 | return result; |
255 | } |
256 | |
257 | static int |
258 | compare_numbers (gconstpointer item1, |
259 | gconstpointer item2, |
260 | gpointer data) |
261 | { |
262 | guint n1 = get_number (G_OBJECT (item1)); |
263 | guint n2 = get_number (G_OBJECT (item2)); |
264 | |
265 | return n1 - n2; |
266 | } |
267 | |
268 | static void |
269 | test_simple (void) |
270 | { |
271 | GtkSortListModel *model; |
272 | GtkSorter *sorter; |
273 | |
274 | model = new_model (size: 20, NULL); |
275 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
276 | |
277 | sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_numbers, NULL, NULL)); |
278 | gtk_sort_list_model_set_sorter (self: model, sorter); |
279 | g_object_unref (object: sorter); |
280 | |
281 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
282 | |
283 | gtk_sort_list_model_set_sorter (self: model, NULL); |
284 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
285 | |
286 | g_object_unref (object: model); |
287 | } |
288 | |
289 | static void |
290 | test_string (void) |
291 | { |
292 | GtkSortListModel *model; |
293 | GtkSorter *sorter; |
294 | GtkExpression *expression; |
295 | |
296 | model = new_model (size: 20, NULL); |
297 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
298 | |
299 | sorter = GTK_SORTER (ptr: gtk_string_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 0, NULL, callback_func: (GCallback)get_string, NULL, NULL))); |
300 | |
301 | gtk_sort_list_model_set_sorter (self: model, sorter); |
302 | g_object_unref (object: sorter); |
303 | |
304 | assert_model (model, "1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9" ); |
305 | |
306 | expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 0, NULL, callback_func: (GCallback)get_spelled_out, NULL, NULL); |
307 | gtk_string_sorter_set_expression (self: GTK_STRING_SORTER (ptr: sorter), expression); |
308 | gtk_expression_unref (self: expression); |
309 | |
310 | assert_model (model, "8 18 11 15 5 4 14 9 19 1 7 17 6 16 10 13 3 12 20 2" ); |
311 | |
312 | gtk_string_sorter_set_expression (self: GTK_STRING_SORTER (ptr: sorter), NULL); |
313 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
314 | |
315 | g_object_unref (object: model); |
316 | } |
317 | |
318 | static void |
319 | inc_counter (GtkSorter *sorter, int change, gpointer data) |
320 | { |
321 | int *counter = data; |
322 | |
323 | (*counter)++; |
324 | } |
325 | |
326 | static void |
327 | test_change (void) |
328 | { |
329 | GtkSorter *sorter; |
330 | GtkExpression *expression; |
331 | int counter = 0; |
332 | |
333 | sorter = GTK_SORTER (ptr: gtk_string_sorter_new (NULL)); |
334 | g_signal_connect (sorter, "changed" , G_CALLBACK (inc_counter), &counter); |
335 | |
336 | expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 0, NULL, callback_func: (GCallback)get_string, NULL, NULL); |
337 | gtk_string_sorter_set_expression (self: GTK_STRING_SORTER (ptr: sorter), expression); |
338 | g_assert_cmpint (counter, ==, 1); |
339 | |
340 | gtk_string_sorter_set_expression (self: GTK_STRING_SORTER (ptr: sorter), expression); |
341 | g_assert_cmpint (counter, ==, 1); |
342 | |
343 | gtk_expression_unref (self: expression); |
344 | |
345 | gtk_string_sorter_set_ignore_case (self: GTK_STRING_SORTER (ptr: sorter), FALSE); |
346 | g_assert_cmpint (counter, ==, 2); |
347 | |
348 | gtk_string_sorter_set_ignore_case (self: GTK_STRING_SORTER (ptr: sorter), FALSE); |
349 | g_assert_cmpint (counter, ==, 2); |
350 | |
351 | g_object_unref (object: sorter); |
352 | } |
353 | |
354 | static void |
355 | test_numeric (void) |
356 | { |
357 | GtkSortListModel *model; |
358 | GtkSorter *sorter; |
359 | |
360 | model = new_model (size: 20, NULL); |
361 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
362 | |
363 | sorter = GTK_SORTER (ptr: gtk_numeric_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL))); |
364 | gtk_sort_list_model_set_sorter (self: model, sorter); |
365 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
366 | |
367 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_DESCENDING); |
368 | assert_model (model, "20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1" ); |
369 | |
370 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_ASCENDING); |
371 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
372 | |
373 | gtk_numeric_sorter_set_expression (self: GTK_NUMERIC_SORTER (ptr: sorter), NULL); |
374 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
375 | |
376 | g_object_unref (object: sorter); |
377 | g_object_unref (object: model); |
378 | } |
379 | |
380 | /* sort even numbers before odd, don't care about anything else */ |
381 | static int |
382 | compare_even (gconstpointer item1, |
383 | gconstpointer item2, |
384 | gpointer data) |
385 | { |
386 | guint n1 = get_number (G_OBJECT (item1)); |
387 | guint n2 = get_number (G_OBJECT (item2)); |
388 | int r1 = n1 % 2; |
389 | int r2 = n2 % 2; |
390 | |
391 | if (r1 == r2) |
392 | return 0; |
393 | |
394 | if (r1 == 1) |
395 | return 1; |
396 | |
397 | return -1; |
398 | } |
399 | |
400 | static void |
401 | test_multi (void) |
402 | { |
403 | GtkSortListModel *model; |
404 | GtkSorter *sorter; |
405 | GtkSorter *sorter1; |
406 | GtkSorter *sorter2; |
407 | GtkExpression *expression; |
408 | gpointer item; |
409 | |
410 | model = new_model (size: 20, NULL); |
411 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
412 | |
413 | sorter2 = GTK_SORTER (ptr: gtk_numeric_sorter_new (NULL)); |
414 | gtk_sort_list_model_set_sorter (self: model, sorter: sorter2); |
415 | expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL); |
416 | gtk_numeric_sorter_set_expression (self: GTK_NUMERIC_SORTER (ptr: sorter2), expression); |
417 | gtk_expression_unref (self: expression); |
418 | |
419 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
420 | |
421 | sorter = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
422 | gtk_sort_list_model_set_sorter (self: model, sorter); |
423 | |
424 | sorter1 = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_even, NULL, NULL)); |
425 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: sorter), sorter: sorter1); |
426 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: sorter), sorter: sorter2); |
427 | |
428 | g_assert_true (GTK_TYPE_SORTER == g_list_model_get_item_type (G_LIST_MODEL (sorter))); |
429 | g_assert_cmpuint (2, ==, g_list_model_get_n_items (G_LIST_MODEL (sorter))); |
430 | item = g_list_model_get_item (list: G_LIST_MODEL (ptr: sorter), position: 1); |
431 | g_assert_true (item == sorter2); |
432 | g_object_unref (object: item); |
433 | |
434 | assert_model (model, "2 4 6 8 10 12 14 16 18 20 1 3 5 7 9 11 13 15 17 19" ); |
435 | |
436 | /* This doesn't do anything */ |
437 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: sorter), position: 12345); |
438 | assert_model (model, "2 4 6 8 10 12 14 16 18 20 1 3 5 7 9 11 13 15 17 19" ); |
439 | |
440 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: sorter), position: 0); |
441 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
442 | |
443 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: sorter), position: 0); |
444 | assert_not_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
445 | |
446 | g_object_unref (object: model); |
447 | g_object_unref (object: sorter); |
448 | } |
449 | |
450 | /* Check that the multi sorter properly disconnects its changed signal */ |
451 | static void |
452 | test_multi_destruct (void) |
453 | { |
454 | GtkSorter *multi, *sorter; |
455 | |
456 | multi = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
457 | sorter = GTK_SORTER (ptr: gtk_numeric_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL))); |
458 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), g_object_ref (sorter)); |
459 | g_object_unref (object: multi); |
460 | |
461 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_DESCENDING); |
462 | g_object_unref (object: sorter); |
463 | } |
464 | |
465 | static void |
466 | test_multi_changes (void) |
467 | { |
468 | GtkSortListModel *model; |
469 | GtkSorter *multi; |
470 | GtkSorter *sorter1; |
471 | GtkSorter *sorter2; |
472 | GtkSorter *sorter3; |
473 | GtkExpression *expression; |
474 | int counter = 0; |
475 | |
476 | /* We want a sorted model, so that we can be sure partial sorts do the right thing */ |
477 | model = gtk_sort_list_model_new (model: G_LIST_MODEL (ptr: new_store (start: 1, end: 20, step: 1)), NULL); |
478 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
479 | |
480 | multi = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
481 | g_signal_connect (multi, "changed" , G_CALLBACK (inc_counter), &counter); |
482 | gtk_sort_list_model_set_sorter (self: model, sorter: multi); |
483 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
484 | g_assert_cmpint (counter, ==, 0); |
485 | |
486 | sorter1 = GTK_SORTER (ptr: gtk_numeric_sorter_new (NULL)); |
487 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: sorter1); |
488 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
489 | g_assert_cmpint (counter, ==, 1); |
490 | |
491 | expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number_mod_5, NULL, NULL); |
492 | gtk_numeric_sorter_set_expression (self: GTK_NUMERIC_SORTER (ptr: sorter1), expression); |
493 | gtk_expression_unref (self: expression); |
494 | assert_model (model, "5 10 15 20 1 6 11 16 2 7 12 17 3 8 13 18 4 9 14 19" ); |
495 | g_assert_cmpint (counter, ==, 2); |
496 | |
497 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter1), sort_order: GTK_SORT_DESCENDING); |
498 | assert_model (model, "4 9 14 19 3 8 13 18 2 7 12 17 1 6 11 16 5 10 15 20" ); |
499 | g_assert_cmpint (counter, ==, 3); |
500 | |
501 | sorter2 = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_even, NULL, NULL)); |
502 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: sorter2); |
503 | assert_model (model, "4 14 9 19 8 18 3 13 2 12 7 17 6 16 1 11 10 20 5 15" ); |
504 | g_assert_cmpint (counter, ==, 4); |
505 | |
506 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter1), sort_order: GTK_SORT_ASCENDING); |
507 | assert_model (model, "10 20 5 15 6 16 1 11 2 12 7 17 8 18 3 13 4 14 9 19" ); |
508 | g_assert_cmpint (counter, ==, 5); |
509 | |
510 | sorter3 = GTK_SORTER (ptr: gtk_string_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_STRING, NULL, n_params: 0, NULL, callback_func: (GCallback)get_spelled_out, NULL, NULL))); |
511 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: sorter3); |
512 | assert_model (model, "10 20 15 5 6 16 11 1 12 2 7 17 8 18 13 3 4 14 9 19" ); |
513 | g_assert_cmpint (counter, ==, 6); |
514 | |
515 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: multi), position: 1); |
516 | assert_model (model, "15 5 10 20 11 1 6 16 7 17 12 2 8 18 13 3 4 14 9 19" ); |
517 | g_assert_cmpint (counter, ==, 7); |
518 | |
519 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: multi), position: 1); |
520 | assert_model (model, "5 10 15 20 1 6 11 16 2 7 12 17 3 8 13 18 4 9 14 19" ); |
521 | g_assert_cmpint (counter, ==, 8); |
522 | |
523 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: multi), position: 0); |
524 | assert_model (model, "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ); |
525 | g_assert_cmpint (counter, ==, 9); |
526 | |
527 | g_object_unref (object: multi); |
528 | g_object_unref (object: model); |
529 | } |
530 | |
531 | static GtkSorter * |
532 | even_odd_sorter_new (void) |
533 | { |
534 | return GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_even, NULL, NULL)); |
535 | } |
536 | |
537 | static GtkSorter * |
538 | numeric_sorter_new (void) |
539 | { |
540 | return GTK_SORTER (ptr: gtk_numeric_sorter_new (expression: gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL))); |
541 | } |
542 | |
543 | static void |
544 | switch_order (GtkSorter *sorter) |
545 | { |
546 | if (gtk_numeric_sorter_get_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter)) == GTK_SORT_ASCENDING) |
547 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_DESCENDING); |
548 | else |
549 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_ASCENDING); |
550 | } |
551 | |
552 | static void |
553 | set_order_ascending (GtkSorter *sorter) |
554 | { |
555 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_ASCENDING); |
556 | } |
557 | |
558 | static void |
559 | set_order_descending (GtkSorter *sorter) |
560 | { |
561 | gtk_numeric_sorter_set_sort_order (self: GTK_NUMERIC_SORTER (ptr: sorter), sort_order: GTK_SORT_DESCENDING); |
562 | } |
563 | |
564 | static void |
565 | set_expression_get_number (GtkSorter *sorter) |
566 | { |
567 | GtkExpression *expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL); |
568 | gtk_numeric_sorter_set_expression (self: GTK_NUMERIC_SORTER (ptr: sorter), expression); |
569 | gtk_expression_unref (self: expression); |
570 | } |
571 | |
572 | static void |
573 | set_expression_get_number_mod_5 (GtkSorter *sorter) |
574 | { |
575 | GtkExpression *expression = gtk_cclosure_expression_new (G_TYPE_UINT, NULL, n_params: 0, NULL, callback_func: (GCallback)get_number, NULL, NULL); |
576 | gtk_numeric_sorter_set_expression (self: GTK_NUMERIC_SORTER (ptr: sorter), expression); |
577 | gtk_expression_unref (self: expression); |
578 | } |
579 | |
580 | static void |
581 | modify_sorter (GtkSorter *multi) |
582 | { |
583 | struct { |
584 | GType type; |
585 | GtkSorter * (* create_func) (void); |
586 | void (* modify_func) (GtkSorter *); |
587 | } options[] = { |
588 | { GTK_TYPE_CUSTOM_SORTER, even_odd_sorter_new, NULL }, |
589 | { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, switch_order }, |
590 | { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_order_ascending }, |
591 | { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_order_descending }, |
592 | { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_expression_get_number, }, |
593 | { GTK_TYPE_NUMERIC_SORTER, numeric_sorter_new, set_expression_get_number_mod_5, } |
594 | }; |
595 | GtkSorter *current; |
596 | guint option; |
597 | |
598 | current = g_list_model_get_item (list: G_LIST_MODEL (ptr: multi), position: 0); |
599 | option = g_random_int_range (begin: 0, G_N_ELEMENTS (options)); |
600 | |
601 | if (current == NULL || options[option].type != G_OBJECT_TYPE (current) || options[option].modify_func == NULL) |
602 | { |
603 | g_clear_object (¤t); |
604 | gtk_multi_sorter_remove (self: GTK_MULTI_SORTER (ptr: multi), position: 0); |
605 | |
606 | current = options[option].create_func (); |
607 | if (options[option].modify_func) |
608 | options[option].modify_func (current); |
609 | |
610 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: current); |
611 | } |
612 | else |
613 | { |
614 | options[option].modify_func (current); |
615 | } |
616 | } |
617 | |
618 | static void |
619 | test_stable (void) |
620 | { |
621 | GtkSortListModel *model1, *model2, *model2b; |
622 | GtkSorter *multi, *a, *b; |
623 | guint i; |
624 | |
625 | a = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
626 | b = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
627 | /* We create 2 setups: |
628 | * 1. sortmodel (multisorter [a, b]) |
629 | * 2. sortmodel (b) => sortmodel (a) |
630 | * Given stability of the sort, these 2 setups should always produce the |
631 | * same results, namely the list should be sorter by a before it's sorted |
632 | * by b. |
633 | * |
634 | * All we do is make a and b random sorters and assert that the 2 setups |
635 | * produce the same order every time. |
636 | */ |
637 | multi = GTK_SORTER (ptr: gtk_multi_sorter_new ()); |
638 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: a); |
639 | gtk_multi_sorter_append (self: GTK_MULTI_SORTER (ptr: multi), sorter: b); |
640 | model1 = new_model (size: 20, sorter: multi); |
641 | g_object_unref (object: multi); |
642 | model2b = gtk_sort_list_model_new (g_object_ref (gtk_sort_list_model_get_model (model1)), g_object_ref (b)); |
643 | model2 = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (model2b)), g_object_ref (a)); |
644 | assert_model_equal (model1, model2); |
645 | |
646 | modify_sorter (multi: a); |
647 | assert_model_equal (model1, model2); |
648 | modify_sorter (multi: b); |
649 | assert_model_equal (model1, model2); |
650 | |
651 | for (i = 0; i < 100; i++) |
652 | { |
653 | modify_sorter (g_random_boolean () ? a : b); |
654 | assert_model_equal (model1, model2); |
655 | } |
656 | |
657 | g_object_unref (object: model1); |
658 | g_object_unref (object: model2); |
659 | g_object_unref (object: model2b); |
660 | } |
661 | |
662 | int |
663 | main (int argc, char *argv[]) |
664 | { |
665 | (g_test_init) (argc: &argc, argv: &argv, NULL); |
666 | setlocale (LC_ALL, locale: "C" ); |
667 | |
668 | number_quark = g_quark_from_static_string (string: "Like a trashcan fire in a prison cell" ); |
669 | |
670 | g_test_add_func (testpath: "/sorter/simple" , test_func: test_simple); |
671 | g_test_add_func (testpath: "/sorter/string" , test_func: test_string); |
672 | g_test_add_func (testpath: "/sorter/change" , test_func: test_change); |
673 | g_test_add_func (testpath: "/sorter/numeric" , test_func: test_numeric); |
674 | g_test_add_func (testpath: "/sorter/multi" , test_func: test_multi); |
675 | g_test_add_func (testpath: "/sorter/multi-destruct" , test_func: test_multi_destruct); |
676 | g_test_add_func (testpath: "/sorter/multi-changes" , test_func: test_multi_changes); |
677 | g_test_add_func (testpath: "/sorter/stable" , test_func: test_stable); |
678 | |
679 | return g_test_run (); |
680 | } |
681 | |