]> Pileus Git - ~andy/gtk/blob - gtk/tests/treeview-scrolling.c
Revert "Add length to gtk_tree_path_get_indices"
[~andy/gtk] / gtk / tests / treeview-scrolling.c
1 /* Scrolling test suite for GtkTreeView
2  * Copyright (C) 2006  Kristian Rietveld  <kris@gtk.org>
3  * Copyright (C) 2007  Imendio AB,  Kristian Rietveld
4  * Copyright (C) 2009  Kristian Rietveld  <kris@gtk.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, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /* Original v1.0 -- December 26, 2006
23  * Conversion to GLib/GTK+ test framework during December, 2007
24  */
25
26
27 #include <gtk/gtk.h>
28 #include <unistd.h>
29
30 #define VIEW_WIDTH 320
31 #define VIEW_HEIGHT 240
32
33 #define N_ROWS 1000
34 #define BIG_N_ROWS N_ROWS * 100
35
36 /*
37  * To do:
38  *   - Test that nothing happens if the row is fully visible.
39  *   - The tests are dependent on the theme/font (size measurements,
40  *     chosen paths).
41  *   - Convert to proper GTK+ coding style.
42  *   - Briefly test scrolling in tree stores as well.
43  */
44
45
46 /* Constructing models for testing */
47 static GtkTreeModel *
48 create_model (gboolean constant)
49 {
50         int i;
51
52         GtkTreeIter iter;
53         GtkListStore *store;
54
55         store = gtk_list_store_new (1, G_TYPE_STRING);
56
57         for (i = 0; i < N_ROWS; i++) {
58                 gtk_list_store_append (store, &iter);
59                 if (constant || i % 2 == 0)
60                         gtk_list_store_set (store, &iter, 0, "Foo", -1);
61                 else
62                         gtk_list_store_set (store, &iter, 0, "Sliff\nSloff\nBleh", -1);
63         }
64
65         return GTK_TREE_MODEL (store);
66 }
67
68 static GtkTreeModel *
69 create_big_model (gboolean constant)
70 {
71         int i;
72
73         GtkTreeIter iter;
74         GtkListStore *store;
75
76         store = gtk_list_store_new (1, G_TYPE_STRING);
77
78         for (i = 0; i < BIG_N_ROWS; i++) {
79                 gtk_list_store_append (store, &iter);
80                 if (constant || i % 2 == 0)
81                         gtk_list_store_set (store, &iter, 0, "Foo", -1);
82                 else
83                         gtk_list_store_set (store, &iter, 0, "Sliff\nSloff\nBleh", -1);
84         }
85
86         return GTK_TREE_MODEL (store);
87 }
88
89 /*
90  * Fixtures
91  */
92
93 typedef struct
94 {
95         GtkWidget *window;
96         GtkWidget *tree_view;
97 }
98 ScrollFixture;
99
100 static void
101 scroll_fixture_setup (ScrollFixture *fixture,
102                       GtkTreeModel  *model,
103                       gconstpointer  test_data)
104 {
105         GtkWidget *sw;
106         GtkCellRenderer *renderer;
107         GtkTreeViewColumn *column;
108
109         fixture->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
110
111         sw = gtk_scrolled_window_new (NULL, NULL);
112         gtk_container_add (GTK_CONTAINER (fixture->window), sw);
113
114         fixture->tree_view = gtk_tree_view_new_with_model (model);
115         g_object_unref (model);
116         gtk_widget_set_size_request (fixture->tree_view, VIEW_WIDTH, VIEW_HEIGHT);
117
118         renderer = gtk_cell_renderer_text_new ();
119         g_object_set (renderer, "editable", TRUE, NULL);
120         column = gtk_tree_view_column_new_with_attributes ("Title",
121                                                            renderer,
122                                                            "text", 0,
123                                                            NULL);
124
125         gtk_tree_view_append_column (GTK_TREE_VIEW (fixture->tree_view), column);
126         gtk_container_add (GTK_CONTAINER (sw), fixture->tree_view);
127 }
128
129 /* sets up a fixture with a model with constant row heights */
130 static void
131 scroll_fixture_constant_setup (ScrollFixture *fixture,
132                                gconstpointer  test_data)
133 {
134         scroll_fixture_setup (fixture, create_model (TRUE), test_data);
135 }
136
137 /* sets up a fixture with a model with varying row heights */
138 static void
139 scroll_fixture_mixed_setup (ScrollFixture *fixture,
140                             gconstpointer  test_data)
141 {
142         scroll_fixture_setup (fixture, create_model (FALSE), test_data);
143 }
144
145 /* sets up a fixture with a large model with constant row heights */
146 static void
147 scroll_fixture_constant_big_setup (ScrollFixture *fixture,
148                                    gconstpointer  test_data)
149 {
150         scroll_fixture_setup (fixture, create_big_model (TRUE), test_data);
151 }
152
153 /* sets up a fixture with a large model with varying row heights */
154 static void
155 scroll_fixture_mixed_big_setup (ScrollFixture *fixture,
156                                 gconstpointer  test_data)
157 {
158         scroll_fixture_setup (fixture, create_big_model (FALSE), test_data);
159 }
160
161 /* sets up a fixture with only a single row for the "single row scroll" test */
162 static void
163 scroll_fixture_single_setup (ScrollFixture *fixture,
164                              gconstpointer  test_data)
165 {
166         GtkTreeStore *store;
167         GtkTreeIter iter, child;
168
169         store = gtk_tree_store_new (1, G_TYPE_STRING);
170
171         gtk_tree_store_append (store, &iter, NULL);
172         gtk_tree_store_set (store, &iter, 0, "Foo", -1);
173
174         gtk_tree_store_append (store, &child, &iter);
175         gtk_tree_store_set (store, &child, 0, "Two\nLines", -1);
176
177         /* The teardown will also destroy the model */
178         scroll_fixture_setup (fixture, GTK_TREE_MODEL (store), test_data);
179 }
180
181 /* sets up a fixture with a tree store */
182 static void
183 scroll_fixture_tree_setup (ScrollFixture *fixture,
184                            gconstpointer   test_data)
185 {
186         GtkTreeStore *store;
187         GtkTreeIter iter, child;
188         int i;
189
190         store = gtk_tree_store_new (1, G_TYPE_STRING);
191
192         gtk_tree_store_append (store, &iter, NULL);
193         gtk_tree_store_set (store, &iter, 0, "Root node", -1);
194
195         for (i = 0; i < 5; i++) {
196                 gtk_tree_store_append (store, &child, &iter);
197                 gtk_tree_store_set (store, &child, 0, "Child node", -1);
198         }
199
200         for (i = 0; i < 5; i++) {
201                 gtk_tree_store_append (store, &iter, NULL);
202                 gtk_tree_store_set (store, &iter, 0, "Other node", -1);
203         }
204
205         /* The teardown will also destroy the model */
206         scroll_fixture_setup (fixture, GTK_TREE_MODEL (store), test_data);
207 }
208
209 static void
210 scroll_fixture_teardown (ScrollFixture *fixture,
211                          gconstpointer  test_data)
212 {
213         gtk_widget_destroy (fixture->window);
214 }
215
216 /*
217  * Position check and helpers.
218  */
219 enum Pos
220 {
221         POS_TOP,
222         POS_CENTER,
223         POS_BOTTOM
224 };
225
226 static int
227 get_row_start_for_index (GtkTreeView *tree_view, int index)
228 {
229         gint height1, height2;
230         gint row_start;
231         GtkTreePath *path;
232         GdkRectangle rect;
233
234         path = gtk_tree_path_new_from_indices (0, -1);
235         gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
236         height1 = rect.height;
237
238         gtk_tree_path_next (path);
239         gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
240         height2 = rect.height;
241         gtk_tree_path_free (path);
242
243         row_start = (index / 2) * height1 + (index / 2) * height2;
244         if (index % 2)
245                 row_start += height1;
246
247         return row_start;
248 }
249
250 static enum Pos
251 get_pos_from_path (GtkTreeView   *tree_view,
252                    GtkTreePath   *path,
253                    gint           row_height,
254                    GtkAdjustment *vadj)
255 {
256         int row_start;
257
258         row_start = get_row_start_for_index (tree_view,
259                                              gtk_tree_path_get_indices (path)[0]);
260
261         if (row_start + row_height < vadj->page_size)
262                 return POS_TOP;
263
264         if (row_start >= vadj->upper - vadj->page_size)
265                 return POS_BOTTOM;
266
267         return POS_CENTER;
268 }
269
270 static gboolean
271 test_position_with_align (GtkTreeView  *tree_view,
272                           enum Pos      pos,
273                           gint          row_y,
274                           gint          row_start,
275                           gint          row_height,
276                           gfloat        row_align)
277 {
278         gboolean passed = TRUE;
279         GtkAdjustment *vadj = gtk_tree_view_get_vadjustment (tree_view);
280
281         /* Switch on row-align: 0.0, 0.5, 1.0 */
282         switch ((int)(row_align * 2.)) {
283         case 0:
284                 if (pos == POS_TOP || pos == POS_CENTER) {
285                         /* The row in question is the first row
286                          * in the view.
287                          *    - rect.y should be zero
288                          *    - dy should be equal to the top
289                          *      y coordinate of the row.
290                          */
291                         if (row_y != 0)
292                                 passed = FALSE;
293                         if (vadj->value != row_start)
294                                 passed = FALSE;
295                 } else {
296                         /* The row can be anywhere at the last
297                          * page of the tree view.
298                          *   - dy is set to the start of the
299                          *     last page.
300                          */
301                         if (vadj->value != vadj->upper - vadj->page_size)
302                                 passed = FALSE;
303                 }
304                 break;
305
306         case 1:
307                 /* 0.5 */
308                 if (pos == POS_TOP
309                     && row_start < vadj->page_size / 2) {
310                         /* For the first half of the top view we can't
311                          * center the row in the view, instead we
312                          * show the first page.
313                          *   - dy should be zero
314                          */
315                         if (vadj->value != 0)
316                                 passed = FALSE;
317                 } else if (pos == POS_BOTTOM
318                            && row_start >= vadj->upper - vadj->page_size / 2) {
319                         /* For the last half of the bottom view we
320                          * can't center the row in the view, instead
321                          * we show the last page.
322                          *   - dy should be the start of the 
323                          *     last page.
324                          */
325                         if (vadj->value != vadj->upper - vadj->page_size)
326                                 passed = FALSE;
327                 } else {
328                         /* The row is located in the middle of
329                          * the view.
330                          *    - top y coordinate is equal to
331                          *      middle of the view minus
332                          *      half the height of the row.
333                          *      (ie. the row's center is at the
334                          *       center of the view).
335                          */
336                         if (row_y != (int)(vadj->page_size / 2 - row_height / 2))
337                                 passed = FALSE;
338                 }
339                 break;
340
341         case 2:
342                 /* 1.0 */
343                 if (pos == POS_TOP) {
344                         /* The row can be anywhere on the
345                          * first page of the tree view.
346                          *   - dy is zero.
347                          */
348                         if (vadj->value != 0)
349                                 passed = FALSE;
350                 } else if (pos == POS_CENTER || pos == POS_BOTTOM) {
351                         /* The row is the last row visible in the
352                          * view.
353                          *   - rect.y is set to the top of the
354                          *     last row.
355                          *   - row_start is greater than page_size
356                          *     (ie we are not on the first page).
357                          *   - dy is greater than zero
358                          */
359                         if (row_start < vadj->page_size
360                             && row_start + row_height < vadj->page_size)
361                                 passed = FALSE;
362                         if (vadj->value <= 0)
363                                 passed = FALSE;
364                         if (row_y != vadj->page_size - row_height)
365                                 passed = FALSE;
366                 }
367                 break;
368         }
369
370         return passed;
371 }
372
373 static gboolean
374 test_position_without_align (GtkTreeView *tree_view,
375                              gint         row_start,
376                              gint         row_height)
377 {
378         GtkAdjustment *vadj = gtk_tree_view_get_vadjustment (tree_view);
379
380         /* Without align the tree view does as less work as possible,
381          * so basically we only have to check whether the row
382          * is visible on the screen.
383          */
384         if (vadj->value <= row_start
385             && vadj->value + vadj->page_size >= row_start + row_height)
386                 return TRUE;
387
388         return FALSE;
389 }
390
391 static void
392 test_position (GtkTreeView *tree_view,
393                GtkTreePath *path,
394                gboolean     use_align,
395                gfloat       row_align,
396                gfloat       col_align)
397 {
398         gint pos;
399         gchar *path_str;
400         GdkRectangle rect;
401         GtkTreeModel *model;
402         gint row_start;
403
404         /* Get the location of the path we scrolled to */
405         gtk_tree_view_get_background_area (GTK_TREE_VIEW (tree_view),
406                                            path, NULL, &rect);
407
408         row_start = get_row_start_for_index (GTK_TREE_VIEW (tree_view),
409                                              gtk_tree_path_get_indices (path)[0]);
410
411         /* Ugh */
412         pos = get_pos_from_path (GTK_TREE_VIEW (tree_view),
413                                  path, rect.height,
414                                  gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree_view)));
415
416         /* This is only tested for during test_single() */
417         model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
418         if (gtk_tree_model_iter_n_children (model, NULL) == 1) {
419                 GtkTreePath *tmppath;
420
421                 /* Test nothing is dangling at the bottom; read
422                  * description for test_single() for more information.
423                  */
424
425                 /* FIXME: hardcoded width */
426                 if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view), 0, GTK_WIDGET (tree_view)->allocation.height - 30, &tmppath, NULL, NULL, NULL)) {
427                         g_assert_not_reached ();
428                         gtk_tree_path_free (tmppath);
429                 }
430         }
431
432         path_str = gtk_tree_path_to_string (path);
433         if (use_align) {
434                 g_assert (test_position_with_align (tree_view, pos, rect.y,
435                                                     row_start, rect.height, row_align));
436         } else {
437                 g_assert (test_position_without_align (tree_view, row_start, rect.height));
438         }
439
440         g_free (path_str);
441 }
442
443 /*
444  * Scrolling code
445  */
446
447
448 /* Testing scrolling to various positions with various alignments */
449
450 static void
451 scroll (ScrollFixture *fixture,
452         GtkTreePath   *path,
453         gboolean       use_align,
454         gfloat         row_align)
455 {
456         gtk_tree_view_set_cursor (GTK_TREE_VIEW (fixture->tree_view), path,
457                                   NULL, FALSE);
458         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fixture->tree_view),
459                                       path, NULL,
460                                       use_align, row_align, 0.0);
461
462         gtk_widget_show_all (fixture->window);
463
464         while (gtk_events_pending ())
465                 gtk_main_iteration ();
466
467         test_position (GTK_TREE_VIEW (fixture->tree_view), path,
468                        use_align, row_align, 0.0);
469 }
470
471 static void
472 scroll_no_align (ScrollFixture *fixture,
473                  gconstpointer  test_data)
474 {
475         GtkTreePath *path;
476
477         path = gtk_tree_path_new_from_string (test_data);
478         scroll (fixture, path, FALSE, 0.0);
479         gtk_tree_path_free (path);
480 }
481
482 static void
483 scroll_align_0_0 (ScrollFixture *fixture,
484                   gconstpointer  test_data)
485 {
486         GtkTreePath *path;
487
488         path = gtk_tree_path_new_from_string (test_data);
489         scroll (fixture, path, TRUE, 0.0);
490         gtk_tree_path_free (path);
491 }
492
493 static void
494 scroll_align_0_5 (ScrollFixture *fixture,
495                   gconstpointer  test_data)
496 {
497         GtkTreePath *path;
498
499         path = gtk_tree_path_new_from_string (test_data);
500         scroll (fixture, path, TRUE, 0.5);
501         gtk_tree_path_free (path);
502 }
503
504 static void
505 scroll_align_1_0 (ScrollFixture *fixture,
506                   gconstpointer  test_data)
507 {
508         GtkTreePath *path;
509
510         path = gtk_tree_path_new_from_string (test_data);
511         scroll (fixture, path, TRUE, 1.0);
512         gtk_tree_path_free (path);
513 }
514
515
516 static void
517 scroll_after_realize (ScrollFixture *fixture,
518                       GtkTreePath   *path,
519                       gboolean       use_align,
520                       gfloat         row_align)
521 {
522         gtk_widget_show_all (fixture->window);
523
524         while (gtk_events_pending ())
525                 gtk_main_iteration ();
526
527         gtk_tree_view_set_cursor (GTK_TREE_VIEW (fixture->tree_view), path,
528                                   NULL, FALSE);
529         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fixture->tree_view),
530                                       path, NULL,
531                                       use_align, row_align, 0.0);
532
533         while (gtk_events_pending ())
534                 gtk_main_iteration ();
535
536         test_position (GTK_TREE_VIEW (fixture->tree_view), path,
537                        use_align, row_align, 0.0);
538 }
539
540 static void
541 scroll_after_no_align (ScrollFixture *fixture,
542                        gconstpointer  test_data)
543 {
544         GtkTreePath *path;
545
546         path = gtk_tree_path_new_from_string (test_data);
547         scroll_after_realize (fixture, path, FALSE, 0.0);
548         gtk_tree_path_free (path);
549 }
550
551 static void
552 scroll_after_align_0_0 (ScrollFixture *fixture,
553                         gconstpointer  test_data)
554 {
555         GtkTreePath *path;
556
557         path = gtk_tree_path_new_from_string (test_data);
558         scroll_after_realize (fixture, path, TRUE, 0.0);
559         gtk_tree_path_free (path);
560 }
561
562 static void
563 scroll_after_align_0_5 (ScrollFixture *fixture,
564                         gconstpointer  test_data)
565 {
566         GtkTreePath *path;
567
568         path = gtk_tree_path_new_from_string (test_data);
569         scroll_after_realize (fixture, path, TRUE, 0.5);
570         gtk_tree_path_free (path);
571 }
572
573 static void
574 scroll_after_align_1_0 (ScrollFixture *fixture,
575                         gconstpointer  test_data)
576 {
577         GtkTreePath *path;
578
579         path = gtk_tree_path_new_from_string (test_data);
580         scroll_after_realize (fixture, path, TRUE, 1.0);
581         gtk_tree_path_free (path);
582 }
583
584
585 static void
586 scroll_both_realize (ScrollFixture *fixture,
587                      GtkTreePath   *path,
588                      gboolean       use_align,
589                      gfloat         row_align)
590 {
591         GtkTreePath *end;
592
593         gtk_widget_show_all (fixture->window);
594
595         /* Scroll to end */
596         end = gtk_tree_path_new_from_indices (999, -1);
597
598         gtk_tree_view_set_cursor (GTK_TREE_VIEW (fixture->tree_view), end,
599                                   NULL, FALSE);
600         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fixture->tree_view),
601                                       end, NULL,
602                                       use_align, row_align, 0.0);
603         gtk_tree_path_free (end);
604
605         while (gtk_events_pending ())
606                 gtk_main_iteration ();
607
608         /* Scroll to final position */
609         gtk_tree_view_set_cursor (GTK_TREE_VIEW (fixture->tree_view), path,
610                                   NULL, FALSE);
611         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fixture->tree_view),
612                                       path, NULL,
613                                       use_align, row_align, 0.0);
614
615         while (gtk_events_pending ())
616                 gtk_main_iteration ();
617
618         test_position (GTK_TREE_VIEW (fixture->tree_view), path,
619                        use_align, row_align, 0.0);
620 }
621
622 static void
623 scroll_both_no_align (ScrollFixture *fixture,
624                       gconstpointer  test_data)
625 {
626         GtkTreePath *path;
627
628         path = gtk_tree_path_new_from_string (test_data);
629         scroll_both_realize (fixture, path, FALSE, 0.0);
630         gtk_tree_path_free (path);
631 }
632
633 static void
634 scroll_both_align_0_0 (ScrollFixture *fixture,
635                        gconstpointer  test_data)
636 {
637         GtkTreePath *path;
638
639         path = gtk_tree_path_new_from_string (test_data);
640         scroll_both_realize (fixture, path, TRUE, 0.0);
641         gtk_tree_path_free (path);
642 }
643
644 static void
645 scroll_both_align_0_5 (ScrollFixture *fixture,
646                        gconstpointer  test_data)
647 {
648         GtkTreePath *path;
649
650         path = gtk_tree_path_new_from_string (test_data);
651         scroll_both_realize (fixture, path, TRUE, 0.5);
652         gtk_tree_path_free (path);
653 }
654
655 static void
656 scroll_both_align_1_0 (ScrollFixture *fixture,
657                        gconstpointer  test_data)
658 {
659         GtkTreePath *path;
660
661         path = gtk_tree_path_new_from_string (test_data);
662         scroll_both_realize (fixture, path, TRUE, 1.0);
663         gtk_tree_path_free (path);
664 }
665
666 /* Testing scrolling to a newly created row */
667 static void
668 create_new_row (GtkListStore *store,
669                 int           n,
670                 GtkTreeIter  *iter)
671 {
672         switch (n) {
673                 case 0:
674                         /* Prepend a row */
675                         gtk_list_store_prepend (store, iter);
676                         break;
677
678                 case 4:
679                         /* Add a row in the middle of the visible area */
680                         gtk_list_store_insert (store, iter, 4);
681                         break;
682
683                 case 8:
684                         /* Add a row which is not completely visible */
685                         gtk_list_store_insert (store, iter, 8);
686                         break;
687
688                 case 500:
689                         /* Add a row in the middle */
690                         gtk_list_store_insert (store, iter, 500);
691                         break;
692
693                 case 999:
694                         /* Append a row */
695                         gtk_list_store_append (store, iter);
696                         break;
697         }
698
699         gtk_list_store_set (store, iter, 0, "New...", -1);
700 }
701
702 static void
703 scroll_new_row_editing_started (GtkCellRenderer *cell,
704                                 GtkCellEditable *editable,
705                                 const char      *path,
706                                 gpointer         user_data)
707 {
708         GtkWidget **widget = user_data;
709
710         *widget = GTK_WIDGET (editable);
711 }
712
713 static void
714 test_editable_position (GtkWidget   *tree_view,
715                         GtkWidget   *editable,
716                         GtkTreePath *cursor_path)
717 {
718         GdkRectangle rect;
719         GtkAdjustment *vadj;
720
721         gtk_tree_view_get_background_area (GTK_TREE_VIEW (tree_view),
722                                            cursor_path, NULL, &rect);
723
724         vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree_view));
725
726         /* There are all in bin_window coordinates */
727         g_assert (editable->allocation.y == rect.y + ((rect.height - editable->allocation.height) / 2));
728 }
729
730 static void
731 scroll_new_row (ScrollFixture *fixture,
732                 gconstpointer  test_data)
733 {
734         GtkTreeIter scroll_iter;
735         GtkTreePath *scroll_path;
736         GtkTreeModel *model;
737         GList *renderers;
738         GtkTreeViewColumn *column;
739         GtkWidget *editable;
740
741         /* The aim of this test is creating a new row at several places,
742          * and immediately put the cursor on it.  TreeView should correctly
743          * scroll to the row and show the editable widget.
744          *
745          * See #81627.
746          */
747
748         g_test_bug ("81627");
749
750         gtk_widget_show_all (fixture->window);
751
752         while (gtk_events_pending ())
753                 gtk_main_iteration ();
754
755         /* Create the new row and scroll to it */
756         model = gtk_tree_view_get_model (GTK_TREE_VIEW (fixture->tree_view));
757         create_new_row (GTK_LIST_STORE (model), GPOINTER_TO_INT (test_data),
758                         &scroll_iter);
759
760         /* Set up a signal handler to acquire the editable widget */
761         column = gtk_tree_view_get_column (GTK_TREE_VIEW (fixture->tree_view), 0);
762         renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
763
764         g_signal_connect (G_OBJECT (renderers->data), "editing-started",
765                           G_CALLBACK (scroll_new_row_editing_started),
766                           &editable);
767
768         /* Now set the cursor on the path and start editing */
769         scroll_path = gtk_tree_model_get_path (model, &scroll_iter);
770         gtk_tree_view_set_cursor (GTK_TREE_VIEW (fixture->tree_view),
771                                   scroll_path,
772                                   column,
773                                   TRUE);
774
775         while (gtk_events_pending ())
776                 gtk_main_iteration ();
777
778         /* Test position */
779         test_position (GTK_TREE_VIEW (fixture->tree_view), scroll_path,
780                        FALSE, 0.0, 0.0);
781         test_editable_position (fixture->tree_view, editable, scroll_path);
782
783         gtk_tree_path_free (scroll_path);
784 }
785
786 static void
787 scroll_new_row_tree (ScrollFixture *fixture,
788                      gconstpointer  test_data)
789 {
790         GtkTreeModel *model;
791         GtkAdjustment *vadjustment;
792         int i;
793
794         /* The goal of this test is to append new rows at the end of a tree
795          * store and immediately scroll to them.  If there is a parent
796          * node with a couple of childs in the "area above" to explore,
797          * this used to lead to unexpected results due to a bug.
798          *
799          * This issue has been reported by Miroslav Rajcic on
800          * gtk-app-devel-list:
801          * http://mail.gnome.org/archives/gtk-app-devel-list/2008-December/msg00068.html
802          */
803
804         gtk_widget_show_all (fixture->window);
805
806         gtk_tree_view_expand_all (GTK_TREE_VIEW (fixture->tree_view));
807
808         while (gtk_events_pending ())
809                 gtk_main_iteration ();
810
811         model = gtk_tree_view_get_model (GTK_TREE_VIEW (fixture->tree_view));
812         vadjustment = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (fixture->tree_view));
813
814         for (i = 0; i < 5; i++) {
815                 GtkTreeIter scroll_iter;
816                 GtkTreePath *scroll_path;
817
818                 gtk_tree_store_append (GTK_TREE_STORE (model), &scroll_iter,
819                                        NULL);
820                 gtk_tree_store_set (GTK_TREE_STORE (model), &scroll_iter,
821                                     0, "New node", -1);
822
823                 scroll_path = gtk_tree_model_get_path (model, &scroll_iter);
824                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (fixture->tree_view),
825                                               scroll_path, NULL, FALSE, 0.0, 0.0);
826                 gtk_tree_path_free (scroll_path);
827
828                 while (gtk_events_pending ())
829                         gtk_main_iteration ();
830
831                 /* Test position, the scroll bar must be at the end */
832                 g_assert (vadjustment->value == vadjustment->upper - vadjustment->page_size);
833         }
834 }
835
836 /* Test for GNOME bugzilla bug 359231; tests "recovery when removing a bunch of
837  * rows at the bottom.
838  */
839 static void
840 test_bug316689 (ScrollFixture *fixture,
841                 gconstpointer  test_data)
842 {
843         GtkTreeIter iter;
844         GtkTreePath *path;
845         GtkAdjustment *vadj;
846         GtkTreeModel *model;
847
848         /* The aim of this test is to scroll to the bottom of a TreeView,
849          * remove at least one page_size of items and check if TreeView
850          * correctly corrects the scroll bar (else they will look "broken").
851          *
852          * See #316689.
853          */
854
855         g_test_bug ("316689");
856
857         /* Scroll to some place close to the end */
858         path = gtk_tree_path_new_from_indices (N_ROWS - 4, -1);
859         scroll (fixture, path, FALSE, 0.0);
860         gtk_tree_path_free (path);
861
862         /* No need for a while events pending loop here, scroll() does this for us.
863          *
864          * We now remove a bunch of rows, wait for events to process and then
865          * check the adjustments to see if the TreeView gracefully recovered.
866          */
867         model = gtk_tree_view_get_model (GTK_TREE_VIEW (fixture->tree_view));
868
869         while (gtk_tree_model_iter_nth_child (model, &iter, NULL, N_ROWS - 15))
870                 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
871
872         while (gtk_events_pending ())
873                 gtk_main_iteration ();
874
875         vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (fixture->tree_view));
876
877         g_assert (vadj->value + vadj->page_size <= vadj->upper);
878         g_assert (vadj->value == vadj->upper - vadj->page_size);
879 }
880
881
882 /* Test for GNOME bugzilla bug 359231 */
883 static void
884 test_bug359231 (void)
885 {
886         int i;
887         int height1, height2;
888         GtkTreePath *path;
889         GtkTreeIter iter, child;
890         GtkTreeStore *store;
891         GdkRectangle rect;
892         ScrollFixture *fixture;
893
894         /* See #359231. */
895         g_test_bug ("359231");
896
897         /* Create model (GtkTreeStore in this case) */
898         store = gtk_tree_store_new (1, G_TYPE_STRING);
899
900         gtk_tree_store_append (store, &iter, NULL);
901         gtk_tree_store_set (store, &iter, 0, "Foo", -1);
902
903         for (i = 0; i < 4; i++) {
904                 gtk_tree_store_append (store, &child, &iter);
905                 gtk_tree_store_set (store, &child, 0, "Two\nLines", -1);
906         }
907         
908         fixture = g_new0 (ScrollFixture, 1);
909         scroll_fixture_setup (fixture, GTK_TREE_MODEL (store), NULL);
910         gtk_widget_show_all (fixture->window);
911
912         while (gtk_events_pending ())
913                 gtk_main_iteration ();
914
915         /* Prepend some rows at the top, expand */
916         gtk_tree_store_prepend (store, &iter, NULL);
917         gtk_tree_store_set (store, &iter, 0, "Foo", -1);
918
919         gtk_tree_store_prepend (store, &child, &iter);
920         gtk_tree_store_set (store, &child, 0, "Two\nLines", -1);
921
922         gtk_tree_view_expand_all (GTK_TREE_VIEW (fixture->tree_view));
923
924         while (gtk_events_pending ())
925                 gtk_main_iteration ();
926
927         /* Test if height of row 0:0 is correct */
928         path = gtk_tree_path_new_from_indices (0, -1);
929         gtk_tree_view_get_background_area (GTK_TREE_VIEW (fixture->tree_view),
930                                            path, NULL, &rect);
931         height1 = rect.height;
932
933         gtk_tree_path_down (path);
934         gtk_tree_view_get_background_area (GTK_TREE_VIEW (fixture->tree_view),
935                                            path, NULL, &rect);
936         height2 = rect.height;
937         gtk_tree_path_free (path);
938
939         g_assert (height2 > height1);
940
941         /* Clean up; the tear down also cleans up the model */
942         scroll_fixture_teardown (fixture, NULL);
943 }
944
945 /* Infrastructure for automatically adding tests */
946 enum
947 {
948         BEFORE,
949         AFTER,
950         BOTH
951 };
952
953 static const char *
954 test_type_string (int test_type)
955 {
956         switch (test_type) {
957                 case BEFORE:
958                         return "before-realize";
959
960                 case AFTER:
961                         return "after-realize";
962
963                 case BOTH:
964                         return "both";
965         }
966
967         return "???";
968 }
969
970 static char *
971 align_string (gboolean use_align,
972               gfloat   row_align)
973 {
974         char *ret;
975
976         if (!use_align)
977                 return g_strdup ("no-align");
978
979         ret = g_strdup_printf ("align-%1.1f", row_align);
980         return ret;
981 }
982
983 static void
984 add_test (const char *path,
985           gboolean    mixed,
986           int         test_type,
987           gboolean    use_align,
988           gfloat      row_align,
989           void (* setup) (ScrollFixture *, gconstpointer),
990           void (* scroll_func) (ScrollFixture *, gconstpointer))
991 {
992         gchar *test_path;
993         gchar *align;
994
995         align = align_string (use_align, row_align);
996
997         test_path = g_strdup_printf ("/TreeView/scrolling/%s/%s-height/path-%s-%s",
998                                      test_type_string (test_type),
999                                      mixed ? "mixed" : "constant",
1000                                      path, align);
1001         g_free (align);
1002
1003         g_test_add (test_path, ScrollFixture, path,
1004                     setup, scroll_func, scroll_fixture_teardown);
1005
1006         g_free (test_path);
1007 }
1008
1009 static void
1010 add_tests (gboolean mixed,
1011            int test_type,
1012            gboolean use_align,
1013            gfloat row_align,
1014            void (*scroll_func) (ScrollFixture *, gconstpointer))
1015 {
1016         void (* setup) (ScrollFixture *, gconstpointer);
1017
1018         if (mixed)
1019                 setup = scroll_fixture_mixed_setup;
1020         else
1021                 setup = scroll_fixture_constant_setup;
1022
1023         add_test ("0", mixed, test_type, use_align, row_align, setup, scroll_func);
1024         add_test ("2", mixed, test_type, use_align, row_align, setup, scroll_func);
1025         add_test ("5", mixed, test_type, use_align, row_align, setup, scroll_func);
1026         /* We scroll to 8 to test a partial visible row.  The 8 is
1027          * based on my font setting of "Vera Sans 11" and
1028          * the separators set to 0.  (This should be made dynamic; FIXME).
1029          */
1030         add_test ("8", mixed, test_type, use_align, row_align, setup, scroll_func);
1031         add_test ("10", mixed, test_type, use_align, row_align, setup, scroll_func);
1032         add_test ("250", mixed, test_type, use_align, row_align, setup, scroll_func);
1033         add_test ("500", mixed, test_type, use_align, row_align, setup, scroll_func);
1034         add_test ("750", mixed, test_type, use_align, row_align, setup, scroll_func);
1035         add_test ("990", mixed, test_type, use_align, row_align, setup, scroll_func);
1036         add_test ("991", mixed, test_type, use_align, row_align, setup, scroll_func);
1037         add_test ("995", mixed, test_type, use_align, row_align, setup, scroll_func);
1038         add_test ("997", mixed, test_type, use_align, row_align, setup, scroll_func);
1039         add_test ("999", mixed, test_type, use_align, row_align, setup, scroll_func);
1040 }
1041
1042 int
1043 main (int argc, char **argv)
1044 {
1045         gtk_test_init (&argc, &argv);
1046
1047         /* Scrolls before realization */
1048         add_tests (FALSE, BEFORE, FALSE, 0.0, scroll_no_align);
1049         if (g_test_thorough ())
1050                 add_tests (TRUE, BEFORE, FALSE, 0.0, scroll_no_align);
1051
1052         add_tests (FALSE, BEFORE, TRUE, 0.0, scroll_align_0_0);
1053         if (g_test_thorough ())
1054                 add_tests (TRUE, BEFORE, TRUE, 0.0, scroll_align_0_0);
1055
1056         add_tests (FALSE, BEFORE, TRUE, 0.5, scroll_align_0_5);
1057         if (g_test_thorough ())
1058                 add_tests (TRUE, BEFORE, TRUE, 0.5, scroll_align_0_5);
1059
1060         add_tests (FALSE, BEFORE, TRUE, 1.0, scroll_align_1_0);
1061         if (g_test_thorough ())
1062                 add_tests (TRUE, BEFORE, TRUE, 1.0, scroll_align_1_0);
1063
1064         /* Scrolls after realization */
1065         add_tests (FALSE, AFTER, FALSE, 0.0, scroll_after_no_align);
1066         if (g_test_thorough ())
1067                 add_tests (TRUE, AFTER, FALSE, 0.0, scroll_after_no_align);
1068
1069         add_tests (FALSE, AFTER, TRUE, 0.0, scroll_after_align_0_0);
1070         if (g_test_thorough ())
1071                 add_tests (TRUE, AFTER, TRUE, 0.0, scroll_after_align_0_0);
1072
1073         add_tests (FALSE, AFTER, TRUE, 0.5, scroll_after_align_0_5);
1074         if (g_test_thorough ())
1075                 add_tests (TRUE, AFTER, TRUE, 0.5, scroll_after_align_0_5);
1076
1077         add_tests (FALSE, AFTER, TRUE, 1.0, scroll_after_align_1_0);
1078         if (g_test_thorough ())
1079                 add_tests (TRUE, AFTER, TRUE, 1.0, scroll_after_align_1_0);
1080
1081         /* Scroll to end before realization, to a real position after */
1082         if (g_test_thorough ()) {
1083                 add_tests (FALSE, BOTH, FALSE, 0.0, scroll_both_no_align);
1084                 add_tests (TRUE, BOTH, FALSE, 0.0, scroll_both_no_align);
1085
1086                 add_tests (FALSE, BOTH, TRUE, 0.0, scroll_both_align_0_0);
1087                 add_tests (TRUE, BOTH, TRUE, 0.0, scroll_both_align_0_0);
1088
1089                 add_tests (FALSE, BOTH, TRUE, 0.5, scroll_both_align_0_5);
1090                 add_tests (TRUE, BOTH, TRUE, 0.5, scroll_both_align_0_5);
1091
1092                 add_tests (FALSE, BOTH, TRUE, 1.0, scroll_both_align_1_0);
1093                 add_tests (TRUE, BOTH, TRUE, 1.0, scroll_both_align_1_0);
1094         }
1095
1096         /* Test different alignments in view with single row */
1097         g_test_add ("/TreeView/scrolling/single-row/no-align",
1098                     ScrollFixture, "0",
1099                     scroll_fixture_single_setup,
1100                     scroll_no_align,
1101                     scroll_fixture_teardown);
1102         g_test_add ("/TreeView/scrolling/single-row/align-0.0",
1103                     ScrollFixture, "0",
1104                     scroll_fixture_single_setup,
1105                     scroll_align_0_0,
1106                     scroll_fixture_teardown);
1107         g_test_add ("/TreeView/scrolling/single-row/align-0.5",
1108                     ScrollFixture, "0",
1109                     scroll_fixture_single_setup,
1110                     scroll_align_0_5,
1111                     scroll_fixture_teardown);
1112         g_test_add ("/TreeView/scrolling/single-row/align-1.0",
1113                     ScrollFixture, "0",
1114                     scroll_fixture_single_setup,
1115                     scroll_align_1_0,
1116                     scroll_fixture_teardown);
1117
1118         /* Test scrolling in a very large model; also very slow */
1119         if (g_test_slow ()) {
1120                 g_test_add ("/TreeView/scrolling/large-model/constant-height/middle-no-align",
1121                             ScrollFixture, "50000",
1122                             scroll_fixture_constant_big_setup,
1123                             scroll_no_align,
1124                             scroll_fixture_teardown);
1125                 g_test_add ("/TreeView/scrolling/large-model/constant-height/end-no-align",
1126                             ScrollFixture, "99999",
1127                             scroll_fixture_constant_big_setup,
1128                             scroll_no_align,
1129                             scroll_fixture_teardown);
1130
1131                 g_test_add ("/TreeView/scrolling/large-model/mixed-height/middle-no-align",
1132                             ScrollFixture, "50000",
1133                             scroll_fixture_mixed_big_setup,
1134                             scroll_no_align,
1135                             scroll_fixture_teardown);
1136                 g_test_add ("/TreeView/scrolling/large-model/mixed-height/end-no-align",
1137                             ScrollFixture, "99999",
1138                             scroll_fixture_mixed_big_setup,
1139                             scroll_no_align,
1140                             scroll_fixture_teardown);
1141         }
1142
1143         /* Test scrolling to a newly created row */
1144         g_test_add ("/TreeView/scrolling/new-row/path-0", ScrollFixture,
1145                     GINT_TO_POINTER (0),
1146                     scroll_fixture_constant_setup,
1147                     scroll_new_row,
1148                     scroll_fixture_teardown);
1149         g_test_add ("/TreeView/scrolling/new-row/path-4", ScrollFixture,
1150                     GINT_TO_POINTER (4),
1151                     scroll_fixture_constant_setup,
1152                     scroll_new_row,
1153                     scroll_fixture_teardown);
1154         /* We scroll to 8 to test a partial visible row.  The 8 is
1155          * based on my font setting of "Vera Sans 11" and
1156          * the separators set to 0.  (This should be made dynamic; FIXME).
1157          */
1158         g_test_add ("/TreeView/scrolling/new-row/path-8", ScrollFixture,
1159                     GINT_TO_POINTER (8),
1160                     scroll_fixture_constant_setup,
1161                     scroll_new_row,
1162                     scroll_fixture_teardown);
1163         g_test_add ("/TreeView/scrolling/new-row/path-500", ScrollFixture,
1164                     GINT_TO_POINTER (500),
1165                     scroll_fixture_constant_setup,
1166                     scroll_new_row,
1167                     scroll_fixture_teardown);
1168         g_test_add ("/TreeView/scrolling/new-row/path-999", ScrollFixture,
1169                     GINT_TO_POINTER (999),
1170                     scroll_fixture_constant_setup,
1171                     scroll_new_row,
1172                     scroll_fixture_teardown);
1173
1174         g_test_add ("/TreeView/scrolling/new-row/tree", ScrollFixture,
1175                     NULL,
1176                     scroll_fixture_tree_setup,
1177                     scroll_new_row_tree,
1178                     scroll_fixture_teardown);
1179
1180         /* Misc. tests */
1181         g_test_add ("/TreeView/scrolling/specific/bug-316689",
1182                         ScrollFixture, NULL,
1183                     scroll_fixture_constant_setup, test_bug316689,
1184                     scroll_fixture_teardown);
1185         g_test_add_func ("/TreeView/scrolling/specific/bug-359231",
1186                         test_bug359231);
1187
1188         return g_test_run ();
1189 }