/* GObject - GLib Type, Object, Parameter and Signal Library * * Copyright (C) 2015-2022 Christian Hergert * Copyright (C) 2015 Garrett Regier * * 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 . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "glib.h" #include "glibintl.h" #include "gbindinggroup.h" #include "gparamspecs.h" /** * SECTION:gbindinggroup * @Title: GBindingGroup * @Short_description: Binding multiple properties as a group * @include: glib-object.h * * The #GBindingGroup can be used to bind multiple properties * from an object collectively. * * Use the various methods to bind properties from a single source * object to multiple destination objects. Properties can be bound * bidirectionally and are connected when the source object is set * with g_binding_group_set_source(). * * Since: 2.72 */ #if 0 # define DEBUG_BINDINGS #endif struct _GBindingGroup { GObject parent_instance; GMutex mutex; GObject *source; /* (owned weak) */ GPtrArray *lazy_bindings; /* (owned) (element-type LazyBinding) */ }; typedef struct _GBindingGroupClass { GObjectClass parent_class; } GBindingGroupClass; typedef struct { GBindingGroup *group; /* (unowned) */ const char *source_property; /* (interned) */ const char *target_property; /* (interned) */ GObject *target; /* (owned weak) */ GBinding *binding; /* (unowned) */ gpointer user_data; GDestroyNotify user_data_destroy; gpointer transform_to; /* (nullable) (owned) */ gpointer transform_from; /* (nullable) (owned) */ GBindingFlags binding_flags; guint using_closures : 1; } LazyBinding; G_DEFINE_TYPE (GBindingGroup, g_binding_group, G_TYPE_OBJECT) typedef enum { PROP_SOURCE = 1, N_PROPS } GBindingGroupProperty; static void lazy_binding_free (gpointer data); static GParamSpec *properties[N_PROPS]; static void g_binding_group_connect (GBindingGroup *self, LazyBinding *lazy_binding) { GBinding *binding; g_assert (G_IS_BINDING_GROUP (self)); g_assert (self->source != NULL); g_assert (lazy_binding != NULL); g_assert (lazy_binding->binding == NULL); g_assert (lazy_binding->target != NULL); g_assert (lazy_binding->target_property != NULL); g_assert (lazy_binding->source_property != NULL); #ifdef DEBUG_BINDINGS { GFlagsClass *flags_class; g_autofree gchar *flags_str = NULL; flags_class = g_type_class_ref (G_TYPE_BINDING_FLAGS); flags_str = g_flags_to_string (flags_class, lazy_binding->binding_flags); g_print ("Binding %s(%p):%s to %s(%p):%s (flags=%s)\n", G_OBJECT_TYPE_NAME (self->source), self->source, lazy_binding->source_property, G_OBJECT_TYPE_NAME (lazy_binding->target), lazy_binding->target, lazy_binding->target_property, flags_str); g_type_class_unref (flags_class); } #endif if (!lazy_binding->using_closures) binding = g_object_bind_property_full (self->source, lazy_binding->source_property, lazy_binding->target, lazy_binding->target_property, lazy_binding->binding_flags, lazy_binding->transform_to, lazy_binding->transform_from, lazy_binding->user_data, NULL); else binding = g_object_bind_property_with_closures (self->source, lazy_binding->source_property, lazy_binding->target, lazy_binding->target_property, lazy_binding->binding_flags, lazy_binding->transform_to, lazy_binding->transform_from); lazy_binding->binding = binding; } static void g_binding_group_disconnect (LazyBinding *lazy_binding) { g_assert (lazy_binding != NULL); if (lazy_binding->binding != NULL) { g_binding_unbind (lazy_binding->binding); lazy_binding->binding = NULL; } } static void g_binding_group__source_weak_notify (gpointer data, GObject *where_object_was) { GBindingGroup *self = data; guint i; g_assert (G_IS_BINDING_GROUP (self)); g_mutex_lock (&self->mutex); self->source = NULL; for (i = 0; i < self->lazy_bindings->len; i++) { LazyBinding *lazy_binding = g_ptr_array_index (self->lazy_bindings, i); lazy_binding->binding = NULL; } g_mutex_unlock (&self->mutex); } static void g_binding_group__target_weak_notify (gpointer data, GObject *where_object_was) { GBindingGroup *self = data; LazyBinding *to_free = NULL; guint i; g_assert (G_IS_BINDING_GROUP (self)); g_mutex_lock (&self->mutex); for (i = 0; i < self->lazy_bindings->len; i++) { LazyBinding *lazy_binding = g_ptr_array_index (self->lazy_bindings, i); if (lazy_binding->target == where_object_was) { lazy_binding->target = NULL; lazy_binding->binding = NULL; to_free = g_ptr_array_steal_index_fast (self->lazy_bindings, i); break; } } g_mutex_unlock (&self->mutex); if (to_free != NULL) lazy_binding_free (to_free); } static void lazy_binding_free (gpointer data) { LazyBinding *lazy_binding = data; if (lazy_binding->target != NULL) { g_object_weak_unref (lazy_binding->target, g_binding_group__target_weak_notify, lazy_binding->group); lazy_binding->target = NULL; } g_binding_group_disconnect (lazy_binding); lazy_binding->group = NULL; lazy_binding->source_property = NULL; lazy_binding->target_property = NULL; if (lazy_binding->user_data_destroy) lazy_binding->user_data_destroy (lazy_binding->user_data); if (lazy_binding->using_closures) { g_clear_pointer (&lazy_binding->transform_to, g_closure_unref); g_clear_pointer (&lazy_binding->transform_from, g_closure_unref); } g_slice_free (LazyBinding, lazy_binding); } static void g_binding_group_dispose (GObject *object) { GBindingGroup *self = (GBindingGroup *)object; LazyBinding **lazy_bindings = NULL; gsize len = 0; gsize i; g_assert (G_IS_BINDING_GROUP (self)); g_mutex_lock (&self->mutex); if (self->source != NULL) { g_object_weak_unref (self->source, g_binding_group__source_weak_notify, self); self->source = NULL; } if (self->lazy_bindings->len > 0) lazy_bindings = (LazyBinding **)g_ptr_array_steal (self->lazy_bindings, &len); g_mutex_unlock (&self->mutex); /* Free bindings without holding self->mutex to avoid re-entrancy * from collateral damage through release of binding closure data, * GDataList, etc. */ for (i = 0; i < len; i++) lazy_binding_free (lazy_bindings[i]); g_free (lazy_bindings); G_OBJECT_CLASS (g_binding_group_parent_class)->dispose (object); } static void g_binding_group_finalize (GObject *object) { GBindingGroup *self = (GBindingGroup *)object; g_assert (self->lazy_bindings != NULL); g_assert (self->lazy_bindings->len == 0); g_clear_pointer (&self->lazy_bindings, g_ptr_array_unref); g_mutex_clear (&self->mutex); G_OBJECT_CLASS (g_binding_group_parent_class)->finalize (object); } static void g_binding_group_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GBindingGroup *self = G_BINDING_GROUP (object); switch ((GBindingGroupProperty) prop_id) { case PROP_SOURCE: g_value_take_object (value, g_binding_group_dup_source (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_binding_group_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GBindingGroup *self = G_BINDING_GROUP (object); switch ((GBindingGroupProperty) prop_id) { case PROP_SOURCE: g_binding_group_set_source (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_binding_group_class_init (GBindingGroupClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = g_binding_group_dispose; object_class->finalize = g_binding_group_finalize; object_class->get_property = g_binding_group_get_property; object_class->set_property = g_binding_group_set_property; /** * GBindingGroup:source: (nullable) * * The source object used for binding properties. * * Since: 2.72 */ properties[PROP_SOURCE] = g_param_spec_object ("source", "Source", "The source GObject used for binding properties.", G_TYPE_OBJECT, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void g_binding_group_init (GBindingGroup *self) { g_mutex_init (&self->mutex); self->lazy_bindings = g_ptr_array_new_with_free_func (lazy_binding_free); } /** * g_binding_group_new: * * Creates a new #GBindingGroup. * * Returns: (transfer full): a new #GBindingGroup * * Since: 2.72 */ GBindingGroup * g_binding_group_new (void) { return g_object_new (G_TYPE_BINDING_GROUP, NULL); } /** * g_binding_group_dup_source: * @self: the #GBindingGroup * * Gets the source object used for binding properties. * * Returns: (transfer none) (nullable) (type GObject): a #GObject or %NULL. * * Since: 2.72 */ gpointer g_binding_group_dup_source (GBindingGroup *self) { GObject *source; g_return_val_if_fail (G_IS_BINDING_GROUP (self), NULL); g_mutex_lock (&self->mutex); source = self->source ? g_object_ref (self->source) : NULL; g_mutex_unlock (&self->mutex); return source; } static gboolean g_binding_group_check_source (GBindingGroup *self, gpointer source) { guint i; g_assert (G_IS_BINDING_GROUP (self)); g_assert (!source || G_IS_OBJECT (source)); for (i = 0; i < self->lazy_bindings->len; i++) { LazyBinding *lazy_binding = g_ptr_array_index (self->lazy_bindings, i); g_return_val_if_fail (g_object_class_find_property (G_OBJECT_GET_CLASS (source), lazy_binding->source_property) != NULL, FALSE); } return TRUE; } /** * g_binding_group_set_source: * @self: the #GBindingGroup * @source: (type GObject) (nullable) (transfer none): the source #GObject, * or %NULL to clear it * * Sets @source as the source object used for creating property * bindings. If there is already a source object all bindings from it * will be removed. * * Note that all properties that have been bound must exist on @source. * * Since: 2.72 */ void g_binding_group_set_source (GBindingGroup *self, gpointer source) { gboolean notify = FALSE; g_return_if_fail (G_IS_BINDING_GROUP (self)); g_return_if_fail (!source || G_IS_OBJECT (source)); g_return_if_fail (source != (gpointer) self); g_mutex_lock (&self->mutex); if (source == (gpointer) self->source) goto unlock; if (self->source != NULL) { guint i; g_object_weak_unref (self->source, g_binding_group__source_weak_notify, self); self->source = NULL; for (i = 0; i < self->lazy_bindings->len; i++) { LazyBinding *lazy_binding = g_ptr_array_index (self->lazy_bindings, i); g_binding_group_disconnect (lazy_binding); } } if (source != NULL && g_binding_group_check_source (self, source)) { guint i; self->source = source; g_object_weak_ref (self->source, g_binding_group__source_weak_notify, self); for (i = 0; i < self->lazy_bindings->len; i++) { LazyBinding *lazy_binding; lazy_binding = g_ptr_array_index (self->lazy_bindings, i); g_binding_group_connect (self, lazy_binding); } } notify = TRUE; unlock: g_mutex_unlock (&self->mutex); if (notify) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SOURCE]); } static void g_binding_group_bind_helper (GBindingGroup *self, const gchar *source_property, gpointer target, const gchar *target_property, GBindingFlags flags, gpointer transform_to, gpointer transform_from, gpointer user_data, GDestroyNotify user_data_destroy, gboolean using_closures) { LazyBinding *lazy_binding; g_return_if_fail (G_IS_BINDING_GROUP (self)); g_return_if_fail (source_property != NULL); g_return_if_fail (self->source == NULL || g_object_class_find_property (G_OBJECT_GET_CLASS (self->source), source_property) != NULL); g_return_if_fail (G_IS_OBJECT (target)); g_return_if_fail (target_property != NULL); g_return_if_fail (g_object_class_find_property (G_OBJECT_GET_CLASS (target), target_property) != NULL); g_return_if_fail (target != (gpointer) self || strcmp (source_property, target_property) != 0); g_mutex_lock (&self->mutex); lazy_binding = g_slice_new0 (LazyBinding); lazy_binding->group = self; lazy_binding->source_property = g_intern_string (source_property); lazy_binding->target_property = g_intern_string (target_property); lazy_binding->target = target; lazy_binding->binding_flags = flags | G_BINDING_SYNC_CREATE; lazy_binding->user_data = user_data; lazy_binding->user_data_destroy = user_data_destroy; lazy_binding->transform_to = transform_to; lazy_binding->transform_from = transform_from; if (using_closures) { lazy_binding->using_closures = TRUE; if (transform_to != NULL) g_closure_sink (g_closure_ref (transform_to)); if (transform_from != NULL) g_closure_sink (g_closure_ref (transform_from)); } g_object_weak_ref (target, g_binding_group__target_weak_notify, self); g_ptr_array_add (self->lazy_bindings, lazy_binding); if (self->source != NULL) g_binding_group_connect (self, lazy_binding); g_mutex_unlock (&self->mutex); } /** * g_binding_group_bind: * @self: the #GBindingGroup * @source_property: the property on the source to bind * @target: (type GObject) (transfer none) (not nullable): the target #GObject * @target_property: the property on @target to bind * @flags: the flags used to create the #GBinding * * Creates a binding between @source_property on the source object * and @target_property on @target. Whenever the @source_property * is changed the @target_property is updated using the same value. * The binding flag %G_BINDING_SYNC_CREATE is automatically specified. * * See g_object_bind_property() for more information. * * Since: 2.72 */ void g_binding_group_bind (GBindingGroup *self, const gchar *source_property, gpointer target, const gchar *target_property, GBindingFlags flags) { g_binding_group_bind_full (self, source_property, target, target_property, flags, NULL, NULL, NULL, NULL); } /** * g_binding_group_bind_full: * @self: the #GBindingGroup * @source_property: the property on the source to bind * @target: (type GObject) (transfer none) (not nullable): the target #GObject * @target_property: the property on @target to bind * @flags: the flags used to create the #GBinding * @transform_to: (scope notified) (nullable): the transformation function * from the source object to the @target, or %NULL to use the default * @transform_from: (scope notified) (nullable): the transformation function * from the @target to the source object, or %NULL to use the default * @user_data: custom data to be passed to the transformation * functions, or %NULL * @user_data_destroy: function to be called when disposing the binding, * to free the resources used by the transformation functions * * Creates a binding between @source_property on the source object and * @target_property on @target, allowing you to set the transformation * functions to be used by the binding. The binding flag * %G_BINDING_SYNC_CREATE is automatically specified. * * See g_object_bind_property_full() for more information. * * Since: 2.72 */ void g_binding_group_bind_full (GBindingGroup *self, const gchar *source_property, gpointer target, const gchar *target_property, GBindingFlags flags, GBindingTransformFunc transform_to, GBindingTransformFunc transform_from, gpointer user_data, GDestroyNotify user_data_destroy) { g_binding_group_bind_helper (self, source_property, target, target_property, flags, transform_to, transform_from, user_data, user_data_destroy, FALSE); } /** * g_binding_group_bind_with_closures: (rename-to g_binding_group_bind_full) * @self: the #GBindingGroup * @source_property: the property on the source to bind * @target: (type GObject) (transfer none) (not nullable): the target #GObject * @target_property: the property on @target to bind * @flags: the flags used to create the #GBinding * @transform_to: (nullable) (transfer none): a #GClosure wrapping the * transformation function from the source object to the @target, * or %NULL to use the default * @transform_from: (nullable) (transfer none): a #GClosure wrapping the * transformation function from the @target to the source object, * or %NULL to use the default * * Creates a binding between @source_property on the source object and * @target_property on @target, allowing you to set the transformation * functions to be used by the binding. The binding flag * %G_BINDING_SYNC_CREATE is automatically specified. * * This function is the language bindings friendly version of * g_binding_group_bind_property_full(), using #GClosures * instead of function pointers. * * See g_object_bind_property_with_closures() for more information. * * Since: 2.72 */ void g_binding_group_bind_with_closures (GBindingGroup *self, const gchar *source_property, gpointer target, const gchar *target_property, GBindingFlags flags, GClosure *transform_to, GClosure *transform_from) { g_binding_group_bind_helper (self, source_property, target, target_property, flags, transform_to, transform_from, NULL, NULL, TRUE); }