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