| /* GObject - GLib Type, Object, Parameter and Signal Library |
| * |
| * Copyright (C) 2015-2022 Christian Hergert <christian@hergert.me> |
| * Copyright (C) 2015 Garrett Regier <garrettregier@gmail.com> |
| * |
| * 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/>. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #include "config.h" |
| #include "glib.h" |
| #include "glibintl.h" |
| |
| #include "gbindinggroup.h" |
| #include "gparamspecs.h" |
| |
| /** |
| * GBindingGroup: |
| * |
| * `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 [method@GObject.BindingGroup.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: |
| * |
| * The source object used for binding properties. |
| * |
| * Since: 2.72 |
| */ |
| properties[PROP_SOURCE] = |
| g_param_spec_object ("source", NULL, NULL, |
| 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); |
| } |