From 24ab6e6e4079f5546d979e79abc731bb4e6217a6 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 16 Feb 2012 14:48:24 +0000 Subject: Add back compat for GObject 2.22 which lacks GBinding --- cfg.mk | 4 +- po/POTFILES.in | 1 + src/Makefile.am | 4 +- src/gbinding.c | 1221 +++++++++++++++++++++++++++++++++++++++ src/gbinding.h | 129 +++++ src/virt-viewer-session-spice.c | 5 + 6 files changed, 1361 insertions(+), 3 deletions(-) create mode 100644 src/gbinding.c create mode 100644 src/gbinding.h diff --git a/cfg.mk b/cfg.mk index ca46013..e8d80a0 100644 --- a/cfg.mk +++ b/cfg.mk @@ -127,8 +127,8 @@ sc_check_author_list: exclude_file_name_regexp--sc_preprocessor_indentation = ^*/*.[ch] exclude_file_name_regexp--sc_prohibit_strcmp = ^*/*.[ch] -exclude_file_name_regexp--sc_require_config_h = ^plugin/ -exclude_file_name_regexp--sc_require_config_h_first = ^plugin/ +exclude_file_name_regexp--sc_require_config_h = ^plugin/|src/gbinding\.c +exclude_file_name_regexp--sc_require_config_h_first = ^plugin/|src/gbinding\.c exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = ^icons/ exclude_file_name_regexp--sc_trailing_blank = ^icons/ diff --git a/po/POTFILES.in b/po/POTFILES.in index 3065991..6b998e7 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,3 +1,4 @@ +src/gbinding.c src/remote-viewer-main.c src/remote-viewer.c [type: gettext/glade] src/virt-viewer-about.xml diff --git a/src/Makefile.am b/src/Makefile.am index c408cdd..abb8c15 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,9 @@ builderxml_DATA = \ virt-viewer-auth.xml \ $(NULL) -EXTRA_DIST = $(builderxml_DATA) +EXTRA_DIST = $(builderxml_DATA) \ + gbinding.c \ + gbinding.h COMMON_SOURCES = \ virt-viewer-util.h virt-viewer-util.c \ diff --git a/src/gbinding.c b/src/gbinding.c new file mode 100644 index 0000000..5b5757c --- /dev/null +++ b/src/gbinding.c @@ -0,0 +1,1221 @@ +/* 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 + */ + +/** + * 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 object2:property-b to be updated every + * time g_object_set() or the specific accessor changes the value of + * object1:property-a. + * + * 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 value property of the two adjustments + * in sync; the celsius_to_fahrenheit function will be + * called whenever the adjustment1:value property changes + * and will transform the current value of the property before applying it + * to the adjustment2:value property; vice versa, the + * fahrenheit_to_celsius function will be called whenever + * the adjustment2:value property changes, and will + * transform the current value of the property before applying it to the + * adjustment1:value. + * + * 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 + +#ifndef G_VALUE_INIT +#define G_VALUE_INIT { 0, { { 0 } } } +#endif + +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer + +static void +g_cclosure_user_marshal_BOOLEAN__BOXED_BOXED (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__BOXED_BOXED) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_BOOLEAN__BOXED_BOXED callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 3); + + 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__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_boxed (param_values + 2), + data2); + + g_value_set_boolean (return_value, v_return); +} + +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" }, + { G_BINDING_SYNC_CREATE, "G_BINDING_SYNC_CREATE", "sync-create" }, + { G_BINDING_INVERT_BOOLEAN, "G_BINDING_INVERT_BOOLEAN", "invert-boolean" }, + { 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 */ + const gchar *source_property; + const 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_00, + + 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) +{ + GHashTable *bindings; + + bindings = g_object_get_qdata (gobject, quark_gbinding); + if (bindings == NULL) + { + bindings = g_hash_table_new (NULL, NULL); + + g_object_set_qdata_full (gobject, quark_gbinding, + bindings, + (GDestroyNotify) g_hash_table_destroy); + } + + g_hash_table_insert (bindings, binding, GUINT_TO_POINTER (1)); +} + +static inline void +remove_binding_qdata (GObject *gobject, + GBinding *binding) +{ + GHashTable *bindings; + + bindings = g_object_get_qdata (gobject, quark_gbinding); + g_hash_table_remove (bindings, binding); +} + +/* the basic assumption is that if either the source or the target + * goes away then the binding does not exist any more and it should + * be reaped as well + */ +static void +weak_unbind (gpointer user_data, + GObject *where_the_object_was) +{ + GBinding *binding = user_data; + + /* if what went away was the source, unset it so that GBinding::finalize + * does not try to access it; otherwise, disconnect everything and remove + * the GBinding instance from the object's qdata + */ + 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; + } + + /* as above, but with the target */ + 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; + } + + /* this will take care of the binding itself */ + 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 inline gboolean +default_invert_boolean_transform (const GValue *value_a, + GValue *value_b) +{ + gboolean value; + + g_assert (G_VALUE_HOLDS_BOOLEAN (value_a)); + g_assert (G_VALUE_HOLDS_BOOLEAN (value_b)); + + value = g_value_get_boolean (value_a); + value = !value; + + g_value_set_boolean (value_b, value); + + return TRUE; +} + +static gboolean +default_transform_to (GBinding *binding, + const GValue *value_a, + GValue *value_b, + gpointer user_data G_GNUC_UNUSED) +{ + if (binding->flags & G_BINDING_INVERT_BOOLEAN) + return default_invert_boolean_transform (value_a, value_b); + + return default_transform (value_a, value_b); +} + +static gboolean +default_transform_from (GBinding *binding, + const GValue *value_a, + GValue *value_b, + gpointer user_data G_GNUC_UNUSED) +{ + if (binding->flags & G_BINDING_INVERT_BOOLEAN) + return default_invert_boolean_transform (value_a, value_b); + + 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 = G_VALUE_INIT; + GValue target_value = G_VALUE_INIT; + gboolean res; + + if (binding->is_frozen) + return; + + 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 = G_VALUE_INIT; + GValue target_value = G_VALUE_INIT; + gboolean res; + + if (binding->is_frozen) + return; + + 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); + + /* dispose of the transformation data */ + if (binding->notify != NULL) + { + binding->notify (binding->transform_data); + + binding->transform_data = NULL; + binding->notify = NULL; + } + + /* we need this in case the source and target instance are still + * valid, and it was the GBinding that was unreferenced + */ + 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", + _("Source"), + _("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", + _("Target"), + _("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", + _("Source Property"), + _("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", + _("Target Property"), + _("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", + _("Flags"), + _("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 + */ +const 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 + */ +const 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: (type GObject.Object): the source #GObject + * @source_property: the property on @source to bind + * @target: (type GObject.Object): 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. + * + * The same @user_data parameter will be used for both @transform_to + * and @transform_from transformation functions; the @notify function will + * be called once, when the binding is removed. If you need different data + * for each transformation function, please use + * g_object_bind_property_with_closures() instead. + * + * 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; + } + + /* remove the G_BINDING_INVERT_BOOLEAN flag in case we have + * custom transformation functions + */ + if ((flags & G_BINDING_INVERT_BOOLEAN) && + (transform_to != NULL || transform_from != NULL)) + { + flags &= ~G_BINDING_INVERT_BOOLEAN; + } + + 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; + } + + if ((flags & G_BINDING_INVERT_BOOLEAN) && + !(G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)) + { + g_warning ("%s: The G_BINDING_INVERT_BOOLEAN flag can only be used " + "when binding boolean properties; the source property '%s' " + "is of type '%s'", + G_STRLOC, + source_property, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + 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 target object of type %s has no readable property called '%s'", + G_STRLOC, + G_OBJECT_TYPE_NAME (target), + target_property); + return NULL; + } + + if ((flags & G_BINDING_INVERT_BOOLEAN) && + !(G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)) + { + g_warning ("%s: The G_BINDING_INVERT_BOOLEAN flag can only be used " + "when binding boolean properties; the target property '%s' " + "is of type '%s'", + G_STRLOC, + target_property, + g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec))); + return NULL; + } + + binding = g_object_new (G_TYPE_BINDING, + "source", source, + "source-property", source_property, + "target", target, + "target-property", target_property, + "flags", flags, + NULL); + + 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; + + /* synchronize the target with the source by faking an emission of + * the ::notify signal for the source property; this will also take + * care of the bidirectional binding case because the eventual change + * will emit a notification on the target + */ + if (flags & G_BINDING_SYNC_CREATE) + on_source_notify (binding->source, binding->source_pspec, binding); + + return binding; +} + +/** + * g_object_bind_property: + * @source: (type GObject.Object): the source #GObject + * @source_property: the property on @source to bind + * @target: (type GObject.Object): 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); +} + +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] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT }; + GValue retval = G_VALUE_INIT; + 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] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT }; + GValue retval = G_VALUE_INIT; + 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: (type GObject.Object): the source #GObject + * @source_property: the property on @source to bind + * @target: (type GObject.Object): 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) + { + if (G_CLOSURE_NEEDS_MARSHAL (transform_to)) + g_closure_set_marshal (transform_to, g_cclosure_user_marshal_BOOLEAN__BOXED_BOXED); + + data->transform_to_closure = g_closure_ref (transform_to); + g_closure_sink (data->transform_to_closure); + } + + if (transform_from != NULL) + { + if (G_CLOSURE_NEEDS_MARSHAL (transform_from)) + g_closure_set_marshal (transform_from, g_cclosure_user_marshal_BOOLEAN__BOXED_BOXED); + + 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/src/gbinding.h b/src/gbinding.h new file mode 100644 index 0000000..5e60c86 --- /dev/null +++ b/src/gbinding.h @@ -0,0 +1,129 @@ +/* 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 + */ + +#ifndef __G_BINDING_H__ +#define __G_BINDING_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: + * + * GBinding 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. + * @G_BINDING_SYNC_CREATE: Synchronize the values of the source and + * target properties when creating the binding; the direction of + * the synchronization is always from the source to the target. + * @G_BINDING_INVERT_BOOLEAN: If the two properties being bound are + * booleans, setting one to %TRUE will result in the other being + * set to %FALSE and vice versa. This flag will only work for + * boolean properties, and cannot be used when passing custom + * transformation functions to g_object_bind_property_full(). + * + * 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, + G_BINDING_SYNC_CREATE = 1 << 1, + G_BINDING_INVERT_BOOLEAN = 1 << 2 +} 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); +const gchar * g_binding_get_source_property (GBinding *binding); +const 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_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 + +#endif /* __G_BINDING_H__ */ diff --git a/src/virt-viewer-session-spice.c b/src/virt-viewer-session-spice.c index cc43bed..4b5edbf 100644 --- a/src/virt-viewer-session-spice.c +++ b/src/virt-viewer-session-spice.c @@ -34,6 +34,11 @@ #include "virt-viewer-display-spice.h" #include "virt-viewer-auth.h" +#if !GLIB_CHECK_VERSION(2, 26, 0) +#include "gbinding.h" +#include "gbinding.c" +#endif + G_DEFINE_TYPE (VirtViewerSessionSpice, virt_viewer_session_spice, VIRT_VIEWER_TYPE_SESSION) -- cgit v1.2.3