#include #include #include #define assert_cmpsource(binding, op, expected_source) G_STMT_START { \ GObject *tmp, *tmp2; \ tmp = g_binding_dup_source ((binding)); \ G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ tmp2 = g_binding_get_source ((binding)); \ G_GNUC_END_IGNORE_DEPRECATIONS \ g_assert_nonnull (tmp); \ g_assert_true ((gpointer) tmp op (gpointer) (expected_source)); \ g_assert_true (tmp == tmp2); \ g_object_unref (tmp); \ } G_STMT_END #define assert_cmptarget(binding, op, expected_target) G_STMT_START { \ GObject *tmp, *tmp2; \ tmp = g_binding_dup_target ((binding)); \ G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ tmp2 = g_binding_get_target ((binding)); \ G_GNUC_END_IGNORE_DEPRECATIONS \ g_assert_nonnull (tmp); \ g_assert_true ((gpointer) tmp op (gpointer) (expected_target)); \ g_assert_true (tmp == tmp2); \ g_object_unref (tmp); \ } G_STMT_END typedef struct { GTypeInterface g_iface; } FooInterface; GType foo_get_type (void); G_DEFINE_INTERFACE (Foo, foo, G_TYPE_OBJECT) static void foo_default_init (FooInterface *iface) { } typedef struct { GObject parent; } Baa; typedef struct { GObjectClass parent_class; } BaaClass; static void baa_init_foo (FooInterface *iface) { } GType baa_get_type (void); G_DEFINE_TYPE_WITH_CODE (Baa, baa, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (foo_get_type (), baa_init_foo)) static void baa_init (Baa *baa) { } static void baa_class_init (BaaClass *class) { } typedef struct _BindingSource { GObject parent_instance; gint foo; gint bar; gdouble double_value; gboolean toggle; gpointer item; } BindingSource; typedef struct _BindingSourceClass { GObjectClass parent_class; } BindingSourceClass; enum { PROP_SOURCE_0, PROP_SOURCE_FOO, PROP_SOURCE_BAR, PROP_SOURCE_DOUBLE_VALUE, PROP_SOURCE_TOGGLE, PROP_SOURCE_OBJECT }; static GType binding_source_get_type (void); G_DEFINE_TYPE (BindingSource, binding_source, G_TYPE_OBJECT) static void binding_source_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { BindingSource *source = (BindingSource *) gobject; switch (prop_id) { case PROP_SOURCE_FOO: source->foo = g_value_get_int (value); break; case PROP_SOURCE_BAR: source->bar = g_value_get_int (value); break; case PROP_SOURCE_DOUBLE_VALUE: source->double_value = g_value_get_double (value); break; case PROP_SOURCE_TOGGLE: source->toggle = g_value_get_boolean (value); break; case PROP_SOURCE_OBJECT: source->item = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void binding_source_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { BindingSource *source = (BindingSource *) gobject; switch (prop_id) { case PROP_SOURCE_FOO: g_value_set_int (value, source->foo); break; case PROP_SOURCE_BAR: g_value_set_int (value, source->bar); break; case PROP_SOURCE_DOUBLE_VALUE: g_value_set_double (value, source->double_value); break; case PROP_SOURCE_TOGGLE: g_value_set_boolean (value, source->toggle); break; case PROP_SOURCE_OBJECT: g_value_set_object (value, source->item); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void binding_source_class_init (BindingSourceClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = binding_source_set_property; gobject_class->get_property = binding_source_get_property; g_object_class_install_property (gobject_class, PROP_SOURCE_FOO, g_param_spec_int ("foo", "Foo", "Foo", -1, 100, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_SOURCE_BAR, g_param_spec_int ("bar", "Bar", "Bar", -1, 100, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_SOURCE_DOUBLE_VALUE, g_param_spec_double ("double-value", "Value", "Value", -100.0, 200.0, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_SOURCE_TOGGLE, g_param_spec_boolean ("toggle", "Toggle", "Toggle", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_SOURCE_OBJECT, g_param_spec_object ("object", "Object", "Object", G_TYPE_OBJECT, G_PARAM_READWRITE)); } static void binding_source_init (BindingSource *self) { } typedef struct _BindingTarget { GObject parent_instance; gint bar; gdouble double_value; gboolean toggle; gpointer foo; } BindingTarget; typedef struct _BindingTargetClass { GObjectClass parent_class; } BindingTargetClass; enum { PROP_TARGET_0, PROP_TARGET_BAR, PROP_TARGET_DOUBLE_VALUE, PROP_TARGET_TOGGLE, PROP_TARGET_FOO }; static GType binding_target_get_type (void); G_DEFINE_TYPE (BindingTarget, binding_target, G_TYPE_OBJECT) static void binding_target_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { BindingTarget *target = (BindingTarget *) gobject; switch (prop_id) { case PROP_TARGET_BAR: target->bar = g_value_get_int (value); break; case PROP_TARGET_DOUBLE_VALUE: target->double_value = g_value_get_double (value); break; case PROP_TARGET_TOGGLE: target->toggle = g_value_get_boolean (value); break; case PROP_TARGET_FOO: target->foo = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void binding_target_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { BindingTarget *target = (BindingTarget *) gobject; switch (prop_id) { case PROP_TARGET_BAR: g_value_set_int (value, target->bar); break; case PROP_TARGET_DOUBLE_VALUE: g_value_set_double (value, target->double_value); break; case PROP_TARGET_TOGGLE: g_value_set_boolean (value, target->toggle); break; case PROP_TARGET_FOO: g_value_set_object (value, target->foo); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); } } static void binding_target_class_init (BindingTargetClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = binding_target_set_property; gobject_class->get_property = binding_target_get_property; g_object_class_install_property (gobject_class, PROP_TARGET_BAR, g_param_spec_int ("bar", "Bar", "Bar", -1, 100, 0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_TARGET_DOUBLE_VALUE, g_param_spec_double ("double-value", "Value", "Value", -100.0, 200.0, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_TARGET_TOGGLE, g_param_spec_boolean ("toggle", "Toggle", "Toggle", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_TARGET_FOO, g_param_spec_object ("foo", "Foo", "Foo", foo_get_type (), G_PARAM_READWRITE)); } static void binding_target_init (BindingTarget *self) { } static gboolean celsius_to_fahrenheit (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data G_GNUC_UNUSED) { gdouble celsius, fahrenheit; g_assert_true (G_VALUE_HOLDS (from_value, G_TYPE_DOUBLE)); g_assert_true (G_VALUE_HOLDS (to_value, G_TYPE_DOUBLE)); celsius = g_value_get_double (from_value); fahrenheit = (9 * celsius / 5) + 32.0; if (g_test_verbose ()) g_printerr ("Converting %.2fC to %.2fF\n", celsius, fahrenheit); g_value_set_double (to_value, fahrenheit); return TRUE; } static gboolean fahrenheit_to_celsius (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data G_GNUC_UNUSED) { gdouble celsius, fahrenheit; g_assert_true (G_VALUE_HOLDS (from_value, G_TYPE_DOUBLE)); g_assert_true (G_VALUE_HOLDS (to_value, G_TYPE_DOUBLE)); fahrenheit = g_value_get_double (from_value); celsius = 5 * (fahrenheit - 32.0) / 9; if (g_test_verbose ()) g_printerr ("Converting %.2fF to %.2fC\n", fahrenheit, celsius); g_value_set_double (to_value, celsius); return TRUE; } static void binding_default (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); assert_cmpsource (binding, ==, source); assert_cmptarget (binding, ==, target); g_assert_cmpstr (g_binding_get_source_property (binding), ==, "foo"); g_assert_cmpstr (g_binding_get_target_property (binding), ==, "bar"); g_assert_cmpint (g_binding_get_flags (binding), ==, G_BINDING_DEFAULT); g_object_set (source, "foo", 42, NULL); g_assert_cmpint (source->foo, ==, target->bar); g_object_set (target, "bar", 47, NULL); g_assert_cmpint (source->foo, !=, target->bar); g_object_unref (binding); g_object_set (source, "foo", 0, NULL); g_assert_cmpint (source->foo, !=, target->bar); g_object_unref (source); g_object_unref (target); g_assert_null (binding); } static void binding_canonicalisation (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; g_test_summary ("Test that bindings set up with non-canonical property names work"); binding = g_object_bind_property (source, "double_value", target, "double_value", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); assert_cmpsource (binding, ==, source); assert_cmptarget (binding, ==, target); g_assert_cmpstr (g_binding_get_source_property (binding), ==, "double-value"); g_assert_cmpstr (g_binding_get_target_property (binding), ==, "double-value"); g_assert_cmpint (g_binding_get_flags (binding), ==, G_BINDING_DEFAULT); g_object_set (source, "double-value", 24.0, NULL); g_assert_cmpfloat (target->double_value, ==, source->double_value); g_object_set (target, "double-value", 69.0, NULL); g_assert_cmpfloat (source->double_value, !=, target->double_value); g_object_unref (target); g_object_unref (source); g_assert_null (binding); } static void binding_bidirectional (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_BIDIRECTIONAL); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_set (source, "foo", 42, NULL); g_assert_cmpint (source->foo, ==, target->bar); g_object_set (target, "bar", 47, NULL); g_assert_cmpint (source->foo, ==, target->bar); g_object_unref (binding); g_object_set (source, "foo", 0, NULL); g_assert_cmpint (source->foo, !=, target->bar); g_object_unref (source); g_object_unref (target); g_assert_null (binding); } static void data_free (gpointer data) { gboolean *b = data; *b = TRUE; } static void binding_transform_default (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; gpointer src, trg; gchar *src_prop, *trg_prop; GBindingFlags flags; binding = g_object_bind_property (source, "foo", target, "double-value", G_BINDING_BIDIRECTIONAL); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_get (binding, "source", &src, "source-property", &src_prop, "target", &trg, "target-property", &trg_prop, "flags", &flags, NULL); g_assert_true (src == source); g_assert_true (trg == target); g_assert_cmpstr (src_prop, ==, "foo"); g_assert_cmpstr (trg_prop, ==, "double-value"); g_assert_cmpint (flags, ==, G_BINDING_BIDIRECTIONAL); g_object_unref (src); g_object_unref (trg); g_free (src_prop); g_free (trg_prop); g_object_set (source, "foo", 24, NULL); g_assert_cmpfloat (target->double_value, ==, 24.0); g_object_set (target, "double-value", 69.0, NULL); g_assert_cmpint (source->foo, ==, 69); g_object_unref (target); g_object_unref (source); g_assert_null (binding); } static void binding_transform (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding G_GNUC_UNUSED; gboolean unused_data = FALSE; binding = g_object_bind_property_full (source, "double-value", target, "double-value", G_BINDING_BIDIRECTIONAL, celsius_to_fahrenheit, fahrenheit_to_celsius, &unused_data, data_free); g_object_set (source, "double-value", 24.0, NULL); g_assert_cmpfloat (target->double_value, ==, ((9 * 24.0 / 5) + 32.0)); g_object_set (target, "double-value", 69.0, NULL); g_assert_cmpfloat (source->double_value, ==, (5 * (69.0 - 32.0) / 9)); g_object_unref (source); g_object_unref (target); g_assert_true (unused_data); } static void binding_transform_closure (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding G_GNUC_UNUSED; gboolean unused_data_1 = FALSE, unused_data_2 = FALSE; GClosure *c2f_clos, *f2c_clos; c2f_clos = g_cclosure_new (G_CALLBACK (celsius_to_fahrenheit), &unused_data_1, (GClosureNotify) data_free); f2c_clos = g_cclosure_new (G_CALLBACK (fahrenheit_to_celsius), &unused_data_2, (GClosureNotify) data_free); binding = g_object_bind_property_with_closures (source, "double-value", target, "double-value", G_BINDING_BIDIRECTIONAL, c2f_clos, f2c_clos); g_object_set (source, "double-value", 24.0, NULL); g_assert_cmpfloat (target->double_value, ==, ((9 * 24.0 / 5) + 32.0)); g_object_set (target, "double-value", 69.0, NULL); g_assert_cmpfloat (source->double_value, ==, (5 * (69.0 - 32.0) / 9)); g_object_unref (source); g_object_unref (target); g_assert_true (unused_data_1); g_assert_true (unused_data_2); } static void binding_chain (void) { BindingSource *a = g_object_new (binding_source_get_type (), NULL); BindingSource *b = g_object_new (binding_source_get_type (), NULL); BindingSource *c = g_object_new (binding_source_get_type (), NULL); GBinding *binding_1, *binding_2; g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=621782"); /* A -> B, B -> C */ binding_1 = g_object_bind_property (a, "foo", b, "foo", G_BINDING_BIDIRECTIONAL); g_object_add_weak_pointer (G_OBJECT (binding_1), (gpointer *) &binding_1); binding_2 = g_object_bind_property (b, "foo", c, "foo", G_BINDING_BIDIRECTIONAL); g_object_add_weak_pointer (G_OBJECT (binding_2), (gpointer *) &binding_2); /* verify the chain */ g_object_set (a, "foo", 42, NULL); g_assert_cmpint (a->foo, ==, b->foo); g_assert_cmpint (b->foo, ==, c->foo); /* unbind A -> B and B -> C */ g_object_unref (binding_1); g_assert_null (binding_1); g_object_unref (binding_2); g_assert_null (binding_2); /* bind A -> C directly */ binding_2 = g_object_bind_property (a, "foo", c, "foo", G_BINDING_BIDIRECTIONAL); /* verify the chain is broken */ g_object_set (a, "foo", 47, NULL); g_assert_cmpint (a->foo, !=, b->foo); g_assert_cmpint (a->foo, ==, c->foo); g_object_unref (a); g_object_unref (b); g_object_unref (c); } static void binding_sync_create (void) { BindingSource *source = g_object_new (binding_source_get_type (), "foo", 42, NULL); BindingTarget *target = g_object_new (binding_target_get_type (), "bar", 47, NULL); GBinding *binding; binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); g_assert_cmpint (source->foo, ==, 42); g_assert_cmpint (target->bar, ==, 42); g_object_set (source, "foo", 47, NULL); g_assert_cmpint (source->foo, ==, target->bar); g_object_unref (binding); g_object_set (target, "bar", 49, NULL); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); g_assert_cmpint (source->foo, ==, 47); g_assert_cmpint (target->bar, ==, 47); g_object_unref (source); g_object_unref (target); } static void binding_invert_boolean (void) { BindingSource *source = g_object_new (binding_source_get_type (), "toggle", TRUE, NULL); BindingTarget *target = g_object_new (binding_target_get_type (), "toggle", FALSE, NULL); GBinding *binding; binding = g_object_bind_property (source, "toggle", target, "toggle", G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN); g_assert_true (source->toggle); g_assert_false (target->toggle); g_object_set (source, "toggle", FALSE, NULL); g_assert_false (source->toggle); g_assert_true (target->toggle); g_object_set (target, "toggle", FALSE, NULL); g_assert_true (source->toggle); g_assert_false (target->toggle); g_object_unref (binding); g_object_unref (source); g_object_unref (target); } static void binding_same_object (void) { BindingSource *source = g_object_new (binding_source_get_type (), "foo", 100, "bar", 50, NULL); GBinding *binding; binding = g_object_bind_property (source, "foo", source, "bar", G_BINDING_BIDIRECTIONAL); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_set (source, "foo", 10, NULL); g_assert_cmpint (source->foo, ==, 10); g_assert_cmpint (source->bar, ==, 10); g_object_set (source, "bar", 30, NULL); g_assert_cmpint (source->foo, ==, 30); g_assert_cmpint (source->bar, ==, 30); g_object_unref (source); g_assert_null (binding); } static void binding_unbind (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_set (source, "foo", 42, NULL); g_assert_cmpint (source->foo, ==, target->bar); g_object_set (target, "bar", 47, NULL); g_assert_cmpint (source->foo, !=, target->bar); g_binding_unbind (binding); g_assert_null (binding); g_object_set (source, "foo", 0, NULL); g_assert_cmpint (source->foo, !=, target->bar); g_object_unref (source); g_object_unref (target); /* g_binding_unbind() has a special case for this */ source = g_object_new (binding_source_get_type (), NULL); binding = g_object_bind_property (source, "foo", source, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_binding_unbind (binding); g_assert_null (binding); g_object_unref (source); } /* When source or target die, so does the binding if there is no other ref */ static void binding_unbind_weak (void) { GBinding *binding; BindingSource *source; BindingTarget *target; /* first source, then target */ source = g_object_new (binding_source_get_type (), NULL); target = g_object_new (binding_target_get_type (), NULL); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_assert_nonnull (binding); g_object_unref (source); g_assert_null (binding); g_object_unref (target); g_assert_null (binding); /* first target, then source */ source = g_object_new (binding_source_get_type (), NULL); target = g_object_new (binding_target_get_type (), NULL); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_assert_nonnull (binding); g_object_unref (target); g_assert_null (binding); g_object_unref (source); g_assert_null (binding); /* target and source are the same */ source = g_object_new (binding_source_get_type (), NULL); binding = g_object_bind_property (source, "foo", source, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_assert_nonnull (binding); g_object_unref (source); g_assert_null (binding); } /* Test that every call to unbind() after the first is a noop */ static void binding_unbind_multiple (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; guint i; g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1373"); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_ref (binding); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_assert_nonnull (binding); /* this shouldn't crash */ for (i = 0; i < 50; i++) { g_binding_unbind (binding); g_assert_nonnull (binding); } g_object_unref (binding); g_assert_null (binding); g_object_unref (source); g_object_unref (target); } static void binding_fail (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; /* double -> boolean is not supported */ binding = g_object_bind_property (source, "double-value", target, "toggle", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_CRITICAL, "*Unable to convert*double*boolean*"); g_object_set (source, "double-value", 1.0, NULL); g_test_assert_expected_messages (); g_object_unref (source); g_object_unref (target); g_assert_null (binding); } static gboolean transform_to_func (GBinding *binding, const GValue *value_a, GValue *value_b, gpointer user_data) { if (g_value_type_compatible (G_VALUE_TYPE (value_a), G_VALUE_TYPE (value_b))) { g_value_copy (value_a, value_b); return TRUE; } if (g_value_type_transformable (G_VALUE_TYPE (value_a), G_VALUE_TYPE (value_b))) { if (g_value_transform (value_a, value_b)) return TRUE; } return FALSE; } static void binding_interface (void) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GObject *baa; GBinding *binding; GClosure *transform_to; /* binding a generic object property to an interface-valued one */ binding = g_object_bind_property (source, "object", target, "foo", G_BINDING_DEFAULT); baa = g_object_new (baa_get_type (), NULL); g_object_set (source, "object", baa, NULL); g_object_unref (baa); g_binding_unbind (binding); /* the same, with a generic marshaller */ transform_to = g_cclosure_new (G_CALLBACK (transform_to_func), NULL, NULL); g_closure_set_marshal (transform_to, g_cclosure_marshal_generic); binding = g_object_bind_property_with_closures (source, "object", target, "foo", G_BINDING_DEFAULT, transform_to, NULL); baa = g_object_new (baa_get_type (), NULL); g_object_set (source, "object", baa, NULL); g_object_unref (baa); g_binding_unbind (binding); g_object_unref (source); g_object_unref (target); } typedef struct { GThread *thread; GBinding *binding; GMutex *lock; GCond *cond; gboolean *wait; gint *count; /* (atomic) */ } ConcurrentUnbindData; static gpointer concurrent_unbind_func (gpointer data) { ConcurrentUnbindData *unbind_data = data; g_mutex_lock (unbind_data->lock); g_atomic_int_inc (unbind_data->count); while (*unbind_data->wait) g_cond_wait (unbind_data->cond, unbind_data->lock); g_mutex_unlock (unbind_data->lock); g_binding_unbind (unbind_data->binding); g_object_unref (unbind_data->binding); return NULL; } static void binding_concurrent_unbind (void) { guint i, j; g_test_summary ("Test that unbinding from multiple threads concurrently works correctly"); for (i = 0; i < 50; i++) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; GQueue threads = G_QUEUE_INIT; GMutex lock; GCond cond; gboolean wait = TRUE; gint count = 0; /* (atomic) */ ConcurrentUnbindData *data; g_mutex_init (&lock); g_cond_init (&cond); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_BIDIRECTIONAL); g_object_ref (binding); for (j = 0; j < 10; j++) { data = g_new0 (ConcurrentUnbindData, 1); data->binding = g_object_ref (binding); data->lock = &lock; data->cond = &cond; data->wait = &wait; data->count = &count; data->thread = g_thread_new ("binding-concurrent", concurrent_unbind_func, data); g_queue_push_tail (&threads, data); } /* wait until all threads are started */ while (g_atomic_int_get (&count) < 10) g_thread_yield (); g_mutex_lock (&lock); wait = FALSE; g_cond_broadcast (&cond); g_mutex_unlock (&lock); while ((data = g_queue_pop_head (&threads))) { g_thread_join (data->thread); g_free (data); } g_mutex_clear (&lock); g_cond_clear (&cond); g_object_unref (binding); g_object_unref (source); g_object_unref (target); } } typedef struct { GObject *object; GMutex *lock; GCond *cond; gint *count; /* (atomic) */ gboolean *wait; } ConcurrentFinalizeData; static gpointer concurrent_finalize_func (gpointer data) { ConcurrentFinalizeData *finalize_data = data; g_mutex_lock (finalize_data->lock); g_atomic_int_inc (finalize_data->count); while (*finalize_data->wait) g_cond_wait (finalize_data->cond, finalize_data->lock); g_mutex_unlock (finalize_data->lock); g_object_unref (finalize_data->object); g_free (finalize_data); return NULL; } static void binding_concurrent_finalizing (void) { guint i; g_test_summary ("Test that finalizing source/target from multiple threads concurrently works correctly"); for (i = 0; i < 50; i++) { BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; GMutex lock; GCond cond; gboolean wait = TRUE; ConcurrentFinalizeData *data; GThread *source_thread, *target_thread; gint count = 0; /* (atomic) */ g_mutex_init (&lock); g_cond_init (&cond); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_BIDIRECTIONAL); g_object_ref (binding); data = g_new0 (ConcurrentFinalizeData, 1); data->object = (GObject *) source; data->wait = &wait; data->lock = &lock; data->cond = &cond; data->count = &count; source_thread = g_thread_new ("binding-concurrent", concurrent_finalize_func, data); data = g_new0 (ConcurrentFinalizeData, 1); data->object = (GObject *) target; data->wait = &wait; data->lock = &lock; data->cond = &cond; data->count = &count; target_thread = g_thread_new ("binding-concurrent", concurrent_finalize_func, data); /* wait until all threads are started */ while (g_atomic_int_get (&count) < 2) g_thread_yield (); g_mutex_lock (&lock); wait = FALSE; g_cond_broadcast (&cond); g_mutex_unlock (&lock); g_thread_join (source_thread); g_thread_join (target_thread); g_mutex_clear (&lock); g_cond_clear (&cond); g_object_unref (binding); } } static void binding_dispose_source (void) { /* Test that the source can be disposed */ BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2676"); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_run_dispose (G_OBJECT (source)); g_assert_null (binding); g_object_unref (target); g_object_unref (source); } static void binding_dispose_target (void) { /* Test that the target can be disposed */ BindingSource *source = g_object_new (binding_source_get_type (), NULL); BindingTarget *target = g_object_new (binding_target_get_type (), NULL); GBinding *binding; g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2676"); binding = g_object_bind_property (source, "foo", target, "bar", G_BINDING_DEFAULT); g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding); g_object_run_dispose (G_OBJECT (target)); g_assert_null (binding); g_object_unref (target); g_object_unref (source); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/binding/default", binding_default); g_test_add_func ("/binding/canonicalisation", binding_canonicalisation); g_test_add_func ("/binding/bidirectional", binding_bidirectional); g_test_add_func ("/binding/transform", binding_transform); g_test_add_func ("/binding/transform-default", binding_transform_default); g_test_add_func ("/binding/transform-closure", binding_transform_closure); g_test_add_func ("/binding/chain", binding_chain); g_test_add_func ("/binding/sync-create", binding_sync_create); g_test_add_func ("/binding/invert-boolean", binding_invert_boolean); g_test_add_func ("/binding/same-object", binding_same_object); g_test_add_func ("/binding/unbind", binding_unbind); g_test_add_func ("/binding/unbind-weak", binding_unbind_weak); g_test_add_func ("/binding/unbind-multiple", binding_unbind_multiple); g_test_add_func ("/binding/fail", binding_fail); g_test_add_func ("/binding/interface", binding_interface); g_test_add_func ("/binding/concurrent-unbind", binding_concurrent_unbind); g_test_add_func ("/binding/concurrent-finalizing", binding_concurrent_finalizing); g_test_add_func ("/binding/dispose-source", binding_dispose_source); g_test_add_func ("/binding/dispose-target", binding_dispose_target); return g_test_run (); }