1 | /* testtreeview.c |
2 | * Copyright (C) 2011 Red Hat, Inc |
3 | * Author: Benjamin Otte <otte@gnome.org> |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library 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 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include <gtk/gtk.h> |
20 | |
21 | #define MIN_ROWS 50 |
22 | #define MAX_ROWS 150 |
23 | |
24 | typedef void (* DoStuffFunc) (GtkTreeView *treeview); |
25 | |
26 | static guint |
27 | count_children (GtkTreeModel *model, |
28 | GtkTreeIter *parent) |
29 | { |
30 | GtkTreeIter iter; |
31 | guint count = 0; |
32 | gboolean valid; |
33 | |
34 | for (valid = gtk_tree_model_iter_children (tree_model: model, iter: &iter, parent); |
35 | valid; |
36 | valid = gtk_tree_model_iter_next (tree_model: model, iter: &iter)) |
37 | { |
38 | count += count_children (model, parent: &iter) + 1; |
39 | } |
40 | |
41 | return count; |
42 | } |
43 | |
44 | static void |
45 | set_rows (GtkTreeView *treeview, guint i) |
46 | { |
47 | g_assert (i == count_children (gtk_tree_view_get_model (treeview), NULL)); |
48 | g_object_set_data (G_OBJECT (treeview), key: "rows" , GUINT_TO_POINTER (i)); |
49 | } |
50 | |
51 | static guint |
52 | get_rows (GtkTreeView *treeview) |
53 | { |
54 | return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (treeview), "rows" )); |
55 | } |
56 | |
57 | static void |
58 | log_operation_for_path (GtkTreePath *path, |
59 | const char *operation_name) |
60 | { |
61 | char *path_string; |
62 | |
63 | path_string = path ? gtk_tree_path_to_string (path) : g_strdup (str: "" ); |
64 | |
65 | g_printerr (format: "%10s %s\n" , operation_name, path_string); |
66 | |
67 | g_free (mem: path_string); |
68 | } |
69 | |
70 | static void |
71 | log_operation (GtkTreeModel *model, |
72 | GtkTreeIter *iter, |
73 | const char *operation_name) |
74 | { |
75 | GtkTreePath *path; |
76 | |
77 | path = gtk_tree_model_get_path (tree_model: model, iter); |
78 | |
79 | log_operation_for_path (path, operation_name); |
80 | |
81 | gtk_tree_path_free (path); |
82 | } |
83 | |
84 | /* moves iter to the next iter in the model in the display order |
85 | * inside a treeview. Returns FALSE if no more rows exist. |
86 | */ |
87 | static gboolean |
88 | tree_model_iter_step (GtkTreeModel *model, |
89 | GtkTreeIter *iter) |
90 | { |
91 | GtkTreeIter tmp; |
92 | |
93 | if (gtk_tree_model_iter_children (tree_model: model, iter: &tmp, parent: iter)) |
94 | { |
95 | *iter = tmp; |
96 | return TRUE; |
97 | } |
98 | |
99 | do { |
100 | tmp = *iter; |
101 | |
102 | if (gtk_tree_model_iter_next (tree_model: model, iter)) |
103 | return TRUE; |
104 | } |
105 | while (gtk_tree_model_iter_parent (tree_model: model, iter, child: &tmp)); |
106 | |
107 | return FALSE; |
108 | } |
109 | |
110 | /* NB: may include invisible iters (because they are collapsed) */ |
111 | static void |
112 | tree_view_random_iter (GtkTreeView *treeview, |
113 | GtkTreeIter *iter) |
114 | { |
115 | guint n_rows = get_rows (treeview); |
116 | guint i = g_random_int_range (begin: 0, end: n_rows); |
117 | GtkTreeModel *model; |
118 | |
119 | model = gtk_tree_view_get_model (tree_view: treeview); |
120 | |
121 | if (!gtk_tree_model_get_iter_first (tree_model: model, iter)) |
122 | return; |
123 | |
124 | while (i-- > 0) |
125 | { |
126 | if (!tree_model_iter_step (model, iter)) |
127 | { |
128 | g_assert_not_reached (); |
129 | return; |
130 | } |
131 | } |
132 | |
133 | return; |
134 | } |
135 | |
136 | static void |
137 | delete (GtkTreeView *treeview) |
138 | { |
139 | guint n_rows = get_rows (treeview); |
140 | GtkTreeModel *model; |
141 | GtkTreeIter iter; |
142 | |
143 | model = gtk_tree_view_get_model (tree_view: treeview); |
144 | |
145 | tree_view_random_iter (treeview, iter: &iter); |
146 | |
147 | n_rows -= count_children (model, parent: &iter) + 1; |
148 | log_operation (model, iter: &iter, operation_name: "remove" ); |
149 | gtk_tree_store_remove (GTK_TREE_STORE (model), iter: &iter); |
150 | set_rows (treeview, i: n_rows); |
151 | } |
152 | |
153 | static void |
154 | add_one (GtkTreeModel *model, |
155 | GtkTreeIter *iter) |
156 | { |
157 | guint n = gtk_tree_model_iter_n_children (tree_model: model, iter); |
158 | GtkTreeIter new_iter; |
159 | static guint counter = 0; |
160 | |
161 | if (n > 0 && g_random_boolean ()) |
162 | { |
163 | GtkTreeIter child; |
164 | gtk_tree_model_iter_nth_child (tree_model: model, iter: &child, parent: iter, n: g_random_int_range (begin: 0, end: n)); |
165 | add_one (model, iter: &child); |
166 | return; |
167 | } |
168 | |
169 | gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), |
170 | iter: &new_iter, |
171 | parent: iter, |
172 | position: g_random_int_range (begin: -1, end: n), |
173 | 0, ++counter, |
174 | -1); |
175 | log_operation (model, iter: &new_iter, operation_name: "add" ); |
176 | } |
177 | |
178 | static void |
179 | add (GtkTreeView *treeview) |
180 | { |
181 | GtkTreeModel *model; |
182 | |
183 | model = gtk_tree_view_get_model (tree_view: treeview); |
184 | add_one (model, NULL); |
185 | |
186 | set_rows (treeview, i: get_rows (treeview) + 1); |
187 | } |
188 | |
189 | static void |
190 | add_or_delete (GtkTreeView *treeview) |
191 | { |
192 | guint n_rows = get_rows (treeview); |
193 | |
194 | if (g_random_int_range (MIN_ROWS, MAX_ROWS) >= n_rows) |
195 | add (treeview); |
196 | else |
197 | delete (treeview); |
198 | } |
199 | |
200 | /* XXX: We only expand/collapse from the top and not randomly */ |
201 | static void |
202 | expand (GtkTreeView *treeview) |
203 | { |
204 | GtkTreeModel *model; |
205 | GtkTreeIter iter; |
206 | GtkTreePath *path; |
207 | gboolean valid; |
208 | |
209 | model = gtk_tree_view_get_model (tree_view: treeview); |
210 | |
211 | for (valid = gtk_tree_model_get_iter_first (tree_model: model, iter: &iter); |
212 | valid; |
213 | valid = tree_model_iter_step (model, iter: &iter)) |
214 | { |
215 | if (gtk_tree_model_iter_has_child (tree_model: model, iter: &iter)) |
216 | { |
217 | path = gtk_tree_model_get_path (tree_model: model, iter: &iter); |
218 | if (!gtk_tree_view_row_expanded (tree_view: treeview, path)) |
219 | { |
220 | log_operation (model, iter: &iter, operation_name: "expand" ); |
221 | gtk_tree_view_expand_row (tree_view: treeview, path, FALSE); |
222 | gtk_tree_path_free (path); |
223 | return; |
224 | } |
225 | gtk_tree_path_free (path); |
226 | } |
227 | } |
228 | } |
229 | |
230 | static void |
231 | collapse (GtkTreeView *treeview) |
232 | { |
233 | GtkTreeModel *model; |
234 | GtkTreeIter iter; |
235 | GtkTreePath *last, *path; |
236 | gboolean valid; |
237 | |
238 | model = gtk_tree_view_get_model (tree_view: treeview); |
239 | last = NULL; |
240 | |
241 | for (valid = gtk_tree_model_get_iter_first (tree_model: model, iter: &iter); |
242 | valid; |
243 | valid = tree_model_iter_step (model, iter: &iter)) |
244 | { |
245 | path = gtk_tree_model_get_path (tree_model: model, iter: &iter); |
246 | if (gtk_tree_view_row_expanded (tree_view: treeview, path)) |
247 | { |
248 | if (last) |
249 | gtk_tree_path_free (path: last); |
250 | last = path; |
251 | } |
252 | else |
253 | gtk_tree_path_free (path); |
254 | } |
255 | |
256 | if (last) |
257 | { |
258 | log_operation_for_path (path: last, operation_name: "collapse" ); |
259 | gtk_tree_view_collapse_row (tree_view: treeview, path: last); |
260 | gtk_tree_path_free (path: last); |
261 | } |
262 | } |
263 | |
264 | static void |
265 | select_ (GtkTreeView *treeview) |
266 | { |
267 | GtkTreeIter iter; |
268 | |
269 | tree_view_random_iter (treeview, iter: &iter); |
270 | |
271 | log_operation (model: gtk_tree_view_get_model (tree_view: treeview), iter: &iter, operation_name: "select" ); |
272 | gtk_tree_selection_select_iter (selection: gtk_tree_view_get_selection (tree_view: treeview), |
273 | iter: &iter); |
274 | } |
275 | |
276 | static void |
277 | unselect (GtkTreeView *treeview) |
278 | { |
279 | GtkTreeIter iter; |
280 | |
281 | tree_view_random_iter (treeview, iter: &iter); |
282 | |
283 | log_operation (model: gtk_tree_view_get_model (tree_view: treeview), iter: &iter, operation_name: "unselect" ); |
284 | gtk_tree_selection_unselect_iter (selection: gtk_tree_view_get_selection (tree_view: treeview), |
285 | iter: &iter); |
286 | } |
287 | |
288 | static void |
289 | reset_model (GtkTreeView *treeview) |
290 | { |
291 | GtkTreeSelection *selection; |
292 | GtkTreeModel *model; |
293 | GList *list, *selected; |
294 | GtkTreePath *cursor; |
295 | |
296 | selection = gtk_tree_view_get_selection (tree_view: treeview); |
297 | model = g_object_ref (gtk_tree_view_get_model (treeview)); |
298 | |
299 | log_operation_for_path (NULL, operation_name: "reset" ); |
300 | |
301 | selected = gtk_tree_selection_get_selected_rows (selection, NULL); |
302 | gtk_tree_view_get_cursor (tree_view: treeview, path: &cursor, NULL); |
303 | |
304 | gtk_tree_view_set_model (tree_view: treeview, NULL); |
305 | gtk_tree_view_set_model (tree_view: treeview, model); |
306 | |
307 | if (cursor) |
308 | { |
309 | gtk_tree_view_set_cursor (tree_view: treeview, path: cursor, NULL, FALSE); |
310 | gtk_tree_path_free (path: cursor); |
311 | } |
312 | for (list = selected; list; list = list->next) |
313 | { |
314 | gtk_tree_selection_select_path (selection, path: list->data); |
315 | } |
316 | g_list_free_full (list: selected, free_func: (GDestroyNotify) gtk_tree_path_free); |
317 | |
318 | g_object_unref (object: model); |
319 | } |
320 | |
321 | /* sanity checks */ |
322 | |
323 | static void |
324 | assert_row_reference_is_path (GtkTreeRowReference *ref, |
325 | GtkTreePath *path) |
326 | { |
327 | GtkTreePath *expected; |
328 | |
329 | if (ref == NULL) |
330 | { |
331 | g_assert (path == NULL); |
332 | return; |
333 | } |
334 | |
335 | g_assert (path != NULL); |
336 | g_assert (gtk_tree_row_reference_valid (ref)); |
337 | |
338 | expected = gtk_tree_row_reference_get_path (reference: ref); |
339 | g_assert (expected != NULL); |
340 | g_assert (gtk_tree_path_compare (expected, path) == 0); |
341 | gtk_tree_path_free (path: expected); |
342 | } |
343 | |
344 | static void |
345 | check_cursor (GtkTreeView *treeview) |
346 | { |
347 | GtkTreeRowReference *ref = g_object_get_data (G_OBJECT (treeview), key: "cursor" ); |
348 | GtkTreePath *cursor; |
349 | |
350 | gtk_tree_view_get_cursor (tree_view: treeview, path: &cursor, NULL); |
351 | assert_row_reference_is_path (ref, path: cursor); |
352 | |
353 | if (cursor) |
354 | gtk_tree_path_free (path: cursor); |
355 | } |
356 | |
357 | static void |
358 | check_selection_item (GtkTreeModel *model, |
359 | GtkTreePath *path, |
360 | GtkTreeIter *iter, |
361 | gpointer listp) |
362 | { |
363 | GList **list = listp; |
364 | |
365 | g_assert (*list); |
366 | assert_row_reference_is_path (ref: (*list)->data, path); |
367 | *list = (*list)->next; |
368 | } |
369 | |
370 | static void |
371 | check_selection (GtkTreeView *treeview) |
372 | { |
373 | GList *selection = g_object_get_data (G_OBJECT (treeview), key: "selection" ); |
374 | |
375 | gtk_tree_selection_selected_foreach (selection: gtk_tree_view_get_selection (tree_view: treeview), |
376 | func: check_selection_item, |
377 | data: &selection); |
378 | } |
379 | |
380 | static void |
381 | check_sanity (GtkTreeView *treeview) |
382 | { |
383 | check_cursor (treeview); |
384 | check_selection (treeview); |
385 | } |
386 | |
387 | static gboolean |
388 | dance (gpointer treeview) |
389 | { |
390 | static const DoStuffFunc funcs[] = { |
391 | add_or_delete, |
392 | add_or_delete, |
393 | expand, |
394 | collapse, |
395 | select_, |
396 | unselect, |
397 | reset_model |
398 | }; |
399 | guint i; |
400 | |
401 | i = g_random_int_range (begin: 0, G_N_ELEMENTS(funcs)); |
402 | |
403 | funcs[i] (treeview); |
404 | |
405 | check_sanity (treeview); |
406 | |
407 | return G_SOURCE_CONTINUE; |
408 | } |
409 | |
410 | static void |
411 | cursor_changed_cb (GtkTreeView *treeview, |
412 | gpointer unused) |
413 | { |
414 | GtkTreePath *path; |
415 | GtkTreeRowReference *ref; |
416 | |
417 | gtk_tree_view_get_cursor (tree_view: treeview, path: &path, NULL); |
418 | if (path) |
419 | { |
420 | ref = gtk_tree_row_reference_new (model: gtk_tree_view_get_model (tree_view: treeview), |
421 | path); |
422 | gtk_tree_path_free (path); |
423 | } |
424 | else |
425 | ref = NULL; |
426 | g_object_set_data_full (G_OBJECT (treeview), key: "cursor" , data: ref, destroy: (GDestroyNotify) gtk_tree_row_reference_free); |
427 | } |
428 | |
429 | static void |
430 | selection_list_free (gpointer list) |
431 | { |
432 | g_list_free_full (list, free_func: (GDestroyNotify) gtk_tree_row_reference_free); |
433 | } |
434 | |
435 | static void |
436 | selection_changed_cb (GtkTreeSelection *tree_selection, |
437 | gpointer unused) |
438 | { |
439 | GList *selected, *list; |
440 | GtkTreeModel *model; |
441 | |
442 | selected = gtk_tree_selection_get_selected_rows (selection: tree_selection, model: &model); |
443 | |
444 | for (list = selected; list; list = list->next) |
445 | { |
446 | GtkTreePath *path = list->data; |
447 | |
448 | list->data = gtk_tree_row_reference_new (model, path); |
449 | gtk_tree_path_free (path); |
450 | } |
451 | |
452 | g_object_set_data_full (G_OBJECT (gtk_tree_selection_get_tree_view (tree_selection)), |
453 | key: "selection" , |
454 | data: selected, |
455 | destroy: selection_list_free); |
456 | } |
457 | |
458 | static void |
459 | setup_sanity_checks (GtkTreeView *treeview) |
460 | { |
461 | g_signal_connect (treeview, "cursor-changed" , G_CALLBACK (cursor_changed_cb), NULL); |
462 | cursor_changed_cb (treeview, NULL); |
463 | g_signal_connect (gtk_tree_view_get_selection (treeview), "changed" , G_CALLBACK (selection_changed_cb), NULL); |
464 | selection_changed_cb (tree_selection: gtk_tree_view_get_selection (tree_view: treeview), NULL); |
465 | } |
466 | |
467 | static void |
468 | quit_cb (GtkWidget *widget, |
469 | gpointer data) |
470 | { |
471 | gboolean *done = data; |
472 | |
473 | *done = TRUE; |
474 | |
475 | g_main_context_wakeup (NULL); |
476 | } |
477 | |
478 | int |
479 | main (int argc, |
480 | char **argv) |
481 | { |
482 | GtkWidget *window; |
483 | GtkWidget *sw; |
484 | GtkWidget *treeview; |
485 | GtkTreeModel *model; |
486 | guint i; |
487 | gboolean done = FALSE; |
488 | |
489 | gtk_init (); |
490 | |
491 | if (g_getenv (variable: "RTL" )) |
492 | gtk_widget_set_default_direction (dir: GTK_TEXT_DIR_RTL); |
493 | |
494 | window = gtk_window_new (); |
495 | g_signal_connect (window, "destroy" , G_CALLBACK (quit_cb), &done); |
496 | gtk_window_set_default_size (GTK_WINDOW (window), width: 430, height: 400); |
497 | |
498 | sw = gtk_scrolled_window_new (); |
499 | gtk_widget_set_hexpand (widget: sw, TRUE); |
500 | gtk_widget_set_vexpand (widget: sw, TRUE); |
501 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), |
502 | hscrollbar_policy: GTK_POLICY_AUTOMATIC, |
503 | vscrollbar_policy: GTK_POLICY_AUTOMATIC); |
504 | gtk_window_set_child (GTK_WINDOW (window), child: sw); |
505 | |
506 | model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_UINT)); |
507 | treeview = gtk_tree_view_new_with_model (model); |
508 | g_object_unref (object: model); |
509 | setup_sanity_checks (GTK_TREE_VIEW (treeview)); |
510 | gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), |
511 | position: 0, |
512 | title: "Counter" , |
513 | cell: gtk_cell_renderer_text_new (), |
514 | "text" , 0, |
515 | NULL); |
516 | for (i = 0; i < (MIN_ROWS + MAX_ROWS) / 2; i++) |
517 | add (GTK_TREE_VIEW (treeview)); |
518 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: treeview); |
519 | |
520 | gtk_widget_show (widget: window); |
521 | |
522 | g_idle_add (function: dance, data: treeview); |
523 | |
524 | while (!done) |
525 | g_main_context_iteration (NULL, TRUE); |
526 | |
527 | return 0; |
528 | } |
529 | |
530 | |