diff options
-rw-r--r-- | docs/reference/gobject/gobject-docs.sgml | 1 | ||||
-rw-r--r-- | docs/reference/gobject/gobject-sections.txt | 23 | ||||
-rw-r--r-- | docs/reference/gobject/gobject.types | 1 | ||||
-rw-r--r-- | glib/glib-object.h | 1 | ||||
-rw-r--r-- | gobject/Makefile.am | 5 | ||||
-rw-r--r-- | gobject/gbinding.c | 937 | ||||
-rw-r--r-- | gobject/gbinding.h | 119 | ||||
-rw-r--r-- | gobject/gobject.symbols | 15 | ||||
-rw-r--r-- | gobject/tests/.gitignore | 1 | ||||
-rw-r--r-- | gobject/tests/Makefile.am | 4 | ||||
-rw-r--r-- | gobject/tests/binding.c | 326 |
11 files changed, 1430 insertions, 3 deletions
diff --git a/docs/reference/gobject/gobject-docs.sgml b/docs/reference/gobject/gobject-docs.sgml index 61704eacb..dc4d38649 100644 --- a/docs/reference/gobject/gobject-docs.sgml +++ b/docs/reference/gobject/gobject-docs.sgml @@ -83,6 +83,7 @@ <xi:include href="xml/signals.xml" /> <xi:include href="xml/gclosure.xml" /> <xi:include href="xml/value_arrays.xml" /> + <xi:include href="xml/gbinding.xml" /> </reference> <reference label="III"> <title>Tools Reference</title> diff --git a/docs/reference/gobject/gobject-sections.txt b/docs/reference/gobject/gobject-sections.txt index 6c5344821..4b1bf0f3e 100644 --- a/docs/reference/gobject/gobject-sections.txt +++ b/docs/reference/gobject/gobject-sections.txt @@ -839,3 +839,26 @@ g_closure_get_type g_io_channel_get_type g_io_condition_get_type </SECTION> + +<SECTION> +<FILE>gbinding</FILE> +GBinding +GBindingFlags +g_binding_get_source +g_binding_get_source_property +g_binding_get_target +g_binding_get_target_property +g_binding_get_flags +<SUBSECTION> +g_object_bind_property +GBindingTransformFunc +g_object_bind_property_full +<SUBSECTION Standard> +G_TYPE_BINDING +G_TYPE_BINDING_FLAGS +G_BINDING +G_IS_BINDING +<SUBSECTION Private> +g_binding_flags_get_type +g_binding_get_type +</SECTION> diff --git a/docs/reference/gobject/gobject.types b/docs/reference/gobject/gobject.types index b8d745f46..bb4e7b875 100644 --- a/docs/reference/gobject/gobject.types +++ b/docs/reference/gobject/gobject.types @@ -1,6 +1,7 @@ #include <glib/glib-object.h> #include "gobject.cI" +g_binding_get_type g_object_get_type g_type_module_get_type g_type_plugin_get_type diff --git a/glib/glib-object.h b/glib/glib-object.h index 8687ef1f2..10cff1b0e 100644 --- a/glib/glib-object.h +++ b/glib/glib-object.h @@ -22,6 +22,7 @@ #define __GLIB_GOBJECT_H_INSIDE__ /* topmost include file for GObject header files */ +#include <gobject/gbinding.h> #include <gobject/gboxed.h> #include <gobject/genums.h> #include <gobject/gobject.h> diff --git a/gobject/Makefile.am b/gobject/Makefile.am index 6bcd5ae01..c5be202b1 100644 --- a/gobject/Makefile.am +++ b/gobject/Makefile.am @@ -97,6 +97,7 @@ libgobject_2_0_la_DEPENDENCIES = $(gobject_win32_res) $(gobject_def) # # GObject library header files for public installation gobject_public_h_sources = \ + gbinding.h \ gboxed.h \ gclosure.h \ genums.h \ @@ -124,6 +125,7 @@ gobject_private_h_sources = \ gobject_c_sources = \ gobject_probes.d \ gatomicarray.c \ + gbinding.c \ gboxed.c \ gclosure.c \ genums.c \ @@ -188,7 +190,7 @@ EXTRA_DIST += \ # # setup autogeneration dependancies gen_sources = xgen-gmh xgen-gmc xgen-gms -CLEANFILES = $(gen_sources) +CLEANFILES += $(gen_sources) # normal autogeneration rules # all autogenerated files need to be generated in the srcdir, @@ -229,7 +231,6 @@ gmarshal.strings: @REBUILD@ $(srcdir)/gmarshal.list glib-genmarshal.o: gmarshal.strings gsignal.lo: gmarshal.c - # target platform: libgobjectinclude_HEADERS = $(gobject_target_headers) libgobject_2_0_la_SOURCES = $(gobject_target_sources) diff --git a/gobject/gbinding.c b/gobject/gbinding.c new file mode 100644 index 000000000..200d72ff8 --- /dev/null +++ b/gobject/gbinding.c @@ -0,0 +1,937 @@ +/* gbinding.c: Binding for object properties + * + * Copyright (C) 2010 Intel Corp. + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Emmanuele Bassi <ebassi@linux.intel.com> + */ + +/** + * SECTION:gbinding + * @Title: GBinding + * @Short_Description: Bind two object properties + * + * #GBinding is the representation of a binding between a property on a + * #GObject instance (or source) and another property on another #GObject + * instance (or target). Whenever the source property changes, the same + * value is applied to the target property; for instance, the following + * binding: + * + * |[ + * g_object_bind_property (object1, "property-a", + * object2, "property-b", + * G_BINDING_DEFAULT); + * ]| + * + * will cause <emphasis>object2:property-b</emphasis> to be updated every + * time g_object_set() or the specific accessor changes the value of + * <emphasis>object1:property-a</emphasis>. + * + * It is possible to create a bidirectional binding between two properties + * of two #GObject instances, so that if either property changes, the + * other is updated as well, for instance: + * + * |[ + * g_object_bind_property (object1, "property-a", + * object2, "property-b", + * G_BINDING_BIDIRECTIONAL); + * ]| + * + * will keep the two properties in sync. + * + * It is also possible to set a custom transformation function (in both + * directions, in case of a bidirectional binding) to apply a custom + * transformation from the source value to the target value before + * applying it; for instance, the following binding: + * + * |[ + * g_object_bind_property_full (adjustment1, "value", + * adjustment2, "value", + * G_BINDING_BIDIRECTIONAL, + * celsius_to_fahrenheit, + * fahrenheit_to_celsius, + * NULL, NULL); + * ]| + * + * will keep the <emphasis>value</emphasis> property of the two adjustments + * in sync; the <function>celsius_to_fahrenheit</function> function will be + * called whenever the <emphasis>adjustment1:value</emphasis> property changes + * and will transform the current value of the property before applying it + * to the <emphasis>adjustment2:value</emphasis> property; vice versa, the + * <function>fahrenheit_to_celsius</function> function will be called whenever + * the <emphasis>adjustment2:value</emphasis> property changes, and will + * transform the current value of the property before applying it to the + * <emphasis>adjustment1:value</emphasis>. + * + * Note that #GBinding does not resolve cycles by itself; a cycle like + * + * |[ + * object1:propertyA -> object2:propertyB + * object2:propertyB -> object3:propertyC + * object3:propertyC -> object1:propertyA + * ]| + * + * might lead to an infinite loop. The loop, in this particular case, + * can be avoided if the objects emit the #GObject::notify signal only + * if the value has effectively been changed. A binding is implemented + * using the #GObject::notify signal, so it is susceptible to all the + * various ways of blocking a signal emission, like g_signal_stop_emission() + * or g_signal_handler_block(). + * + * A binding will be severed, and the resources it allocates freed, whenever + * either one of the #GObject instances it refers to are finalized, or when + * the #GBinding instance loses its last reference. + * + * #GBinding is available since GObject 2.26 + */ + +#include "config.h" + +#include <string.h> + +#include "gbinding.h" +#include "genums.h" +#include "gobject.h" +#include "gsignal.h" +#include "gparamspecs.h" +#include "gvaluetypes.h" + +#include "glibintl.h" + +#include "gobjectalias.h" + +GType +g_binding_flags_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const GFlagsValue values[] = { + { G_BINDING_DEFAULT, "G_BINDING_DEFAULT", "default" }, + { G_BINDING_BIDIRECTIONAL, "G_BINDING_BIDIRECTIONAL", "bidirectional" }, + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_flags_register_static (g_intern_static_string ("GBindingFlags"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +#define G_BINDING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_BINDING, GBindingClass)) +#define G_IS_BINDING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_BINDING)) +#define G_BINDING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_BINDING, GBindingClass)) + +typedef struct _GBindingClass GBindingClass; + +struct _GBinding +{ + GObject parent_instance; + + /* no reference is held on the objects, to avoid cycles */ + GObject *source; + GObject *target; + + /* the property names are interned, so they should not be freed */ + gchar *source_property; + gchar *target_property; + + GParamSpec *source_pspec; + GParamSpec *target_pspec; + + GBindingTransformFunc transform_s2t; + GBindingTransformFunc transform_t2s; + + GBindingFlags flags; + + guint source_notify; + guint target_notify; + + gpointer transform_data; + GDestroyNotify notify; + + /* a guard, to avoid loops */ + guint is_frozen : 1; +}; + +struct _GBindingClass +{ + GObjectClass parent_class; +}; + +enum +{ + PROP_0, + + PROP_SOURCE, + PROP_TARGET, + PROP_SOURCE_PROPERTY, + PROP_TARGET_PROPERTY, + PROP_FLAGS +}; + +static GQuark quark_gbinding = 0; + +G_DEFINE_TYPE (GBinding, g_binding, G_TYPE_OBJECT); + +static inline void +add_binding_qdata (GObject *gobject, + GBinding *binding) +{ + GList *bindings; + + bindings = g_object_get_qdata (gobject, quark_gbinding); + if (bindings == NULL) + { + bindings = g_list_prepend (NULL, binding); + g_object_set_qdata (gobject, quark_gbinding, bindings); + } + else + bindings = g_list_prepend (bindings, binding); +} + +static inline void +remove_binding_qdata (GObject *gobject, + GBinding *binding) +{ + GList *bindings; + + bindings = g_object_get_qdata (gobject, quark_gbinding); + bindings = g_list_remove (bindings, binding); +} + +static void +weak_unbind (gpointer user_data, + GObject *where_the_object_was) +{ + GBinding *binding = user_data; + + if (binding->source == where_the_object_was) + binding->source = NULL; + else + { + if (binding->source_notify != 0) + g_signal_handler_disconnect (binding->source, binding->source_notify); + + g_object_weak_unref (binding->source, weak_unbind, user_data); + remove_binding_qdata (binding->source, binding); + binding->source = NULL; + } + + if (binding->target == where_the_object_was) + binding->target = NULL; + else + { + if (binding->target_notify != 0) + g_signal_handler_disconnect (binding->target, binding->target_notify); + + g_object_weak_unref (binding->target, weak_unbind, user_data); + remove_binding_qdata (binding->target, binding); + binding->target = NULL; + } + + g_object_unref (binding); +} + +static inline gboolean +default_transform (const GValue *value_a, + GValue *value_b) +{ + /* if it's not the same type, try to convert it using the GValue + * transformation API; otherwise just copy it + */ + if (!g_type_is_a (G_VALUE_TYPE (value_a), G_VALUE_TYPE (value_b))) + { + /* are these two types compatible (can be directly copied)? */ + if (g_value_type_compatible (G_VALUE_TYPE (value_a), + G_VALUE_TYPE (value_b))) + { + g_value_copy (value_a, value_b); + goto done; + } + + if (g_value_type_transformable (G_VALUE_TYPE (value_a), + G_VALUE_TYPE (value_b))) + { + if (g_value_transform (value_a, value_b)) + goto done; + + g_warning ("%s: Unable to convert a value of type %s to a " + "value of type %s", + G_STRLOC, + g_type_name (G_VALUE_TYPE (value_a)), + g_type_name (G_VALUE_TYPE (value_b))); + + return FALSE; + } + } + else + g_value_copy (value_a, value_b); + +done: + return TRUE; +} + +static gboolean +default_transform_to (GBinding *binding G_GNUC_UNUSED, + const GValue *value_a, + GValue *value_b, + gpointer user_data G_GNUC_UNUSED) +{ + return default_transform (value_a, value_b); +} + +static gboolean +default_transform_from (GBinding *binding G_GNUC_UNUSED, + const GValue *value_a, + GValue *value_b, + gpointer user_data G_GNUC_UNUSED) +{ + return default_transform (value_a, value_b); +} + +static void +on_source_notify (GObject *gobject, + GParamSpec *pspec, + GBinding *binding) +{ + const gchar *p_name; + GValue source_value = { 0, }; + GValue target_value = { 0, }; + gboolean res; + + if (binding->is_frozen) + return; + + if (pspec->flags & G_PARAM_STATIC_NAME) + p_name = g_intern_static_string (pspec->name); + else + p_name = g_intern_string (pspec->name); + + if (p_name != binding->source_property) + return; + + g_value_init (&source_value, G_PARAM_SPEC_VALUE_TYPE (binding->source_pspec)); + g_value_init (&target_value, G_PARAM_SPEC_VALUE_TYPE (binding->target_pspec)); + + g_object_get_property (binding->source, binding->source_pspec->name, &source_value); + + res = binding->transform_s2t (binding, + &source_value, + &target_value, + binding->transform_data); + if (res) + { + binding->is_frozen = TRUE; + + g_param_value_validate (binding->target_pspec, &target_value); + g_object_set_property (binding->target, binding->target_pspec->name, &target_value); + + binding->is_frozen = FALSE; + } + + g_value_unset (&source_value); + g_value_unset (&target_value); +} + +static void +on_target_notify (GObject *gobject, + GParamSpec *pspec, + GBinding *binding) +{ + const gchar *p_name; + GValue source_value = { 0, }; + GValue target_value = { 0, }; + gboolean res; + + if (binding->is_frozen) + return; + + if (pspec->flags & G_PARAM_STATIC_NAME) + p_name = g_intern_static_string (pspec->name); + else + p_name = g_intern_string (pspec->name); + + if (p_name != binding->target_property) + return; + + g_value_init (&source_value, G_PARAM_SPEC_VALUE_TYPE (binding->target_pspec)); + g_value_init (&target_value, G_PARAM_SPEC_VALUE_TYPE (binding->source_pspec)); + + g_object_get_property (binding->target, binding->target_pspec->name, &source_value); + + res = binding->transform_t2s (binding, + &source_value, + &target_value, + binding->transform_data); + if (res) + { + binding->is_frozen = TRUE; + + g_param_value_validate (binding->source_pspec, &target_value); + g_object_set_property (binding->source, binding->source_pspec->name, &target_value); + + binding->is_frozen = FALSE; + } + + g_value_unset (&source_value); + g_value_unset (&target_value); +} + +static void +g_binding_finalize (GObject *gobject) +{ + GBinding *binding = G_BINDING (gobject); + + if (binding->notify != NULL) + { + binding->notify (binding->transform_data); + + binding->transform_data = NULL; + binding->notify = NULL; + } + + if (binding->source != NULL) + { + if (binding->source_notify != 0) + g_signal_handler_disconnect (binding->source, binding->source_notify); + + g_object_weak_unref (binding->source, weak_unbind, binding); + remove_binding_qdata (binding->source, binding); + } + + if (binding->target != NULL) + { + if (binding->target_notify != 0) + g_signal_handler_disconnect (binding->target, binding->target_notify); + + g_object_weak_unref (binding->target, weak_unbind, binding); + remove_binding_qdata (binding->target, binding); + } + + G_OBJECT_CLASS (g_binding_parent_class)->finalize (gobject); +} + +static void +g_binding_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GBinding *binding = G_BINDING (gobject); + + switch (prop_id) + { + case PROP_SOURCE: + binding->source = g_value_get_object (value); + break; + + case PROP_SOURCE_PROPERTY: + binding->source_property = g_intern_string (g_value_get_string (value)); + break; + + case PROP_TARGET: + binding->target = g_value_get_object (value); + break; + + case PROP_TARGET_PROPERTY: + binding->target_property = g_intern_string (g_value_get_string (value)); + break; + + case PROP_FLAGS: + binding->flags = g_value_get_flags (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +g_binding_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GBinding *binding = G_BINDING (gobject); + + switch (prop_id) + { + case PROP_SOURCE: + g_value_set_object (value, binding->source); + break; + + case PROP_SOURCE_PROPERTY: + g_value_set_string (value, binding->source_property); + break; + + case PROP_TARGET: + g_value_set_object (value, binding->target); + break; + + case PROP_TARGET_PROPERTY: + g_value_set_string (value, binding->target_property); + break; + + case PROP_FLAGS: + g_value_set_flags (value, binding->flags); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +g_binding_constructed (GObject *gobject) +{ + GBinding *binding = G_BINDING (gobject); + + /* assert that we were constructed correctly */ + g_assert (binding->source != NULL); + g_assert (binding->target != NULL); + g_assert (binding->source_property != NULL); + g_assert (binding->target_property != NULL); + + /* we assume a check was performed prior to construction - since + * g_object_bind_property_full() does it; we cannot fail construction + * anyway, so it would be hard for use to properly warn here + */ + binding->source_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (binding->source), binding->source_property); + binding->target_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (binding->target), binding->target_property); + g_assert (binding->source_pspec != NULL); + g_assert (binding->target_pspec != NULL); + + /* set the default transformation functions here */ + binding->transform_s2t = default_transform_to; + binding->transform_t2s = default_transform_from; + + binding->transform_data = NULL; + binding->notify = NULL; + + binding->source_notify = g_signal_connect (binding->source, "notify", + G_CALLBACK (on_source_notify), + binding); + + g_object_weak_ref (binding->source, weak_unbind, binding); + add_binding_qdata (binding->source, binding); + + if (binding->flags & G_BINDING_BIDIRECTIONAL) + binding->target_notify = g_signal_connect (binding->target, "notify", + G_CALLBACK (on_target_notify), + binding); + + g_object_weak_ref (binding->target, weak_unbind, binding); + add_binding_qdata (binding->target, binding); + +} + +static void +g_binding_class_init (GBindingClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + quark_gbinding = g_quark_from_static_string ("g-binding"); + + gobject_class->constructed = g_binding_constructed; + gobject_class->set_property = g_binding_set_property; + gobject_class->get_property = g_binding_get_property; + gobject_class->finalize = g_binding_finalize; + + /** + * GBinding:source: + * + * The #GObject that should be used as the source of the binding + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, PROP_SOURCE, + g_param_spec_object ("source", + P_("Source"), + P_("The source of the binding"), + G_TYPE_OBJECT, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + /** + * GBinding:target: + * + * The #GObject that should be used as the target of the binding + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, PROP_TARGET, + g_param_spec_object ("target", + P_("Target"), + P_("The target of the binding"), + G_TYPE_OBJECT, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + /** + * GBinding:source-property: + * + * The name of the property of #GBinding:source that should be used + * as the source of the binding + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, PROP_SOURCE_PROPERTY, + g_param_spec_string ("source-property", + P_("Source Property"), + P_("The property on the source to bind"), + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + /** + * GBinding:target-property: + * + * The name of the property of #GBinding:target that should be used + * as the target of the binding + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, PROP_TARGET_PROPERTY, + g_param_spec_string ("target-property", + P_("Target Property"), + P_("The property on the target to bind"), + NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + /** + * GBinding:flags: + * + * Flags to be used to control the #GBinding + * + * Since: 2.26 + */ + g_object_class_install_property (gobject_class, PROP_FLAGS, + g_param_spec_flags ("flags", + P_("Flags"), + P_("The binding flags"), + G_TYPE_BINDING_FLAGS, + G_BINDING_DEFAULT, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +g_binding_init (GBinding *binding) +{ +} + +/** + * g_binding_get_flags: + * @binding: a #GBinding + * + * Retrieves the flags passed when constructing the #GBinding + * + * Return value: the #GBindingFlags used by the #GBinding + * + * Since: 2.26 + */ +GBindingFlags +g_binding_get_flags (GBinding *binding) +{ + g_return_val_if_fail (G_IS_BINDING (binding), G_BINDING_DEFAULT); + + return binding->flags; +} + +/** + * g_binding_get_source: + * @binding: a #GBinding + * + * Retrieves the #GObject instance used as the source of the binding + * + * Return value: (transfer none): the source #GObject + * + * Since: 2.26 + */ +GObject * +g_binding_get_source (GBinding *binding) +{ + g_return_val_if_fail (G_IS_BINDING (binding), NULL); + + return binding->source; +} + +/** + * g_binding_get_target: + * @binding: a #GBinding + * + * Retrieves the #GObject instance used as the target of the binding + * + * Return value: (transfer none): the target #GObject + * + * Since: 2.26 + */ +GObject * +g_binding_get_target (GBinding *binding) +{ + g_return_val_if_fail (G_IS_BINDING (binding), NULL); + + return binding->target; +} + +/** + * g_binding_get_source_property: + * @binding: a #GBinding + * + * Retrieves the name of the property of #GBinding:source used as the source + * of the binding + * + * Return value: the name of the source property + * + * Since: 2.26 + */ +G_CONST_RETURN gchar * +g_binding_get_source_property (GBinding *binding) +{ + g_return_val_if_fail (G_IS_BINDING (binding), NULL); + + return binding->source_property; +} + +/** + * g_binding_get_target_property: + * @binding: a #GBinding + * + * Retrieves the name of the property of #GBinding:target used as the target + * of the binding + * + * Return value: the name of the target property + * + * Since: 2.26 + */ +G_CONST_RETURN gchar * +g_binding_get_target_property (GBinding *binding) +{ + g_return_val_if_fail (G_IS_BINDING (binding), NULL); + + return binding->target_property; +} + +/** + * g_object_bind_property_full: + * @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: (scope notified) (allow-none): the transformation function + * from the @source to the @target, or %NULL to use the default + * @transform_from: (scope notified) (allow-none): the transformation function + * from the @target to the @source, or %NULL to use the default + * @user_data: custom data to be passed to the transformation functions, + * or %NULL + * @notify: function to be called when disposing the binding, to free the + * resources used by the transformation functions + * + * Complete version of g_object_bind_property(). + * + * 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. + * + * If @flags contains %G_BINDING_BIDIRECTIONAL then the binding will be mutual: + * if @target_property on @target changes then the @source_property on @source + * will be updated as well. The @transform_from function is only used in case + * of bidirectional bindings, otherwise it will be ignored + * + * The binding will automatically be removed when either the @source or the + * @target instances are finalized. To remove the binding without affecting the + * @source and the @target you can just call g_object_unref() on the returned + * #GBinding instance. + * + * A #GObject can have multiple bindings. + * + * 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_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) +{ + GParamSpec *pspec; + GBinding *binding; + + g_return_val_if_fail (G_IS_OBJECT (source), NULL); + g_return_val_if_fail (source_property != NULL, NULL); + g_return_val_if_fail (G_IS_OBJECT (target), NULL); + g_return_val_if_fail (target_property != NULL, NULL); + + if (source == target && g_strcmp0 (source_property, target_property) == 0) + { + g_warning ("Unable to bind the same property on the same instance"); + 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) + { + g_warning ("%s: The source object of type %s has no property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (source), + source_property); + return NULL; + } + + if (!(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("%s: The source object of type %s has no readable property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (source), + source_property); + return NULL; + } + + if ((flags & G_BINDING_BIDIRECTIONAL) && + ((pspec->flags & G_PARAM_CONSTRUCT_ONLY) || !(pspec->flags & G_PARAM_WRITABLE))) + { + g_warning ("%s: The source object of type %s has no writable property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (source), + source_property); + return NULL; + } + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (target), target_property); + if (pspec == NULL) + { + g_warning ("%s: The target object of type %s has no property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (target), + target_property); + return NULL; + } + + if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY) || !(pspec->flags & G_PARAM_WRITABLE)) + { + g_warning ("%s: The target object of type %s has no writable property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (target), + target_property); + return NULL; + } + + if ((flags & G_BINDING_BIDIRECTIONAL) && + !(pspec->flags & G_PARAM_READABLE)) + { + g_warning ("%s: The starget object of type %s has no writable property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (target), + target_property); + return NULL; + } + + binding = g_object_new (G_TYPE_BINDING, + "source", source, + "source-property", source_property, + "target", target, + "target-property", target_property, + "flags", flags, + NULL); + + /* making these properties would be awkward, though not impossible */ + binding->transform_s2t = transform_to; + binding->transform_t2s = transform_from; + binding->transform_data = user_data; + binding->notify = notify; + + return binding; +} + +/** + * g_object_bind_property: + * @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 + * + * Creates a binding between @source_property on @source and @target_property + * on @target. Whenever the @source_property is changed the @target_property is + * updated using the same value. For instance: + * + * |[ + * g_object_bind_property (action, "active", widget, "sensitive", 0); + * ]| + * + * Will result in the "sensitive" property of the widget #GObject instance to be + * updated with the same value of the "active" property of the action #GObject + * instance. + * + * If @flags contains %G_BINDING_BIDIRECTIONAL then the binding will be mutual: + * if @target_property on @target changes then the @source_property on @source + * will be updated as well. + * + * The binding will automatically be removed when either the @source or the + * @target instances are finalized. To remove the binding without affecting the + * @source and the @target you can just call g_object_unref() on the returned + * #GBinding instance. + * + * A #GObject can have multiple bindings. + * + * 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 (gpointer source, + const gchar *source_property, + gpointer target, + const gchar *target_property, + GBindingFlags flags) +{ + /* type checking is done in g_object_bind_property_full() */ + + return g_object_bind_property_full (source, source_property, + target, target_property, + flags, + NULL, + NULL, + NULL, NULL); +} + +#define __G_BINDING_C__ +#include "gobjectaliasdef.c" diff --git a/gobject/gbinding.h b/gobject/gbinding.h new file mode 100644 index 000000000..90dac6092 --- /dev/null +++ b/gobject/gbinding.h @@ -0,0 +1,119 @@ +/* gbinding.h: Binding for object properties + * + * Copyright (C) 2010 Intel Corp. + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Emmanuele Bassi <ebassi@linux.intel.com> + */ + +#if !defined (__GLIB_GOBJECT_H_INSIDE__) && !defined (GOBJECT_COMPILATION) +#error "Only <glib-object.h> can be included directly." +#endif + +#ifndef __G_BINDING_H__ +#define __G_BINDING_H__ + +#include <glib.h> +#include <gobject/gobject.h> + +G_BEGIN_DECLS + +#define G_TYPE_BINDING_FLAGS (g_binding_flags_get_type ()) + +#define G_TYPE_BINDING (g_binding_get_type ()) +#define G_BINDING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_BINDING, GBinding)) +#define G_IS_BINDING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_BINDING)) + +/** + * GBinding: + * + * <structname>GBinding</structname> is an opaque structure whose members + * cannot be accessed directly. + * + * Since: 2.26 + */ +typedef struct _GBinding GBinding; + +/** + * GBindingTransformFunc: + * @binding: a #GBinding + * @source_value: the value of the source property + * @target_value: the value of the target property + * @user_data: data passed to the transform function + * + * A function to be called to transform the source property of @source + * from @source_value into the target property of @target using + * @target_value + * + * Return value: %TRUE if the transformation was successful, and %FALSE + * otherwise + * + * Since: 2.26 + */ +typedef gboolean (* GBindingTransformFunc) (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data); + +/** + * GBindingFlags: + * @G_BINDING_DEFAULT: The default binding; if the source property + * changes, the target property is updated with its value + * @G_BINDING_BIDIRECTIONAL: Bidirectional binding; if either the + * property of the source or the property of the target changes, + * the other is updated + * + * Flags to be passed to g_object_bind_property() or + * g_object_bind_property_full(). + * + * This enumeration can be extended at later date. + * + * Since: 2.26 + */ +typedef enum { /*< prefix=G_BINDING >*/ + G_BINDING_DEFAULT = 0, + + G_BINDING_BIDIRECTIONAL = 1 << 0 +} GBindingFlags; + +GType g_binding_flags_get_type (void) G_GNUC_CONST; +GType g_binding_get_type (void) G_GNUC_CONST; + +GBindingFlags g_binding_get_flags (GBinding *binding); +GObject * g_binding_get_source (GBinding *binding); +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); + +G_END_DECLS + +#endif /* __G_BINDING_H__ */ diff --git a/gobject/gobject.symbols b/gobject/gobject.symbols index df9c39b54..f47f9e6e7 100644 --- a/gobject/gobject.symbols +++ b/gobject/gobject.symbols @@ -11,6 +11,21 @@ #define IN_FILE(x) 1 #define IN_HEADER(x) 1 #endif + +#if IN_HEADER(__G_BINDING_H__) +#if IN_FILE(__G_BINDING_C__) +g_binding_flags_get_type G_GNUC_CONST +g_binding_get_type G_GNUC_CONST +g_binding_get_flags +g_binding_get_source +g_binding_get_target +g_binding_get_source_property +g_binding_get_target_property +g_object_bind_property +g_object_bind_property_full +#endif +#endif + #if IN_HEADER(__G_BOXED_H__) #if IN_FILE(__G_BOXED_C__) g_boxed_copy diff --git a/gobject/tests/.gitignore b/gobject/tests/.gitignore index bb48529e6..ca0d62868 100644 --- a/gobject/tests/.gitignore +++ b/gobject/tests/.gitignore @@ -1,2 +1,3 @@ +binding dynamictests threadtests diff --git a/gobject/tests/Makefile.am b/gobject/tests/Makefile.am index 33b3e0b01..d1735a2be 100644 --- a/gobject/tests/Makefile.am +++ b/gobject/tests/Makefile.am @@ -5,8 +5,10 @@ INCLUDES = -g -I$(top_srcdir) -I$(top_srcdir)/glib $(GLIB_DEBUG_FLAGS) noinst_PROGRAMS = $(TEST_PROGS) libgobject_LDADD = ../libgobject-2.0.la $(top_builddir)/gthread/libgthread-2.0.la $(top_builddir)/glib/libglib-2.0.la -TEST_PROGS += threadtests dynamictests +TEST_PROGS += threadtests dynamictests binding threadtests_SOURCES = threadtests.c threadtests_LDADD = $(libgobject_LDADD) dynamictests_SOURCES = dynamictests.c dynamictests_LDADD = $(libgobject_LDADD) +binding_SOURCES = binding.c +binding_LDADD = $(libgobject_LDADD) diff --git a/gobject/tests/binding.c b/gobject/tests/binding.c new file mode 100644 index 000000000..15036fae0 --- /dev/null +++ b/gobject/tests/binding.c @@ -0,0 +1,326 @@ +#include <stdlib.h> +#include <gstdio.h> +#include <glib-object.h> + +typedef struct _BindingSource +{ + GObject parent_instance; + + gint foo; + gdouble value; +} BindingSource; + +typedef struct _BindingSourceClass +{ + GObjectClass parent_class; +} BindingSourceClass; + +enum +{ + PROP_SOURCE_0, + + PROP_SOURCE_FOO, + + PROP_SOURCE_VALUE +}; + +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_VALUE: + source->value = g_value_get_double (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_VALUE: + g_value_set_double (value, source->value); + 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_VALUE, + g_param_spec_double ("value", "Value", "Value", + -100.0, 200.0, + 0.0, + G_PARAM_READWRITE)); +} + +static void +binding_source_init (BindingSource *self) +{ +} + +typedef struct _BindingTarget +{ + GObject parent_instance; + + gint bar; + gdouble value; +} BindingTarget; + +typedef struct _BindingTargetClass +{ + GObjectClass parent_class; +} BindingTargetClass; + +enum +{ + PROP_TARGET_0, + + PROP_TARGET_BAR, + + PROP_TARGET_VALUE +}; + +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_VALUE: + target->value = g_value_get_double (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_VALUE: + g_value_set_double (value, target->value); + 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_SOURCE_VALUE, + g_param_spec_double ("value", "Value", "Value", + -100.0, 200.0, + 0.0, + G_PARAM_READWRITE)); +} + +static void +binding_target_init (BindingTarget *self) +{ +} + +static gboolean +celsius_to_fahrenheit (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data G_GNUC_UNUSED) +{ + gdouble celsius, fahrenheit; + + g_assert (G_VALUE_HOLDS (source_value, G_TYPE_DOUBLE)); + g_assert (G_VALUE_HOLDS (target_value, G_TYPE_DOUBLE)); + + celsius = g_value_get_double (source_value); + fahrenheit = (9 * celsius / 5) + 32.0; + + if (g_test_verbose ()) + g_print ("Converting %.2fC to %.2fF\n", celsius, fahrenheit); + + g_value_set_double (target_value, fahrenheit); + + return TRUE; +} + +static gboolean +fahrenheit_to_celsius (GBinding *binding, + const GValue *source_value, + GValue *target_value, + gpointer user_data G_GNUC_UNUSED) +{ + gdouble celsius, fahrenheit; + + g_assert (G_VALUE_HOLDS (source_value, G_TYPE_DOUBLE)); + g_assert (G_VALUE_HOLDS (target_value, G_TYPE_DOUBLE)); + + fahrenheit = g_value_get_double (source_value); + celsius = 5 * (fahrenheit - 32.0) / 9; + + if (g_test_verbose ()) + g_print ("Converting %.2fF to %.2fC\n", fahrenheit, celsius); + + g_value_set_double (target_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_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); +} + +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_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); +} + +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; + + binding = g_object_bind_property_full (source, "value", + target, "value", + G_BINDING_BIDIRECTIONAL, + celsius_to_fahrenheit, + fahrenheit_to_celsius, + NULL, NULL); + + 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); +} + +int +main (int argc, char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/binding/default", binding_default); + g_test_add_func ("/binding/bidirectional", binding_bidirectional); + g_test_add_func ("/binding/transform", binding_transform); + + return g_test_run (); +} |