| /* gbinding.c: Binding for object properties | |
| * | |
| * Copyright (C) 2010 Intel Corp. | |
| * | |
| * SPDX-License-Identifier: LGPL-2.1-or-later | |
| * | |
| * 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> | |
| */ | |
| /** | |
| * GBinding: | |
| * | |
| * `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: | |
| * | |
| * ```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 [method@GObject.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: | |
| * | |
| * ```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: | |
| * | |
| * ```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 [func@GObject.signal_stop_emission] | |
| * or [func@GObject.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 | |
| * [method@GObject.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. | |
| * | |
| * Since: 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 GType static_g_define_type_id = 0; | |
| if (g_once_init_enter_pointer (&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_pointer (&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_critical ("%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; | |
| (void) 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; | |
| (void) 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 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", NULL, NULL, | |
| 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", NULL, NULL, | |
| 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", NULL, NULL, | |
| 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", NULL, NULL, | |
| 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", NULL, NULL, | |
| 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 (g_param_spec_is_valid_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 (g_param_spec_is_valid_name (target_property), NULL); | |
| if (source == target && g_strcmp0 (source_property, target_property) == 0) | |
| { | |
| g_critical ("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_critical ("%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_critical ("%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_critical ("%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_critical ("%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_critical ("%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_critical ("%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_critical ("%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_critical ("%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: | |
| * | |
| * |[<!-- language="C" --> | |
| * 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); | |
| } |