From 3be3ad61d142ca5bbd5659809af749ea5bf441ac Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 13 Jul 2010 06:03:03 +0100 Subject: [PATCH] binding: Add a closure-based variant of bind_property_full() Since using the function pointer version muddles the memory management requirements of language bindings, we should implement a GClosure-based variant on top of g_object_bind_property_full(). https://bugzilla.gnome.org/show_bug.cgi?id=622278 --- docs/reference/gobject/gobject-sections.txt | 1 + gobject/gbinding.c | 183 +++++++++++++++++++- gobject/gbinding.h | 35 ++-- gobject/gobject.symbols | 1 + gobject/tests/binding.c | 76 +++++++- 5 files changed, 271 insertions(+), 25 deletions(-) diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt index ad5cc4fe0..d1f4a059e 100644 --- a/docs/reference/gobject/gobject-sections.txt +++ b/docs/reference/gobject/gobject-sections.txt @@ -867,6 +867,7 @@ g_binding_get_flags g_object_bind_property GBindingTransformFunc g_object_bind_property_full +g_object_bind_property_with_closures G_TYPE_BINDING G_TYPE_BINDING_FLAGS diff --git a/gobject/gbinding.c b/gobject/gbinding.c index f3d773d08..b8ccdb9b9 100644 --- a/gobject/gbinding.c +++ b/gobject/gbinding.c @@ -809,12 +809,6 @@ g_object_bind_property_full (gpointer source, return NULL; } - if (transform_to == NULL) - transform_to = default_transform_to; - - if (transform_from == NULL) - transform_from = default_transform_from; - pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (source), source_property); if (pspec == NULL) { @@ -881,9 +875,12 @@ g_object_bind_property_full (gpointer source, "flags", flags, NULL); - /* making these properties would be awkward, though not impossible */ - binding->transform_s2t = transform_to; - binding->transform_t2s = transform_from; + if (transform_to != NULL) + binding->transform_s2t = transform_to; + + if (transform_from != NULL) + binding->transform_t2s = transform_from; + binding->transform_data = user_data; binding->notify = notify; @@ -951,3 +948,171 @@ g_object_bind_property (gpointer source, NULL, NULL, NULL); } + +typedef struct _TransformData +{ + GClosure *transform_to_closure; + GClosure *transform_from_closure; +} TransformData; + +static gboolean +bind_with_closures_transform_to (GBinding *binding, + const GValue *source, + GValue *target, + gpointer data) +{ + TransformData *t_data = data; + GValue params[3] = { { 0, }, { 0, }, { 0, } }; + GValue retval = { 0, }; + gboolean res; + + g_value_init (¶ms[0], G_TYPE_BINDING); + g_value_set_object (¶ms[0], binding); + + g_value_init (¶ms[1], G_TYPE_VALUE); + g_value_set_boxed (¶ms[1], source); + + g_value_init (¶ms[2], G_TYPE_VALUE); + g_value_set_boxed (¶ms[2], target); + + g_value_init (&retval, G_TYPE_BOOLEAN); + g_value_set_boolean (&retval, FALSE); + + g_closure_invoke (t_data->transform_to_closure, &retval, 3, params, NULL); + + res = g_value_get_boolean (&retval); + if (res) + { + const GValue *out_value = g_value_get_boxed (¶ms[2]); + + g_assert (out_value != NULL); + + g_value_copy (out_value, target); + } + + g_value_unset (¶ms[0]); + g_value_unset (¶ms[1]); + g_value_unset (¶ms[2]); + g_value_unset (&retval); + + return res; +} + +static gboolean +bind_with_closures_transform_from (GBinding *binding, + const GValue *source, + GValue *target, + gpointer data) +{ + TransformData *t_data = data; + GValue params[3] = { { 0, }, { 0, }, { 0, } }; + GValue retval = { 0, }; + gboolean res; + + g_value_init (¶ms[0], G_TYPE_BINDING); + g_value_set_object (¶ms[0], binding); + + g_value_init (¶ms[1], G_TYPE_VALUE); + g_value_set_boxed (¶ms[1], source); + + g_value_init (¶ms[2], G_TYPE_VALUE); + g_value_set_boxed (¶ms[2], target); + + g_value_init (&retval, G_TYPE_BOOLEAN); + g_value_set_boolean (&retval, FALSE); + + g_closure_invoke (t_data->transform_from_closure, &retval, 3, params, NULL); + + res = g_value_get_boolean (&retval); + if (res) + { + const GValue *out_value = g_value_get_boxed (¶ms[2]); + + g_assert (out_value != NULL); + + g_value_copy (out_value, target); + } + + g_value_unset (¶ms[0]); + g_value_unset (¶ms[1]); + g_value_unset (¶ms[2]); + g_value_unset (&retval); + + return res; +} + +static void +bind_with_closures_free_func (gpointer data) +{ + TransformData *t_data = data; + + if (t_data->transform_to_closure != NULL) + g_closure_unref (t_data->transform_to_closure); + + if (t_data->transform_from_closure != NULL) + g_closure_unref (t_data->transform_from_closure); + + g_slice_free (TransformData, t_data); +} + +/** + * g_object_bind_property_with_closures: + * @source: the source #GObject + * @source_property: the property on @source to bind + * @target: the target #GObject + * @target_property: the property on @target to bind + * @flags: flags to pass to #GBinding + * @transform_to: a #GClosure wrapping the transformation function + * from the @source to the @target, or %NULL to use the default + * @transform_from: a #GClosure wrapping the transformation function + * from the @target to the @source, or %NULL to use the default + * + * Creates a binding between @source_property on @source and @target_property + * on @target, allowing you to set the transformation functions to be used by + * the binding. + * + * This function is the language bindings friendly version of + * g_object_bind_property_full(), using #GClosures instead of + * function pointers. + * + * Rename to: g_object_bind_property_full + * + * Return value: (transfer none): the #GBinding instance representing the + * binding between the two #GObject instances. The binding is released + * whenever the #GBinding reference count reaches zero. + * + * Since: 2.26 + */ +GBinding * +g_object_bind_property_with_closures (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags, + GClosure *transform_to, + GClosure *transform_from) +{ + TransformData *data; + + data = g_slice_new0 (TransformData); + + if (transform_to != NULL) + { + data->transform_to_closure = g_closure_ref (transform_to); + g_closure_sink (data->transform_to_closure); + } + + if (transform_from != NULL) + { + data->transform_from_closure = g_closure_ref (transform_from); + g_closure_sink (data->transform_from_closure); + } + + return g_object_bind_property_full (source, source_property, + target, target_property, + flags, + transform_to != NULL ? bind_with_closures_transform_to : NULL, + transform_from != NULL ? bind_with_closures_transform_from : NULL, + data, + bind_with_closures_free_func); +} diff --git a/gobject/gbinding.h b/gobject/gbinding.h index 4ea342539..361eef9b6 100644 --- a/gobject/gbinding.h +++ b/gobject/gbinding.h @@ -103,20 +103,27 @@ GObject * g_binding_get_target (GBinding *binding); G_CONST_RETURN gchar *g_binding_get_source_property (GBinding *binding); G_CONST_RETURN gchar *g_binding_get_target_property (GBinding *binding); -GBinding *g_object_bind_property (gpointer source, - const gchar *source_property, - gpointer target, - const gchar *target_property, - GBindingFlags flags); -GBinding *g_object_bind_property_full (gpointer source, - const gchar *source_property, - gpointer target, - const gchar *target_property, - GBindingFlags flags, - GBindingTransformFunc transform_to, - GBindingTransformFunc transform_from, - gpointer user_data, - GDestroyNotify notify); +GBinding *g_object_bind_property (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags); +GBinding *g_object_bind_property_full (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags, + GBindingTransformFunc transform_to, + GBindingTransformFunc transform_from, + gpointer user_data, + GDestroyNotify notify); +GBinding *g_object_bind_property_with_closures (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags, + GClosure *transform_to, + GClosure *transform_from); G_END_DECLS diff --git a/gobject/gobject.symbols b/gobject/gobject.symbols index b2cd130fb..0eca5e71b 100644 --- a/gobject/gobject.symbols +++ b/gobject/gobject.symbols @@ -21,6 +21,7 @@ g_binding_get_source_property g_binding_get_target_property g_object_bind_property g_object_bind_property_full +g_object_bind_property_with_closures #endif #endif diff --git a/gobject/tests/binding.c b/gobject/tests/binding.c index 1750e2a57..710776bb5 100644 --- a/gobject/tests/binding.c +++ b/gobject/tests/binding.c @@ -247,8 +247,8 @@ binding_default (void) target, "bar", G_BINDING_DEFAULT); - g_assert (g_binding_get_source (binding) == G_OBJECT (source)); - g_assert (g_binding_get_target (binding) == G_OBJECT (target)); + g_assert ((BindingSource *) g_binding_get_source (binding) == source); + g_assert ((BindingTarget *) g_binding_get_target (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); @@ -329,6 +329,77 @@ binding_transform (void) g_assert (unused_data); } +static void +binding_transform_marshal (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (* GMarshalFunc_BOOLEAN__VALUE_VALUE) (gpointer data1, + gpointer arg_2, + gpointer arg_3, + gpointer data2); + register GMarshalFunc_BOOLEAN__VALUE_VALUE callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + gboolean v_return; + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + + callback = (GMarshalFunc_BOOLEAN__VALUE_VALUE) (marshal_data ? marshal_data : cc->callback); + v_return = callback (data1, + g_value_get_boxed (param_values + 1), + g_value_get_boxed (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + +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; + 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); + g_closure_set_marshal (c2f_clos, binding_transform_marshal); + + f2c_clos = g_cclosure_new (G_CALLBACK (fahrenheit_to_celsius), &unused_data_2, (GClosureNotify) data_free); + g_closure_set_marshal (f2c_clos, binding_transform_marshal); + + binding = g_object_bind_property_with_closures (source, "value", + target, "value", + G_BINDING_BIDIRECTIONAL, + c2f_clos, + f2c_clos); + + g_object_set (source, "value", 24.0, NULL); + g_assert_cmpfloat (target->value, ==, ((9 * 24.0 / 5) + 32.0)); + + g_object_set (target, "value", 69.0, NULL); + g_assert_cmpfloat (source->value, ==, (5 * (69.0 - 32.0) / 9)); + + g_object_unref (source); + g_object_unref (target); + + g_assert (unused_data_1); + g_assert (unused_data_2); +} + static void binding_chain (void) { @@ -407,6 +478,7 @@ main (int argc, char *argv[]) g_test_add_func ("/binding/default", binding_default); g_test_add_func ("/binding/bidirectional", binding_bidirectional); g_test_add_func ("/binding/transform", binding_transform); + 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);