X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Ftests%2Ffiltermodel.c;h=507ceefa305aa785314ff5b51a929236f39cc238;hb=9d0febc9a64a5bfb0fcfc3a88de4757f6c1ff090;hp=048a3074535adc108133d0d90bbba8fa714b8d59;hpb=e89782742df542ecbde582ccca6177a09103e5bb;p=~andy%2Fgtk diff --git a/gtk/tests/filtermodel.c b/gtk/tests/filtermodel.c index 048a30745..507ceefa3 100644 --- a/gtk/tests/filtermodel.c +++ b/gtk/tests/filtermodel.c @@ -1,5 +1,5 @@ /* Extensive GtkTreeModelFilter tests. - * Copyright (C) 2009 Kristian Rietveld + * Copyright (C) 2009,2011 Kristian Rietveld * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -12,22 +12,29 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include +#include +#include "treemodel.h" +#include "gtktreemodelrefcount.h" /* Left to do: * - Proper coverage checking to see if the unit tests cover * all possible cases. - * - Verify if the ref counting is done properly for both the - * normal ref_count and the zero_ref_count. One way to test - * this area is by collapsing/expanding branches on the view - * that is connected to the filter model. * - Check if the iterator stamp is incremented at the correct times. + * + * For more thorough testing: + * - Test with randomized models. + * - Extensively test a filter model wrapping a sort model, + * or a sort model wrapping a filter model by: + * # Checking structure. + * # Checking for correct signals emissions. + * # Checking correct reference counting. + * # Tests should be done with the sort and filter model + * in various filtering and sorting states. */ @@ -90,278 +97,6 @@ create_tree_store (int depth, return store; } -/* - * Signal monitor - */ - -typedef enum -{ - ROW_INSERTED, - ROW_DELETED, - ROW_CHANGED, - ROW_HAS_CHILD_TOGGLED, - ROWS_REORDERED, - LAST_SIGNAL -} -SignalName; - -static const char * -signal_name_to_string (SignalName signal) -{ - switch (signal) - { - case ROW_INSERTED: - return "row-inserted"; - - case ROW_DELETED: - return "row-deleted"; - - case ROW_CHANGED: - return "row-changed"; - - case ROW_HAS_CHILD_TOGGLED: - return "row-has-child-toggled"; - - case ROWS_REORDERED: - return "rows-reordered"; - - default: - /* Fall through */ - break; - } - - return "(unknown)"; -} - -typedef struct -{ - SignalName signal; - GtkTreePath *path; -} -Signal; - - -static Signal * -signal_new (SignalName signal, GtkTreePath *path) -{ - Signal *s; - - s = g_new0 (Signal, 1); - s->signal = signal; - s->path = gtk_tree_path_copy (path); - - return s; -} - -static void -signal_free (Signal *s) -{ - if (s->path) - gtk_tree_path_free (s->path); - - g_free (s); -} - - -typedef struct -{ - GQueue *queue; - GtkTreeModel *client; - gulong signal_ids[LAST_SIGNAL]; -} -SignalMonitor; - - -static void -signal_monitor_generic_handler (SignalMonitor *m, - SignalName signal, - GtkTreeModel *model, - GtkTreePath *path) -{ - Signal *s; - - if (g_queue_is_empty (m->queue)) - { - gchar *path_str; - - path_str = gtk_tree_path_to_string (path); - g_error ("Signal queue empty, got signal %s path %s\n", - signal_name_to_string (signal), path_str); - g_free (path_str); - - g_assert_not_reached (); - } - - if (m->client != model) - { - g_error ("Model mismatch; expected %p, got %p\n", - m->client, model); - g_assert_not_reached (); - } - - s = g_queue_peek_tail (m->queue); - -#if 0 - /* For debugging: output signals that are coming in. Leaks memory. */ - g_print ("signal=%s path=%s\n", signal_name_to_string (signal), - gtk_tree_path_to_string (path)); -#endif - - if (s->signal != signal - || gtk_tree_path_compare (s->path, path) != 0) - { - gchar *path_str, *s_path_str; - - s_path_str = gtk_tree_path_to_string (s->path); - path_str = gtk_tree_path_to_string (path); - - g_error ("Signals don't match; expected signal %s path %s, got signal %s path %s\n", - signal_name_to_string (s->signal), s_path_str, - signal_name_to_string (signal), path_str); - - g_free (s_path_str); - g_free (path_str); - - g_assert_not_reached (); - } - - s = g_queue_pop_tail (m->queue); - - signal_free (s); -} - -static void -signal_monitor_row_inserted (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - signal_monitor_generic_handler (data, ROW_INSERTED, - model, path); -} - -static void -signal_monitor_row_deleted (GtkTreeModel *model, - GtkTreePath *path, - gpointer data) -{ - signal_monitor_generic_handler (data, ROW_DELETED, - model, path); -} - -static void -signal_monitor_row_changed (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - signal_monitor_generic_handler (data, ROW_CHANGED, - model, path); -} - -static void -signal_monitor_row_has_child_toggled (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - signal_monitor_generic_handler (data, ROW_HAS_CHILD_TOGGLED, - model, path); -} - -static void -signal_monitor_rows_reordered (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gint *new_order, - gpointer data) -{ - signal_monitor_generic_handler (data, ROWS_REORDERED, - model, path); -} - -static SignalMonitor * -signal_monitor_new (GtkTreeModel *client) -{ - SignalMonitor *m; - - m = g_new0 (SignalMonitor, 1); - m->client = g_object_ref (client); - m->queue = g_queue_new (); - - m->signal_ids[ROW_INSERTED] = g_signal_connect (client, - "row-inserted", - G_CALLBACK (signal_monitor_row_inserted), - m); - m->signal_ids[ROW_DELETED] = g_signal_connect (client, - "row-deleted", - G_CALLBACK (signal_monitor_row_deleted), - m); - m->signal_ids[ROW_CHANGED] = g_signal_connect (client, - "row-changed", - G_CALLBACK (signal_monitor_row_changed), - m); - m->signal_ids[ROW_HAS_CHILD_TOGGLED] = g_signal_connect (client, - "row-has-child-toggled", - G_CALLBACK (signal_monitor_row_has_child_toggled), - m); - m->signal_ids[ROWS_REORDERED] = g_signal_connect (client, - "rows-reordered", - G_CALLBACK (signal_monitor_rows_reordered), - m); - - return m; -} - -static void -signal_monitor_free (SignalMonitor *m) -{ - int i; - - for (i = 0; i < LAST_SIGNAL; i++) - g_signal_handler_disconnect (m->client, m->signal_ids[i]); - - g_object_unref (m->client); - - if (m->queue) - g_queue_free (m->queue); - - g_free (m); -} - -static void -signal_monitor_assert_is_empty (SignalMonitor *m) -{ - g_assert (g_queue_is_empty (m->queue)); -} - -static void -signal_monitor_append_signal_path (SignalMonitor *m, - SignalName signal, - GtkTreePath *path) -{ - Signal *s; - - s = signal_new (signal, path); - g_queue_push_head (m->queue, s); -} - -static void -signal_monitor_append_signal (SignalMonitor *m, - SignalName signal, - const gchar *path_string) -{ - Signal *s; - GtkTreePath *path; - - path = gtk_tree_path_new_from_string (path_string); - - s = signal_new (signal, path); - g_queue_push_head (m->queue, s); - - gtk_tree_path_free (path); -} - /* * Fixture */ @@ -418,6 +153,23 @@ filter_test_setup_generic (FilterTest *fixture, fixture->monitor = signal_monitor_new (filter); } +static void +filter_test_setup_expand_root (FilterTest *fixture) +{ + int i; + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (0, -1); + + for (i = 0; i < LEVEL_LENGTH; i++) + { + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), + path, FALSE); + gtk_tree_path_next (path); + } + gtk_tree_path_free (path); +} + static void filter_test_setup (FilterTest *fixture, gconstpointer test_data) @@ -439,6 +191,14 @@ filter_test_setup_unfiltered (FilterTest *fixture, filter_test_setup_generic (fixture, test_data, 3, FALSE, TRUE); } +static void +filter_test_setup_unfiltered_root_expanded (FilterTest *fixture, + gconstpointer test_data) +{ + filter_test_setup_unfiltered (fixture, test_data); + filter_test_setup_expand_root (fixture); +} + static void filter_test_setup_empty_unfiltered (FilterTest *fixture, gconstpointer test_data) @@ -446,6 +206,14 @@ filter_test_setup_empty_unfiltered (FilterTest *fixture, filter_test_setup_generic (fixture, test_data, 3, TRUE, TRUE); } +static void +filter_test_setup_empty_unfiltered_root_expanded (FilterTest *fixture, + gconstpointer test_data) +{ + filter_test_setup_empty_unfiltered (fixture, test_data); + filter_test_setup_expand_root (fixture); +} + static GtkTreePath * strip_virtual_root (GtkTreePath *path, GtkTreePath *root_path) @@ -470,6 +238,32 @@ strip_virtual_root (GtkTreePath *path, return real_path; } +static int +count_visible (FilterTest *fixture, + GtkTreePath *store_path) +{ + int i; + int n_visible = 0; + GtkTreeIter iter; + + gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store), + &iter, store_path); + + for (i = 0; i < LEVEL_LENGTH; i++) + { + gboolean visible; + + gtk_tree_model_get (GTK_TREE_MODEL (fixture->store), &iter, + 1, &visible, + -1); + + if (visible) + n_visible++; + } + + return n_visible; +} + static void filter_test_append_refilter_signals_recurse (FilterTest *fixture, GtkTreePath *store_path, @@ -537,26 +331,48 @@ filter_test_append_refilter_signals_recurse (FilterTest *fixture, /* This row will be inserted */ signal_monitor_append_signal_path (fixture->monitor, ROW_CHANGED, real_path); - signal_monitor_append_signal_path (fixture->monitor, - ROW_HAS_CHILD_TOGGLED, - real_path); - if (depth > 1 - && gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->store), - &iter)) + if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (fixture->store), + &iter)) { - GtkTreePath *store_copy; - GtkTreePath *filter_copy; - - store_copy = gtk_tree_path_copy (store_path); - filter_copy = gtk_tree_path_copy (filter_path); - filter_test_append_refilter_signals_recurse (fixture, - store_copy, - filter_copy, - depth - 1, - root_path); - gtk_tree_path_free (store_copy); - gtk_tree_path_free (filter_copy); + signal_monitor_append_signal_path (fixture->monitor, + ROW_HAS_CHILD_TOGGLED, + real_path); + + if (depth > 1) + { + GtkTreePath *store_copy; + GtkTreePath *filter_copy; + + store_copy = gtk_tree_path_copy (store_path); + filter_copy = gtk_tree_path_copy (filter_path); + filter_test_append_refilter_signals_recurse (fixture, + store_copy, + filter_copy, + depth - 1, + root_path); + gtk_tree_path_free (store_copy); + gtk_tree_path_free (filter_copy); + } + else if (depth == 1) + { + GtkTreePath *tmp_path; + + /* If all child rows are invisible, then the last row to + * become invisible will emit row-has-child-toggled on the + * parent. + */ + + tmp_path = gtk_tree_path_copy (store_path); + gtk_tree_path_append_index (tmp_path, 0); + + if (count_visible (fixture, tmp_path) == 0) + signal_monitor_append_signal_path (fixture->monitor, + ROW_HAS_CHILD_TOGGLED, + real_path); + + gtk_tree_path_free (tmp_path); + } } gtk_tree_path_next (filter_path); @@ -922,6 +738,59 @@ static void filled_hide_child_levels (FilterTest *fixture, gconstpointer user_data) { + set_path_visibility (fixture, "0:2", FALSE); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 1); + + set_path_visibility (fixture, "0:4", FALSE); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + set_path_visibility (fixture, "0:4:3", FALSE); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + set_path_visibility (fixture, "0:4:0", FALSE); + set_path_visibility (fixture, "0:4:1", FALSE); + set_path_visibility (fixture, "0:4:2", FALSE); + set_path_visibility (fixture, "0:4:4", FALSE); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */ + set_path_visibility (fixture, "0:4", TRUE); + check_filter_model (fixture); + check_level_length (fixture->filter, "0:3", 0); + + set_path_visibility (fixture, "0:2", TRUE); + check_filter_model (fixture); + check_level_length (fixture->filter, "0:2", LEVEL_LENGTH); + check_level_length (fixture->filter, "0:3", LEVEL_LENGTH); + check_level_length (fixture->filter, "0:4", 0); + + /* Once 0:4:0 got inserted, 0:4 became a parent. Because 0:4 is + * not visible, not signals are emitted. + */ + set_path_visibility (fixture, "0:4:2", TRUE); + set_path_visibility (fixture, "0:4:4", TRUE); + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, "0:4", 2); +} + +static void +filled_hide_child_levels_root_expanded (FilterTest *fixture, + gconstpointer user_data) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, FALSE); + gtk_tree_path_free (path); + signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:2"); set_path_visibility (fixture, "0:2", FALSE); check_filter_model (fixture); @@ -949,10 +818,6 @@ filled_hide_child_levels (FilterTest *fixture, /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */ signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:3"); - /* FIXME: Actually, the filter model should not be emitted the - * row-has-child-toggled signal here. *However* an extraneous emission - * of this signal does not hurt and is allowed. - */ signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:3"); set_path_visibility (fixture, "0:4", TRUE); check_filter_model (fixture); @@ -966,13 +831,8 @@ filled_hide_child_levels (FilterTest *fixture, check_level_length (fixture->filter, "0:3", LEVEL_LENGTH); check_level_length (fixture->filter, "0:4", 0); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:0"); - /* Once 0:4:0 got inserted, 0:4 became a parent */ + /* has-child-toggled for 0:4 is required. */ signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4:0"); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:1"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4:1"); - set_path_visibility (fixture, "0:4:2", TRUE); set_path_visibility (fixture, "0:4:4", TRUE); signal_monitor_assert_is_empty (fixture->monitor); @@ -1115,13 +975,11 @@ filled_vroot_hide_child_levels (FilterTest *fixture, { GtkTreePath *path = (GtkTreePath *)user_data; - signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:2"); set_path_visibility (fixture, "2:0:2", FALSE); check_filter_model_with_root (fixture, path); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); check_level_length (fixture->filter, "0", LEVEL_LENGTH - 1); - signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:3"); set_path_visibility (fixture, "2:0:4", FALSE); check_filter_model_with_root (fixture, path); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); @@ -1141,47 +999,94 @@ filled_vroot_hide_child_levels (FilterTest *fixture, check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */ - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:3"); - /* FIXME: Actually, the filter model should not be emitted the - * row-has-child-toggled signal here. *However* an extraneous emission - * of this signal does not hurt and is allowed. - */ - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:3"); set_path_visibility (fixture, "2:0:4", TRUE); check_filter_model_with_root (fixture, path); check_level_length (fixture->filter, "0:3", 0); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:2"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:2"); set_path_visibility (fixture, "2:0:2", TRUE); check_filter_model_with_root (fixture, path); check_level_length (fixture->filter, "0:2", LEVEL_LENGTH); check_level_length (fixture->filter, "0:3", LEVEL_LENGTH); check_level_length (fixture->filter, "0:4", 0); - /* FIXME: Inconsistency! For the non-vroot case we also receive two - * row-has-child-toggled signals here. + /* Once 0:4:0 got inserted, 0:4 became a parent. However, 0:4 is not + * visible, so no signal should be emitted. */ - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:0"); - /* Once 0:4:0 got inserted, 0:4 became a parent */ - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4"); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:4:1"); set_path_visibility (fixture, "2:0:4:2", TRUE); set_path_visibility (fixture, "2:0:4:4", TRUE); check_level_length (fixture->filter, "0:4", 2); + signal_monitor_assert_is_empty (fixture->monitor); } - static void -empty_show_nodes (FilterTest *fixture, - gconstpointer user_data) +filled_vroot_hide_child_levels_root_expanded (FilterTest *fixture, + gconstpointer user_data) { - check_filter_model (fixture); - check_level_length (fixture->filter, NULL, 0); + GtkTreePath *path = (GtkTreePath *)user_data; + GtkTreePath *tmp_path; - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); - set_path_visibility (fixture, "3", TRUE); + tmp_path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), tmp_path, FALSE); + gtk_tree_path_free (tmp_path); + + signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:2"); + set_path_visibility (fixture, "2:0:2", FALSE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 1); + + signal_monitor_append_signal (fixture->monitor, ROW_DELETED, "0:3"); + set_path_visibility (fixture, "2:0:4", FALSE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + set_path_visibility (fixture, "2:0:4:3", FALSE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + set_path_visibility (fixture, "2:0:4:0", FALSE); + set_path_visibility (fixture, "2:0:4:1", FALSE); + set_path_visibility (fixture, "2:0:4:2", FALSE); + set_path_visibility (fixture, "2:0:4:4", FALSE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "0", LEVEL_LENGTH - 2); + + /* Since "0:2" is hidden, "0:4" must be "0:3" in the filter model */ + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:3"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:3"); + set_path_visibility (fixture, "2:0:4", TRUE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, "0:3", 0); + + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:2"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:2"); + set_path_visibility (fixture, "2:0:2", TRUE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, "0:2", LEVEL_LENGTH); + check_level_length (fixture->filter, "0:3", LEVEL_LENGTH); + check_level_length (fixture->filter, "0:4", 0); + + /* Once 0:4:0 got inserted, 0:4 became a parent */ + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:4"); + set_path_visibility (fixture, "2:0:4:2", TRUE); + set_path_visibility (fixture, "2:0:4:4", TRUE); + check_level_length (fixture->filter, "0:4", 2); + signal_monitor_assert_is_empty (fixture->monitor); +} + +static void +empty_show_nodes (FilterTest *fixture, + gconstpointer user_data) +{ + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, 0); + + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "3", TRUE); check_filter_model (fixture); check_level_length (fixture->filter, NULL, 1); check_level_length (fixture->filter, "0", 0); @@ -1191,9 +1096,7 @@ empty_show_nodes (FilterTest *fixture, check_level_length (fixture->filter, NULL, 1); check_level_length (fixture->filter, "0", 0); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:0"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:0"); set_path_visibility (fixture, "3:2", TRUE); check_filter_model (fixture); check_level_length (fixture->filter, NULL, 1); @@ -1231,8 +1134,6 @@ empty_show_multiple_nodes (FilterTest *fixture, signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "1"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "1"); - signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "1"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "1"); /* We simulate a change in visible func condition with this. The * visibility state of multiple nodes changes at once, we emit row-changed @@ -1247,6 +1148,7 @@ empty_show_multiple_nodes (FilterTest *fixture, gtk_tree_path_append_index (changed_path, 2); gtk_tree_model_get_iter (GTK_TREE_MODEL (fixture->store), &iter, changed_path); + /* Invisible node - so no signals expected */ gtk_tree_model_row_changed (GTK_TREE_MODEL (fixture->store), changed_path, &iter); @@ -1271,9 +1173,7 @@ empty_show_multiple_nodes (FilterTest *fixture, check_level_length (fixture->filter, NULL, 2); check_level_length (fixture->filter, "0", 0); - signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0:0"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0:0"); set_path_visibility (fixture, "3:2", TRUE); check_filter_model (fixture); check_level_length (fixture->filter, NULL, 2); @@ -1465,9 +1365,28 @@ unfiltered_hide_single (FilterTest *fixture, signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. + /* The view only shows the root level, so we only expect signals + * for the root level. */ + filter_test_append_refilter_signals (fixture, 1); + filter_test_enable_filter (fixture); + + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH - 1); +} + +static void +unfiltered_hide_single_root_expanded (FilterTest *fixture, + gconstpointer user_data) + +{ + signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2"); + set_path_visibility (fixture, "2", FALSE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + filter_test_append_refilter_signals (fixture, 2); filter_test_enable_filter (fixture); @@ -1479,6 +1398,29 @@ static void unfiltered_hide_single_child (FilterTest *fixture, gconstpointer user_data) +{ + /* This row is not shown, so its signal is not propagated */ + set_path_visibility (fixture, "2:2", FALSE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + + /* The view only shows the root level, so we only expect signals + * for the root level. + */ + filter_test_append_refilter_signals (fixture, 0); + filter_test_enable_filter (fixture); + + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1); +} + +static void +unfiltered_hide_single_child_root_expanded (FilterTest *fixture, + gconstpointer user_data) + { signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); @@ -1488,9 +1430,6 @@ unfiltered_hide_single_child (FilterTest *fixture, check_level_length (fixture->filter, NULL, LEVEL_LENGTH); check_level_length (fixture->filter, "2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ filter_test_append_refilter_signals (fixture, 2); filter_test_enable_filter (fixture); @@ -1503,6 +1442,40 @@ static void unfiltered_hide_single_multi_level (FilterTest *fixture, gconstpointer user_data) +{ + /* This row is not shown, so its signal is not propagated */ + set_path_visibility (fixture, "2:2:2", FALSE); + + /* This row is not shown, so its signal is not propagated */ + set_path_visibility (fixture, "2:2", FALSE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); + + /* The view only shows the root level, so we only expect signals + * for the root level. + */ + filter_test_append_refilter_signals (fixture, 1); + filter_test_enable_filter (fixture); + + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1); + + set_path_visibility (fixture, "2:2", TRUE); + + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH - 1); +} + +static void +unfiltered_hide_single_multi_level_root_expanded (FilterTest *fixture, + gconstpointer user_data) + { /* This row is not shown, so its signal is not propagated */ set_path_visibility (fixture, "2:2:2", FALSE); @@ -1516,9 +1489,6 @@ unfiltered_hide_single_multi_level (FilterTest *fixture, check_level_length (fixture->filter, "2", LEVEL_LENGTH); check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ filter_test_append_refilter_signals (fixture, 2); filter_test_enable_filter (fixture); @@ -1537,6 +1507,7 @@ unfiltered_hide_single_multi_level (FilterTest *fixture, } + static void unfiltered_vroot_hide_single (FilterTest *fixture, gconstpointer user_data) @@ -1551,11 +1522,11 @@ unfiltered_vroot_hide_single (FilterTest *fixture, signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. (We add an additional level to + /* The view only shows the root level, so we only expect signals + * for the root level. (Though for the depth argument, we have to * take the virtual root into account). */ - filter_test_append_refilter_signals_with_vroot (fixture, 3, path); + filter_test_append_refilter_signals_with_vroot (fixture, 2, path); filter_test_enable_filter (fixture); check_filter_model_with_root (fixture, path); @@ -1566,6 +1537,32 @@ static void unfiltered_vroot_hide_single_child (FilterTest *fixture, gconstpointer user_data) +{ + GtkTreePath *path = (GtkTreePath *)user_data; + + /* Not visible, so no signal will be received. */ + set_path_visibility (fixture, "2:2:2", FALSE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + + /* The view only shows the root level, so we only expect signals + * for the root level. (Though for the depth argument, we have to + * take the virtual root into account). + */ + filter_test_append_refilter_signals_with_vroot (fixture, 2, path); + filter_test_enable_filter (fixture); + + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1); +} + +static void +unfiltered_vroot_hide_single_child_root_expanded (FilterTest *fixture, + gconstpointer user_data) + { GtkTreePath *path = (GtkTreePath *)user_data; @@ -1577,10 +1574,6 @@ unfiltered_vroot_hide_single_child (FilterTest *fixture, check_level_length (fixture->filter, NULL, LEVEL_LENGTH); check_level_length (fixture->filter, "2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. (We add an additional level to take - * the virtual root into account). - */ filter_test_append_refilter_signals_with_vroot (fixture, 3, path); filter_test_enable_filter (fixture); @@ -1593,6 +1586,43 @@ static void unfiltered_vroot_hide_single_multi_level (FilterTest *fixture, gconstpointer user_data) +{ + GtkTreePath *path = (GtkTreePath *)user_data; + + /* This row is not shown, so its signal is not propagated */ + set_path_visibility (fixture, "2:2:2:2", FALSE); + + /* Not shown, so no signal */ + set_path_visibility (fixture, "2:2:2", FALSE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); + + /* We only expect signals for the root level. The depth is 2 + * because we have to take the virtual root into account. + */ + filter_test_append_refilter_signals_with_vroot (fixture, 2, path); + filter_test_enable_filter (fixture); + + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH - 1); + + /* Not shown, so no signal */ + set_path_visibility (fixture, "2:2:2", TRUE); + + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH - 1); +} + +static void +unfiltered_vroot_hide_single_multi_level_root_expanded (FilterTest *fixture, + gconstpointer user_data) + { GtkTreePath *path = (GtkTreePath *)user_data; @@ -1608,9 +1638,6 @@ unfiltered_vroot_hide_single_multi_level (FilterTest *fixture, check_level_length (fixture->filter, "2", LEVEL_LENGTH); check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ filter_test_append_refilter_signals_with_vroot (fixture, 3, path); filter_test_enable_filter (fixture); @@ -1628,8 +1655,6 @@ unfiltered_vroot_hide_single_multi_level (FilterTest *fixture, check_level_length (fixture->filter, "2:2", LEVEL_LENGTH - 1); } - - static void unfiltered_show_single (FilterTest *fixture, gconstpointer user_data) @@ -1642,10 +1667,8 @@ unfiltered_show_single (FilterTest *fixture, signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ - filter_test_append_refilter_signals (fixture, 2); + /* We only expect signals for the root level */ + filter_test_append_refilter_signals (fixture, 1); filter_test_enable_filter (fixture); check_filter_model (fixture); @@ -1657,18 +1680,14 @@ unfiltered_show_single_child (FilterTest *fixture, gconstpointer user_data) { - signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); set_path_visibility (fixture, "2:2", TRUE); signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); check_level_length (fixture->filter, "2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ - filter_test_append_refilter_signals (fixture, 3); + /* We only expect signals for the root level */ + filter_test_append_refilter_signals (fixture, 1); filter_test_enable_filter (fixture); check_filter_model (fixture); @@ -1686,15 +1705,10 @@ unfiltered_show_single_child (FilterTest *fixture, } static void -unfiltered_show_single_multi_level (FilterTest *fixture, - gconstpointer user_data) +unfiltered_show_single_child_root_expanded (FilterTest *fixture, + gconstpointer user_data) { - /* The view is not showing this row (collapsed state), so it is not - * referenced. The signal should not go through. - */ - set_path_visibility (fixture, "2:2:2", TRUE); - signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); set_path_visibility (fixture, "2:2", TRUE); @@ -1702,12 +1716,8 @@ unfiltered_show_single_multi_level (FilterTest *fixture, signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); check_level_length (fixture->filter, "2", LEVEL_LENGTH); - check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ - filter_test_append_refilter_signals (fixture, 3); + filter_test_append_refilter_signals (fixture, 2); filter_test_enable_filter (fixture); check_filter_model (fixture); @@ -1719,46 +1729,114 @@ unfiltered_show_single_multi_level (FilterTest *fixture, signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); set_path_visibility (fixture, "2", TRUE); - check_filter_model (fixture); + signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, 1); check_level_length (fixture->filter, "0", 1); - check_level_length (fixture->filter, "0:0", 1); } - static void -unfiltered_vroot_show_single (FilterTest *fixture, - gconstpointer user_data) +unfiltered_show_single_multi_level (FilterTest *fixture, + gconstpointer user_data) { - GtkTreePath *path = (GtkTreePath *)user_data; - - signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2"); + /* The view is not showing these rows (collapsed state), so it is not + * referenced. The signal should not go through. + */ + set_path_visibility (fixture, "2:2:2", TRUE); set_path_visibility (fixture, "2:2", TRUE); signal_monitor_assert_is_empty (fixture->monitor); check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ - filter_test_append_refilter_signals_with_vroot (fixture, 3, path); + /* We only expect signals for the first level */ + filter_test_append_refilter_signals (fixture, 1); filter_test_enable_filter (fixture); - check_filter_model_with_root (fixture, path); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, 0); + + /* From here we are filtered, "2" in the real model is "0" in the filter + * model. + */ + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "2", TRUE); + check_filter_model (fixture); check_level_length (fixture->filter, NULL, 1); + check_level_length (fixture->filter, "0", 1); + check_level_length (fixture->filter, "0:0", 1); } static void -unfiltered_vroot_show_single_child (FilterTest *fixture, - gconstpointer user_data) +unfiltered_show_single_multi_level_root_expanded (FilterTest *fixture, + gconstpointer user_data) { - GtkTreePath *path = (GtkTreePath *)user_data; + /* The view is not showing this row (collapsed state), so it is not + * referenced. The signal should not go through. + */ + set_path_visibility (fixture, "2:2:2", TRUE); signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); + set_path_visibility (fixture, "2:2", TRUE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); + + filter_test_append_refilter_signals (fixture, 2); + filter_test_enable_filter (fixture); + + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, 0); + + /* From here we are filtered, "2" in the real model is "0" in the filter + * model. + */ + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "2", TRUE); + check_filter_model (fixture); + check_level_length (fixture->filter, NULL, 1); + check_level_length (fixture->filter, "0", 1); + check_level_length (fixture->filter, "0:0", 1); +} + +static void +unfiltered_vroot_show_single (FilterTest *fixture, + gconstpointer user_data) + +{ + GtkTreePath *path = (GtkTreePath *)user_data; + + signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2"); + set_path_visibility (fixture, "2:2", TRUE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + + /* The view only shows the root level, so the filter model only has + * the first two levels cached. + */ + filter_test_append_refilter_signals_with_vroot (fixture, 2, path); + filter_test_enable_filter (fixture); + + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, 1); +} + +static void +unfiltered_vroot_show_single_child (FilterTest *fixture, + gconstpointer user_data) + +{ + GtkTreePath *path = (GtkTreePath *)user_data; + set_path_visibility (fixture, "2:2:2", TRUE); signal_monitor_assert_is_empty (fixture->monitor); @@ -1785,6 +1863,39 @@ unfiltered_vroot_show_single_child (FilterTest *fixture, check_level_length (fixture->filter, "0", 1); } +static void +unfiltered_vroot_show_single_child_root_expanded (FilterTest *fixture, + gconstpointer user_data) + +{ + GtkTreePath *path = (GtkTreePath *)user_data; + + signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); + set_path_visibility (fixture, "2:2:2", TRUE); + + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + + filter_test_append_refilter_signals_with_vroot (fixture, 3, path); + filter_test_enable_filter (fixture); + + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, 0); + + /* From here we are filtered, "2" in the real model is "0" in the filter + * model. + */ + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "2:2", TRUE); + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, 1); + check_level_length (fixture->filter, "0", 1); +} + + static void unfiltered_vroot_show_single_multi_level (FilterTest *fixture, gconstpointer user_data) @@ -1797,8 +1908,6 @@ unfiltered_vroot_show_single_multi_level (FilterTest *fixture, */ set_path_visibility (fixture, "2:2:2:2", TRUE); - signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); - signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); set_path_visibility (fixture, "2:2:2", TRUE); signal_monitor_assert_is_empty (fixture->monitor); @@ -1806,10 +1915,8 @@ unfiltered_vroot_show_single_multi_level (FilterTest *fixture, check_level_length (fixture->filter, "2", LEVEL_LENGTH); check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* The view only shows the root level, so the filter model only has - * the first two levels cached. - */ - filter_test_append_refilter_signals_with_vroot (fixture, 4, path); + /* We only expect signals for the root level */ + filter_test_append_refilter_signals_with_vroot (fixture, 2, path); filter_test_enable_filter (fixture); check_filter_model_with_root (fixture, path); @@ -1827,134 +1934,540 @@ unfiltered_vroot_show_single_multi_level (FilterTest *fixture, check_level_length (fixture->filter, "0:0", 1); } - static void -insert_before (void) +unfiltered_vroot_show_single_multi_level_root_expanded (FilterTest *fixture, + gconstpointer user_data) + { - GtkTreeStore *store; - GtkTreeModel *filter; - GtkWidget *tree_view; - SignalMonitor *monitor; - GtkTreeIter iter; - GtkTreeIter last_iter; - GtkTreePath *path; + GtkTreePath *path = (GtkTreePath *)user_data; - /* This tests two aspects of the row-inserted handling: - * 1) If the newly inserted node was already handled by building - * the root level, don't handle it a second time. - * 2) Offsets of existing nodes must be updated when a new - * node is inserted. + /* The view is not showing this row (collapsed state), so it is not + * referenced. The signal should not go through. */ + set_path_visibility (fixture, "2:2:2:2", TRUE); - store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); - filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); - gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), - 1); - - tree_view = gtk_tree_view_new_with_model (filter); - monitor = signal_monitor_new (filter); + signal_monitor_append_signal (fixture->monitor, ROW_CHANGED, "2:2"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "2:2"); + set_path_visibility (fixture, "2:2:2", TRUE); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 0); + signal_monitor_assert_is_empty (fixture->monitor); + check_level_length (fixture->filter, NULL, LEVEL_LENGTH); + check_level_length (fixture->filter, "2", LEVEL_LENGTH); + check_level_length (fixture->filter, "2:2", LEVEL_LENGTH); - /* Insert 0 */ - path = gtk_tree_path_new_from_indices (0, -1); - signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); - gtk_tree_path_free (path); + filter_test_append_refilter_signals_with_vroot (fixture, 3, path); + filter_test_enable_filter (fixture); - gtk_tree_store_insert_with_values (store, &iter, NULL, 0, - 0, "Foo", 1, TRUE, -1); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, 0); - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + /* From here we are filtered, "2" in the real model is "0" in the filter + * model. + */ + signal_monitor_append_signal (fixture->monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture->monitor, ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "2:2", TRUE); + check_filter_model_with_root (fixture, path); + check_level_length (fixture->filter, NULL, 1); + check_level_length (fixture->filter, "0", 1); + check_level_length (fixture->filter, "0:0", 1); +} - /* Insert 1 */ - path = gtk_tree_path_new_from_indices (1, -1); - signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); - gtk_tree_path_free (path); +static void +unfiltered_rows_reordered_root_level (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 1, 2, 3, 4, 0 }; + int order1[] = { 0, 2, 1, 3, 4 }; + int order2[] = { 4, 0, 1, 2, 3 }; + GtkTreeIter iter0, iter1, iter2, iter3, iter4; + GtkTreePath *path; - gtk_tree_store_insert_with_values (store, &iter, NULL, 1, - 0, "Foo", 1, TRUE, -1); - last_iter = iter; + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter0, "0"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter2, "2"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "4"); - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 2); + path = gtk_tree_path_new (); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 5); + gtk_tree_store_move_after (fixture->store, &iter0, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); - /* Insert on 1 again -- invisible */ - gtk_tree_store_insert_with_values (store, &iter, NULL, 1, - 0, "Foo", 1, FALSE, -1); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 5); + gtk_tree_store_move_after (fixture->store, &iter2, &iter3); + signal_monitor_assert_is_empty (fixture->monitor); - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 2); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 5); + gtk_tree_store_move_before (fixture->store, &iter0, &iter1); + signal_monitor_assert_is_empty (fixture->monitor); - /* Insert on 1 again -- visible */ - path = gtk_tree_path_new_from_indices (1, -1); - signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); gtk_tree_path_free (path); +} - gtk_tree_store_insert_with_values (store, &iter, NULL, 1, - 0, "Foo", 1, TRUE, -1); +static void +unfiltered_rows_reordered_child_level (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 1, 2, 3, 4, 0 }; + int order1[] = { 0, 2, 1, 3, 4 }; + int order2[] = { 4, 0, 1, 2, 3 }; + GtkTreeIter iter0, iter1, iter2, iter3, iter4; + GtkTreePath *path; - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 3); + /* Expand row 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, FALSE); - /* Modify the iter that should be at the last position and check the - * signal we get. - */ - path = gtk_tree_path_new_from_indices (2, -1); - signal_monitor_append_signal_path (monitor, ROW_CHANGED, path); - gtk_tree_path_free (path); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter0, "0:0"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "0:1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter2, "0:2"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "0:3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "0:4"); - gtk_tree_store_set (store, &last_iter, 0, "Foo changed", -1); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 5); + gtk_tree_store_move_after (fixture->store, &iter0, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 3); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 5); + gtk_tree_store_move_after (fixture->store, &iter2, &iter3); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 5); + gtk_tree_store_move_before (fixture->store, &iter0, &iter1); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_path_free (path); } static void -insert_child (void) +filtered_rows_reordered_root_level_first_hidden (FilterTest *fixture, + gconstpointer user_data) { - GtkTreeStore *store; - GtkTreeModel *filter; - GtkWidget *tree_view; - SignalMonitor *monitor; - GtkTreeIter parent, iter; + int order0[] = { 1, 2, 3, 0 }; + int order1[] = { 0, 2, 1, 3 }; + int order2[] = { 3, 0, 1, 2 }; + GtkTreeIter iter1, iter2, iter3, iter4; GtkTreePath *path; - store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + /* Hide middle path */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0"); + set_path_visibility (fixture, "0", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); - gtk_tree_store_insert_with_values (store, &parent, NULL, 0, - 0, "Parent", 1, TRUE, -1); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter2, "2"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "4"); + path = gtk_tree_path_new (); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 4); + gtk_tree_store_move_after (fixture->store, &iter1, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); - filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); - gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), - 1); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 4); + gtk_tree_store_move_after (fixture->store, &iter3, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); - tree_view = gtk_tree_view_new_with_model (filter); - monitor = signal_monitor_new (filter); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 4); + gtk_tree_store_move_before (fixture->store, &iter1, &iter2); + signal_monitor_assert_is_empty (fixture->monitor); - /* Insert child -- invisible */ - path = gtk_tree_path_new_from_indices (0, -1); - signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); - /* The signal is received twice, once a pass through from GtkTreeStore - * and one generated by GtkTreeModelFilter. Not accurate, but cannot - * hurt. - */ - signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); gtk_tree_path_free (path); +} - gtk_tree_store_insert_with_values (store, &iter, &parent, 1, - 0, "Child", 1, FALSE, -1); +static void +filtered_rows_reordered_root_level_middle_hidden (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 1, 2, 3, 0 }; + int order1[] = { 0, 2, 1, 3 }; + int order2[] = { 3, 0, 1, 2 }; + GtkTreeIter iter0, iter1, iter3, iter4; + GtkTreePath *path; - signal_monitor_assert_is_empty (monitor); - check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + /* Hide middle path */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "2"); + set_path_visibility (fixture, "2", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); - /* Insert child */ - path = gtk_tree_path_new_from_indices (0, 0, -1); - signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); - gtk_tree_path_up (path); /* 0 */ - signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); - gtk_tree_path_free (path); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter0, "0"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "4"); + + path = gtk_tree_path_new (); + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 4); + gtk_tree_store_move_after (fixture->store, &iter0, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 4); + gtk_tree_store_move_after (fixture->store, &iter3, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 4); + gtk_tree_store_move_before (fixture->store, &iter0, &iter1); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_path_free (path); +} + +static void +filtered_rows_reordered_child_level_first_hidden (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 1, 2, 3, 0 }; + int order1[] = { 0, 2, 1, 3 }; + int order2[] = { 3, 0, 1, 2 }; + GtkTreeIter iter1, iter2, iter3, iter4; + GtkTreePath *path; + + /* Expand row 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, TRUE); + + /* Hide middle path */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:0"); + set_path_visibility (fixture, "0:0", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "0:1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter2, "0:2"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "0:3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "0:4"); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 4); + gtk_tree_store_move_after (fixture->store, &iter1, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 4); + gtk_tree_store_move_after (fixture->store, &iter3, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 4); + gtk_tree_store_move_before (fixture->store, &iter1, &iter2); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_path_free (path); +} + +static void +filtered_rows_reordered_child_level_middle_hidden (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 1, 2, 3, 0 }; + int order1[] = { 0, 2, 1, 3 }; + int order2[] = { 3, 0, 1, 2 }; + GtkTreeIter iter0, iter1, iter3, iter4; + GtkTreePath *path; + + /* Expand row 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, FALSE); + + /* Hide middle path */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:2"); + set_path_visibility (fixture, "0:2", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter0, "0:0"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "0:1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter3, "0:3"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "0:4"); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 4); + gtk_tree_store_move_after (fixture->store, &iter0, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order1, 4); + gtk_tree_store_move_after (fixture->store, &iter3, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order2, 4); + gtk_tree_store_move_before (fixture->store, &iter0, &iter1); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_path_free (path); +} + +static void +filtered_rows_reordered_child_level_4_hidden (FilterTest *fixture, + gconstpointer user_data) +{ + int order0[] = { 0 }; + GtkTreeIter iter1, iter4; + GtkTreePath *path; + + /* Expand row 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, FALSE); + + /* Hide last 4 paths */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:4"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:3"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:2"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:0"); + set_path_visibility (fixture, "0:4", FALSE); + set_path_visibility (fixture, "0:3", FALSE); + set_path_visibility (fixture, "0:2", FALSE); + set_path_visibility (fixture, "0:0", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "0:1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "0:4"); + + signal_monitor_append_signal_reordered (fixture->monitor, + ROWS_REORDERED, + path, order0, 1); + gtk_tree_store_move_after (fixture->store, &iter1, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_path_free (path); +} + +static void +filtered_rows_reordered_child_level_all_hidden (FilterTest *fixture, + gconstpointer user_data) +{ + GtkTreeIter iter1, iter4; + GtkTreePath *path; + + /* Expand row 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (fixture->tree_view), path, FALSE); + gtk_tree_path_free (path); + + /* Hide last 4 paths */ + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:4"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:3"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:2"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:1"); + signal_monitor_append_signal (fixture->monitor, + ROW_DELETED, "0:0"); + signal_monitor_append_signal (fixture->monitor, + ROW_HAS_CHILD_TOGGLED, "0"); + set_path_visibility (fixture, "0:4", FALSE); + set_path_visibility (fixture, "0:3", FALSE); + set_path_visibility (fixture, "0:2", FALSE); + set_path_visibility (fixture, "0:1", FALSE); + set_path_visibility (fixture, "0:0", FALSE); + signal_monitor_assert_is_empty (fixture->monitor); + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter1, "0:1"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture->store), + &iter4, "0:4"); + + gtk_tree_store_move_after (fixture->store, &iter1, &iter4); + signal_monitor_assert_is_empty (fixture->monitor); +} + +static void +insert_before (void) +{ + GtkTreeStore *store; + GtkTreeModel *filter; + GtkWidget *tree_view; + SignalMonitor *monitor; + GtkTreeIter iter; + GtkTreeIter last_iter; + GtkTreePath *path; + + /* This tests two aspects of the row-inserted handling: + * 1) If the newly inserted node was already handled by building + * the root level, don't handle it a second time. + * 2) Offsets of existing nodes must be updated when a new + * node is inserted. + */ + + store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), + 1); + + tree_view = gtk_tree_view_new_with_model (filter); + monitor = signal_monitor_new (filter); + + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 0); + + /* Insert 0 */ + path = gtk_tree_path_new_from_indices (0, -1); + signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); + gtk_tree_path_free (path); + + gtk_tree_store_insert_with_values (store, &iter, NULL, 0, + 0, "Foo", 1, TRUE, -1); + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + + /* Insert 1 */ + path = gtk_tree_path_new_from_indices (1, -1); + signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); + gtk_tree_path_free (path); + + gtk_tree_store_insert_with_values (store, &iter, NULL, 1, + 0, "Foo", 1, TRUE, -1); + last_iter = iter; + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 2); + + /* Insert on 1 again -- invisible */ + gtk_tree_store_insert_with_values (store, &iter, NULL, 1, + 0, "Foo", 1, FALSE, -1); + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 2); + + /* Insert on 1 again -- visible */ + path = gtk_tree_path_new_from_indices (1, -1); + signal_monitor_append_signal_path (monitor, ROW_INSERTED, path); + gtk_tree_path_free (path); + + gtk_tree_store_insert_with_values (store, &iter, NULL, 1, + 0, "Foo", 1, TRUE, -1); + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 3); + + /* Modify the iter that should be at the last position and check the + * signal we get. + */ + path = gtk_tree_path_new_from_indices (2, -1); + signal_monitor_append_signal_path (monitor, ROW_CHANGED, path); + gtk_tree_path_free (path); + + gtk_tree_store_set (store, &last_iter, 0, "Foo changed", -1); + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 3); + + g_object_unref (filter); + g_object_unref (store); + gtk_widget_destroy (tree_view); +} + +static void +insert_child (void) +{ + GtkTreeStore *store; + GtkTreeModel *filter; + GtkWidget *tree_view; + SignalMonitor *monitor; + GtkTreeIter parent, iter; + GtkTreePath *path; + + store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + gtk_tree_store_insert_with_values (store, &parent, NULL, 0, + 0, "Parent", 1, TRUE, -1); + + + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter), + 1); + + tree_view = gtk_tree_view_new_with_model (filter); + monitor = signal_monitor_new (filter); + + /* Insert child -- invisible */ + path = gtk_tree_path_new_from_indices (0, -1); + signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); + /* The signal is received twice, once a pass through from GtkTreeStore + * and one generated by GtkTreeModelFilter. Not accurate, but cannot + * hurt. + */ + signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); + gtk_tree_path_free (path); + + gtk_tree_store_insert_with_values (store, &iter, &parent, 1, + 0, "Child", 1, FALSE, -1); + + signal_monitor_assert_is_empty (monitor); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + + /* Insert child */ + path = gtk_tree_path_new_from_indices (0, 0, -1); + gtk_tree_path_up (path); /* 0 */ + signal_monitor_append_signal_path (monitor, ROW_HAS_CHILD_TOGGLED, path); + gtk_tree_path_free (path); gtk_tree_store_insert_with_values (store, &iter, &parent, 0, 0, "Child", 1, TRUE, -1); @@ -1968,6 +2481,10 @@ insert_child (void) signal_monitor_assert_is_empty (monitor); check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + + g_object_unref (filter); + g_object_unref (store); + gtk_widget_destroy (tree_view); } @@ -2076,6 +2593,1992 @@ remove_vroot_ancestor (void) g_object_unref (tree); } +static void +ref_count_single_level (void) +{ + GtkTreeIter iter[5]; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_append (GTK_TREE_STORE (model), &iter[0], NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter[1], NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter[2], NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter[3], NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter[4], NULL); + + assert_root_level_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &iter[0], 2); + assert_node_ref_count (ref_model, &iter[1], 1); + assert_node_ref_count (ref_model, &iter[2], 1); + assert_node_ref_count (ref_model, &iter[3], 1); + assert_node_ref_count (ref_model, &iter[4], 1); + + gtk_widget_destroy (tree_view); + + assert_node_ref_count (ref_model, &iter[0], 1); + assert_node_ref_count (ref_model, &iter[1], 0); + assert_node_ref_count (ref_model, &iter[2], 0); + assert_node_ref_count (ref_model, &iter[3], 0); + assert_node_ref_count (ref_model, &iter[4], 0); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &iter[0], 0); + + g_object_unref (ref_model); +} + +static void +ref_count_two_levels (void) +{ + GtkTreeIter parent1, parent2, iter, iter_first; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_first, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent2); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + /* This is quite confusing: + * - node 0 has a ref count of 2 because it is referenced as the + * first node in a level and by the tree view. + * - node 1 has a ref count of 2 because it is referenced by its + * child level and by the tree view. + */ + assert_root_level_referenced (ref_model, 2); + assert_node_ref_count (ref_model, &iter_first, 1); + assert_node_ref_count (ref_model, &iter, 0); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_first, 2); + assert_node_ref_count (ref_model, &iter, 1); + + gtk_tree_view_collapse_all (GTK_TREE_VIEW (tree_view)); + + /* The child level is not destroyed because its parent is visible */ + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_first, 1); + assert_node_ref_count (ref_model, &iter, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_first, 1); + assert_node_ref_count (ref_model, &iter, 0); + + gtk_widget_destroy (tree_view); + + assert_root_level_referenced (ref_model, 1); + assert_node_ref_count (ref_model, &iter_first, 1); + assert_node_ref_count (ref_model, &iter, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + /* The root level and first level remain cached, only the references on the + * first nodes of these levels are kept. + */ + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &iter_first, 1); + assert_node_ref_count (ref_model, &iter, 0); + + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_three_levels (void) +{ + GtkTreeIter grandparent1, grandparent2, parent1, parent2; + GtkTreeIter iter_parent1, iter_parent2, iter_parent2_first; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + parent1 + * + iter_parent1 + * + parent2 + * + iter_parent2_first + * + iter_parent2 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent1, &parent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2_first, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2, &parent2); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + /* This is quite confusing: + * - node 0 has a ref count of 2 because it is referenced as the + * first node in a level and by the tree view. + * - node 1 has a ref count of 2 because it is referenced by its + * child level and by the tree view. + */ + assert_root_level_referenced (ref_model, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_level_unreferenced (ref_model, &parent1); + assert_level_unreferenced (ref_model, &parent2); + + path = gtk_tree_path_new_from_indices (1, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, TRUE); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 2); + assert_node_ref_count (ref_model, &iter_parent2_first, 2); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_tree_view_collapse_all (GTK_TREE_VIEW (tree_view)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 0); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_path_append_index (path, 1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 2); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_tree_view_collapse_row (GTK_TREE_VIEW (tree_view), path); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_path_up (path); + gtk_tree_view_collapse_row (GTK_TREE_VIEW (tree_view), path); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 0); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_widget_destroy (tree_view); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + /* The root level and first level remain cached, only the references on the + * first nodes of these levels are kept. Grandparent2 is the parent + * of the first level with parent1, so grandparent2 keeps a reference + * as well. + */ + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 0); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_delete_row (void) +{ + GtkTreeIter grandparent1, grandparent2, parent1, parent2; + GtkTreeIter iter_parent1, iter_parent2, iter_parent2_first; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + parent1 + * + iter_parent1 + * + parent2 + * + iter_parent2_first + * + iter_parent2 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent1, &parent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2_first, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2, &parent2); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_root_level_referenced (ref_model, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_level_unreferenced (ref_model, &parent1); + assert_level_unreferenced (ref_model, &parent2); + + path = gtk_tree_path_new_from_indices (1, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, TRUE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 2); + assert_node_ref_count (ref_model, &iter_parent2_first, 2); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &iter_parent2); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 2); + assert_node_ref_count (ref_model, &iter_parent2_first, 2); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent1); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent2, 3); + assert_level_referenced (ref_model, 2, &parent2); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent2); + + assert_node_ref_count (ref_model, &grandparent1, 2); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &grandparent1, 1); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_filter_row_length_1 (void) +{ + GtkTreeIter level1_1; + GtkTreeIter level2_1; + GtkTreeIter level3_1; + GtkTreeIter level4_1; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + + /* + level1_1 + * + level2_1 + * + level3_1 + * + level4_1 + * + * Node level1_1 is expanded. This makes that levels 1 and 2 are + * visible. Level 3 is cached because its parent is visible. Level 4 + * is not cached. + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_1, &level1_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_1, &level2_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_1, &level3_1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, TRUE, -1); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 3); + assert_node_ref_count (ref_model, &level3_1, 1); + assert_node_ref_count (ref_model, &level4_1, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 3); + assert_node_ref_count (ref_model, &level3_1, 1); + assert_node_ref_count (ref_model, &level4_1, 0); + + /* level3_1 has a visible parent, so the node is kept in the cache. */ + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 3); + assert_node_ref_count (ref_model, &level3_1, 1); + assert_node_ref_count (ref_model, &level4_1, 0); + + /* level2_1 has a visible parent, so is kept in the cache. However, + * the external reference should be released. + */ + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_filter_row_length_1_remove_in_root_level (void) +{ + GtkTreeIter level1_1; + GtkTreeIter level2_1; + GtkTreeIter level3_1; + GtkTreeIter level4_1; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + + /* + level1_1 + * + level2_1 + * + level3_1 + * + level4_1 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_1, &level1_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_1, &level2_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_1, &level3_1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, TRUE, -1); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, TRUE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 3); + assert_node_ref_count (ref_model, &level3_1, 3); + assert_node_ref_count (ref_model, &level4_1, 2); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 0); + assert_node_ref_count (ref_model, &level2_1, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_filter_row_length_1_remove_in_child_level (void) +{ + GtkTreeIter level1_1; + GtkTreeIter level2_1; + GtkTreeIter level3_1; + GtkTreeIter level4_1; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + + /* + level1_1 + * + level2_1 + * + level3_1 + * + level4_1 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_1, &level1_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_1, &level2_1); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_1, &level3_1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, TRUE, -1); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, TRUE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 3); + assert_node_ref_count (ref_model, &level3_1, 3); + assert_node_ref_count (ref_model, &level4_1, 2); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 3); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 0); + assert_node_ref_count (ref_model, &level2_1, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_filter_row_length_gt_1 (void) +{ + GtkTreeIter level1_1, level1_2; + GtkTreeIter level2_1, level2_2; + GtkTreeIter level3_1, level3_2; + GtkTreeIter level4_1, level4_2; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + + /* + level1_1 + * + level1_2 + * + level2_1 + * + level2_2 + * + level3_1 + * + level3_2 + * + level4_1 + * + level4_2 + * + * Node level1_2 is expanded. This makes that levels 1 and 2 are + * visible. Level 3 is cached because its parent is visible. Level 4 + * is not cached. + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_1, &level1_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_2, &level1_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_1, &level2_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_2, &level2_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_1, &level3_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_2, &level3_2); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_2, 0, TRUE, -1); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + path = gtk_tree_path_new_from_indices (1, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 2); + assert_node_ref_count (ref_model, &level3_1, 1); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 2); + assert_node_ref_count (ref_model, &level3_1, 1); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 2); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 1); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 0); + assert_node_ref_count (ref_model, &level2_1, 0); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &level1_1, 1); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_filter_row_length_gt_1_visible_children (void) +{ + GtkTreeIter level1_1, level1_2; + GtkTreeIter level2_1, level2_2; + GtkTreeIter level3_1, level3_2; + GtkTreeIter level4_1, level4_2; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkTreePath *path; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + + /* + level1_1 + * + level1_2 + * + level2_1 + * + level2_2 + * + level3_1 + * + level3_2 + * + level4_1 + * + level4_2 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level1_2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_1, &level1_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level2_2, &level1_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_1, &level2_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level3_2, &level2_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_1, &level3_2); + gtk_tree_store_append (GTK_TREE_STORE (model), &level4_2, &level3_2); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level1_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level3_2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &level4_2, 0, TRUE, -1); + + assert_entire_model_unreferenced (ref_model); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + path = gtk_tree_path_new_from_indices (1, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, TRUE); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 2); + assert_node_ref_count (ref_model, &level3_1, 2); + assert_node_ref_count (ref_model, &level3_2, 2); + assert_node_ref_count (ref_model, &level4_1, 2); + assert_node_ref_count (ref_model, &level4_2, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &level2_2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &level1_1, 2); + assert_node_ref_count (ref_model, &level1_2, 2); + assert_node_ref_count (ref_model, &level2_1, 2); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + gtk_widget_destroy (tree_view); + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + assert_node_ref_count (ref_model, &level1_1, 1); + assert_node_ref_count (ref_model, &level1_2, 1); + assert_node_ref_count (ref_model, &level2_1, 1); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + g_object_unref (filter_model); + + assert_node_ref_count (ref_model, &level1_1, 0); + assert_node_ref_count (ref_model, &level1_2, 0); + assert_node_ref_count (ref_model, &level2_1, 0); + assert_node_ref_count (ref_model, &level2_2, 0); + assert_node_ref_count (ref_model, &level3_1, 0); + assert_node_ref_count (ref_model, &level3_2, 0); + assert_node_ref_count (ref_model, &level4_1, 0); + assert_node_ref_count (ref_model, &level4_2, 0); + + g_object_unref (ref_model); +} + +static void +ref_count_cleanup (void) +{ + GtkTreeIter grandparent1, grandparent2, parent1, parent2; + GtkTreeIter iter_parent1, iter_parent2, iter_parent2_first; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + parent1 + * + iter_parent1 + * + parent2 + * + iter_parent2_first + * + iter_parent2 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent1, &parent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2_first, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2, &parent2); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 3); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 2); + assert_node_ref_count (ref_model, &iter_parent2_first, 2); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_widget_destroy (tree_view); + + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &parent1, 2); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &iter_parent1, 1); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + /* The root level and first level remain cached, only the references on the + * first nodes of these levels are kept. Grandparent2 is the parent + * of the first level with parent1, so grandparent2 keeps a reference + * as well. + */ + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 0); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_row_ref (void) +{ + GtkTreeIter grandparent1, grandparent2, parent1, parent2; + GtkTreeIter iter_parent1, iter_parent2, iter_parent2_first; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GtkTreePath *path; + GtkTreeRowReference *row_ref; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + parent1 + * + iter_parent1 + * + parent2 + * + iter_parent2 + * + iter_parent2 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent1, &parent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2_first, &parent2); + gtk_tree_store_append (GTK_TREE_STORE (model), &iter_parent2, &parent2); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + path = gtk_tree_path_new_from_indices (1, 1, 1, -1); + row_ref = gtk_tree_row_reference_new (filter_model, path); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_tree_row_reference_free (row_ref); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 0); + + path = gtk_tree_path_new_from_indices (1, 1, 1, -1); + row_ref = gtk_tree_row_reference_new (filter_model, path); + gtk_tree_path_free (path); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 2); + assert_node_ref_count (ref_model, &iter_parent1, 0); + assert_node_ref_count (ref_model, &iter_parent2_first, 1); + assert_node_ref_count (ref_model, &iter_parent2, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent2); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &iter_parent1, 0); + + gtk_tree_row_reference_free (row_ref); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &iter_parent1, 0); + + gtk_widget_destroy (tree_view); + + gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (filter_model)); + + /* The root level and first level remain cached, only the references on the + * first nodes of these levels are kept. Grandparent2 is the parent + * of the first level with parent1, so grandparent2 keeps a reference + * as well. + */ + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &parent1, 1); + + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_insert (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3; + GtkTreeIter new_node; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 1); + + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, NULL); + + assert_node_ref_count (ref_model, &new_node, 2); + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_remove (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent1); + + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent2); + + assert_node_ref_count (ref_model, &grandparent3, 2); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_remove_filtered (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3, grandparent4; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + * + grandparent4 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent4, NULL); + + /* Filter first node */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent2); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent3, 2); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent3); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent4, 2); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent4); + + /* Check level length to get root level cached again */ + check_level_length (GTK_TREE_MODEL_FILTER (filter_model), NULL, 0); + + assert_node_ref_count (ref_model, &grandparent1, 1); + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 1); + + check_level_length (GTK_TREE_MODEL_FILTER (filter_model), NULL, 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_reordered (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 1); + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_after (GTK_TREE_STORE (model), + &grandparent1, &grandparent3); + + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + assert_node_ref_count (ref_model, &grandparent1, 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_reordered_filtered (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + + /* Test with 1 node filtered */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + + /* Move the invisible node grandparent1 */ + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_after (GTK_TREE_STORE (model), + &grandparent1, &grandparent3); + + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + assert_node_ref_count (ref_model, &grandparent1, 0); + + /* Move the invisible node grandparent1 */ + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_before (GTK_TREE_STORE (model), + &grandparent1, &grandparent2); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + + /* Now swap grandparent2 and grandparent3, first reference must transfer */ + /* gtk_tree_store_swap() will emit rows-reordered */ + gtk_tree_store_swap (GTK_TREE_STORE (model), + &grandparent2, &grandparent3); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent3, 2); + assert_node_ref_count (ref_model, &grandparent2, 1); + + /* Swap back */ + gtk_tree_store_swap (GTK_TREE_STORE (model), + &grandparent2, &grandparent3); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 1); + + /* Test with two nodes filtered */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 2); + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_before (GTK_TREE_STORE (model), + &grandparent3, &grandparent1); + + assert_node_ref_count (ref_model, &grandparent3, 2); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent1, 0); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_root_level_filter (void) +{ + GtkTreeIter grandparent1, grandparent2, grandparent3, grandparent4; + GtkTreeIter new_node; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + grandparent1 + * + grandparent2 + * + grandparent3 + * + grandparent4 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent4, NULL); + + /* Filter first node */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 1); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 2); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 2); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, NULL); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 2); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &new_node); + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, NULL); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &new_node, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &new_node, 2); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_remove (GTK_TREE_STORE (model), &new_node); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 2); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, TRUE, -1); + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent2); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_insert (void) +{ + GtkTreeIter grandparent1; + GtkTreeIter parent1, parent2, parent3; + GtkTreeIter new_node; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + parent1 + * + parent2 + * + parent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent3, &grandparent1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 0); + + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, &grandparent1); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &new_node, 1); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 0); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_remove (void) +{ + GtkTreeIter grandparent1; + GtkTreeIter parent1, parent2, parent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + parent1 + * + parent2 + * + parent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent3, &grandparent1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 0); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent1); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent2); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent3, 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_remove_filtered (void) +{ + GtkTreeIter grandparent1; + GtkTreeIter parent1, parent2, parent3, parent4; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + grandparent1 + * + parent1 + * + parent2 + * + parent3 + * + parent4 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent3, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent4, &grandparent1); + + /* Filter first node */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent3, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent4, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + assert_node_ref_count (ref_model, &parent4, 0); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent2); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent3, 1); + assert_node_ref_count (ref_model, &parent4, 0); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent3); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent4, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent4); + + /* Check level length to get level cached again */ + check_level_length (GTK_TREE_MODEL_FILTER (filter_model), "0", 0); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &parent2, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + + check_level_length (GTK_TREE_MODEL_FILTER (filter_model), "0", 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_reordered (void) +{ + GtkTreeIter grandparent1; + GtkTreeIter parent1, parent2, parent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + /* + grandparent1 + * + parent1 + * + parent2 + * + parent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent3, &grandparent1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 0); + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_after (GTK_TREE_STORE (model), + &parent1, &parent3); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + assert_node_ref_count (ref_model, &parent1, 0); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_reordered_filtered (void) +{ + GtkTreeIter grandparent1; + GtkTreeIter parent1, parent2, parent3; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + grandparent1 + * + parent1 + * + parent2 + * + parent3 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent1, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent2, &grandparent1); + gtk_tree_store_append (GTK_TREE_STORE (model), &parent3, &grandparent1); + + /* Test with 1 node filtered (parent1) */ + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent3, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + + /* Move invisible node parent 1 */ + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_after (GTK_TREE_STORE (model), + &parent1, &parent3); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + assert_node_ref_count (ref_model, &parent1, 0); + + /* Move invisible node parent 1 */ + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_before (GTK_TREE_STORE (model), + &parent1, &parent2); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + + /* Now swap parent2 and parent2, first reference must transfer */ + /* gtk_tree_store_swap() will emit rows-reordered */ + gtk_tree_store_swap (GTK_TREE_STORE (model), + &parent2, &parent3); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent3, 1); + assert_node_ref_count (ref_model, &parent2, 0); + + /* Swap back */ + gtk_tree_store_swap (GTK_TREE_STORE (model), + &parent2, &parent3); + + assert_node_ref_count (ref_model, &grandparent1, 3); + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 1); + assert_node_ref_count (ref_model, &parent3, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &parent1, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &parent1, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 0); + + /* Test with two nodes filtered */ + gtk_tree_store_set (GTK_TREE_STORE (model), &parent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &parent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &parent1, 0); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent3, 1); + + /* gtk_tree_store_move() will emit rows-reordered */ + gtk_tree_store_move_before (GTK_TREE_STORE (model), + &parent3, &parent1); + + assert_node_ref_count (ref_model, &parent3, 1); + assert_node_ref_count (ref_model, &parent2, 0); + assert_node_ref_count (ref_model, &parent1, 0); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static void +ref_count_transfer_child_level_filter (void) +{ + GtkTreeIter root; + GtkTreeIter grandparent1, grandparent2, grandparent3, grandparent4; + GtkTreeIter new_node; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + /* + root + * + grandparent1 + * + grandparent2 + * + grandparent3 + * + grandparent4 + */ + + gtk_tree_store_append (GTK_TREE_STORE (model), &root, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent1, &root); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent2, &root); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent3, &root); + gtk_tree_store_append (GTK_TREE_STORE (model), &grandparent4, &root); + + /* Filter first node */ + gtk_tree_store_set (GTK_TREE_STORE (model), &root, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 1); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent3, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, &root); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 1); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent1, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &new_node); + gtk_tree_store_prepend (GTK_TREE_STORE (model), &new_node, &root); + + assert_node_ref_count (ref_model, &new_node, 0); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 1); + + gtk_tree_store_set (GTK_TREE_STORE (model), &new_node, 0, TRUE, -1); + + assert_node_ref_count (ref_model, &new_node, 1); + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 0); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent2, 0, TRUE, -1); + gtk_tree_store_remove (GTK_TREE_STORE (model), &new_node); + + assert_node_ref_count (ref_model, &grandparent1, 0); + assert_node_ref_count (ref_model, &grandparent2, 1); + assert_node_ref_count (ref_model, &grandparent3, 0); + assert_node_ref_count (ref_model, &grandparent4, 0); + + gtk_tree_store_set (GTK_TREE_STORE (model), &grandparent4, 0, TRUE, -1); + gtk_tree_store_remove (GTK_TREE_STORE (model), &grandparent2); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + static gboolean specific_path_dependent_filter_func (GtkTreeModel *model, @@ -2347,71 +4850,347 @@ specific_sort_filter_remove_root (void) gtk_tree_path_free (path); - gtk_tree_store_remove (GTK_TREE_STORE (model), &root); + gtk_tree_store_remove (GTK_TREE_STORE (model), &root); + + g_object_unref (filter); + g_object_unref (sort); + g_object_unref (model); +} + + +static void +specific_root_mixed_visibility (void) +{ + int i; + GtkTreeModel *filter; + /* A bit nasty, apologies */ + FilterTest fixture; + + fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + for (i = 0; i < LEVEL_LENGTH; i++) + { + GtkTreeIter iter; + + gtk_tree_store_insert (fixture.store, &iter, NULL, i); + if (i % 2 == 0) + create_tree_store_set_values (fixture.store, &iter, TRUE); + else + create_tree_store_set_values (fixture.store, &iter, FALSE); + } + + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); + fixture.filter = GTK_TREE_MODEL_FILTER (filter); + fixture.monitor = NULL; + + gtk_tree_model_filter_set_visible_column (fixture.filter, 1); + + /* In order to trigger the potential bug, we should not access + * the filter model here (so don't call the check functions). + */ + + /* Change visibility of an odd row to TRUE */ + set_path_visibility (&fixture, "3", TRUE); + check_filter_model (&fixture); + check_level_length (fixture.filter, NULL, 4); +} + + + +static gboolean +specific_has_child_filter_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + return gtk_tree_model_iter_has_child (model, iter); +} + +static void +specific_has_child_filter (void) +{ + GtkTreeModel *filter; + GtkTreeIter iter, root; + FilterTest fixture; /* This is not how it should be done */ + GtkWidget *tree_view; + + fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); + fixture.filter = GTK_TREE_MODEL_FILTER (filter); + fixture.monitor = signal_monitor_new (filter); + + tree_view = gtk_tree_view_new_with_model (filter); + + /* We will filter on parent state using a filter function. We will + * manually keep the boolean column in sync, so that we can use + * check_filter_model() to check the consistency of the model. + */ + /* FIXME: We need a check_filter_model() that is not tied to LEVEL_LENGTH + * to be able to check the structure here. We keep the calls to + * check_filter_model() commented out until then. + */ + gtk_tree_model_filter_set_visible_func (fixture.filter, + specific_has_child_filter_filter_func, + NULL, NULL); + + /* The first node will be initially invisible: no signals */ + gtk_tree_store_append (fixture.store, &root, NULL); + create_tree_store_set_values (fixture.store, &root, FALSE); + + /* check_filter_model (&fixture); */ + check_level_length (fixture.filter, NULL, 0); + signal_monitor_assert_is_empty (fixture.monitor); + + /* Insert a child node. This will cause the parent to become visible + * since there is a child now. + */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + gtk_tree_store_append (fixture.store, &iter, &root); + create_tree_store_set_values (fixture.store, &iter, TRUE); + + /* Parent must now be visible. Do the level length check first, + * to avoid modifying the child model triggering a row-changed to + * the filter model. + */ + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 0); + signal_monitor_assert_is_empty (fixture.monitor); + + /* This should propagate row-changed */ + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + set_path_visibility (&fixture, "0", TRUE); + /* check_filter_model (&fixture); */ + signal_monitor_assert_is_empty (fixture.monitor); + + /* New root node, no child, so no signal */ + gtk_tree_store_append (fixture.store, &root, NULL); + check_level_length (fixture.filter, NULL, 1); + signal_monitor_assert_is_empty (fixture.monitor); + + /* When the child comes in, this node will become visible */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); + + gtk_tree_store_append (fixture.store, &iter, &root); + check_level_length (fixture.filter, NULL, 2); + check_level_length (fixture.filter, "1", 0); + + create_tree_store_set_values (fixture.store, &root, TRUE); + create_tree_store_set_values (fixture.store, &iter, TRUE); + + /* check_filter_model (&fixture); */ + signal_monitor_assert_is_empty (fixture.monitor); + + /* Add another child for 1 */ + gtk_tree_store_append (fixture.store, &iter, &root); + create_tree_store_set_values (fixture.store, &iter, TRUE); + check_level_length (fixture.filter, NULL, 2); + check_level_length (fixture.filter, "0", 0); + check_level_length (fixture.filter, "1", 0); + signal_monitor_assert_is_empty (fixture.monitor); + + /* Now remove one of the remaining child rows */ + signal_monitor_append_signal (fixture.monitor, ROW_DELETED, "0"); + + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture.store), + &iter, "0:0"); + gtk_tree_store_remove (fixture.store, &iter); + + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 0); + + set_path_visibility (&fixture, "0", FALSE); + /* check_filter_model (&fixture); */ + signal_monitor_assert_is_empty (fixture.monitor); + + g_object_unref (fixture.filter); + g_object_unref (fixture.store); + gtk_widget_destroy (tree_view); +} + + +static gboolean +specific_root_has_child_filter_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int depth; + GtkTreePath *path; + + path = gtk_tree_model_get_path (model, iter); + depth = gtk_tree_path_get_depth (path); + gtk_tree_path_free (path); + + if (depth > 1) + return TRUE; + /* else */ + return gtk_tree_model_iter_has_child (model, iter); +} + +static void +specific_root_has_child_filter (void) +{ + GtkTreeModel *filter; + GtkTreeIter iter, root; + FilterTest fixture; /* This is not how it should be done ... */ + GtkWidget *tree_view; + + /* This is a variation on the above test case, specific has-child-filter, + * herein the has-child check for visibility only applies to root level + * nodes. In this test, children are always visible because we + * only filter based on the "has child" criterion. + */ + + fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); + fixture.filter = GTK_TREE_MODEL_FILTER (filter); + fixture.monitor = signal_monitor_new (filter); + + tree_view = gtk_tree_view_new_with_model (filter); + + /* We will filter on parent state using a filter function. We will + * manually keep the boolean column in sync, so that we can use + * check_filter_model() to check the consistency of the model. + */ + /* FIXME: We need a check_filter_model() that is not tied to LEVEL_LENGTH + * to be able to check the structure here. We keep the calls to + * check_filter_model() commented out until then. + */ + gtk_tree_model_filter_set_visible_func (fixture.filter, + specific_root_has_child_filter_filter_func, + NULL, NULL); + + /* Add a first node, this will be invisible initially, so no signal + * should be emitted. + */ + gtk_tree_store_append (fixture.store, &root, NULL); + create_tree_store_set_values (fixture.store, &root, FALSE); + + signal_monitor_assert_is_empty (fixture.monitor); + /* check_filter_model (&fixture); */ + check_level_length (fixture.filter, NULL, 0); + + /* Add a child node. This will cause the parent to become visible, + * so we expect row-inserted signals for both. + */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + gtk_tree_store_append (fixture.store, &iter, &root); + signal_monitor_assert_is_empty (fixture.monitor); + + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 1); + + /* Modify the content of iter, no signals because the parent is not + * expanded. + */ + create_tree_store_set_values (fixture.store, &iter, TRUE); + signal_monitor_assert_is_empty (fixture.monitor); + + /* Parent must now be visible. Do the level length check first, + * to avoid modifying the child model triggering a row-changed to + * the filter model. + */ + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 1); + + /* Modify path 0 */ + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + set_path_visibility (&fixture, "0", TRUE); + /* check_filter_model (&fixture); */ + + signal_monitor_assert_is_empty (fixture.monitor); - g_object_unref (filter); - g_object_unref (sort); - g_object_unref (model); -} + /* Insert another node in the root level. Initially invisible, so + * not expecting any signal. + */ + gtk_tree_store_append (fixture.store, &root, NULL); + check_level_length (fixture.filter, NULL, 1); + signal_monitor_assert_is_empty (fixture.monitor); -static void -specific_root_mixed_visibility (void) -{ - int i; - GtkTreeModel *filter; - /* A bit nasty, apologies */ - FilterTest fixture; + /* Adding a child node which also makes parent at path 1 visible. */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); - fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + gtk_tree_store_append (fixture.store, &iter, &root); + check_level_length (fixture.filter, NULL, 2); + check_level_length (fixture.filter, "1", 1); - for (i = 0; i < LEVEL_LENGTH; i++) - { - GtkTreeIter iter; + signal_monitor_assert_is_empty (fixture.monitor); - gtk_tree_store_insert (fixture.store, &iter, NULL, i); - if (i % 2 == 0) - create_tree_store_set_values (fixture.store, &iter, TRUE); - else - create_tree_store_set_values (fixture.store, &iter, FALSE); - } + /* Check if row-changed is propagated */ + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); - filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); - fixture.filter = GTK_TREE_MODEL_FILTER (filter); - fixture.monitor = NULL; + create_tree_store_set_values (fixture.store, &root, TRUE); + create_tree_store_set_values (fixture.store, &iter, TRUE); + /* check_filter_model (&fixture); */ + signal_monitor_assert_is_empty (fixture.monitor); - gtk_tree_model_filter_set_visible_column (fixture.filter, 1); + /* Insert another child under node 1 */ + gtk_tree_store_append (fixture.store, &iter, &root); + create_tree_store_set_values (fixture.store, &iter, TRUE); + check_level_length (fixture.filter, NULL, 2); + check_level_length (fixture.filter, "0", 1); + check_level_length (fixture.filter, "1", 2); + signal_monitor_assert_is_empty (fixture.monitor); - /* In order to trigger the potential bug, we should not access - * the filter model here (so don't call the check functions). + /* Set a child node to invisible. This should not yield any + * change, because filtering is only done on whether the root + * node has a child, which it still has. */ + set_path_visibility (&fixture, "0:0", FALSE); + signal_monitor_assert_is_empty (fixture.monitor); - /* Change visibility of an odd row to TRUE */ - set_path_visibility (&fixture, "3", TRUE); - check_filter_model (&fixture); - check_level_length (fixture.filter, NULL, 4); -} + /* Now remove one of the remaining child rows */ + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_DELETED, "0"); + gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture.store), + &iter, "0:0"); + gtk_tree_store_remove (fixture.store, &iter); + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 2); + signal_monitor_assert_is_empty (fixture.monitor); -static gboolean -specific_has_child_filter_filter_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - return gtk_tree_model_iter_has_child (model, iter); + /* Set visibility of 0 to FALSE, no-op for filter model since + * the child 0:0 is already gone + */ + set_path_visibility (&fixture, "0", FALSE); + /* check_filter_model (&fixture); */ + signal_monitor_assert_is_empty (fixture.monitor); + + g_object_unref (fixture.filter); + g_object_unref (fixture.store); + gtk_widget_destroy (tree_view); } static void -specific_has_child_filter (void) +specific_has_child_filter_on_sort_model (void) { GtkTreeModel *filter; + GtkTreeModel *sort_model; GtkTreeIter iter, root; FilterTest fixture; /* This is not how it should be done */ GtkWidget *tree_view; fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); - filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); + sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (fixture.store)); + filter = gtk_tree_model_filter_new (sort_model, NULL); fixture.filter = GTK_TREE_MODEL_FILTER (filter); fixture.monitor = signal_monitor_new (filter); @@ -2442,7 +5221,6 @@ specific_has_child_filter (void) */ signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); gtk_tree_store_append (fixture.store, &iter, &root); create_tree_store_set_values (fixture.store, &iter, TRUE); @@ -2471,7 +5249,6 @@ specific_has_child_filter (void) /* When the child comes in, this node will become visible */ signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "1"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); @@ -2506,41 +5283,28 @@ specific_has_child_filter (void) set_path_visibility (&fixture, "0", FALSE); /* check_filter_model (&fixture); */ signal_monitor_assert_is_empty (fixture.monitor); -} + g_object_unref (fixture.filter); + g_object_unref (fixture.store); + gtk_widget_destroy (tree_view); +} static gboolean -specific_root_has_child_filter_filter_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) +specific_at_least_2_children_filter_filter_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) { - int depth; - GtkTreePath *path; - - path = gtk_tree_model_get_path (model, iter); - depth = gtk_tree_path_get_depth (path); - gtk_tree_path_free (path); - - if (depth > 1) - return TRUE; - /* else */ - return gtk_tree_model_iter_has_child (model, iter); + return gtk_tree_model_iter_n_children (model, iter) >= 2; } static void -specific_root_has_child_filter (void) +specific_at_least_2_children_filter (void) { GtkTreeModel *filter; GtkTreeIter iter, root; - FilterTest fixture; /* This is not how it should be done ... */ + FilterTest fixture; /* This is not how it should be done */ GtkWidget *tree_view; - /* This is a variation on the above test case, specific has-child-filter, - * herein the has-child check for visibility only applies to root level - * nodes. In this test, children are always visible because we - * only filter based on the "has child" criterion. - */ - fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (fixture.store), NULL); fixture.filter = GTK_TREE_MODEL_FILTER (filter); @@ -2548,116 +5312,86 @@ specific_root_has_child_filter (void) tree_view = gtk_tree_view_new_with_model (filter); - /* We will filter on parent state using a filter function. We will - * manually keep the boolean column in sync, so that we can use - * check_filter_model() to check the consistency of the model. - */ - /* FIXME: We need a check_filter_model() that is not tied to LEVEL_LENGTH - * to be able to check the structure here. We keep the calls to - * check_filter_model() commented out until then. - */ gtk_tree_model_filter_set_visible_func (fixture.filter, - specific_root_has_child_filter_filter_func, + specific_at_least_2_children_filter_filter_func, NULL, NULL); - /* Add a first node, this will be invisible initially, so no signal - * should be emitted. - */ + /* The first node will be initially invisible: no signals */ gtk_tree_store_append (fixture.store, &root, NULL); create_tree_store_set_values (fixture.store, &root, FALSE); - signal_monitor_assert_is_empty (fixture.monitor); /* check_filter_model (&fixture); */ check_level_length (fixture.filter, NULL, 0); + signal_monitor_assert_is_empty (fixture.monitor); - /* Add a child node. This will cause the parent to become visible, - * so we expect row-inserted signals for both. + /* Insert a child node. Nothing should happen. */ - signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); - gtk_tree_store_append (fixture.store, &iter, &root); - signal_monitor_assert_is_empty (fixture.monitor); + create_tree_store_set_values (fixture.store, &iter, TRUE); - check_level_length (fixture.filter, NULL, 1); - check_level_length (fixture.filter, "0", 1); + check_level_length (fixture.filter, NULL, 0); + signal_monitor_assert_is_empty (fixture.monitor); - /* Modify the content of iter, yields row-changed signals */ - signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0:0"); + /* Insert a second child node. This will cause the parent to become + * visible. + */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + gtk_tree_store_append (fixture.store, &iter, &root); create_tree_store_set_values (fixture.store, &iter, TRUE); - signal_monitor_assert_is_empty (fixture.monitor); /* Parent must now be visible. Do the level length check first, * to avoid modifying the child model triggering a row-changed to * the filter model. */ check_level_length (fixture.filter, NULL, 1); - check_level_length (fixture.filter, "0", 1); + check_level_length (fixture.filter, "0", 0); + signal_monitor_assert_is_empty (fixture.monitor); - /* Modify path 0 */ + /* This should propagate row-changed */ signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); set_path_visibility (&fixture, "0", TRUE); /* check_filter_model (&fixture); */ - signal_monitor_assert_is_empty (fixture.monitor); - /* Insert another node in the root level. Initially invisible, so - * not expecting any signal. - */ + /* New root node, no child, so no signal */ gtk_tree_store_append (fixture.store, &root, NULL); check_level_length (fixture.filter, NULL, 1); + signal_monitor_assert_is_empty (fixture.monitor); + /* First child, no signal, no change */ + gtk_tree_store_append (fixture.store, &iter, &root); + check_level_length (fixture.filter, NULL, 1); signal_monitor_assert_is_empty (fixture.monitor); - /* Adding a child node which also makes parent at path 1 visible. */ + /* When the second child comes in, this node will become visible */ signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "1"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1"); signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); gtk_tree_store_append (fixture.store, &iter, &root); check_level_length (fixture.filter, NULL, 2); - check_level_length (fixture.filter, "1", 1); - - signal_monitor_assert_is_empty (fixture.monitor); - - /* Check if row-changed is propagated */ - signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1"); - /* is row-has-child-toggled really necessary? */ - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "1"); - signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1:0"); + check_level_length (fixture.filter, "1", 0); create_tree_store_set_values (fixture.store, &root, TRUE); create_tree_store_set_values (fixture.store, &iter, TRUE); + /* check_filter_model (&fixture); */ signal_monitor_assert_is_empty (fixture.monitor); - /* Insert another child under node 1 */ - signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "1:1"); - signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "1:1"); - + /* Add another child for 1 */ gtk_tree_store_append (fixture.store, &iter, &root); create_tree_store_set_values (fixture.store, &iter, TRUE); check_level_length (fixture.filter, NULL, 2); - check_level_length (fixture.filter, "0", 1); - check_level_length (fixture.filter, "1", 2); - signal_monitor_assert_is_empty (fixture.monitor); - - /* Set a child node to invisible. This should not yield any - * change, because filtering is only done on whether the root - * node has a child, which it still has. - */ - signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0:0"); - - set_path_visibility (&fixture, "0:0", FALSE); + check_level_length (fixture.filter, "0", 0); + check_level_length (fixture.filter, "1", 0); signal_monitor_assert_is_empty (fixture.monitor); /* Now remove one of the remaining child rows */ - signal_monitor_append_signal (fixture.monitor, ROW_DELETED, "0:0"); - signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); signal_monitor_append_signal (fixture.monitor, ROW_DELETED, "0"); gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (fixture.store), @@ -2665,15 +5399,96 @@ specific_root_has_child_filter (void) gtk_tree_store_remove (fixture.store, &iter); check_level_length (fixture.filter, NULL, 1); - check_level_length (fixture.filter, "0", 2); + check_level_length (fixture.filter, "0", 0); + + set_path_visibility (&fixture, "0", FALSE); + /* check_filter_model (&fixture); */ signal_monitor_assert_is_empty (fixture.monitor); - /* Set visibility of 0 to FALSE, no-op for filter model since - * the child 0:0 is already gone + g_object_unref (fixture.filter); + g_object_unref (fixture.store); + gtk_widget_destroy (tree_view); +} + +static void +specific_at_least_2_children_filter_on_sort_model (void) +{ + GtkTreeRowReference *ref; + GtkTreeModel *filter; + GtkTreeModel *sort_model; + GtkTreeIter iter, root; + FilterTest fixture; /* This is not how it should be done */ + GtkWidget *tree_view; + + fixture.store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (fixture.store)); + filter = gtk_tree_model_filter_new (sort_model, NULL); + fixture.filter = GTK_TREE_MODEL_FILTER (filter); + fixture.monitor = signal_monitor_new (filter); + + tree_view = gtk_tree_view_new_with_model (filter); + + gtk_tree_model_filter_set_visible_func (fixture.filter, + specific_at_least_2_children_filter_filter_func, + NULL, NULL); + + /* The first node will be initially invisible: no signals */ + gtk_tree_store_append (fixture.store, &root, NULL); + create_tree_store_set_values (fixture.store, &root, FALSE); + + /* check_filter_model (&fixture); */ + check_level_length (fixture.filter, NULL, 0); + signal_monitor_assert_is_empty (fixture.monitor); + + /* Insert a child node. Nothing should happen. */ - set_path_visibility (&fixture, "0", FALSE); + gtk_tree_store_append (fixture.store, &iter, &root); + create_tree_store_set_values (fixture.store, &iter, TRUE); + + check_level_length (fixture.filter, NULL, 0); + signal_monitor_assert_is_empty (fixture.monitor); + + { + GtkTreePath *path = gtk_tree_path_new_from_indices (0, 0, -1); + + ref = gtk_tree_row_reference_new (sort_model, path); + gtk_tree_path_free (path); + } + + /* Insert a second child node. This will cause the parent to become + * visible. + */ + signal_monitor_append_signal (fixture.monitor, ROW_INSERTED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + gtk_tree_store_append (fixture.store, &iter, &root); + create_tree_store_set_values (fixture.store, &iter, TRUE); + + /* Parent must now be visible. Do the level length check first, + * to avoid modifying the child model triggering a row-changed to + * the filter model. + */ + check_level_length (fixture.filter, NULL, 1); + check_level_length (fixture.filter, "0", 0); + signal_monitor_assert_is_empty (fixture.monitor); + + /* This should propagate row-changed */ + signal_monitor_append_signal (fixture.monitor, ROW_CHANGED, "0"); + signal_monitor_append_signal (fixture.monitor, ROW_HAS_CHILD_TOGGLED, "0"); + + set_path_visibility (&fixture, "0", TRUE); /* check_filter_model (&fixture); */ signal_monitor_assert_is_empty (fixture.monitor); + + /* New root node, no child, so no signal */ + gtk_tree_store_append (fixture.store, &root, NULL); + check_level_length (fixture.filter, NULL, 1); + signal_monitor_assert_is_empty (fixture.monitor); + + gtk_tree_row_reference_free (ref); + g_object_unref (fixture.filter); + g_object_unref (fixture.store); + gtk_widget_destroy (tree_view); } @@ -2986,6 +5801,7 @@ specific_bug_311955 (void) GtkWidget *tree_view; int i; int n; + GtkTreePath *path; g_test_bug ("311955"); @@ -3016,6 +5832,9 @@ specific_bug_311955 (void) while (gtk_events_pending ()) gtk_main_iteration (); + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 2); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0", 1); + /* Fill model */ for (i = 0; i < 4; i++) { @@ -3036,6 +5855,9 @@ specific_bug_311955 (void) while (gtk_events_pending ()) gtk_main_iteration (); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0", 3); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0:2", 1); + /* Remove bottommost child from the tree. */ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &root); n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), &root); @@ -3047,6 +5869,86 @@ specific_bug_311955 (void) } else g_assert_not_reached (); + + path = gtk_tree_path_new_from_indices (0, 2, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + gtk_tree_path_free (path); + + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0", 3); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0:2", 0); +} + +static void +specific_bug_311955_clean (void) +{ + /* Cleaned up version of the test case for GNOME Bugzilla bug 311955, + * which is easier to understand. + */ + GtkTreeIter iter, child, grandchild; + GtkTreeStore *store; + GtkTreeModel *sort; + GtkTreeModel *filter; + + GtkWidget *tree_view; + GtkTreePath *path; + + store = gtk_tree_store_new (1, G_TYPE_INT); + + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, 0, 1, -1); + + gtk_tree_store_append (store, &child, &iter); + gtk_tree_store_set (store, &child, 0, 1, -1); + + sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store)); + filter = gtk_tree_model_filter_new (sort, NULL); + + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), + specific_bug_311955_filter_func, + NULL, NULL); + + tree_view = gtk_tree_view_new_with_model (filter); + g_object_unref (store); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + while (gtk_events_pending ()) + gtk_main_iteration (); + + check_level_length (GTK_TREE_MODEL_FILTER (filter), NULL, 1); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0", 1); + + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + + gtk_tree_store_append (store, &child, &iter); + gtk_tree_store_set (store, &child, 0, 0, -1); + + gtk_tree_store_append (store, &child, &iter); + gtk_tree_store_set (store, &child, 0, 1, -1); + + gtk_tree_store_append (store, &child, &iter); + gtk_tree_store_set (store, &child, 0, 1, -1); + + gtk_tree_store_append (store, &grandchild, &child); + gtk_tree_store_set (store, &grandchild, 0, 1, -1); + + gtk_tree_store_append (store, &child, &iter); + /* Don't set a value: assume 0 */ + + /* Remove leaf node, check trigger row-has-child-toggled */ + path = gtk_tree_path_new_from_indices (0, 3, 0, -1); + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + gtk_tree_path_free (path); + gtk_tree_store_remove (store, &iter); + + path = gtk_tree_path_new_from_indices (0, 2, -1); + gtk_tree_view_expand_row (GTK_TREE_VIEW (tree_view), path, FALSE); + gtk_tree_path_free (path); + + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0", 3); + check_level_length (GTK_TREE_MODEL_FILTER (filter), "0:2", 0); + + gtk_widget_destroy (tree_view); } static void @@ -3421,7 +6323,6 @@ specific_bug_621076 (void) signal_monitor_assert_is_empty (monitor); /* group-2 is already visible, so this time it is a normal insertion */ - signal_monitor_append_signal (monitor, ROW_INSERTED, "2:1"); gtk_tree_store_insert_with_values (store, NULL, &group_iter, -1, 0, "visible-2:2", -1); @@ -3449,7 +6350,6 @@ specific_bug_621076 (void) /* This will make group 3 visible. */ signal_monitor_append_signal (monitor, ROW_INSERTED, "3"); signal_monitor_append_signal (monitor, ROW_HAS_CHILD_TOGGLED, "3"); - signal_monitor_append_signal (monitor, ROW_INSERTED, "3:0"); signal_monitor_append_signal (monitor, ROW_HAS_CHILD_TOGGLED, "3"); gtk_tree_store_set (store, &item_iter, 0, "visible-3:1", -1); signal_monitor_assert_is_empty (monitor); @@ -3532,6 +6432,330 @@ specific_bug_621076 (void) g_object_unref (filter); } +static void +specific_bug_657353_related (void) +{ + GtkTreeIter node1, node2, node3, node4; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeModel *filter_model; + GtkWidget *tree_view; + GType column_types[] = { G_TYPE_BOOLEAN }; + + /* gtk_tree_model_filter_rows_reordered() used to have a problem to + * not properly transfer the first ref count when the first node in + * the level does not have elt->offset == 0. This test checks for + * that. This bug could cause the faulty condition + * elt->ext_ref_count > elt->ref_count + * to raise. + */ + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), 1, + column_types); + + gtk_tree_store_append (GTK_TREE_STORE (model), &node1, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &node2, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &node3, NULL); + gtk_tree_store_append (GTK_TREE_STORE (model), &node4, NULL); + + /* Hide the first node */ + gtk_tree_store_set (GTK_TREE_STORE (model), &node1, 0, FALSE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &node2, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &node3, 0, TRUE, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &node4, 0, TRUE, -1); + + filter_model = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER (filter_model), 0); + tree_view = gtk_tree_view_new_with_model (filter_model); + + assert_node_ref_count (ref_model, &node1, 0); + assert_node_ref_count (ref_model, &node2, 2); + assert_node_ref_count (ref_model, &node3, 1); + assert_node_ref_count (ref_model, &node4, 1); + + /* Swap nodes 2 and 3 */ + + /* gtk_tree_store_swap() will emit rows-reordered */ + gtk_tree_store_swap (GTK_TREE_STORE (model), + &node2, &node3); + + assert_node_ref_count (ref_model, &node1, 0); + assert_node_ref_count (ref_model, &node3, 2); + assert_node_ref_count (ref_model, &node2, 1); + assert_node_ref_count (ref_model, &node4, 1); + + /* Hide node 3 */ + gtk_tree_store_set (GTK_TREE_STORE (model), &node3, 0, FALSE, -1); + + assert_node_ref_count (ref_model, &node1, 0); + assert_node_ref_count (ref_model, &node3, 0); + assert_node_ref_count (ref_model, &node2, 2); + assert_node_ref_count (ref_model, &node4, 1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (ref_model); +} + +static gboolean +specific_bug_657353_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *str; + gboolean ret = FALSE; + + gtk_tree_model_get (model, iter, 0, &str, -1); + ret = strstr (str, "hidden") ? FALSE : TRUE; + g_free (str); + + return ret; +} + +static void +specific_bug_657353 (void) +{ + GtkListStore *store; + GtkTreeModel *sort_model; + GtkTreeModel *filter_model; + GtkTreeIter iter, iter_a, iter_b, iter_c; + GtkWidget *tree_view; + + /* This is a very carefully crafted test case that is triggering the + * situation described in bug 657353. + * + * GtkListStore acts like EphyCompletionModel + * GtkTreeModelSort acts like the sort model added in + * ephy_location_entry_set_completion. + * GtkTreeModelFilter acts like the filter model in + * GtkEntryCompletion. + */ + + /* Set up a model that's wrapped in a GtkTreeModelSort. The first item + * will be hidden. + */ + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_list_store_insert_with_values (store, &iter_b, 0, 0, "BBB hidden", -1); + gtk_list_store_insert_with_values (store, &iter, 1, 0, "EEE", -1); + gtk_list_store_insert_with_values (store, &iter, 2, 0, "DDD", -1); + gtk_list_store_insert_with_values (store, &iter_c, 3, 0, "CCC", -1); + + sort_model = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (store)); + + filter_model = gtk_tree_model_filter_new (sort_model, NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), + specific_bug_657353_visible_func, + filter_model, NULL); + + tree_view = gtk_tree_view_new_with_model (filter_model); + + /* This triggers emission of rows-reordered. The elt with offset == 0 + * is hidden, which used to cause misbehavior. (The first reference should + * have moved to CCC, which did not happen). + */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model), + 0, GTK_SORT_ASCENDING); + + /* By inserting another item that will appear at the first position, a + * reference transfer is done from CCC (which failed to get this reference + * earlier) to AAA. At this point, the rule + * elt->ref_count >= elt->ext_ref_count is broken for CCC. + */ + gtk_list_store_insert_with_values (store, &iter_a, 6, 0, "AAA", -1); + + /* When we hide CCC, the references cannot be correctly released, because + * CCC failed to get a reference during rows-reordered. The faulty condition + * only manifests itself here with MODEL_FILTER_DEBUG disabled (as is usual + * in production). + */ + gtk_list_store_set (store, &iter_c, 0, "CCC hidden", -1); + + gtk_widget_destroy (tree_view); + g_object_unref (filter_model); + g_object_unref (sort_model); + g_object_unref (store); +} + +static void +specific_bug_658696 (void) +{ + GtkTreeStore *store; + GtkTreeModel *filter; + GtkTreePath *vroot; + GtkTreeIter iter; + + store = create_tree_store (4, TRUE); + + vroot = gtk_tree_path_new_from_indices (0, 0, -1); + filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), vroot); + gtk_tree_path_free (vroot); + + /* This used to cause a crash in gtk_tree_model_filter_check_ancestors() */ + gtk_tree_store_append (store, &iter, NULL); + + g_object_unref (store); + g_object_unref (filter); +} + +static gboolean +specific_bug_659022_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + GtkTreeIter tmp; + + if (!gtk_tree_model_iter_parent (model, &tmp, iter)) + { + if (gtk_tree_model_iter_n_children (model, iter) >= 2) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +static void +specific_bug_659022_row_changed_emission (void) +{ + GtkTreeModel *filter; + GtkTreeModel *model; + GtkTreeIter parent, child, child2; + GtkTreePath *path; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + + filter = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), + specific_bug_659022_visible_func, + NULL, NULL); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (filter)); + + gtk_tree_store_insert (GTK_TREE_STORE (model), &parent, NULL, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child, &parent, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child2, &parent, 0); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter)); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &child2); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter)); + + path = gtk_tree_model_get_path (model, &child); + gtk_tree_model_row_changed (model, path, &child); + gtk_tree_path_free (path); + + gtk_widget_destroy (tree_view); + g_object_unref (filter); + g_object_unref (model); +} + +static void +specific_bug_659022_row_deleted_node_invisible (void) +{ + GtkTreeModel *filter; + GtkTreeModel *model; + GtkTreeIter parent, child; + GtkTreeIter parent2, child2, child3; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + + filter = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), + specific_bug_659022_visible_func, + NULL, NULL); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (filter)); + + gtk_tree_store_insert (GTK_TREE_STORE (model), &parent, NULL, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child, &parent, 0); + + gtk_tree_store_insert (GTK_TREE_STORE (model), &parent2, NULL, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child2, &parent2, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child3, &parent2, 0); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter)); + + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent); + + gtk_widget_destroy (tree_view); + g_object_unref (filter); + g_object_unref (model); +} + +static void +specific_bug_659022_row_deleted_free_level (void) +{ + GtkTreeModel *filter; + GtkTreeModel *model; + GtkTreeModelRefCount *ref_model; + GtkTreeIter parent, child; + GtkTreeIter parent2, child2, child3; + GtkWidget *tree_view; + + model = gtk_tree_model_ref_count_new (); + ref_model = GTK_TREE_MODEL_REF_COUNT (model); + + filter = gtk_tree_model_filter_new (model, NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter), + specific_bug_659022_visible_func, + NULL, NULL); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (filter)); + + /* Carefully construct a model */ + gtk_tree_store_insert (GTK_TREE_STORE (model), &parent, NULL, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child, &parent, 0); + + gtk_tree_store_insert (GTK_TREE_STORE (model), &parent2, NULL, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child2, &parent2, 0); + gtk_tree_store_insert (GTK_TREE_STORE (model), &child3, &parent2, 0); + + /* Only parent2 is visible, child3 holds first ref count for that level + * (Note that above, both child2 as child3 are inserted at position 0). + */ + assert_node_ref_count (ref_model, &parent, 0); + assert_node_ref_count (ref_model, &child, 0); + assert_node_ref_count (ref_model, &parent2, 3); + assert_node_ref_count (ref_model, &child3, 1); + assert_node_ref_count (ref_model, &child2, 0); + + /* Make sure child level is cached */ + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + assert_node_ref_count (ref_model, &parent, 0); + assert_node_ref_count (ref_model, &child, 0); + assert_node_ref_count (ref_model, &parent2, 3); + assert_node_ref_count (ref_model, &child3, 2); + assert_node_ref_count (ref_model, &child2, 1); + + gtk_tree_view_collapse_all (GTK_TREE_VIEW (tree_view)); + + assert_node_ref_count (ref_model, &parent, 0); + assert_node_ref_count (ref_model, &child, 0); + assert_node_ref_count (ref_model, &parent2, 3); + assert_node_ref_count (ref_model, &child3, 1); + assert_node_ref_count (ref_model, &child2, 0); + + /* Remove node with longer child level first */ + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent2); + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent); + + gtk_widget_destroy (tree_view); + g_object_unref (filter); + g_object_unref (model); +} + /* main */ void @@ -3565,6 +6789,11 @@ register_filter_model_tests (void) filter_test_setup, filled_hide_child_levels, filter_test_teardown); + g_test_add ("/TreeModelFilter/filled/hide-child-levels/root-expanded", + FilterTest, NULL, + filter_test_setup, + filled_hide_child_levels_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/filled/hide-root-level/vroot", FilterTest, gtk_tree_path_new_from_indices (2, -1), @@ -3576,6 +6805,11 @@ register_filter_model_tests (void) filter_test_setup, filled_vroot_hide_child_levels, filter_test_teardown); + g_test_add ("/TreeModelFilter/filled/hide-child-levels/vroot-root-expanded", + FilterTest, gtk_tree_path_new_from_indices (2, -1), + filter_test_setup, + filled_vroot_hide_child_levels_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/empty/show-nodes", @@ -3606,16 +6840,31 @@ register_filter_model_tests (void) filter_test_setup_unfiltered, unfiltered_hide_single, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/hide-single/root-expanded", + FilterTest, NULL, + filter_test_setup_unfiltered_root_expanded, + unfiltered_hide_single_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/hide-single-child", FilterTest, NULL, filter_test_setup_unfiltered, unfiltered_hide_single_child, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/hide-single-child/root-expanded", + FilterTest, NULL, + filter_test_setup_unfiltered_root_expanded, + unfiltered_hide_single_child_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/hide-single-multi-level", FilterTest, NULL, filter_test_setup_unfiltered, unfiltered_hide_single_multi_level, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/hide-single-multi-level/root-expanded", + FilterTest, NULL, + filter_test_setup_unfiltered_root_expanded, + unfiltered_hide_single_multi_level_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/hide-single/vroot", FilterTest, gtk_tree_path_new_from_indices (2, -1), @@ -3627,11 +6876,21 @@ register_filter_model_tests (void) filter_test_setup_unfiltered, unfiltered_vroot_hide_single_child, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/hide-single-child/vroot/root-expanded", + FilterTest, gtk_tree_path_new_from_indices (2, -1), + filter_test_setup_unfiltered_root_expanded, + unfiltered_vroot_hide_single_child_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/hide-single-multi-level/vroot", FilterTest, gtk_tree_path_new_from_indices (2, -1), filter_test_setup_unfiltered, unfiltered_vroot_hide_single_multi_level, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/hide-single-multi-level/vroot/root-expanded", + FilterTest, gtk_tree_path_new_from_indices (2, -1), + filter_test_setup_unfiltered_root_expanded, + unfiltered_vroot_hide_single_multi_level_root_expanded, + filter_test_teardown); @@ -3645,11 +6904,21 @@ register_filter_model_tests (void) filter_test_setup_empty_unfiltered, unfiltered_show_single_child, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/show-single-child/root-expanded", + FilterTest, NULL, + filter_test_setup_empty_unfiltered_root_expanded, + unfiltered_show_single_child_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/show-single-multi-level", FilterTest, NULL, filter_test_setup_empty_unfiltered, unfiltered_show_single_multi_level, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/show-single-multi-level/root-expanded", + FilterTest, NULL, + filter_test_setup_empty_unfiltered_root_expanded, + unfiltered_show_single_multi_level_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/show-single/vroot", FilterTest, gtk_tree_path_new_from_indices (2, -1), @@ -3661,11 +6930,64 @@ register_filter_model_tests (void) filter_test_setup_empty_unfiltered, unfiltered_vroot_show_single_child, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/show-single-child/vroot/root-expanded", + FilterTest, gtk_tree_path_new_from_indices (2, -1), + filter_test_setup_empty_unfiltered_root_expanded, + unfiltered_vroot_show_single_child_root_expanded, + filter_test_teardown); g_test_add ("/TreeModelFilter/unfiltered/show-single-multi-level/vroot", FilterTest, gtk_tree_path_new_from_indices (2, -1), filter_test_setup_empty_unfiltered, unfiltered_vroot_show_single_multi_level, filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/show-single-multi-level/vroot/root-expanded", + FilterTest, gtk_tree_path_new_from_indices (2, -1), + filter_test_setup_empty_unfiltered_root_expanded, + unfiltered_vroot_show_single_multi_level_root_expanded, + filter_test_teardown); + + + g_test_add ("/TreeModelFilter/unfiltered/rows-reordered/root-level", + FilterTest, NULL, + filter_test_setup_unfiltered, + unfiltered_rows_reordered_root_level, + filter_test_teardown); + g_test_add ("/TreeModelFilter/unfiltered/rows-reordered/child-level", + FilterTest, NULL, + filter_test_setup_unfiltered, + unfiltered_rows_reordered_child_level, + filter_test_teardown); + + g_test_add ("/TreeModelFilter/filtered/rows-reordered/root-level/first-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_root_level_first_hidden, + filter_test_teardown); + g_test_add ("/TreeModelFilter/filtered/rows-reordered/root-level/middle-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_root_level_middle_hidden, + filter_test_teardown); + g_test_add ("/TreeModelFilter/filtered/rows-reordered/child-level/first-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_child_level_first_hidden, + filter_test_teardown); + g_test_add ("/TreeModelFilter/filtered/rows-reordered/child-level/middle-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_child_level_middle_hidden, + filter_test_teardown); + g_test_add ("/TreeModelFilter/filtered/rows-reordered/child-level/4-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_child_level_4_hidden, + filter_test_teardown); + g_test_add ("/TreeModelFilter/filtered/rows-reordered/child-level/all-hidden", + FilterTest, NULL, + filter_test_setup, + filtered_rows_reordered_child_level_all_hidden, + filter_test_teardown); /* Inserts in child models after creation of filter model */ g_test_add_func ("/TreeModelFilter/insert/before", @@ -3681,6 +7003,58 @@ register_filter_model_tests (void) g_test_add_func ("/TreeModelFilter/remove/vroot-ancestor", remove_vroot_ancestor); + /* Reference counting */ + g_test_add_func ("/TreeModelFilter/ref-count/single-level", + ref_count_single_level); + g_test_add_func ("/TreeModelFilter/ref-count/two-levels", + ref_count_two_levels); + g_test_add_func ("/TreeModelFilter/ref-count/three-levels", + ref_count_three_levels); + g_test_add_func ("/TreeModelFilter/ref-count/delete-row", + ref_count_delete_row); + g_test_add_func ("/TreeModelFilter/ref-count/filter-row/length-1", + ref_count_filter_row_length_1); + g_test_add_func ("/TreeModelFilter/ref-count/filter-row/length-1-remove-in-root-level", + ref_count_filter_row_length_1_remove_in_root_level); + g_test_add_func ("/TreeModelFilter/ref-count/filter-row/length-1-remove-in-child-level", + ref_count_filter_row_length_1_remove_in_child_level); + g_test_add_func ("/TreeModelFilter/ref-count/filter-row/length-gt-1", + ref_count_filter_row_length_gt_1); + g_test_add_func ("/TreeModelFilter/ref-count/filter-row/length-gt-1-visible-children", + ref_count_filter_row_length_gt_1_visible_children); + g_test_add_func ("/TreeModelFilter/ref-count/cleanup", + ref_count_cleanup); + g_test_add_func ("/TreeModelFilter/ref-count/row-ref", + ref_count_row_ref); + + /* Reference counting, transfer of first reference on + * first node in level. This is a GtkTreeModelFilter-specific + * feature. + */ + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/insert", + ref_count_transfer_root_level_insert); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/remove", + ref_count_transfer_root_level_remove); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/remove/filtered", + ref_count_transfer_root_level_remove_filtered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/reordered", + ref_count_transfer_root_level_reordered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/reordered/filtered", + ref_count_transfer_root_level_reordered_filtered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/root-level/filter", + ref_count_transfer_root_level_filter); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/insert", + ref_count_transfer_child_level_insert); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/remove", + ref_count_transfer_child_level_remove); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/remove/filtered", + ref_count_transfer_child_level_remove_filtered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/reordered", + ref_count_transfer_child_level_reordered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/reordered/filtered", + ref_count_transfer_child_level_reordered_filtered); + g_test_add_func ("/TreeModelFilter/ref-count/transfer/child-level/filter", + ref_count_transfer_child_level_filter); g_test_add_func ("/TreeModelFilter/specific/path-dependent-filter", specific_path_dependent_filter); @@ -3694,6 +7068,12 @@ register_filter_model_tests (void) specific_root_mixed_visibility); g_test_add_func ("/TreeModelFilter/specific/has-child-filter", specific_has_child_filter); + g_test_add_func ("/TreeModelFilter/specific/has-child-filter-on-sort-model", + specific_has_child_filter_on_sort_model); + g_test_add_func ("/TreeModelFilter/specific/at-least-2-children-filter", + specific_at_least_2_children_filter); + g_test_add_func ("/TreeModelFilter/specific/at-least-2-children-filter-on-sort-model", + specific_at_least_2_children_filter_on_sort_model); g_test_add_func ("/TreeModelFilter/specific/root-has-child-filter", specific_root_has_child_filter); g_test_add_func ("/TreeModelFilter/specific/filter-add-child", @@ -3711,6 +7091,8 @@ register_filter_model_tests (void) specific_bug_301558); g_test_add_func ("/TreeModelFilter/specific/bug-311955", specific_bug_311955); + g_test_add_func ("/TreeModelFilter/specific/bug-311955-clean", + specific_bug_311955_clean); g_test_add_func ("/TreeModelFilter/specific/bug-346800", specific_bug_346800); g_test_add_func ("/TreeModelFilter/specific/bug-464173", @@ -3721,4 +7103,16 @@ register_filter_model_tests (void) specific_bug_549287); g_test_add_func ("/TreeModelFilter/specific/bug-621076", specific_bug_621076); + g_test_add_func ("/TreeModelFilter/specific/bug-657353-related", + specific_bug_657353_related); + g_test_add_func ("/TreeModelFilter/specific/bug-657353", + specific_bug_657353); + g_test_add_func ("/TreeModelFilter/specific/bug-658696", + specific_bug_658696); + g_test_add_func ("/TreeModelFilter/specific/bug-659022/row-changed-emission", + specific_bug_659022_row_changed_emission); + g_test_add_func ("/TreeModelFilter/specific/bug-659022/row-deleted-node-invisible", + specific_bug_659022_row_deleted_node_invisible); + g_test_add_func ("/TreeModelFilter/specific/bug-659022/row-deleted-free-level", + specific_bug_659022_row_deleted_free_level); }