mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 01:58:54 +01:00 
			
		
		
		
	Currently, there is no quick way to find whether and element is already part of a list store, except for manually writing a for-loop and calling `g_list_model_get_item()` and breaking when you find the item. This is mostly just a small API addition to support this use case. Fixes https://gitlab.gnome.org/GNOME/glib/issues/1011
		
			
				
	
	
		
			910 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			910 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright 2015 Lars Uebernickel
 | 
						|
 *
 | 
						|
 * This library is free software; you can redistribute it and/or
 | 
						|
 * modify it under the terms of the GNU Lesser General Public
 | 
						|
 * License as published by the Free Software Foundation; either
 | 
						|
 * version 2.1 of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This library is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
 * 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, see <http://www.gnu.org/licenses/>.
 | 
						|
 *
 | 
						|
 * Authors: Lars Uebernickel <lars@uebernic.de>
 | 
						|
 */
 | 
						|
 | 
						|
#include <gio/gio.h>
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
/* Wrapper around g_list_model_get_item() and g_list_model_get_object() which
 | 
						|
 * checks they return the same thing. */
 | 
						|
static gpointer
 | 
						|
list_model_get (GListModel *model,
 | 
						|
                guint       position)
 | 
						|
{
 | 
						|
  GObject *item = g_list_model_get_item (model, position);
 | 
						|
  GObject *object = g_list_model_get_object (model, position);
 | 
						|
 | 
						|
  g_assert_true (item == object);
 | 
						|
 | 
						|
  g_clear_object (&object);
 | 
						|
  return g_steal_pointer (&item);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that constructing/getting/setting properties on a #GListStore works. */
 | 
						|
static void
 | 
						|
test_store_properties (void)
 | 
						|
{
 | 
						|
  GListStore *store = NULL;
 | 
						|
  GType item_type;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_MENU_ITEM);
 | 
						|
  g_object_get (store, "item-type", &item_type, NULL);
 | 
						|
  g_assert_cmpint (item_type, ==, G_TYPE_MENU_ITEM);
 | 
						|
 | 
						|
  g_clear_object (&store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that #GListStore rejects non-GObject item types. */
 | 
						|
static void
 | 
						|
test_store_non_gobjects (void)
 | 
						|
{
 | 
						|
  if (g_test_subprocess ())
 | 
						|
    {
 | 
						|
      /* We have to use g_object_new() since g_list_store_new() checks the item
 | 
						|
       * type. We want to check the property setter code works properly. */
 | 
						|
      g_object_new (G_TYPE_LIST_STORE, "item-type", G_TYPE_LONG, NULL);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
  g_test_trap_subprocess (NULL, 0, 0);
 | 
						|
  g_test_trap_assert_failed ();
 | 
						|
  g_test_trap_assert_stderr ("*WARNING*value * of type 'GType' is invalid or "
 | 
						|
                             "out of range for property 'item-type'*");
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_store_boundaries (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GMenuItem *item;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_MENU_ITEM);
 | 
						|
 | 
						|
  item = g_menu_item_new (NULL, NULL);
 | 
						|
 | 
						|
  /* remove an item from an empty list */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
 | 
						|
  g_list_store_remove (store, 0);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
 | 
						|
  /* don't allow inserting an item past the end ... */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
 | 
						|
  g_list_store_insert (store, 1, item);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
 | 
						|
  /* ... except exactly at the end */
 | 
						|
  g_list_store_insert (store, 0, item);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
 | 
						|
 | 
						|
  /* remove a non-existing item at exactly the end of the list */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*g_sequence*");
 | 
						|
  g_list_store_remove (store, 1);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
 | 
						|
  g_list_store_remove (store, 0);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
 | 
						|
 | 
						|
  /* splice beyond the end of the list */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
 | 
						|
  g_list_store_splice (store, 1, 0, NULL, 0);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
 | 
						|
  /* remove items from an empty list */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
 | 
						|
  g_list_store_splice (store, 0, 1, NULL, 0);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_list_store_splice (store, 0, 1, (gpointer *) &item, 1);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
 | 
						|
 | 
						|
  /* remove more items than exist */
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*position*");
 | 
						|
  g_list_store_splice (store, 0, 5, NULL, 0);
 | 
						|
  g_test_assert_expected_messages ();
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 1);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
  g_assert_finalize_object (item);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_store_refcounts (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GMenuItem *items[10];
 | 
						|
  GMenuItem *tmp;
 | 
						|
  guint i;
 | 
						|
  guint n_items;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_MENU_ITEM);
 | 
						|
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 0);
 | 
						|
  g_assert_null (list_model_get (G_LIST_MODEL (store), 0));
 | 
						|
 | 
						|
  n_items = G_N_ELEMENTS (items);
 | 
						|
  for (i = 0; i < n_items; i++)
 | 
						|
    {
 | 
						|
      items[i] = g_menu_item_new (NULL, NULL);
 | 
						|
      g_object_add_weak_pointer (G_OBJECT (items[i]), (gpointer *) &items[i]);
 | 
						|
      g_list_store_append (store, items[i]);
 | 
						|
 | 
						|
      g_object_unref (items[i]);
 | 
						|
      g_assert_nonnull (items[i]);
 | 
						|
    }
 | 
						|
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, n_items);
 | 
						|
  g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
 | 
						|
 | 
						|
  tmp = list_model_get (G_LIST_MODEL (store), 3);
 | 
						|
  g_assert_true (tmp == items[3]);
 | 
						|
  g_object_unref (tmp);
 | 
						|
 | 
						|
  g_list_store_remove (store, 4);
 | 
						|
  g_assert_null (items[4]);
 | 
						|
  n_items--;
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, n_items);
 | 
						|
  g_assert_null (list_model_get (G_LIST_MODEL (store), n_items));
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (items); i++)
 | 
						|
    g_assert_null (items[i]);
 | 
						|
}
 | 
						|
 | 
						|
static gchar *
 | 
						|
make_random_string (void)
 | 
						|
{
 | 
						|
  gchar *str = g_malloc (10);
 | 
						|
  gint i;
 | 
						|
 | 
						|
  for (i = 0; i < 9; i++)
 | 
						|
    str[i] = g_test_rand_int_range ('a', 'z');
 | 
						|
  str[i] = '\0';
 | 
						|
 | 
						|
  return str;
 | 
						|
}
 | 
						|
 | 
						|
static gint
 | 
						|
compare_items (gconstpointer a_p,
 | 
						|
               gconstpointer b_p,
 | 
						|
               gpointer      user_data)
 | 
						|
{
 | 
						|
  GObject *a_o = (GObject *) a_p;
 | 
						|
  GObject *b_o = (GObject *) b_p;
 | 
						|
 | 
						|
  gchar *a = g_object_get_data (a_o, "key");
 | 
						|
  gchar *b = g_object_get_data (b_o, "key");
 | 
						|
 | 
						|
  g_assert (user_data == GUINT_TO_POINTER(0x1234u));
 | 
						|
 | 
						|
  return strcmp (a, b);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
insert_string (GListStore  *store,
 | 
						|
               const gchar *str)
 | 
						|
{
 | 
						|
  GObject *obj;
 | 
						|
 | 
						|
  obj = g_object_new (G_TYPE_OBJECT, NULL);
 | 
						|
  g_object_set_data_full (obj, "key", g_strdup (str), g_free);
 | 
						|
 | 
						|
  g_list_store_insert_sorted (store, obj, compare_items, GUINT_TO_POINTER(0x1234u));
 | 
						|
 | 
						|
  g_object_unref (obj);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_store_sorted (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  guint i;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_OBJECT);
 | 
						|
 | 
						|
  for (i = 0; i < 1000; i++)
 | 
						|
    {
 | 
						|
      gchar *str = make_random_string ();
 | 
						|
      insert_string (store, str);
 | 
						|
      insert_string (store, str); /* multiple copies of the same are OK */
 | 
						|
      g_free (str);
 | 
						|
    }
 | 
						|
 | 
						|
  g_assert_cmpint (g_list_model_get_n_items (G_LIST_MODEL (store)), ==, 2000);
 | 
						|
 | 
						|
  for (i = 0; i < 1000; i++)
 | 
						|
    {
 | 
						|
      GObject *a, *b;
 | 
						|
 | 
						|
      /* should see our two copies */
 | 
						|
      a = list_model_get (G_LIST_MODEL (store), i * 2);
 | 
						|
      b = list_model_get (G_LIST_MODEL (store), i * 2 + 1);
 | 
						|
 | 
						|
      g_assert (compare_items (a, b, GUINT_TO_POINTER(0x1234)) == 0);
 | 
						|
      g_assert (a != b);
 | 
						|
 | 
						|
      if (i)
 | 
						|
        {
 | 
						|
          GObject *c;
 | 
						|
 | 
						|
          c = list_model_get (G_LIST_MODEL (store), i * 2 - 1);
 | 
						|
          g_assert (c != a);
 | 
						|
          g_assert (c != b);
 | 
						|
 | 
						|
          g_assert (compare_items (b, c, GUINT_TO_POINTER(0x1234)) > 0);
 | 
						|
          g_assert (compare_items (a, c, GUINT_TO_POINTER(0x1234)) > 0);
 | 
						|
 | 
						|
          g_object_unref (c);
 | 
						|
        }
 | 
						|
 | 
						|
      g_object_unref (a);
 | 
						|
      g_object_unref (b);
 | 
						|
    }
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that using splice() to replace the middle element in a list store works. */
 | 
						|
static void
 | 
						|
test_store_splice_replace_middle (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GAction *item;
 | 
						|
  GPtrArray *array;
 | 
						|
 | 
						|
  g_test_bug ("795307");
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  array = g_ptr_array_new_full (0, g_object_unref);
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("1", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("2", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("3", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("4", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("5", NULL));
 | 
						|
 | 
						|
  /* Add three items through splice */
 | 
						|
  g_list_store_splice (store, 0, 0, array->pdata, 3);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 3);
 | 
						|
 | 
						|
  item = list_model_get (model, 0);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "1");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 1);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "2");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 2);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "3");
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  /* Replace the middle one with two new items */
 | 
						|
  g_list_store_splice (store, 1, 1, array->pdata + 3, 2);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 4);
 | 
						|
 | 
						|
  item = list_model_get (model, 0);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "1");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 1);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "4");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 2);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "5");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 3);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "3");
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  g_ptr_array_unref (array);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that using splice() to replace the whole list store works. */
 | 
						|
static void
 | 
						|
test_store_splice_replace_all (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GPtrArray *array;
 | 
						|
  GAction *item;
 | 
						|
 | 
						|
  g_test_bug ("795307");
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  array = g_ptr_array_new_full (0, g_object_unref);
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("1", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("2", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("3", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("4", NULL));
 | 
						|
 | 
						|
  /* Add the first two */
 | 
						|
  g_list_store_splice (store, 0, 0, array->pdata, 2);
 | 
						|
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
 | 
						|
  item = list_model_get (model, 0);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "1");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 1);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "2");
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  /* Replace all with the last two */
 | 
						|
  g_list_store_splice (store, 0, 2, array->pdata + 2, 2);
 | 
						|
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
 | 
						|
  item = list_model_get (model, 0);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "3");
 | 
						|
  g_object_unref (item);
 | 
						|
  item = list_model_get (model, 1);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "4");
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  g_ptr_array_unref (array);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that using splice() without removing or adding anything works */
 | 
						|
static void
 | 
						|
test_store_splice_noop (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GAction *item;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  /* splice noop with an empty list */
 | 
						|
  g_list_store_splice (store, 0, 0, NULL, 0);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
 | 
						|
 | 
						|
  /* splice noop with a non-empty list */
 | 
						|
  item = G_ACTION (g_simple_action_new ("1", NULL));
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  g_list_store_splice (store, 0, 0, NULL, 0);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
 | 
						|
 | 
						|
  g_list_store_splice (store, 1, 0, NULL, 0);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
 | 
						|
 | 
						|
  item = list_model_get (model, 0);
 | 
						|
  g_assert_cmpstr (g_action_get_name (item), ==, "1");
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
model_array_equal (GListModel *model, GPtrArray *array)
 | 
						|
{
 | 
						|
  guint i;
 | 
						|
 | 
						|
  if (g_list_model_get_n_items (model) != array->len)
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  for (i = 0; i < array->len; i++)
 | 
						|
    {
 | 
						|
      GObject *ptr;
 | 
						|
      gboolean ptrs_equal;
 | 
						|
 | 
						|
      ptr = list_model_get (model, i);
 | 
						|
      ptrs_equal = (g_ptr_array_index (array, i) == ptr);
 | 
						|
      g_object_unref (ptr);
 | 
						|
      if (!ptrs_equal)
 | 
						|
        return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/* Test that using splice() to remove multiple items at different
 | 
						|
 * positions works */
 | 
						|
static void
 | 
						|
test_store_splice_remove_multiple (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GPtrArray *array;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  array = g_ptr_array_new_full (0, g_object_unref);
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("1", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("2", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("3", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("4", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("5", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("6", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("7", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("8", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("9", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("10", NULL));
 | 
						|
 | 
						|
  /* Add all */
 | 
						|
  g_list_store_splice (store, 0, 0, array->pdata, array->len);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
 | 
						|
  /* Remove the first two */
 | 
						|
  g_list_store_splice (store, 0, 2, NULL, 0);
 | 
						|
  g_assert_false (model_array_equal (model, array));
 | 
						|
  g_ptr_array_remove_range (array, 0, 2);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 8);
 | 
						|
 | 
						|
  /* Remove two in the middle */
 | 
						|
  g_list_store_splice (store, 2, 2, NULL, 0);
 | 
						|
  g_assert_false (model_array_equal (model, array));
 | 
						|
  g_ptr_array_remove_range (array, 2, 2);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 6);
 | 
						|
 | 
						|
  /* Remove two at the end */
 | 
						|
  g_list_store_splice (store, 4, 2, NULL, 0);
 | 
						|
  g_assert_false (model_array_equal (model, array));
 | 
						|
  g_ptr_array_remove_range (array, 4, 2);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 4);
 | 
						|
 | 
						|
  g_ptr_array_unref (array);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that using splice() to add multiple items at different
 | 
						|
 * positions works */
 | 
						|
static void
 | 
						|
test_store_splice_add_multiple (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GPtrArray *array;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  array = g_ptr_array_new_full (0, g_object_unref);
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("1", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("2", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("3", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("4", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("5", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("6", NULL));
 | 
						|
 | 
						|
  /* Add two at the beginning */
 | 
						|
  g_list_store_splice (store, 0, 0, array->pdata, 2);
 | 
						|
 | 
						|
  /* Add two at the end */
 | 
						|
  g_list_store_splice (store, 2, 0, array->pdata + 4, 2);
 | 
						|
 | 
						|
  /* Add two in the middle */
 | 
						|
  g_list_store_splice (store, 2, 0, array->pdata + 2, 2);
 | 
						|
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
 | 
						|
  g_ptr_array_unref (array);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that get_item_type() returns the right type */
 | 
						|
static void
 | 
						|
test_store_item_type (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GType gtype;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  gtype = g_list_model_get_item_type (G_LIST_MODEL (store));
 | 
						|
  g_assert (gtype == G_TYPE_SIMPLE_ACTION);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that remove_all() removes all items */
 | 
						|
static void
 | 
						|
test_store_remove_all (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GSimpleAction *item;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  /* Test with an empty list */
 | 
						|
  g_list_store_remove_all (store);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
 | 
						|
 | 
						|
  /* Test with a non-empty list */
 | 
						|
  item = g_simple_action_new ("42", NULL);
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_object_unref (item);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 2);
 | 
						|
  g_list_store_remove_all (store);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 0);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test that splice() logs an error when passed the wrong item type */
 | 
						|
static void
 | 
						|
test_store_splice_wrong_type (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
 | 
						|
  g_test_expect_message (G_LOG_DOMAIN,
 | 
						|
                         G_LOG_LEVEL_CRITICAL,
 | 
						|
                         "*GListStore instead of a GSimpleAction*");
 | 
						|
  g_list_store_splice (store, 0, 0, (gpointer)&store, 1);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
static gint
 | 
						|
ptr_array_cmp_action_by_name (GAction **a, GAction **b)
 | 
						|
{
 | 
						|
  return g_strcmp0 (g_action_get_name (*a), g_action_get_name (*b));
 | 
						|
}
 | 
						|
 | 
						|
static gint
 | 
						|
list_model_cmp_action_by_name (GAction *a, GAction *b, gpointer user_data)
 | 
						|
{
 | 
						|
  return g_strcmp0 (g_action_get_name (a), g_action_get_name (b));
 | 
						|
}
 | 
						|
 | 
						|
/* Test if sort() works */
 | 
						|
static void
 | 
						|
test_store_sort (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GPtrArray *array;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  array = g_ptr_array_new_full (0, g_object_unref);
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("2", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("3", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("9", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("4", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("5", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("8", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("6", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("7", NULL));
 | 
						|
  g_ptr_array_add (array, g_simple_action_new ("1", NULL));
 | 
						|
 | 
						|
  /* Sort an empty list */
 | 
						|
  g_list_store_sort (store, (GCompareDataFunc)list_model_cmp_action_by_name, NULL);
 | 
						|
 | 
						|
  /* Add all */
 | 
						|
  g_list_store_splice (store, 0, 0, array->pdata, array->len);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
 | 
						|
  /* Sort both and check if the result is the same */
 | 
						|
  g_ptr_array_sort (array, (GCompareFunc)ptr_array_cmp_action_by_name);
 | 
						|
  g_assert_false (model_array_equal (model, array));
 | 
						|
  g_list_store_sort (store, (GCompareDataFunc)list_model_cmp_action_by_name, NULL);
 | 
						|
  g_assert_true (model_array_equal (model, array));
 | 
						|
 | 
						|
  g_ptr_array_unref (array);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Test the cases where the item store tries to speed up item access by caching
 | 
						|
 * the last iter/position */
 | 
						|
static void
 | 
						|
test_store_get_item_cache (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GSimpleAction *item1, *item2, *temp;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  /* Add two */
 | 
						|
  item1 = g_simple_action_new ("1", NULL);
 | 
						|
  g_list_store_append (store, item1);
 | 
						|
  item2 = g_simple_action_new ("2", NULL);
 | 
						|
  g_list_store_append (store, item2);
 | 
						|
 | 
						|
  /* Clear the cache */
 | 
						|
  g_assert_null (list_model_get (model, 42));
 | 
						|
 | 
						|
  /* Access the same position twice */
 | 
						|
  temp = list_model_get (model, 1);
 | 
						|
  g_assert (temp == item2);
 | 
						|
  g_object_unref (temp);
 | 
						|
  temp = list_model_get (model, 1);
 | 
						|
  g_assert (temp == item2);
 | 
						|
  g_object_unref (temp);
 | 
						|
 | 
						|
  g_assert_null (list_model_get (model, 42));
 | 
						|
 | 
						|
  /* Access forwards */
 | 
						|
  temp = list_model_get (model, 0);
 | 
						|
  g_assert (temp == item1);
 | 
						|
  g_object_unref (temp);
 | 
						|
  temp = list_model_get (model, 1);
 | 
						|
  g_assert (temp == item2);
 | 
						|
  g_object_unref (temp);
 | 
						|
 | 
						|
  g_assert_null (list_model_get (model, 42));
 | 
						|
 | 
						|
  /* Access backwards */
 | 
						|
  temp = list_model_get (model, 1);
 | 
						|
  g_assert (temp == item2);
 | 
						|
  g_object_unref (temp);
 | 
						|
  temp = list_model_get (model, 0);
 | 
						|
  g_assert (temp == item1);
 | 
						|
  g_object_unref (temp);
 | 
						|
 | 
						|
  g_object_unref (item1);
 | 
						|
  g_object_unref (item2);
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
struct ItemsChangedData
 | 
						|
{
 | 
						|
  guint position;
 | 
						|
  guint removed;
 | 
						|
  guint added;
 | 
						|
  gboolean called;
 | 
						|
};
 | 
						|
 | 
						|
static void
 | 
						|
expect_items_changed (struct ItemsChangedData *expected,
 | 
						|
                      guint position,
 | 
						|
                      guint removed,
 | 
						|
                      guint added)
 | 
						|
{
 | 
						|
  expected->position = position;
 | 
						|
  expected->removed = removed;
 | 
						|
  expected->added = added;
 | 
						|
  expected->called = FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
on_items_changed (GListModel *model,
 | 
						|
                  guint position,
 | 
						|
                  guint removed,
 | 
						|
                  guint added,
 | 
						|
                  struct ItemsChangedData *expected)
 | 
						|
{
 | 
						|
  g_assert_false (expected->called);
 | 
						|
  g_assert_cmpuint (expected->position, ==, position);
 | 
						|
  g_assert_cmpuint (expected->removed, ==, removed);
 | 
						|
  g_assert_cmpuint (expected->added, ==, added);
 | 
						|
  expected->called = TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/* Test that all operations on the list emit the items-changed signal */
 | 
						|
static void
 | 
						|
test_store_signal_items_changed (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GSimpleAction *item;
 | 
						|
  struct ItemsChangedData expected = {0};
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  g_object_connect (model, "signal::items-changed",
 | 
						|
                    on_items_changed, &expected, NULL);
 | 
						|
 | 
						|
  /* Emit the signal manually */
 | 
						|
  expect_items_changed (&expected, 0, 0, 0);
 | 
						|
  g_list_model_items_changed (model, 0, 0, 0);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Append an item */
 | 
						|
  expect_items_changed (&expected, 0, 0, 1);
 | 
						|
  item = g_simple_action_new ("2", NULL);
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_object_unref (item);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Insert an item */
 | 
						|
  expect_items_changed (&expected, 1, 0, 1);
 | 
						|
  item = g_simple_action_new ("1", NULL);
 | 
						|
  g_list_store_insert (store, 1, item);
 | 
						|
  g_object_unref (item);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Sort the list */
 | 
						|
  expect_items_changed (&expected, 0, 2, 2);
 | 
						|
  g_list_store_sort (store,
 | 
						|
                     (GCompareDataFunc)list_model_cmp_action_by_name,
 | 
						|
                     NULL);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Insert sorted */
 | 
						|
  expect_items_changed (&expected, 2, 0, 1);
 | 
						|
  item = g_simple_action_new ("3", NULL);
 | 
						|
  g_list_store_insert_sorted (store,
 | 
						|
                              item,
 | 
						|
                              (GCompareDataFunc)list_model_cmp_action_by_name,
 | 
						|
                              NULL);
 | 
						|
  g_object_unref (item);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Remove an item */
 | 
						|
  expect_items_changed (&expected, 1, 1, 0);
 | 
						|
  g_list_store_remove (store, 1);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Splice */
 | 
						|
  expect_items_changed (&expected, 0, 2, 1);
 | 
						|
  item = g_simple_action_new ("4", NULL);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), >=, 2);
 | 
						|
  g_list_store_splice (store, 0, 2, (gpointer)&item, 1);
 | 
						|
  g_object_unref (item);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  /* Remove all */
 | 
						|
  expect_items_changed (&expected, 0, 1, 0);
 | 
						|
  g_assert_cmpuint (g_list_model_get_n_items (model), ==, 1);
 | 
						|
  g_list_store_remove_all (store);
 | 
						|
  g_assert_true (expected.called);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
/* Due to an overflow in the list store last-iter optimization,
 | 
						|
 * the sequence 'lookup 0; lookup MAXUINT' was returning the
 | 
						|
 * same item twice, and not NULL for the second lookup.
 | 
						|
 * See #1639.
 | 
						|
 */
 | 
						|
static void
 | 
						|
test_store_past_end (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  GListModel *model;
 | 
						|
  GSimpleAction *item;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
  model = G_LIST_MODEL (store);
 | 
						|
 | 
						|
  item = g_simple_action_new ("2", NULL);
 | 
						|
  g_list_store_append (store, item);
 | 
						|
  g_object_unref (item);
 | 
						|
 | 
						|
  g_assert_cmpint (g_list_model_get_n_items (model), ==, 1);
 | 
						|
  item = g_list_model_get_item (model, 0);
 | 
						|
  g_assert_nonnull (item);
 | 
						|
  g_object_unref (item);
 | 
						|
  item = g_list_model_get_item (model, G_MAXUINT);
 | 
						|
  g_assert_null (item);
 | 
						|
 | 
						|
  g_object_unref (store);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
list_model_casecmp_action_by_name (gconstpointer a,
 | 
						|
                                   gconstpointer b)
 | 
						|
{
 | 
						|
  return g_ascii_strcasecmp (g_action_get_name (G_ACTION (a)),
 | 
						|
                             g_action_get_name (G_ACTION (b))) == 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Test if find() and find_with_equal_func() works */
 | 
						|
static void
 | 
						|
test_store_find (void)
 | 
						|
{
 | 
						|
  GListStore *store;
 | 
						|
  guint position = 100;
 | 
						|
  const gchar *item_strs[4] = { "aaa", "bbb", "xxx", "ccc" };
 | 
						|
  GSimpleAction *items[4] = { NULL, };
 | 
						|
  GSimpleAction *other_item;
 | 
						|
  guint i;
 | 
						|
 | 
						|
  store = g_list_store_new (G_TYPE_SIMPLE_ACTION);
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
 | 
						|
    items[i] = g_simple_action_new (item_strs[i], NULL);
 | 
						|
 | 
						|
  /* Shouldn't crash on an empty list, or change the position pointer */
 | 
						|
  g_assert_false (g_list_store_find (store, items[0], NULL));
 | 
						|
  g_assert_false (g_list_store_find (store, items[0], &position));
 | 
						|
  g_assert_cmpint (position, ==, 100);
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
 | 
						|
    g_list_store_append (store, items[i]);
 | 
						|
 | 
						|
  /* Check whether it could still find the the elements */
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
 | 
						|
    {
 | 
						|
      g_assert_true (g_list_store_find (store, items[i], &position));
 | 
						|
      g_assert_cmpint (position, ==, i);
 | 
						|
      /* Shouldn't try to write to position pointer if NULL given */
 | 
						|
      g_assert_true (g_list_store_find (store, items[i], NULL));
 | 
						|
    }
 | 
						|
 | 
						|
  /* try to find element not part of the list */
 | 
						|
  other_item = g_simple_action_new ("111", NULL);
 | 
						|
  g_assert_false (g_list_store_find (store, other_item, NULL));
 | 
						|
  g_clear_object (&other_item);
 | 
						|
 | 
						|
  /* Re-add item; find() should only return the first position */
 | 
						|
  g_list_store_append (store, items[0]);
 | 
						|
  g_assert_true (g_list_store_find (store, items[0], &position));
 | 
						|
  g_assert_cmpint (position, ==, 0);
 | 
						|
 | 
						|
  /* try to find element which should only work with custom equality check */
 | 
						|
  other_item = g_simple_action_new ("XXX", NULL);
 | 
						|
  g_assert_false (g_list_store_find (store, other_item, NULL));
 | 
						|
  g_assert_true (g_list_store_find_with_equal_func (store,
 | 
						|
                                                    other_item,
 | 
						|
                                                    list_model_casecmp_action_by_name,
 | 
						|
                                                    &position));
 | 
						|
  g_assert_cmpint (position, ==, 2);
 | 
						|
  g_clear_object (&other_item);
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (item_strs); i++)
 | 
						|
    g_clear_object(&items[i]);
 | 
						|
  g_clear_object (&store);
 | 
						|
}
 | 
						|
 | 
						|
int main (int argc, char *argv[])
 | 
						|
{
 | 
						|
  g_test_init (&argc, &argv, NULL);
 | 
						|
  g_test_bug_base ("https://bugzilla.gnome.org/");
 | 
						|
 | 
						|
  g_test_add_func ("/glistmodel/store/properties", test_store_properties);
 | 
						|
  g_test_add_func ("/glistmodel/store/non-gobjects", test_store_non_gobjects);
 | 
						|
  g_test_add_func ("/glistmodel/store/boundaries", test_store_boundaries);
 | 
						|
  g_test_add_func ("/glistmodel/store/refcounts", test_store_refcounts);
 | 
						|
  g_test_add_func ("/glistmodel/store/sorted", test_store_sorted);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-replace-middle",
 | 
						|
                   test_store_splice_replace_middle);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-replace-all",
 | 
						|
                   test_store_splice_replace_all);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-noop", test_store_splice_noop);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-remove-multiple",
 | 
						|
                   test_store_splice_remove_multiple);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-add-multiple",
 | 
						|
                   test_store_splice_add_multiple);
 | 
						|
  g_test_add_func ("/glistmodel/store/splice-wrong-type",
 | 
						|
                   test_store_splice_wrong_type);
 | 
						|
  g_test_add_func ("/glistmodel/store/item-type",
 | 
						|
                   test_store_item_type);
 | 
						|
  g_test_add_func ("/glistmodel/store/remove-all",
 | 
						|
                   test_store_remove_all);
 | 
						|
  g_test_add_func ("/glistmodel/store/sort",
 | 
						|
                   test_store_sort);
 | 
						|
  g_test_add_func ("/glistmodel/store/get-item-cache",
 | 
						|
                   test_store_get_item_cache);
 | 
						|
  g_test_add_func ("/glistmodel/store/items-changed",
 | 
						|
                   test_store_signal_items_changed);
 | 
						|
  g_test_add_func ("/glistmodel/store/past-end", test_store_past_end);
 | 
						|
  g_test_add_func ("/glistmodel/store/find", test_store_find);
 | 
						|
 | 
						|
  return g_test_run ();
 | 
						|
}
 |