1/* GtkRBTree tests.
2 *
3 * Copyright (C) 2011, Red Hat, Inc.
4 * Authors: Benjamin Otte <otte@gnome.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <locale.h>
21
22#include <gtk/gtk.h>
23
24static GQuark number_quark;
25static GQuark changes_quark;
26
27static guint
28get (GListModel *model,
29 guint position)
30{
31 GObject *object = g_list_model_get_item (list: model, position);
32 guint number;
33 g_assert_nonnull (object);
34 number = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
35 g_object_unref (object);
36 return number;
37}
38
39static char *
40model_to_string (GListModel *model)
41{
42 GString *string = g_string_new (NULL);
43 guint i;
44
45 for (i = 0; i < g_list_model_get_n_items (list: model); i++)
46 {
47 if (i > 0)
48 g_string_append (string, val: " ");
49 g_string_append_printf (string, format: "%u", get (model, position: i));
50 }
51
52 return g_string_free (string, FALSE);
53}
54
55static GListStore *
56new_store (guint start,
57 guint end,
58 guint step);
59
60static void
61prepend (GListStore *store,
62 guint number,
63 guint step)
64{
65 GObject *object;
66
67 /* 0 cannot be differentiated from NULL, so don't use it */
68 g_assert_cmpint (number, !=, 0);
69
70 if (step / 10)
71 object = G_OBJECT (new_store (number - 9 * step / 10, number, step / 10));
72 else
73 object = g_object_new (G_TYPE_OBJECT, NULL);
74 g_object_set_qdata (object, quark: number_quark, GUINT_TO_POINTER (number));
75 g_list_store_insert (store, position: 0, item: object);
76 g_object_unref (object);
77}
78
79#define assert_model(model, expected) G_STMT_START{ \
80 char *s = model_to_string (G_LIST_MODEL (model)); \
81 if (!g_str_equal (s, expected)) \
82 g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
83 #model " == " #expected, s, "==", expected); \
84 g_free (s); \
85}G_STMT_END
86
87#define assert_changes(model, expected) G_STMT_START{ \
88 GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
89 if (!g_str_equal (changes->str, expected)) \
90 g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
91 #model " == " #expected, changes->str, "==", expected); \
92 g_string_set_size (changes, 0); \
93}G_STMT_END
94
95static GListStore *
96new_empty_store (void)
97{
98 return g_list_store_new (G_TYPE_OBJECT);
99}
100
101static GListStore *
102new_store (guint start,
103 guint end,
104 guint step)
105{
106 GListStore *store = new_empty_store ();
107 guint i;
108
109 for (i = start; i <= end; i += step)
110 prepend (store, number: i, step);
111
112 return store;
113}
114
115static void
116items_changed (GListModel *model,
117 guint position,
118 guint removed,
119 guint added,
120 GString *changes)
121{
122 g_assert_true (removed != 0 || added != 0);
123
124 if (changes->len)
125 g_string_append (string: changes, val: ", ");
126
127 if (removed == 1 && added == 0)
128 {
129 g_string_append_printf (string: changes, format: "-%u", position);
130 }
131 else if (removed == 0 && added == 1)
132 {
133 g_string_append_printf (string: changes, format: "+%u", position);
134 }
135 else
136 {
137 g_string_append_printf (string: changes, format: "%u", position);
138 if (removed > 0)
139 g_string_append_printf (string: changes, format: "-%u", removed);
140 if (added > 0)
141 g_string_append_printf (string: changes, format: "+%u", added);
142 }
143}
144
145static void
146free_changes (gpointer data)
147{
148 GString *changes = data;
149
150 /* all changes must have been checked via assert_changes() before */
151 g_assert_cmpstr (changes->str, ==, "");
152
153 g_string_free (string: changes, TRUE);
154}
155
156static GListModel *
157create_sub_model_cb (gpointer item,
158 gpointer unused)
159{
160 if (G_IS_LIST_MODEL (ptr: item))
161 return g_object_ref (item);
162
163 return NULL;
164}
165
166static GtkTreeListModel *
167new_model (guint size,
168 gboolean expanded)
169{
170 GtkTreeListModel *tree;
171 GString *changes;
172
173 tree = gtk_tree_list_model_new (root: G_LIST_MODEL (ptr: new_store (start: size, end: size, step: size)), TRUE, autoexpand: expanded, create_func: create_sub_model_cb, NULL, NULL);
174 changes = g_string_new (init: "");
175 g_object_set_qdata_full (G_OBJECT(tree), quark: changes_quark, data: changes, destroy: free_changes);
176 g_signal_connect (tree, "items-changed", G_CALLBACK (items_changed), changes);
177
178 return tree;
179}
180
181static void
182test_expand (void)
183{
184 GtkTreeListModel *tree = new_model (size: 100, FALSE);
185 guint i;
186
187 assert_model (tree, "100");
188
189 for (i = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: tree)); i > 0; i--)
190 {
191 GtkTreeListRow *row = gtk_tree_list_model_get_row (self: tree, position: i - 1);
192 gtk_tree_list_row_set_expanded (self: row, TRUE);
193 g_object_unref (object: row);
194 }
195 assert_model (tree, "100 100 90 80 70 60 50 40 30 20 10");
196 assert_changes (tree, "1+10");
197
198 for (i = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: tree)); i > 0; i--)
199 {
200 GtkTreeListRow *row = gtk_tree_list_model_get_row (self: tree, position: i - 1);
201 gtk_tree_list_row_set_expanded (self: row, TRUE);
202 g_object_unref (object: row);
203 }
204 assert_model (tree, "100 100 100 99 98 97 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
205 assert_changes (tree, "11+10, 10+10, 9+10, 8+10, 7+10, 6+10, 5+10, 4+10, 3+10, 2+10");
206
207 for (i = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: tree)); i > 0; i--)
208 {
209 GtkTreeListRow *row = gtk_tree_list_model_get_row (self: tree, position: i - 1);
210 gtk_tree_list_row_set_expanded (self: row, TRUE);
211 g_object_unref (object: row);
212 }
213 assert_model (tree, "100 100 100 99 98 97 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
214 assert_changes (tree, "");
215
216 g_object_unref (object: tree);
217}
218
219static void
220test_remove_some (void)
221{
222 GtkTreeListModel *tree = new_model (size: 100, TRUE);
223 gpointer item;
224
225 assert_model (tree, "100 100 100 99 98 97 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
226 assert_changes (tree, "");
227
228 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: tree), position: 1);
229 g_assert_true (G_IS_LIST_MODEL (item));
230 g_list_store_remove (store: item, position: 3);
231 assert_model (tree, "100 100 100 99 98 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 70 70 69 68 67 66 65 64 63 62 61 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
232 assert_changes (tree, "-5");
233
234 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: tree), position: 0);
235 g_assert_true (G_IS_LIST_MODEL (item));
236 g_list_store_remove (store: item, position: 3);
237 assert_model (tree, "100 100 100 99 98 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2 1");
238 assert_changes (tree, "33-11");
239
240 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: tree), position: 88);
241 g_assert_true (G_IS_LIST_MODEL (item));
242 g_list_store_remove (store: item, position: 9);
243 assert_model (tree, "100 100 100 99 98 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11 10 10 9 8 7 6 5 4 3 2");
244 assert_changes (tree, "-98");
245
246 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: tree), position: 0);
247 g_assert_true (G_IS_LIST_MODEL (item));
248 g_list_store_remove (store: item, position: 8);
249 assert_model (tree, "100 100 100 99 98 96 95 94 93 92 91 90 90 89 88 87 86 85 84 83 82 81 80 80 79 78 77 76 75 74 73 72 71 60 60 59 58 57 56 55 54 53 52 51 50 50 49 48 47 46 45 44 43 42 41 40 40 39 38 37 36 35 34 33 32 31 30 30 29 28 27 26 25 24 23 22 21 20 20 19 18 17 16 15 14 13 12 11");
250 assert_changes (tree, "88-10");
251
252 g_object_unref (object: tree);
253}
254
255/* Test for https://gitlab.gnome.org/GNOME/gtk/-/issues/4595 */
256typedef struct _DemoNode DemoNode;
257
258struct _DemoNode {
259 GObject parent_instance;
260 char *value;
261 GListStore *children;
262};
263
264G_DECLARE_FINAL_TYPE (DemoNode, demo_node, DEMO, NODE, GObject);
265
266G_DEFINE_TYPE (DemoNode, demo_node, G_TYPE_OBJECT);
267
268static void
269demo_node_init (DemoNode *node)
270{
271}
272
273static void
274demo_node_finalize (GObject *object)
275{
276 g_free (mem: DEMO_NODE (ptr: object)->value);
277
278 G_OBJECT_CLASS (demo_node_parent_class)->finalize (object);
279}
280
281static void
282demo_node_class_init (DemoNodeClass *klass)
283{
284 G_OBJECT_CLASS (klass)->finalize = demo_node_finalize;
285}
286
287static DemoNode *
288demo_node_new (const char *value,
289 GListStore *children)
290{
291 DemoNode *result;
292
293 result = g_object_new (object_type: demo_node_get_type(), NULL);
294 result->value = g_strdup (str: value);
295 if (children)
296 result->children = g_object_ref (children);
297
298 return result;
299}
300
301static GListStore *
302create_model (void)
303{
304 DemoNode *aa, *a, *b, *c;
305 GListStore *a_children, *root;
306
307 aa = demo_node_new (value: "aa", NULL);
308
309 a_children = g_list_store_new (item_type: demo_node_get_type ());
310 g_list_store_append (store: a_children, item: aa);
311
312 a = demo_node_new (value: "a", children: a_children);
313 b = demo_node_new (value: "b", NULL);
314 c = demo_node_new (value: "c", NULL);
315
316 root = g_list_store_new (item_type: demo_node_get_type ());
317 g_list_store_append (store: root, item: a);
318 g_list_store_append (store: root, item: b);
319 g_list_store_append (store: root, item: c);
320
321 g_object_unref (object: aa);
322 g_object_unref (object: a_children);
323 g_object_unref (object: a);
324 g_object_unref (object: b);
325 g_object_unref (object: c);
326
327 return root;
328}
329
330static GListModel *
331model_children (gpointer item,
332 gpointer unused)
333{
334 GListStore *children;
335
336 children = DEMO_NODE (ptr: item)->children;
337 if (children)
338 return G_LIST_MODEL (g_object_ref (children));
339
340 return NULL;
341}
342
343static void
344test_collapse_change (void)
345{
346 GListStore *model;
347 GtkTreeListModel *treemodel;
348 DemoNode *a, *ab;
349 GtkTreeListRow *row;
350
351 model = create_model ();
352 a = g_list_model_get_item (list: G_LIST_MODEL (ptr: model), position: 0);
353
354 treemodel = gtk_tree_list_model_new (root: G_LIST_MODEL (ptr: model),
355 FALSE,
356 FALSE,
357 create_func: model_children,
358 NULL,
359 NULL);
360
361 row = gtk_tree_list_model_get_row (self: treemodel, position: 0);
362 gtk_tree_list_row_set_expanded (self: row, TRUE);
363 gtk_tree_list_row_set_expanded (self: row, FALSE);
364 g_object_unref (object: row);
365
366 ab = demo_node_new (value: "ab", NULL);
367 g_list_store_append (store: a->children, item: ab);
368 g_object_unref (object: ab);
369
370 g_object_unref (object: treemodel);
371 g_object_unref (object: a);
372}
373
374int
375main (int argc, char *argv[])
376{
377 (g_test_init) (argc: &argc, argv: &argv, NULL);
378 setlocale (LC_ALL, locale: "C");
379
380 number_quark = g_quark_from_static_string (string: "Hell and fire was spawned to be released.");
381 changes_quark = g_quark_from_static_string (string: "What did I see? Can I believe what I saw?");
382
383 g_test_add_func (testpath: "/treelistmodel/expand", test_func: test_expand);
384 g_test_add_func (testpath: "/treelistmodel/remove_some", test_func: test_remove_some);
385 g_test_add_func (testpath: "/treelistmodel/collapse-change", test_func: test_collapse_change);
386
387 return g_test_run ();
388}
389

source code of gtk/testsuite/gtk/treelistmodel.c