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
count_children(GtkTreeModel * model,GtkTreeIter * parent)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 (model, &iter, parent);
35        valid;
36        valid = gtk_tree_model_iter_next (model, &iter))
37     {
38       count += count_children (model, &iter) + 1;
39     }
40 
41   return count;
42 }
43 
44 static void
set_rows(GtkTreeView * treeview,guint i)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), "rows", GUINT_TO_POINTER (i));
49 }
50 
51 static guint
get_rows(GtkTreeView * treeview)52 get_rows (GtkTreeView *treeview)
53 {
54   return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (treeview), "rows"));
55 }
56 
57 static void
log_operation_for_path(GtkTreePath * path,const char * operation_name)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 ("");
64 
65   g_printerr ("%10s %s\n", operation_name, path_string);
66 
67   g_free (path_string);
68 }
69 
70 static void
log_operation(GtkTreeModel * model,GtkTreeIter * iter,const char * operation_name)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 (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
tree_model_iter_step(GtkTreeModel * model,GtkTreeIter * iter)88 tree_model_iter_step (GtkTreeModel *model,
89                       GtkTreeIter *iter)
90 {
91   GtkTreeIter tmp;
92 
93   if (gtk_tree_model_iter_children (model, &tmp, iter))
94     {
95       *iter = tmp;
96       return TRUE;
97     }
98 
99   do {
100     tmp = *iter;
101 
102     if (gtk_tree_model_iter_next (model, iter))
103       return TRUE;
104     }
105   while (gtk_tree_model_iter_parent (model, iter, &tmp));
106 
107   return FALSE;
108 }
109 
110 /* NB: may include invisible iters (because they are collapsed) */
111 static void
tree_view_random_iter(GtkTreeView * treeview,GtkTreeIter * iter)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 (0, n_rows);
117   GtkTreeModel *model;
118 
119   model = gtk_tree_view_get_model (treeview);
120 
121   if (!gtk_tree_model_get_iter_first (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
delete(GtkTreeView * treeview)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 (treeview);
144 
145   tree_view_random_iter (treeview, &iter);
146 
147   n_rows -= count_children (model, &iter) + 1;
148   log_operation (model, &iter, "remove");
149   gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
150   set_rows (treeview, n_rows);
151 }
152 
153 static void
add_one(GtkTreeModel * model,GtkTreeIter * iter)154 add_one (GtkTreeModel *model,
155          GtkTreeIter *iter)
156 {
157   guint n = gtk_tree_model_iter_n_children (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 (model, &child, iter, g_random_int_range (0, n));
165       add_one (model, &child);
166       return;
167     }
168 
169   gtk_tree_store_insert_with_values (GTK_TREE_STORE (model),
170                                      &new_iter,
171                                      iter,
172                                      g_random_int_range (-1, n),
173                                      0, ++counter,
174                                      -1);
175   log_operation (model, &new_iter, "add");
176 }
177 
178 static void
add(GtkTreeView * treeview)179 add (GtkTreeView *treeview)
180 {
181   GtkTreeModel *model;
182 
183   model = gtk_tree_view_get_model (treeview);
184   add_one (model, NULL);
185 
186   set_rows (treeview, get_rows (treeview) + 1);
187 }
188 
189 static void
add_or_delete(GtkTreeView * treeview)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
expand(GtkTreeView * treeview)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 (treeview);
210 
211   for (valid = gtk_tree_model_get_iter_first (model, &iter);
212        valid;
213        valid = tree_model_iter_step (model, &iter))
214     {
215       if (gtk_tree_model_iter_has_child (model, &iter))
216         {
217           path = gtk_tree_model_get_path (model, &iter);
218           if (!gtk_tree_view_row_expanded (treeview, path))
219             {
220               log_operation (model, &iter, "expand");
221               gtk_tree_view_expand_row (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
collapse(GtkTreeView * treeview)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 (treeview);
239   last = NULL;
240 
241   for (valid = gtk_tree_model_get_iter_first (model, &iter);
242        valid;
243        valid = tree_model_iter_step (model, &iter))
244     {
245       path = gtk_tree_model_get_path (model, &iter);
246       if (gtk_tree_view_row_expanded (treeview, path))
247         {
248           if (last)
249             gtk_tree_path_free (last);
250           last = path;
251         }
252       else
253         gtk_tree_path_free (path);
254     }
255 
256   if (last)
257     {
258       log_operation_for_path (last, "collapse");
259       gtk_tree_view_collapse_row (treeview, last);
260       gtk_tree_path_free (last);
261     }
262 }
263 
264 static void
select_(GtkTreeView * treeview)265 select_ (GtkTreeView *treeview)
266 {
267   GtkTreeIter iter;
268 
269   tree_view_random_iter (treeview, &iter);
270 
271   log_operation (gtk_tree_view_get_model (treeview), &iter, "select");
272   gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview),
273                                   &iter);
274 }
275 
276 static void
unselect(GtkTreeView * treeview)277 unselect (GtkTreeView *treeview)
278 {
279   GtkTreeIter iter;
280 
281   tree_view_random_iter (treeview, &iter);
282 
283   log_operation (gtk_tree_view_get_model (treeview), &iter, "unselect");
284   gtk_tree_selection_unselect_iter (gtk_tree_view_get_selection (treeview),
285                                     &iter);
286 }
287 
288 static void
reset_model(GtkTreeView * treeview)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 (treeview);
297   model = g_object_ref (gtk_tree_view_get_model (treeview));
298 
299   log_operation_for_path (NULL, "reset");
300 
301   selected = gtk_tree_selection_get_selected_rows (selection, NULL);
302   gtk_tree_view_get_cursor (treeview, &cursor, NULL);
303 
304   gtk_tree_view_set_model (treeview, NULL);
305   gtk_tree_view_set_model (treeview, model);
306 
307   if (cursor)
308     {
309       gtk_tree_view_set_cursor (treeview, cursor, NULL, FALSE);
310       gtk_tree_path_free (cursor);
311     }
312   for (list = selected; list; list = list->next)
313     {
314       gtk_tree_selection_select_path (selection, list->data);
315     }
316   g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
317 
318   g_object_unref (model);
319 }
320 
321 /* sanity checks */
322 
323 static void
assert_row_reference_is_path(GtkTreeRowReference * ref,GtkTreePath * path)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 (ref);
339   g_assert (expected != NULL);
340   g_assert (gtk_tree_path_compare (expected, path) == 0);
341   gtk_tree_path_free (expected);
342 }
343 
344 static void
check_cursor(GtkTreeView * treeview)345 check_cursor (GtkTreeView *treeview)
346 {
347   GtkTreeRowReference *ref = g_object_get_data (G_OBJECT (treeview), "cursor");
348   GtkTreePath *cursor;
349 
350   gtk_tree_view_get_cursor (treeview, &cursor, NULL);
351   assert_row_reference_is_path (ref, cursor);
352 
353   if (cursor)
354     gtk_tree_path_free (cursor);
355 }
356 
357 static void
check_selection_item(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer listp)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 ((*list)->data, path);
367   *list = (*list)->next;
368 }
369 
370 static void
check_selection(GtkTreeView * treeview)371 check_selection (GtkTreeView *treeview)
372 {
373   GList *selection = g_object_get_data (G_OBJECT (treeview), "selection");
374 
375   gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (treeview),
376                                        check_selection_item,
377                                        &selection);
378 }
379 
380 static void
check_sanity(GtkTreeView * treeview)381 check_sanity (GtkTreeView *treeview)
382 {
383   check_cursor (treeview);
384   check_selection (treeview);
385 }
386 
387 static gboolean
dance(gpointer treeview)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 (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
cursor_changed_cb(GtkTreeView * treeview,gpointer unused)411 cursor_changed_cb (GtkTreeView *treeview,
412                    gpointer     unused)
413 {
414   GtkTreePath *path;
415   GtkTreeRowReference *ref;
416 
417   gtk_tree_view_get_cursor (treeview, &path, NULL);
418   if (path)
419     {
420       ref = gtk_tree_row_reference_new (gtk_tree_view_get_model (treeview),
421                                         path);
422       gtk_tree_path_free (path);
423     }
424   else
425     ref = NULL;
426   g_object_set_data_full (G_OBJECT (treeview), "cursor", ref, (GDestroyNotify) gtk_tree_row_reference_free);
427 }
428 
429 static void
selection_list_free(gpointer list)430 selection_list_free (gpointer list)
431 {
432   g_list_free_full (list, (GDestroyNotify) gtk_tree_row_reference_free);
433 }
434 
435 static void
selection_changed_cb(GtkTreeSelection * tree_selection,gpointer unused)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 (tree_selection, &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                           "selection",
454                           selected,
455                           selection_list_free);
456 }
457 
458 static void
setup_sanity_checks(GtkTreeView * treeview)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 (gtk_tree_view_get_selection (treeview), NULL);
465 }
466 
467 int
main(int argc,char ** argv)468 main (int    argc,
469       char **argv)
470 {
471   GtkWidget *window;
472   GtkWidget *sw;
473   GtkWidget *treeview;
474   GtkTreeModel *model;
475   guint i;
476 
477   gtk_init (&argc, &argv);
478 
479   if (g_getenv ("RTL"))
480     gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL);
481 
482   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
483   g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
484   gtk_window_set_default_size (GTK_WINDOW (window), 430, 400);
485 
486   sw = gtk_scrolled_window_new (NULL, NULL);
487   gtk_widget_set_hexpand (sw, TRUE);
488   gtk_widget_set_vexpand (sw, TRUE);
489   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
490                                   GTK_POLICY_AUTOMATIC,
491                                   GTK_POLICY_AUTOMATIC);
492   gtk_container_add (GTK_CONTAINER (window), sw);
493 
494   model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_UINT));
495   treeview = gtk_tree_view_new_with_model (model);
496   g_object_unref (model);
497   setup_sanity_checks (GTK_TREE_VIEW (treeview));
498   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
499                                                0,
500                                                "Counter",
501                                                gtk_cell_renderer_text_new (),
502                                                "text", 0,
503                                                NULL);
504   for (i = 0; i < (MIN_ROWS + MAX_ROWS) / 2; i++)
505     add (GTK_TREE_VIEW (treeview));
506   gtk_container_add (GTK_CONTAINER (sw), treeview);
507 
508   gtk_widget_show_all (window);
509 
510   g_idle_add (dance, treeview);
511 
512   gtk_main ();
513 
514   return 0;
515 }
516 
517