From 6d0fe46cba6966ec1b905513e1c283e25d33571f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 10 Sep 2018 17:01:57 +0200 Subject: propertylookuplistmodel: Add This model just takes an object and a property name and recursively looks it up. In particular, I want it for: widget, widget.parent, widget.parent.parent, ... --- gtk/gtkpropertylookuplistmodel.c | 480 ++++++++++++++++++++++++++++++++ gtk/gtkpropertylookuplistmodelprivate.h | 50 ++++ gtk/meson.build | 1 + testsuite/gtk/meson.build | 1 + testsuite/gtk/propertylookuplistmodel.c | 242 ++++++++++++++++ 5 files changed, 774 insertions(+) create mode 100644 gtk/gtkpropertylookuplistmodel.c create mode 100644 gtk/gtkpropertylookuplistmodelprivate.h create mode 100644 testsuite/gtk/propertylookuplistmodel.c diff --git a/gtk/gtkpropertylookuplistmodel.c b/gtk/gtkpropertylookuplistmodel.c new file mode 100644 index 0000000000..0c0dc4ea91 --- /dev/null +++ b/gtk/gtkpropertylookuplistmodel.c @@ -0,0 +1,480 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * 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 . + * + * Authors: Benjamin Otte + */ + + +/* + * SECTION:gtkpropertylookuplistmodel + * @Short_description: a List model for property lookups + * @Title: GtkPropertyLookupListModel + * @See_also: #GListModel + * + * #GtkPropertyLookupListModel is a #GListModel implementation that takes an + * object and a property and then recursively looks up the next element using + * the property on the previous object. + * + * For example, one could use this list model with the GtkWidget:parent property + * to get a list of a widgets and all its ancestors. + **/ + +#include "config.h" + +#include "gtkpropertylookuplistmodelprivate.h" + +#include "gtkintl.h" +#include "gtkprivate.h" + +enum { + PROP_0, + PROP_ITEM_TYPE, + PROP_OBJECT, + PROP_PROPERTY, + NUM_PROPERTIES +}; + +struct _GtkPropertyLookupListModel +{ + GObject parent_instance; + + GType item_type; + char *property; + GPtrArray *items; /* list of items - lazily expanded if there's a NULL sentinel */ +}; + +struct _GtkPropertyLookupListModelClass +{ + GObjectClass parent_class; +}; + +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static GType +gtk_property_lookup_list_model_get_item_type (GListModel *list) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (list); + + return self->item_type; +} + +static gboolean +gtk_property_lookup_list_model_is_infinite (GtkPropertyLookupListModel *self) +{ + if (self->items->len == 0) + return FALSE; + + return g_ptr_array_index (self->items, self->items->len - 1) == NULL; +} + +static void +gtk_property_lookup_list_model_notify_cb (GObject *object, + GParamSpec *pspec, + GtkPropertyLookupListModel *self); + +static guint +gtk_property_lookup_list_model_clear (GtkPropertyLookupListModel *self, + guint remaining) +{ + guint i; + + for (i = remaining; i < self->items->len; i++) + { + gpointer object = g_ptr_array_index (self->items, i); + if (object == NULL) + break; + + g_signal_handlers_disconnect_by_func (object, gtk_property_lookup_list_model_notify_cb, self); + g_object_unref (object); + } + + /* keeps the sentinel, yay! */ + g_ptr_array_remove_range (self->items, remaining, i - remaining); + + return i - remaining; +} + +static guint +gtk_property_lookup_list_model_append (GtkPropertyLookupListModel *self, + guint n_items) +{ + gpointer last, next; + guint i, start; + + g_assert (self->items->len > 0); + g_assert (!gtk_property_lookup_list_model_is_infinite (self)); + + last = g_ptr_array_index (self->items, self->items->len - 1); + start = self->items->len; + for (i = start; i < n_items; i++) + { + g_object_get (last, self->property, &next, NULL); + if (next == NULL) + return i - start; + + g_signal_connect_closure_by_id (next, + g_signal_lookup ("notify", G_OBJECT_TYPE (next)), + g_quark_from_static_string (self->property), + g_cclosure_new (G_CALLBACK (gtk_property_lookup_list_model_notify_cb), G_OBJECT (self), NULL), + FALSE); + + g_ptr_array_add (self->items, next); + last = next; + } + + return i - start; +} + +static void +gtk_property_lookup_list_model_ensure (GtkPropertyLookupListModel *self, + guint n_items) +{ + if (!gtk_property_lookup_list_model_is_infinite (self)) + return; + + if (self->items->len - 1 >= n_items) + return; + + /* remove NULL sentinel */ + g_ptr_array_remove_index (self->items, self->items->len - 1); + + if (self->items->len == 0) + return; + + if (gtk_property_lookup_list_model_append (self, n_items) == n_items) + { + /* readd NULL sentinel */ + g_ptr_array_add (self->items, NULL); + } +} + +static void +gtk_property_lookup_list_model_notify_cb (GObject *object, + GParamSpec *pspec, + GtkPropertyLookupListModel *self) +{ + guint position, removed, added; + + if (!g_ptr_array_find (self->items, object, &position)) + { + /* can only happen if we forgot to disconnect a signal handler */ + g_assert_not_reached (); + } + /* we found the position of the item that notified, but the first change + * is its child. + */ + position++; + + removed = gtk_property_lookup_list_model_clear (self, position); + + if (!gtk_property_lookup_list_model_is_infinite (self)) + added = gtk_property_lookup_list_model_append (self, G_MAXUINT); + else + added = 0; + + if (removed > 0 || added > 0) + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); +} + +static guint +gtk_property_lookup_list_model_get_n_items (GListModel *list) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (list); + + gtk_property_lookup_list_model_ensure (self, G_MAXUINT); + + return self->items->len; +} + +static gpointer +gtk_property_lookup_list_model_get_item (GListModel *list, + guint position) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (list); + + gtk_property_lookup_list_model_ensure (self, position + 1); + + if (position >= self->items->len) + return NULL; + + return g_object_ref (g_ptr_array_index (self->items, position)); +} + +static void +gtk_property_lookup_list_model_list_model_init (GListModelInterface *iface) +{ + iface->get_item_type = gtk_property_lookup_list_model_get_item_type; + iface->get_n_items = gtk_property_lookup_list_model_get_n_items; + iface->get_item = gtk_property_lookup_list_model_get_item; +} + +G_DEFINE_TYPE_WITH_CODE (GtkPropertyLookupListModel, gtk_property_lookup_list_model, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_property_lookup_list_model_list_model_init)) + +static gboolean +check_pspec (GType type, + GParamSpec *pspec) +{ + if (pspec == NULL) + return FALSE; + + if (!G_IS_PARAM_SPEC_OBJECT (pspec)) + return FALSE; + + if (!g_type_is_a (G_PARAM_SPEC (pspec)->value_type, type)) + return FALSE; + + return TRUE; +} + +static gboolean +lookup_pspec (GType type, + const char *name) +{ + gboolean result; + + if (g_type_is_a (type, G_TYPE_INTERFACE)) + { + gpointer iface; + GParamSpec *pspec; + + iface = g_type_default_interface_ref (type); + pspec = g_object_interface_find_property (iface, name); + result = check_pspec (type, pspec); + g_type_default_interface_unref (iface); + } + else + { + GObjectClass *klass; + GParamSpec *pspec; + + klass = g_type_class_ref (type); + g_return_val_if_fail (klass != NULL, FALSE); + pspec = g_object_class_find_property (klass, name); + result = check_pspec (type, pspec); + g_type_class_unref (klass); + } + + return result; +} + +static void +gtk_property_lookup_list_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + self->item_type = g_value_get_gtype (value); + g_return_if_fail (self->item_type != 0); + break; + + case PROP_OBJECT: + gtk_property_lookup_list_model_set_object (self, g_value_get_object (value)); + break; + + case PROP_PROPERTY: + self->property = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + if (self->property && self->item_type && + !lookup_pspec (self->item_type, self->property)) + { + g_critical ("type %s has no property named \"%s\"", g_type_name (self->item_type), self->property); + } +} + +static void +gtk_property_lookup_list_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM_TYPE: + g_value_set_gtype (value, self->item_type); + break; + + case PROP_OBJECT: + g_value_set_object (value, gtk_property_lookup_list_model_get_object (self)); + break; + + case PROP_PROPERTY: + g_value_set_object (value, self->property); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_property_lookup_list_model_dispose (GObject *object) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (object); + + gtk_property_lookup_list_model_clear (self, 0); + + G_OBJECT_CLASS (gtk_property_lookup_list_model_parent_class)->dispose (object); +} + +static void +gtk_property_lookup_list_model_finalize (GObject *object) +{ + GtkPropertyLookupListModel *self = GTK_PROPERTY_LOOKUP_LIST_MODEL (object); + + g_ptr_array_unref (self->items); + g_free (self->property); + + G_OBJECT_CLASS (gtk_property_lookup_list_model_parent_class)->finalize (object); +} + +static void +gtk_property_lookup_list_model_class_init (GtkPropertyLookupListModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = gtk_property_lookup_list_model_get_property; + object_class->set_property = gtk_property_lookup_list_model_set_property; + object_class->dispose = gtk_property_lookup_list_model_dispose; + object_class->finalize = gtk_property_lookup_list_model_finalize; + + /** + * GtkPropertyLookupListModel:item-type: + * + * The #GType for elements of this object + */ + properties[PROP_ITEM_TYPE] = + g_param_spec_gtype ("item-type", + P_("Item type"), + P_("The type of elements of this object"), + G_TYPE_OBJECT, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkPropertyLookupListModel:property: + * + * Name of the property used for lookups + */ + properties[PROP_PROPERTY] = + g_param_spec_string ("property", + P_("type"), + P_("Name of the property used for lookups"), + NULL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkPropertyLookupListModel:object: + * + * The root object + */ + properties[PROP_OBJECT] = + g_param_spec_object ("object", + P_("Object"), + P_("The root object"), + G_TYPE_LIST_MODEL, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +gtk_property_lookup_list_model_init (GtkPropertyLookupListModel *self) +{ + self->items = g_ptr_array_new (); + /* add sentinel */ + g_ptr_array_add (self->items, NULL); +} + +GtkPropertyLookupListModel * +gtk_property_lookup_list_model_new (GType item_type, + const char *property_name) +{ + g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL); + g_return_val_if_fail (property_name != NULL, NULL); + + return g_object_new (GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL, + "item-type", item_type, + "property", property_name, + NULL); +} + +void +gtk_property_lookup_list_model_set_object (GtkPropertyLookupListModel *self, + gpointer object) +{ + guint removed, added; + + g_return_if_fail (GTK_IS_PROPERTY_LOOKUP_LIST_MODEL (self)); + + if (object) + { + if (self->items->len != 0 && + g_ptr_array_index (self->items, 0) == object) + return; + + removed = gtk_property_lookup_list_model_clear (self, 0); + + g_ptr_array_insert (self->items, 0, g_object_ref (object)); + g_signal_connect_closure_by_id (object, + g_signal_lookup ("notify", G_OBJECT_TYPE (object)), + g_quark_from_static_string (self->property), + g_cclosure_new (G_CALLBACK (gtk_property_lookup_list_model_notify_cb), G_OBJECT (self), NULL), + FALSE); + added = 1; + if (!gtk_property_lookup_list_model_is_infinite (self)) + added += gtk_property_lookup_list_model_append (self, G_MAXUINT); + } + else + { + if (self->items->len == 0 || + g_ptr_array_index (self->items, 0) == NULL) + return; + + removed = gtk_property_lookup_list_model_clear (self, 0); + added = 0; + } + + g_assert (removed != 0 || added != 0); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added); +} + +gpointer +gtk_property_lookup_list_model_get_object (GtkPropertyLookupListModel *self) +{ + g_return_val_if_fail (GTK_IS_PROPERTY_LOOKUP_LIST_MODEL (self), NULL); + + if (self->items->len == 0) + return NULL; + + return g_ptr_array_index (self->items, 0); +} + diff --git a/gtk/gtkpropertylookuplistmodelprivate.h b/gtk/gtkpropertylookuplistmodelprivate.h new file mode 100644 index 0000000000..fae9e239b2 --- /dev/null +++ b/gtk/gtkpropertylookuplistmodelprivate.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * 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 . + * + * Authors: Benjamin Otte + */ + + +#ifndef __GTK_PROPERTY_LOOKUP_LIST_MODEL_H__ +#define __GTK_PROPERTY_LOOKUP_LIST_MODEL_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL (gtk_property_lookup_list_model_get_type ()) +#define GTK_PROPERTY_LOOKUP_LIST_MODEL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL, GtkPropertyLookupListModel)) +#define GTK_PROPERTY_LOOKUP_LIST_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL, GtkPropertyLookupListModelClass)) +#define GTK_IS_PROPERTY_LOOKUP_LIST_MODEL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL)) +#define GTK_IS_PROPERTY_LOOKUP_LIST_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL)) +#define GTK_PROPERTY_LOOKUP_LIST_MODEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_PROPERTY_LOOKUP_LIST_MODEL, GtkPropertyLookupListModelClass)) + +typedef struct _GtkPropertyLookupListModel GtkPropertyLookupListModel; +typedef struct _GtkPropertyLookupListModelClass GtkPropertyLookupListModelClass; + +GType gtk_property_lookup_list_model_get_type (void) G_GNUC_CONST; + +GtkPropertyLookupListModel * gtk_property_lookup_list_model_new (GType item_type, + const char *property_name); + +void gtk_property_lookup_list_model_set_object (GtkPropertyLookupListModel *self, + gpointer object); +gpointer gtk_property_lookup_list_model_get_object (GtkPropertyLookupListModel *self); + + +G_END_DECLS + +#endif /* __GTK_PROPERTY_LOOKUP_LIST_MODEL_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index dbdc455467..9d0f517b49 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -302,6 +302,7 @@ gtk_public_sources = files([ 'gtkprintoperationpreview.c', 'gtkprintsettings.c', 'gtkprogressbar.c', + 'gtkpropertylookuplistmodel.c', 'gtkradiobutton.c', 'gtkradiomenuitem.c', 'gtkradiotoolbutton.c', diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 052de2abfa..e8171ea571 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -36,6 +36,7 @@ tests = [ ['object'], ['objects-finalize'], ['papersize'], + ['propertylookuplistmodel', ['../../gtk/gtkpropertylookuplistmodel.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']], ['rbtree', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']], ['recentmanager'], ['regression-tests'], diff --git a/testsuite/gtk/propertylookuplistmodel.c b/testsuite/gtk/propertylookuplistmodel.c new file mode 100644 index 0000000000..21fa26445d --- /dev/null +++ b/testsuite/gtk/propertylookuplistmodel.c @@ -0,0 +1,242 @@ +/* GtkRBTree tests. + * + * Copyright (C) 2011, Red Hat, Inc. + * Authors: Benjamin Otte + * + * 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 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 . + */ + +#include + +#include + +#include "gtk/gtkpropertylookuplistmodelprivate.h" + +GQuark changes_quark; + +static char * +model_to_string (GListModel *model) +{ + GString *string = g_string_new (NULL); + gpointer item; + guint i; + + for (i = 0; i < g_list_model_get_n_items (model); i++) + { + if (i > 0) + g_string_append (string, " "); + item = g_list_model_get_item (model, i); + g_string_append_printf (string, "%s", G_OBJECT_TYPE_NAME (item)); + g_object_unref (item); + } + + return g_string_free (string, FALSE); +} + +#define assert_model(model, expected) G_STMT_START{ \ + char *s = model_to_string (G_LIST_MODEL (model)); \ + if (!g_str_equal (s, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, s, "==", expected); \ + g_free (s); \ +}G_STMT_END + +#define assert_changes(model, expected) G_STMT_START{ \ + GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \ + if (!g_str_equal (changes->str, expected)) \ + g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #model " == " #expected, changes->str, "==", expected); \ + g_string_set_size (changes, 0); \ +}G_STMT_END + +static void +items_changed (GListModel *model, + guint position, + guint removed, + guint added, + GString *changes) +{ + g_assert (removed != 0 || added != 0); + + if (changes->len) + g_string_append (changes, ", "); + + if (removed == 1 && added == 0) + { + g_string_append_printf (changes, "-%u", position); + } + else if (removed == 0 && added == 1) + { + g_string_append_printf (changes, "+%u", position); + } + else + { + g_string_append_printf (changes, "%u", position); + if (removed > 0) + g_string_append_printf (changes, "-%u", removed); + if (added > 0) + g_string_append_printf (changes, "+%u", added); + } +} + +static void +free_changes (gpointer data) +{ + GString *changes = data; + + /* all changes must have been checked via assert_changes() before */ + g_assert_cmpstr (changes->str, ==, ""); + + g_string_free (changes, TRUE); +} + +static GSList *widgets = NULL; + +static GtkWidget * +create_widget_tree (void) +{ + GtkWidget *window, *box, *grid, *label; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + widgets = g_slist_prepend (widgets, window); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (window), box); + + grid = gtk_grid_new (); + gtk_container_add (GTK_CONTAINER (box), grid); + + label = gtk_label_new ("Hello World"); + gtk_container_add (GTK_CONTAINER (grid), label); + + return label; +} + +static void +destroy_widgets (void) +{ + g_slist_free_full (widgets, (GDestroyNotify) gtk_widget_destroy); + widgets = NULL; +} + +static GtkPropertyLookupListModel * +new_model (gboolean fill) +{ + GtkPropertyLookupListModel *result; + GString *changes; + + result = gtk_property_lookup_list_model_new (GTK_TYPE_WIDGET, "parent"); + if (fill) + { + GtkWidget *widget = create_widget_tree (); + gtk_property_lookup_list_model_set_object (result, widget); + } + changes = g_string_new (""); + g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes); + g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes); + + return result; +} + +static void +test_create_empty (void) +{ + GtkPropertyLookupListModel *model; + + model = new_model (FALSE); + assert_model (model, ""); + assert_changes (model, ""); + + g_object_unref (model); +} + +static void +test_create (void) +{ + GtkPropertyLookupListModel *model; + + model = new_model (TRUE); + assert_model (model, "GtkLabel GtkGrid GtkBox GtkWindow"); + assert_changes (model, ""); + + g_object_unref (model); + destroy_widgets (); +} + +static void +test_set_object (void) +{ + GtkPropertyLookupListModel *model; + GtkWidget *widget; + + widget = create_widget_tree (); + + model = new_model (FALSE); + gtk_property_lookup_list_model_set_object (model, widget); + assert_model (model, "GtkLabel GtkGrid GtkBox GtkWindow"); + assert_changes (model, "+0"); + g_object_unref (model); + + model = new_model (FALSE); + assert_model (model, ""); + gtk_property_lookup_list_model_set_object (model, widget); + assert_model (model, "GtkLabel GtkGrid GtkBox GtkWindow"); + assert_changes (model, "0+4"); + g_object_unref (model); + + destroy_widgets (); +} + +static void +test_change_property (void) +{ + GtkPropertyLookupListModel *model; + GtkWidget *widget, *parent, *grandparent; + + widget = create_widget_tree (); + parent = gtk_widget_get_parent (widget); + grandparent = gtk_widget_get_parent (parent); + + model = new_model (FALSE); + assert_model (model, ""); /* make sure the model has a definite size */ + gtk_property_lookup_list_model_set_object (model, widget); + assert_model (model, "GtkLabel GtkGrid GtkBox GtkWindow"); + assert_changes (model, "0+4"); + + gtk_container_remove (GTK_CONTAINER (parent), widget); + assert_model (model, "GtkLabel"); + assert_changes (model, "1-3"); + + gtk_container_add (GTK_CONTAINER (grandparent), widget); + assert_model (model, "GtkLabel GtkBox GtkWindow"); + assert_changes (model, "1+2"); + + g_object_unref (model); + destroy_widgets (); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv); + + changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?"); + + g_test_add_func ("/propertylookuplistmodel/create_empty", test_create_empty); + g_test_add_func ("/propertylookuplistmodel/create", test_create); + g_test_add_func ("/propertylookuplistmodel/set-object", test_set_object); + g_test_add_func ("/propertylookuplistmodel/change-property", test_change_property); + + return g_test_run (); +} -- cgit v1.2.1