| /* 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.1 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, see <http://www.gnu.org/licenses/>. |
| * |
| * 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: |
| * |
| * |[<!-- language="C" --> |
| * g_object_bind_property (object1, "property-a", |
| * object2, "property-b", |
| * G_BINDING_DEFAULT); |
| * ]| |
| * |
| * will cause the property named "property-b" of @object2 to be updated |
| * every time g_object_set() or the specific accessor changes the value of |
| * the property "property-a" of @object1. |
| * |
| * 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: |
| * |
| * |[<!-- language="C" --> |
| * 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: |
| * |
| * |[<!-- language="C" --> |
| * 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 "value" |
| * property of @adjustment1 changes and will transform the current value |
| * of the property before applying it to the "value" property of @adjustment2. |
| * |
| * Vice versa, the @fahrenheit_to_celsius function will be called whenever |
| * the "value" property of @adjustment2 changes, and will transform the |
| * current value of the property before applying it to the "value" property |
| * of @adjustment1. |
| * |
| * 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. |
| * |
| * Bindings for languages with garbage collection can use |
| * g_binding_unbind() to explicitly release a binding between the source |
| * and target properties, instead of relying on the last reference on the |
| * binding, source, and target instances to drop. |
| * |
| * #GBinding is available since GObject 2.26 |
| */ |
| |
| #include "config.h" |
| |
| #include <string.h> |
| |
| #include "gbinding.h" |
| #include "genums.h" |
| #include "gmarshal.h" |
| #include "gobject.h" |
| #include "gsignal.h" |
| #include "gparamspecs.h" |
| #include "gvaluetypes.h" |
| |
| #include "glibintl.h" |
| |
| |
| GType |
| g_binding_flags_get_type (void) |
| { |
| static gsize static_g_define_type_id = 0; |
| |
| if (g_once_init_enter (&static_g_define_type_id)) |
| { |
| 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 (&static_g_define_type_id, g_define_type_id); |
| } |
| |
| return static_g_define_type_id; |
| } |
| |
| /* Reference counted helper struct that is passed to all callbacks to ensure |
| * that they never work with already freed objects without having to store |
| * strong references for them. |
| * |
| * Using strong references anywhere is not possible because of the API |
| * requirements of GBinding, specifically that the initial reference of the |
| * GBinding is owned by the source/target and the caller and can be released |
| * either by the source/target being finalized or calling g_binding_unbind(). |
| * |
| * As such, the only strong reference has to be owned by both weak notifies of |
| * the source and target and the first to be called has to release it. |
| */ |
| typedef struct { |
| GWeakRef binding; |
| GWeakRef source; |
| GWeakRef target; |
| gboolean binding_removed; |
| } BindingContext; |
| |
| static BindingContext * |
| binding_context_ref (BindingContext *context) |
| { |
| return g_atomic_rc_box_acquire (context); |
| } |
| |
| static void |
| binding_context_clear (BindingContext *context) |
| { |
| g_weak_ref_clear (&context->binding); |
| g_weak_ref_clear (&context->source); |
| g_weak_ref_clear (&context->target); |
| } |
| |
| static void |
| binding_context_unref (BindingContext *context) |
| { |
| g_atomic_rc_box_release_full (context, (GDestroyNotify) binding_context_clear); |
| } |
| |
| /* Reference counting for the transform functions to ensure that they're always |
| * valid while making use of them in the property notify callbacks. |
| * |
| * The transform functions are released when unbinding but unbinding can happen |
| * while the transform functions are currently in use inside the notify callbacks. |
| */ |
| typedef struct { |
| GBindingTransformFunc transform_s2t; |
| GBindingTransformFunc transform_t2s; |
| |
| gpointer transform_data; |
| GDestroyNotify destroy_notify; |
| } TransformFunc; |
| |
| static TransformFunc * |
| transform_func_new (GBindingTransformFunc transform_s2t, |
| GBindingTransformFunc transform_t2s, |
| gpointer transform_data, |
| GDestroyNotify destroy_notify) |
| { |
| TransformFunc *func = g_atomic_rc_box_new0 (TransformFunc); |
| |
| func->transform_s2t = transform_s2t; |
| func->transform_t2s = transform_t2s; |
| func->transform_data = transform_data; |
| func->destroy_notify = destroy_notify; |
| |
| return func; |
| } |
| |
| static TransformFunc * |
| transform_func_ref (TransformFunc *func) |
| { |
| return g_atomic_rc_box_acquire (func); |
| } |
| |
| static void |
| transform_func_clear (TransformFunc *func) |
| { |
| if (func->destroy_notify) |
| func->destroy_notify (func->transform_data); |
| } |
| |
| static void |
| transform_func_unref (TransformFunc *func) |
| { |
| g_atomic_rc_box_release_full (func, (GDestroyNotify) transform_func_clear); |
| } |
| |
| #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 */ |
| BindingContext *context; |
| |
| /* protects transform_func, source, target property notify and |
| * target_weak_notify_installed for unbinding */ |
| GMutex unbind_lock; |
| |
| /* transform functions, only NULL after unbinding */ |
| TransformFunc *transform_func; /* LOCK: unbind_lock */ |
| |
| /* 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; |
| |
| GBindingFlags flags; |
| |
| guint source_notify; /* LOCK: unbind_lock */ |
| guint target_notify; /* LOCK: unbind_lock */ |
| gboolean target_weak_notify_installed; /* LOCK: unbind_lock */ |
| |
| /* 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 guint gobject_notify_signal_id; |
| |
| G_DEFINE_TYPE (GBinding, g_binding, G_TYPE_OBJECT) |
| |
| static void weak_unbind (gpointer user_data, GObject *where_the_object_was); |
| |
| /* Must be called with the unbind lock held, context/binding != NULL and strong |
| * references to source/target or NULL. |
| * Return TRUE if the binding was actually removed and FALSE if it was already |
| * removed before. */ |
| static gboolean |
| unbind_internal_locked (BindingContext *context, GBinding *binding, GObject *source, GObject *target) |
| { |
| gboolean binding_was_removed = FALSE; |
| |
| g_assert (context != NULL); |
| g_assert (binding != NULL); |
| |
| /* If the target went away we still have a strong reference to the source |
| * here and can clear it from the binding. Otherwise if the source went away |
| * we can clear the target from the binding. Finalizing an object clears its |
| * signal handlers and all weak references pointing to it before calling |
| * weak notify callbacks. |
| * |
| * If both still exist we clean up everything set up by the binding. |
| */ |
| if (source) |
| { |
| /* We always add/remove the source property notify and the weak notify |
| * of the source at the same time, and should only ever do that once. */ |
| if (binding->source_notify != 0) |
| { |
| g_signal_handler_disconnect (source, binding->source_notify); |
| |
| g_object_weak_unref (source, weak_unbind, context); |
| binding_context_unref (context); |
| |
| binding->source_notify = 0; |
| } |
| g_weak_ref_set (&context->source, NULL); |
| } |
| |
| /* As above, but with the target. If source==target then no weak notify was |
| * installed for the target, which is why that is stored as a separate |
| * boolean inside the binding. */ |
| if (target) |
| { |
| /* There might be a target property notify without a weak notify on the |
| * target or the other way around, so these have to be handled |
| * independently here unlike for the source. */ |
| if (binding->target_notify != 0) |
| { |
| g_signal_handler_disconnect (target, binding->target_notify); |
| |
| binding->target_notify = 0; |
| } |
| g_weak_ref_set (&context->target, NULL); |
| |
| /* Remove the weak notify from the target, at most once */ |
| if (binding->target_weak_notify_installed) |
| { |
| g_object_weak_unref (target, weak_unbind, context); |
| binding_context_unref (context); |
| binding->target_weak_notify_installed = FALSE; |
| } |
| } |
| |
| /* Make sure to remove the binding only once and return to the caller that |
| * this was the call that actually removed it. */ |
| if (!context->binding_removed) |
| { |
| context->binding_removed = TRUE; |
| binding_was_removed = TRUE; |
| } |
| |
| return binding_was_removed; |
| } |
| |
| /* 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. Each weak notify owns a strong reference to the |
| * binding that should be dropped here. */ |
| static void |
| weak_unbind (gpointer user_data, |
| GObject *where_the_object_was) |
| { |
| BindingContext *context = user_data; |
| GBinding *binding; |
| GObject *source, *target; |
| gboolean binding_was_removed = FALSE; |
| TransformFunc *transform_func; |
| |
| binding = g_weak_ref_get (&context->binding); |
| if (!binding) |
| { |
| /* The binding was already destroyed before so there's nothing to do */ |
| binding_context_unref (context); |
| return; |
| } |
| |
| g_mutex_lock (&binding->unbind_lock); |
| |
| transform_func = g_steal_pointer (&binding->transform_func); |
| |
| source = g_weak_ref_get (&context->source); |
| target = g_weak_ref_get (&context->target); |
| |
| /* If this is called then either the source or target or both must be in the |
| * process of being disposed. If this happens as part of g_object_unref() |
| * then the weak references are actually cleared, otherwise if disposing |
| * happens as part of g_object_run_dispose() then they would still point to |
| * the disposed object. |
| * |
| * If the object this is being called for is either the source or the target |
| * and we actually got a strong reference to it nonetheless (see above), |
| * then signal handlers and weak notifies for it are already disconnected |
| * and they must not be disconnected a second time. Instead simply clear the |
| * weak reference and be done with it. |
| * |
| * See https://gitlab.gnome.org/GNOME/glib/-/issues/2266 */ |
| |
| if (source == where_the_object_was) |
| { |
| g_weak_ref_set (&context->source, NULL); |
| g_clear_object (&source); |
| } |
| |
| if (target == where_the_object_was) |
| { |
| g_weak_ref_set (&context->target, NULL); |
| g_clear_object (&target); |
| } |
| |
| binding_was_removed = unbind_internal_locked (context, binding, source, target); |
| |
| g_mutex_unlock (&binding->unbind_lock); |
| |
| /* Unref source, target and transform_func after the mutex is unlocked as it |
| * might release the last reference, which then accesses the mutex again */ |
| g_clear_object (&target); |
| g_clear_object (&source); |
| |
| g_clear_pointer (&transform_func, transform_func_unref); |
| |
| /* This releases the strong reference we got from the weak ref above */ |
| g_object_unref (binding); |
| |
| /* This will take care of the binding itself. */ |
| if (binding_was_removed) |
| g_object_unref (binding); |
| |
| /* Each weak notify owns a reference to the binding context. */ |
| binding_context_unref (context); |
| } |
| |
| static gboolean |
| default_transform (GBinding *binding, |
| const GValue *value_a, |
| GValue *value_b, |
| gpointer user_data G_GNUC_UNUSED) |
| { |
| /* 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); |
| return TRUE; |
| } |
| |
| if (g_value_type_transformable (G_VALUE_TYPE (value_a), |
| G_VALUE_TYPE (value_b))) |
| { |
| if (g_value_transform (value_a, value_b)) |
| return TRUE; |
| } |
| |
| 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; |
| } |
| |
| g_value_copy (value_a, value_b); |
| return TRUE; |
| } |
| |
| static gboolean |
| default_invert_boolean_transform (GBinding *binding, |
| const GValue *value_a, |
| GValue *value_b, |
| gpointer user_data G_GNUC_UNUSED) |
| { |
| 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 void |
| on_source_notify (GObject *source, |
| GParamSpec *pspec, |
| BindingContext *context) |
| { |
| GBinding *binding; |
| GObject *target; |
| TransformFunc *transform_func; |
| GValue from_value = G_VALUE_INIT; |
| GValue to_value = G_VALUE_INIT; |
| gboolean res; |
| |
| binding = g_weak_ref_get (&context->binding); |
| if (!binding) |
| return; |
| |
| if (binding->is_frozen) |
| { |
| g_object_unref (binding); |
| return; |
| } |
| |
| target = g_weak_ref_get (&context->target); |
| if (!target) |
| { |
| g_object_unref (binding); |
| return; |
| } |
| |
| /* Get the transform function safely */ |
| g_mutex_lock (&binding->unbind_lock); |
| if (!binding->transform_func) |
| { |
| /* it was released already during unbinding, nothing to do here */ |
| g_mutex_unlock (&binding->unbind_lock); |
| return; |
| } |
| transform_func = transform_func_ref (binding->transform_func); |
| g_mutex_unlock (&binding->unbind_lock); |
| |
| g_value_init (&from_value, G_PARAM_SPEC_VALUE_TYPE (binding->source_pspec)); |
| g_value_init (&to_value, G_PARAM_SPEC_VALUE_TYPE (binding->target_pspec)); |
| |
| g_object_get_property (source, binding->source_pspec->name, &from_value); |
| |
| res = transform_func->transform_s2t (binding, |
| &from_value, |
| &to_value, |
| transform_func->transform_data); |
| |
| transform_func_unref (transform_func); |
| |
| if (res) |
| { |
| binding->is_frozen = TRUE; |
| |
| g_param_value_validate (binding->target_pspec, &to_value); |
| g_object_set_property (target, binding->target_pspec->name, &to_value); |
| |
| binding->is_frozen = FALSE; |
| } |
| |
| g_value_unset (&from_value); |
| g_value_unset (&to_value); |
| |
| g_object_unref (target); |
| g_object_unref (binding); |
| } |
| |
| static void |
| on_target_notify (GObject *target, |
| GParamSpec *pspec, |
| BindingContext *context) |
| { |
| GBinding *binding; |
| GObject *source; |
| TransformFunc *transform_func; |
| GValue from_value = G_VALUE_INIT; |
| GValue to_value = G_VALUE_INIT; |
| gboolean res; |
| |
| binding = g_weak_ref_get (&context->binding); |
| if (!binding) |
| return; |
| |
| if (binding->is_frozen) |
| { |
| g_object_unref (binding); |
| return; |
| } |
| |
| source = g_weak_ref_get (&context->source); |
| if (!source) |
| { |
| g_object_unref (binding); |
| return; |
| } |
| |
| /* Get the transform function safely */ |
| g_mutex_lock (&binding->unbind_lock); |
| if (!binding->transform_func) |
| { |
| /* it was released already during unbinding, nothing to do here */ |
| g_mutex_unlock (&binding->unbind_lock); |
| return; |
| } |
| transform_func = transform_func_ref (binding->transform_func); |
| g_mutex_unlock (&binding->unbind_lock); |
| |
| g_value_init (&from_value, G_PARAM_SPEC_VALUE_TYPE (binding->target_pspec)); |
| g_value_init (&to_value, G_PARAM_SPEC_VALUE_TYPE (binding->source_pspec)); |
| |
| g_object_get_property (target, binding->target_pspec->name, &from_value); |
| |
| res = transform_func->transform_t2s (binding, |
| &from_value, |
| &to_value, |
| transform_func->transform_data); |
| transform_func_unref (transform_func); |
| |
| if (res) |
| { |
| binding->is_frozen = TRUE; |
| |
| g_param_value_validate (binding->source_pspec, &to_value); |
| g_object_set_property (source, binding->source_pspec->name, &to_value); |
| |
| binding->is_frozen = FALSE; |
| } |
| |
| g_value_unset (&from_value); |
| g_value_unset (&to_value); |
| |
| g_object_unref (source); |
| g_object_unref (binding); |
| } |
| |
| static inline void |
| g_binding_unbind_internal (GBinding *binding, |
| gboolean unref_binding) |
| { |
| BindingContext *context = binding->context; |
| GObject *source, *target; |
| gboolean binding_was_removed = FALSE; |
| TransformFunc *transform_func; |
| |
| g_mutex_lock (&binding->unbind_lock); |
| |
| transform_func = g_steal_pointer (&binding->transform_func); |
| |
| source = g_weak_ref_get (&context->source); |
| target = g_weak_ref_get (&context->target); |
| |
| binding_was_removed = unbind_internal_locked (context, binding, source, target); |
| |
| g_mutex_unlock (&binding->unbind_lock); |
| |
| /* Unref source, target and transform_func after the mutex is unlocked as it |
| * might release the last reference, which then accesses the mutex again */ |
| g_clear_object (&target); |
| g_clear_object (&source); |
| |
| g_clear_pointer (&transform_func, transform_func_unref); |
| |
| if (binding_was_removed && unref_binding) |
| g_object_unref (binding); |
| } |
| |
| static void |
| g_binding_finalize (GObject *gobject) |
| { |
| GBinding *binding = G_BINDING (gobject); |
| |
| g_binding_unbind_internal (binding, FALSE); |
| |
| binding_context_unref (binding->context); |
| |
| g_mutex_clear (&binding->unbind_lock); |
| |
| G_OBJECT_CLASS (g_binding_parent_class)->finalize (gobject); |
| } |
| |
| /* @key must have already been validated with is_valid() |
| * Modifies @key in place. */ |
| static void |
| canonicalize_key (gchar *key) |
| { |
| gchar *p; |
| |
| for (p = key; *p != 0; p++) |
| { |
| gchar c = *p; |
| |
| if (c == '_') |
| *p = '-'; |
| } |
| } |
| |
| /* @key must have already been validated with is_valid() */ |
| static gboolean |
| is_canonical (const gchar *key) |
| { |
| return (strchr (key, '_') == NULL); |
| } |
| |
| static gboolean |
| is_valid_property_name (const gchar *key) |
| { |
| const gchar *p; |
| |
| /* First character must be a letter. */ |
| if ((key[0] < 'A' || key[0] > 'Z') && |
| (key[0] < 'a' || key[0] > 'z')) |
| return FALSE; |
| |
| for (p = key; *p != 0; p++) |
| { |
| const gchar c = *p; |
| |
| if (c != '-' && c != '_' && |
| (c < '0' || c > '9') && |
| (c < 'A' || c > 'Z') && |
| (c < 'a' || c > 'z')) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| 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: |
| g_weak_ref_set (&binding->context->source, g_value_get_object (value)); |
| break; |
| |
| case PROP_TARGET: |
| g_weak_ref_set (&binding->context->target, g_value_get_object (value)); |
| break; |
| |
| case PROP_SOURCE_PROPERTY: |
| case PROP_TARGET_PROPERTY: |
| { |
| gchar *name_copy = NULL; |
| const gchar *name = g_value_get_string (value); |
| const gchar **dest; |
| |
| /* Ensure the name we intern is canonical. */ |
| if (!is_canonical (name)) |
| { |
| name_copy = g_value_dup_string (value); |
| canonicalize_key (name_copy); |
| name = name_copy; |
| } |
| |
| if (prop_id == PROP_SOURCE_PROPERTY) |
| dest = &binding->source_property; |
| else |
| dest = &binding->target_property; |
| |
| *dest = g_intern_string (name); |
| |
| g_free (name_copy); |
| 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_take_object (value, g_weak_ref_get (&binding->context->source)); |
| break; |
| |
| case PROP_SOURCE_PROPERTY: |
| /* @source_property is interned, so we don’t need to take a copy */ |
| g_value_set_interned_string (value, binding->source_property); |
| break; |
| |
| case PROP_TARGET: |
| g_value_take_object (value, g_weak_ref_get (&binding->context->target)); |
| break; |
| |
| case PROP_TARGET_PROPERTY: |
| /* @target_property is interned, so we don’t need to take a copy */ |
| g_value_set_interned_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); |
| GBindingTransformFunc transform_func = default_transform; |
| GObject *source, *target; |
| GQuark source_property_detail; |
| GClosure *source_notify_closure; |
| |
| /* assert that we were constructed correctly */ |
| source = g_weak_ref_get (&binding->context->source); |
| target = g_weak_ref_get (&binding->context->target); |
| g_assert (source != NULL); |
| g_assert (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 (source), binding->source_property); |
| binding->target_pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (target), binding->target_property); |
| g_assert (binding->source_pspec != NULL); |
| g_assert (binding->target_pspec != NULL); |
| |
| /* switch to the invert boolean transform if needed */ |
| if (binding->flags & G_BINDING_INVERT_BOOLEAN) |
| transform_func = default_invert_boolean_transform; |
| |
| /* set the default transformation functions here */ |
| binding->transform_func = transform_func_new (transform_func, transform_func, NULL, NULL); |
| |
| source_property_detail = g_quark_from_string (binding->source_property); |
| source_notify_closure = g_cclosure_new (G_CALLBACK (on_source_notify), |
| binding_context_ref (binding->context), |
| (GClosureNotify) binding_context_unref); |
| binding->source_notify = g_signal_connect_closure_by_id (source, |
| gobject_notify_signal_id, |
| source_property_detail, |
| source_notify_closure, |
| FALSE); |
| |
| g_object_weak_ref (source, weak_unbind, binding_context_ref (binding->context)); |
| |
| if (binding->flags & G_BINDING_BIDIRECTIONAL) |
| { |
| GQuark target_property_detail; |
| GClosure *target_notify_closure; |
| |
| target_property_detail = g_quark_from_string (binding->target_property); |
| target_notify_closure = g_cclosure_new (G_CALLBACK (on_target_notify), |
| binding_context_ref (binding->context), |
| (GClosureNotify) binding_context_unref); |
| binding->target_notify = g_signal_connect_closure_by_id (target, |
| gobject_notify_signal_id, |
| target_property_detail, |
| target_notify_closure, |
| FALSE); |
| } |
| |
| if (target != source) |
| { |
| g_object_weak_ref (target, weak_unbind, binding_context_ref (binding->context)); |
| |
| /* Need to remember separately if a target weak notify was installed as |
| * unlike for the source it can exist independently of the property |
| * notification callback */ |
| binding->target_weak_notify_installed = TRUE; |
| } |
| |
| g_object_unref (source); |
| g_object_unref (target); |
| } |
| |
| static void |
| g_binding_class_init (GBindingClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_notify_signal_id = g_signal_lookup ("notify", G_TYPE_OBJECT); |
| g_assert (gobject_notify_signal_id != 0); |
| |
| 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. |
| * |
| * This should be in [canonical form][canonical-parameter-names] to get the |
| * best performance. |
| * |
| * 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. |
| * |
| * This should be in [canonical form][canonical-parameter-names] to get the |
| * best performance. |
| * |
| * 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_mutex_init (&binding->unbind_lock); |
| |
| binding->context = g_atomic_rc_box_new0 (BindingContext); |
| g_weak_ref_init (&binding->context->binding, binding); |
| g_weak_ref_init (&binding->context->source, NULL); |
| g_weak_ref_init (&binding->context->target, NULL); |
| } |
| |
| /** |
| * g_binding_get_flags: |
| * @binding: a #GBinding |
| * |
| * Retrieves the flags passed when constructing the #GBinding. |
| * |
| * Returns: 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. |
| * |
| * A #GBinding can outlive the source #GObject as the binding does not hold a |
| * strong reference to the source. If the source is destroyed before the |
| * binding then this function will return %NULL. |
| * |
| * Use g_binding_dup_source() if the source or binding are used from different |
| * threads as otherwise the pointer returned from this function might become |
| * invalid if the source is finalized from another thread in the meantime. |
| * |
| * Returns: (transfer none) (nullable): the source #GObject, or %NULL if the |
| * source does not exist any more. |
| * |
| * Deprecated: 2.68: Use g_binding_dup_source() for a safer version of this |
| * function. |
| * |
| * Since: 2.26 |
| */ |
| GObject * |
| g_binding_get_source (GBinding *binding) |
| { |
| GObject *source; |
| |
| g_return_val_if_fail (G_IS_BINDING (binding), NULL); |
| |
| source = g_weak_ref_get (&binding->context->source); |
| /* Unref here, this API is not thread-safe |
| * FIXME: Remove this API when we next break API */ |
| if (source) |
| g_object_unref (source); |
| |
| return source; |
| } |
| |
| /** |
| * g_binding_dup_source: |
| * @binding: a #GBinding |
| * |
| * Retrieves the #GObject instance used as the source of the binding. |
| * |
| * A #GBinding can outlive the source #GObject as the binding does not hold a |
| * strong reference to the source. If the source is destroyed before the |
| * binding then this function will return %NULL. |
| * |
| * Returns: (transfer full) (nullable): the source #GObject, or %NULL if the |
| * source does not exist any more. |
| * |
| * Since: 2.68 |
| */ |
| GObject * |
| g_binding_dup_source (GBinding *binding) |
| { |
| g_return_val_if_fail (G_IS_BINDING (binding), NULL); |
| |
| return g_weak_ref_get (&binding->context->source); |
| } |
| |
| /** |
| * g_binding_get_target: |
| * @binding: a #GBinding |
| * |
| * Retrieves the #GObject instance used as the target of the binding. |
| * |
| * A #GBinding can outlive the target #GObject as the binding does not hold a |
| * strong reference to the target. If the target is destroyed before the |
| * binding then this function will return %NULL. |
| * |
| * Use g_binding_dup_target() if the target or binding are used from different |
| * threads as otherwise the pointer returned from this function might become |
| * invalid if the target is finalized from another thread in the meantime. |
| * |
| * Returns: (transfer none) (nullable): the target #GObject, or %NULL if the |
| * target does not exist any more. |
| * |
| * Deprecated: 2.68: Use g_binding_dup_target() for a safer version of this |
| * function. |
| * |
| * Since: 2.26 |
| */ |
| GObject * |
| g_binding_get_target (GBinding *binding) |
| { |
| GObject *target; |
| |
| g_return_val_if_fail (G_IS_BINDING (binding), NULL); |
| |
| target = g_weak_ref_get (&binding->context->target); |
| /* Unref here, this API is not thread-safe |
| * FIXME: Remove this API when we next break API */ |
| if (target) |
| g_object_unref (target); |
| |
| return target; |
| } |
| |
| /** |
| * g_binding_dup_target: |
| * @binding: a #GBinding |
| * |
| * Retrieves the #GObject instance used as the target of the binding. |
| * |
| * A #GBinding can outlive the target #GObject as the binding does not hold a |
| * strong reference to the target. If the target is destroyed before the |
| * binding then this function will return %NULL. |
| * |
| * Returns: (transfer full) (nullable): the target #GObject, or %NULL if the |
| * target does not exist any more. |
| * |
| * Since: 2.68 |
| */ |
| GObject * |
| g_binding_dup_target (GBinding *binding) |
| { |
| g_return_val_if_fail (G_IS_BINDING (binding), NULL); |
| |
| return g_weak_ref_get (&binding->context->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. |
| * |
| * Returns: 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. |
| * |
| * Returns: 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_binding_unbind: |
| * @binding: a #GBinding |
| * |
| * Explicitly releases the binding between the source and the target |
| * property expressed by @binding. |
| * |
| * This function will release the reference that is being held on |
| * the @binding instance if the binding is still bound; if you want to hold on |
| * to the #GBinding instance after calling g_binding_unbind(), you will need |
| * to hold a reference to it. |
| * |
| * Note however that this function does not take ownership of @binding, it |
| * only unrefs the reference that was initially created by |
| * g_object_bind_property() and is owned by the binding. |
| * |
| * Since: 2.38 |
| */ |
| void |
| g_binding_unbind (GBinding *binding) |
| { |
| g_return_if_fail (G_IS_BINDING (binding)); |
| |
| g_binding_unbind_internal (binding, TRUE); |
| } |
| |
| /** |
| * 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) (nullable): the transformation function |
| * from the @source to the @target, or %NULL to use the default |
| * @transform_from: (scope notified) (nullable): 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: (nullable): a function to call when disposing the binding, to free |
| * resources used by the transformation functions, or %NULL if not required |
| * |
| * 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. This will release the reference that is |
| * being held on the #GBinding instance; if you want to hold on to the |
| * #GBinding instance, you will need to hold a reference to it. |
| * |
| * To remove the binding, call g_binding_unbind(). |
| * |
| * 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. |
| * |
| * Returns: (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 (is_valid_property_name (source_property), NULL); |
| g_return_val_if_fail (G_IS_OBJECT (target), NULL); |
| g_return_val_if_fail (target_property != NULL, NULL); |
| g_return_val_if_fail (is_valid_property_name (target_property), 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); |
| |
| g_assert (binding->transform_func != NULL); |
| |
| /* Use default functions if not provided here */ |
| if (transform_to == NULL) |
| transform_to = binding->transform_func->transform_s2t; |
| |
| if (transform_from == NULL) |
| transform_from = binding->transform_func->transform_t2s; |
| |
| g_clear_pointer (&binding->transform_func, transform_func_unref); |
| binding->transform_func = transform_func_new (transform_to, transform_from, user_data, 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 (source, binding->source_pspec, binding->context); |
| |
| 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. |
| * |
| * Removing the binding by calling g_object_unref() on it must only be done if |
| * the binding, @source and @target are only used from a single thread and it |
| * is clear that both @source and @target outlive the binding. Especially it |
| * is not safe to rely on this if the binding, @source or @target can be |
| * finalized from different threads. Keep another reference to the binding and |
| * use g_binding_unbind() instead to be on the safe side. |
| * |
| * A #GObject can have multiple bindings. |
| * |
| * Returns: (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: (rename-to 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: 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. |
| * |
| * Returns: (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_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_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); |
| } |