| #include "config.h" |
| |
| #include <string.h> |
| |
| #include "gmultibinding.h" |
| #include "genums.h" |
| #include "gmarshal.h" |
| #include "gobject.h" |
| #include "gsignal.h" |
| #include "gparamspecs.h" |
| #include "gvaluetypes.h" |
| |
| #include "glibintl.h" |
| |
| |
| #define G_MULTI_BINDING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_MULTI_BINDING, GMultiBindingClass)) |
| #define G_IS_MULTI_BINDING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_MULTI_BINDING)) |
| #define G_MULTI_BINDING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_MULTI_BINDING, GMultiBindingClass)) |
| |
| typedef struct _GMultiBindingClass GMultiBindingClass; |
| |
| struct _GMultiBinding |
| { |
| GObject parent_instance; |
| |
| gint n_sources; |
| gint n_targets; |
| |
| /* no reference is held on the objects, to avoid cycles */ |
| GObject **source; |
| GObject **target; |
| |
| /* the property names are interned, so they should not be freed */ |
| GParamSpec **source_pspec; |
| GParamSpec **target_pspec; |
| |
| GMultiBindingTransformFunc transform; |
| gpointer transform_data; |
| GDestroyNotify notify; |
| |
| guint *source_notify; |
| |
| /* a guard, to avoid loops */ |
| guint is_frozen : 1; |
| }; |
| |
| struct _GMultiBindingClass |
| { |
| GObjectClass parent_class; |
| }; |
| |
| static GQuark quark_gbinding = 0; |
| |
| G_DEFINE_TYPE (GMultiBinding, g_multi_binding, G_TYPE_OBJECT); |
| |
| static inline void |
| add_binding_qdata (GObject *gobject, |
| GMultiBinding *binding) |
| { |
| GHashTable *bindings; |
| |
| bindings = g_object_get_qdata (gobject, quark_gbinding); |
| if (bindings == NULL) |
| { |
| bindings = g_hash_table_new (NULL, NULL); |
| |
| g_object_set_qdata_full (gobject, quark_gbinding, |
| bindings, |
| (GDestroyNotify) g_hash_table_unref); |
| } |
| |
| g_hash_table_add (bindings, binding); |
| } |
| |
| static inline gboolean |
| has_binding_qdata (GObject *object, |
| GMultiBinding *binding) |
| { |
| GHashTable *bindings; |
| |
| bindings = g_object_get_qdata (object, quark_gbinding); |
| if (bindings) |
| return g_hash_table_contains (bindings, binding); |
| |
| return FALSE; |
| } |
| |
| static inline void |
| remove_binding_qdata (GObject *gobject, |
| GMultiBinding *binding) |
| { |
| GHashTable *bindings; |
| |
| bindings = g_object_get_qdata (gobject, quark_gbinding); |
| if (binding != NULL) |
| g_hash_table_remove (bindings, binding); |
| } |
| |
| /* the basic assumption is that if either the source or the target |
| * goes away then the binding does not exist any more and it should |
| * be reaped as well |
| */ |
| static void |
| weak_unbind (gpointer user_data, |
| GObject *where_the_object_was) |
| { |
| GMultiBinding *binding = user_data; |
| gint i; |
| |
| /* if what went away was a source, unset it so that GBinding::finalize |
| * does not try to access it; otherwise, disconnect everything and remove |
| * the GBinding instance from the object's qdata |
| */ |
| for (i = 0; i < binding->n_sources; i++) |
| { |
| if (binding->source[i] == where_the_object_was) |
| binding->source[i] = NULL; |
| else |
| { |
| if (binding->source_notify[i] != 0) |
| g_signal_handler_disconnect (binding->source[i], binding->source_notify[i]); |
| |
| g_object_weak_unref (binding->source[i], weak_unbind, user_data); |
| remove_binding_qdata (binding->source[i], binding); |
| |
| binding->source_notify[i] = 0; |
| binding->source[i] = NULL; |
| } |
| } |
| |
| /* as above, but with the targets */ |
| for (i = 0; i < binding->n_targets; i++) |
| { |
| if (binding->target[i] == where_the_object_was) |
| binding->target[i] = NULL; |
| else |
| { |
| g_object_weak_unref (binding->target[i], weak_unbind, user_data); |
| remove_binding_qdata (binding->target[i], binding); |
| binding->target[i] = NULL; |
| } |
| } |
| |
| /* this will take care of the binding itself */ |
| g_object_unref (binding); |
| } |
| |
| static void |
| on_source_notify (GObject *gobject, |
| GParamSpec *pspec, |
| GMultiBinding *binding) |
| { |
| GValue *from_values; |
| GValue *to_values; |
| gboolean res; |
| gint i; |
| gint notified; |
| |
| if (binding->is_frozen) |
| return; |
| |
| notified = -1; |
| from_values = g_new0 (GValue, binding->n_sources); |
| for (i = 0; i < binding->n_sources; i++) |
| { |
| g_value_init (&from_values[i], G_PARAM_SPEC_VALUE_TYPE (binding->source_pspec[i])); |
| g_object_get_property (binding->source[i], binding->source_pspec[i]->name, &from_values[i]); |
| |
| if (gobject == binding->source[i]) |
| notified = i; |
| } |
| |
| g_assert (0 <= notified && notified < binding->n_sources); |
| |
| to_values = g_new0 (GValue, binding->n_targets); |
| for (i = 0; i < binding->n_targets; i++) |
| { |
| g_value_init (&to_values[i], G_PARAM_SPEC_VALUE_TYPE (binding->target_pspec[i])); |
| g_object_get_property (binding->target[i], binding->target_pspec[i]->name, &to_values[i]); |
| } |
| |
| res = binding->transform (binding, notified, (const GValue *)from_values, to_values, binding->transform_data); |
| |
| if (res) |
| { |
| binding->is_frozen = TRUE; |
| for (i = 0; i < binding->n_targets; i++) |
| { |
| g_param_value_validate (binding->target_pspec[i], &to_values[i]); |
| g_object_set_property (binding->target[i], binding->target_pspec[i]->name, &to_values[i]); |
| } |
| binding->is_frozen = FALSE; |
| } |
| |
| for (i = 0; i < binding->n_sources; i++) |
| g_value_unset (&from_values[i]); |
| g_free (from_values); |
| |
| for (i = 0; i < binding->n_targets; i++) |
| g_value_unset (&to_values[i]); |
| g_free (to_values); |
| } |
| |
| static inline void |
| g_multi_binding_unbind_internal (GMultiBinding *binding, |
| gboolean unref_binding) |
| { |
| gint i; |
| |
| /* dispose of the transformation data */ |
| if (binding->notify != NULL) |
| { |
| binding->notify (binding->transform_data); |
| |
| binding->transform_data = NULL; |
| binding->notify = NULL; |
| } |
| |
| for (i = 0; i < binding->n_sources; i++) |
| { |
| if (binding->source[i] != NULL) |
| { |
| if (binding->source_notify[i] != 0) |
| g_signal_handler_disconnect (binding->source[i], binding->source_notify[i]); |
| |
| g_object_weak_unref (binding->source[i], weak_unbind, binding); |
| remove_binding_qdata (binding->source[i], binding); |
| |
| binding->source_notify[i] = 0; |
| binding->source[i] = NULL; |
| } |
| } |
| |
| for (i = 0; i < binding->n_targets; i++) |
| { |
| if (binding->target[i] != NULL) |
| { |
| g_object_weak_unref (binding->target[i], weak_unbind, binding); |
| remove_binding_qdata (binding->target[i], binding); |
| binding->target[i] = NULL; |
| } |
| } |
| |
| if (unref_binding) |
| g_object_unref (binding); |
| } |
| |
| static void |
| g_multi_binding_finalize (GObject *gobject) |
| { |
| GMultiBinding *binding = G_MULTI_BINDING (gobject); |
| |
| g_multi_binding_unbind_internal (binding, FALSE); |
| g_free (binding->source); |
| g_free (binding->source_pspec); |
| g_free (binding->source_notify); |
| g_free (binding->target); |
| g_free (binding->target_pspec); |
| |
| G_OBJECT_CLASS (g_multi_binding_parent_class)->finalize (gobject); |
| } |
| |
| static void |
| g_multi_binding_class_init (GMultiBindingClass *klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| quark_gbinding = g_quark_from_static_string ("g-multi-binding"); |
| |
| gobject_class->finalize = g_multi_binding_finalize; |
| } |
| |
| static void |
| g_multi_binding_init (GMultiBinding *binding) |
| { |
| } |
| |
| gint |
| g_multi_binding_get_n_sources (GMultiBinding *binding) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), 0); |
| |
| return binding->n_sources; |
| } |
| |
| GObject * |
| g_multi_binding_get_source (GMultiBinding *binding, |
| gint idx) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), NULL); |
| g_return_val_if_fail (0 <= idx && idx < binding->n_sources, NULL); |
| |
| return binding->source[idx]; |
| } |
| |
| const gchar * |
| g_multi_binding_get_source_property (GMultiBinding *binding, |
| gint idx) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), NULL); |
| g_return_val_if_fail (0 <= idx && idx < binding->n_sources, NULL); |
| |
| return binding->source_pspec[idx]->name; |
| } |
| |
| gint |
| g_multi_binding_get_n_targets (GMultiBinding *binding) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), 0); |
| |
| return binding->n_targets; |
| } |
| |
| GObject * |
| g_multi_binding_get_target (GMultiBinding *binding, |
| gint idx) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), NULL); |
| g_return_val_if_fail (0 <= idx && idx < binding->n_targets, NULL); |
| |
| return binding->target[idx]; |
| } |
| |
| const gchar * |
| g_multi_binding_get_target_property (GMultiBinding *binding, |
| gint idx) |
| { |
| g_return_val_if_fail (G_IS_MULTI_BINDING (binding), NULL); |
| g_return_val_if_fail (0 <= idx && idx < binding->n_targets, NULL); |
| |
| return binding->target_pspec[idx]->name; |
| } |
| |
| void |
| g_multi_binding_unbind (GMultiBinding *binding) |
| { |
| g_return_if_fail (G_IS_MULTI_BINDING (binding)); |
| |
| g_multi_binding_unbind_internal (binding, TRUE); |
| } |
| |
| GMultiBinding * |
| g_object_multi_bind_property_v (gint n_sources, |
| GObject *sources[], |
| const gchar *source_properties[], |
| gint n_targets, |
| GObject *targets[], |
| const gchar *target_properties[], |
| GMultiBindingFlags flags, |
| GMultiBindingTransformFunc transform, |
| gpointer user_data, |
| GDestroyNotify notify) |
| { |
| GMultiBinding *binding; |
| GParamSpec *pspec; |
| gint i; |
| gchar *signal; |
| |
| /* FIXME: don't look up pspecs twice */ |
| for (i = 0; i < n_sources; i++) |
| { |
| pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (sources[i]), source_properties[i]); |
| if (pspec == NULL) |
| { |
| g_warning ("%s: The source object %d of type %s has no property called '%s'", |
| G_STRLOC, |
| i, |
| G_OBJECT_TYPE_NAME (sources[i]), |
| source_properties[i]); |
| return NULL; |
| } |
| |
| if (!(pspec->flags & G_PARAM_READABLE)) |
| { |
| g_warning ("%s: The source object %d of type %s has no readable property called '%s'", |
| G_STRLOC, |
| i, |
| G_OBJECT_TYPE_NAME (sources[i]), |
| source_properties[i]); |
| return NULL; |
| } |
| } |
| |
| for (i = 0; i < n_targets; i++) |
| { |
| pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (targets[i]), target_properties[i]); |
| if (pspec == NULL) |
| { |
| g_warning ("%s: The target object %d of type %s has no property called '%s'", |
| G_STRLOC, |
| i, |
| G_OBJECT_TYPE_NAME (targets[i]), |
| target_properties[i]); |
| return NULL; |
| } |
| |
| if ((pspec->flags & G_PARAM_CONSTRUCT_ONLY) || !(pspec->flags & G_PARAM_WRITABLE)) |
| { |
| g_warning ("%s: The target object %d of type %s has no writable property called '%s'", |
| G_STRLOC, |
| i, |
| G_OBJECT_TYPE_NAME (targets[i]), |
| target_properties[i]); |
| return NULL; |
| } |
| } |
| |
| binding = g_object_new (G_TYPE_MULTI_BINDING, NULL); |
| |
| binding->transform = transform; |
| binding->transform_data = user_data; |
| binding->notify = notify; |
| |
| binding->n_sources = n_sources; |
| |
| binding->source = g_new (GObject *, n_sources); |
| binding->source_pspec = g_new (GParamSpec *, n_sources); |
| binding->source_notify = g_new (guint, n_sources); |
| |
| for (i = 0; i < n_sources; i++) |
| { |
| pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (sources[i]), source_properties[i]); |
| binding->source[i] = sources[i]; |
| binding->source_pspec[i] = pspec; |
| |
| signal = g_strconcat ("notify::", source_properties[i], NULL); |
| binding->source_notify[i] = g_signal_connect (binding->source[i], signal, |
| G_CALLBACK (on_source_notify), binding); |
| g_free (signal); |
| if (!has_binding_qdata (binding->source[i], binding)) |
| { |
| g_object_weak_ref (binding->source[i], weak_unbind, binding); |
| add_binding_qdata (binding->source[i], binding); |
| } |
| } |
| |
| binding->n_targets = n_targets; |
| binding->target = g_new (GObject *, n_targets); |
| binding->target_pspec = g_new (GParamSpec *, n_targets); |
| |
| for (i = 0; i < n_targets; i++) |
| { |
| pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (targets[i]), target_properties[i]); |
| binding->target[i] = targets[i]; |
| binding->target_pspec[i] = pspec; |
| |
| if (!has_binding_qdata (binding->target[i], binding)) |
| { |
| g_object_weak_ref (binding->target[i], weak_unbind, binding); |
| add_binding_qdata (binding->target[i], binding); |
| } |
| } |
| |
| if (flags & G_MULTI_BINDING_SYNC_CREATE) |
| on_source_notify (binding->source[0], binding->source_pspec[0], binding); |
| |
| return binding; |
| } |