glib/gobject/tests/binding.c
Philip Withnall b89e825cc1 Revert "Replace most GObject warnings with criticals"
This reverts commit 0ffe86a1f7e215e4561c3b9f1d03c3cd638ed00f.

This was intended to land for the 2.75.x unstable series, and not in the
2.74.x stable series.

Fixes: #2788
2022-10-21 12:51:00 +01:00

1165 lines
35 KiB
C

#include <stdlib.h>
#include <gstdio.h>
#include <glib-object.h>
#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_WARNING,
"*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 ();
}