[glib/wip/multibinding] Wip: provide multi-binding functionality



commit d70501d86589323e85ba81532521d852d36f29fe
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Feb 18 22:09:19 2015 -0500

    Wip: provide multi-binding functionality
    
    This lets one bind multiple source properties to multiple target
    properties, e.g. combining string properties a1 and a2 into
    "<a1> and <a2>" and setting the result on property c.

 glib/glib-object.h           |    1 +
 gobject/Makefile.am          |    2 +
 gobject/gmultibinding.c      |  447 ++++++++++++++++++++++++++++++++++++++++++
 gobject/gmultibinding.h      |   88 ++++++++
 gobject/tests/Makefile.am    |    1 +
 gobject/tests/multibinding.c |  341 ++++++++++++++++++++++++++++++++
 6 files changed, 880 insertions(+), 0 deletions(-)
---
diff --git a/glib/glib-object.h b/glib/glib-object.h
index 6ad523e..3e2aa8d 100644
--- a/glib/glib-object.h
+++ b/glib/glib-object.h
@@ -23,6 +23,7 @@
 #include <gobject/gbinding.h>
 #include <gobject/gboxed.h>
 #include <gobject/genums.h>
+#include <gobject/gmultibinding.h>
 #include <gobject/gobject.h>
 #include <gobject/gparam.h>
 #include <gobject/gparamspecs.h>
diff --git a/gobject/Makefile.am b/gobject/Makefile.am
index 72ad2cd..7aca492 100644
--- a/gobject/Makefile.am
+++ b/gobject/Makefile.am
@@ -64,6 +64,7 @@ gobject_public_h_sources = \
        gobject-autocleanups.h  \
        glib-types.h            \
        gbinding.h              \
+       gmultibinding.h         \
        gboxed.h                \
        gclosure.h              \
        genums.h                \
@@ -92,6 +93,7 @@ gobject_c_sources = \
        gobject_probes.d        \
        gatomicarray.c          \
        gbinding.c              \
+       gmultibinding.c         \
        gboxed.c                \
        gclosure.c              \
        genums.c                \
diff --git a/gobject/gmultibinding.c b/gobject/gmultibinding.c
new file mode 100644
index 0000000..9399f1b
--- /dev/null
+++ b/gobject/gmultibinding.c
@@ -0,0 +1,447 @@
+#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;
+
+  if (binding->is_frozen)
+    return;
+
+  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]);
+    }
+
+  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, (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[],
+                                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);
+        }
+    }
+
+  return binding;
+}
diff --git a/gobject/gmultibinding.h b/gobject/gmultibinding.h
new file mode 100644
index 0000000..36c1430
--- /dev/null
+++ b/gobject/gmultibinding.h
@@ -0,0 +1,88 @@
+/* gmultibinding.h: Binding for object properties
+ *
+ * Copyright (C) 2015 Red Hat, Inc
+ *
+ * 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 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: Matthias Clasen
+ */
+
+#ifndef __G_MULTI_BINDING_H__
+#define __G_MULTI_BINDING_H__
+
+#if !defined (__GLIB_GOBJECT_H_INSIDE__) && !defined (GOBJECT_COMPILATION)
+#error "Only <glib-object.h> can be included directly."
+#endif
+
+#include <glib.h>
+#include <gobject/gobject.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_MULTI_BINDING          (g_multi_binding_get_type ())
+#define G_MULTI_BINDING(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_MULTI_BINDING, 
GMultiBinding))
+#define G_IS_MULTI_BINDING(obj)       (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_MULTI_BINDING))
+
+/**
+ * GMultiBinding:
+ *
+ * GMultiBinding is an opaque structure whose members
+ * cannot be accessed directly.
+ *
+ * Since: 2.44
+ */
+typedef struct _GMultiBinding   GMultiBinding;
+
+typedef gboolean (* GMultiBindingTransformFunc) (GMultiBinding  *binding,
+                                                 const GValue    from_values[],
+                                                 GValue          to_values[],
+                                                 gpointer        user_data);
+
+GLIB_AVAILABLE_IN_ALL
+GType                 g_multi_binding_get_type            (void) G_GNUC_CONST;
+
+GLIB_AVAILABLE_IN_ALL
+gint                  g_multi_binding_get_n_sources       (GMultiBinding *binding);
+GLIB_AVAILABLE_IN_ALL
+GObject *             g_multi_binding_get_source          (GMultiBinding *binding,
+                                                           gint           idx);
+GLIB_AVAILABLE_IN_ALL
+const gchar *         g_multi_binding_get_source_property (GMultiBinding *binding,
+                                                           gint           idx);
+
+GLIB_AVAILABLE_IN_ALL
+gint                  g_multi_binding_get_n_targets       (GMultiBinding *binding);
+GLIB_AVAILABLE_IN_ALL
+GObject *             g_multi_binding_get_target          (GMultiBinding *binding,
+                                                           gint           idx);
+GLIB_AVAILABLE_IN_ALL
+const gchar *         g_multi_binding_get_target_property (GMultiBinding *binding,
+                                                           gint           idx);
+
+GLIB_AVAILABLE_IN_ALL
+void                  g_multi_binding_unbind              (GMultiBinding *binding);
+
+GLIB_AVAILABLE_IN_ALL
+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[],
+                                                           GMultiBindingTransformFunc transform,
+                                                           gpointer                   user_data,
+                                                           GDestroyNotify             notify);
+
+
+#endif /* __G_MULTI_BINDING_H__ */
diff --git a/gobject/tests/Makefile.am b/gobject/tests/Makefile.am
index 2c5cc1a..43d9f97 100644
--- a/gobject/tests/Makefile.am
+++ b/gobject/tests/Makefile.am
@@ -15,6 +15,7 @@ test_programs = \
        threadtests                     \
        dynamictests                    \
        binding                         \
+       multibinding                    \
        properties                      \
        reference                       \
        valuearray                      \
diff --git a/gobject/tests/multibinding.c b/gobject/tests/multibinding.c
new file mode 100644
index 0000000..afff184
--- /dev/null
+++ b/gobject/tests/multibinding.c
@@ -0,0 +1,341 @@
+#include <stdlib.h>
+#include <gstdio.h>
+#include <glib-object.h>
+
+typedef struct _BindingSource
+{
+  GObject parent_instance;
+
+  gint foo;
+  gint bar;
+  gdouble value;
+  gboolean toggle;
+} BindingSource;
+
+typedef struct _BindingSourceClass
+{
+  GObjectClass parent_class;
+} BindingSourceClass;
+
+enum
+{
+  PROP_SOURCE_0,
+
+  PROP_SOURCE_FOO,
+  PROP_SOURCE_BAR,
+  PROP_SOURCE_VALUE,
+  PROP_SOURCE_TOGGLE
+};
+
+static GType binding_source_get_type (void);
+G_DEFINE_TYPE (BindingSource, binding_source, G_TYPE_OBJECT);
+
+static void
+binding_source_set_property (GObject      *gobject,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  BindingSource *source = (BindingSource *) gobject;
+
+  switch (prop_id)
+    {
+    case PROP_SOURCE_FOO:
+      source->foo = g_value_get_int (value);
+      break;
+
+    case PROP_SOURCE_BAR:
+      source->bar = g_value_get_int (value);
+      break;
+
+    case PROP_SOURCE_VALUE:
+      source->value = g_value_get_double (value);
+      break;
+
+    case PROP_SOURCE_TOGGLE:
+      source->toggle = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+binding_source_get_property (GObject    *gobject,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  BindingSource *source = (BindingSource *) gobject;
+
+  switch (prop_id)
+    {
+    case PROP_SOURCE_FOO:
+      g_value_set_int (value, source->foo);
+      break;
+
+    case PROP_SOURCE_BAR:
+      g_value_set_int (value, source->bar);
+      break;
+
+    case PROP_SOURCE_VALUE:
+      g_value_set_double (value, source->value);
+      break;
+
+    case PROP_SOURCE_TOGGLE:
+      g_value_set_boolean (value, source->toggle);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+binding_source_class_init (BindingSourceClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = binding_source_set_property;
+  gobject_class->get_property = binding_source_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_SOURCE_FOO,
+                                   g_param_spec_int ("foo", "Foo", "Foo",
+                                                     -1, 100,
+                                                     0,
+                                                     G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_SOURCE_BAR,
+                                   g_param_spec_int ("bar", "Bar", "Bar",
+                                                     -1, 100,
+                                                     0,
+                                                     G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_SOURCE_VALUE,
+                                   g_param_spec_double ("value", "Value", "Value",
+                                                        -100.0, 200.0,
+                                                        0.0,
+                                                        G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_SOURCE_TOGGLE,
+                                   g_param_spec_boolean ("toggle", "Toggle", "Toggle",
+                                                         FALSE,
+                                                         G_PARAM_READWRITE));
+}
+
+static void
+binding_source_init (BindingSource *self)
+{
+}
+
+typedef struct _BindingTarget
+{
+  GObject parent_instance;
+
+  gint bar;
+  gdouble value;
+  gboolean toggle;
+} BindingTarget;
+
+typedef struct _BindingTargetClass
+{
+  GObjectClass parent_class;
+} BindingTargetClass;
+
+enum
+{
+  PROP_TARGET_0,
+
+  PROP_TARGET_BAR,
+  PROP_TARGET_VALUE,
+  PROP_TARGET_TOGGLE
+};
+
+static GType binding_target_get_type (void);
+G_DEFINE_TYPE (BindingTarget, binding_target, G_TYPE_OBJECT);
+
+static void
+binding_target_set_property (GObject      *gobject,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  BindingTarget *target = (BindingTarget *) gobject;
+
+  switch (prop_id)
+    {
+    case PROP_TARGET_BAR:
+      target->bar = g_value_get_int (value);
+      break;
+
+    case PROP_TARGET_VALUE:
+      target->value = g_value_get_double (value);
+      break;
+
+    case PROP_TARGET_TOGGLE:
+      target->toggle = g_value_get_boolean (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+binding_target_get_property (GObject    *gobject,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  BindingTarget *target = (BindingTarget *) gobject;
+
+  switch (prop_id)
+    {
+    case PROP_TARGET_BAR:
+      g_value_set_int (value, target->bar);
+      break;
+
+    case PROP_TARGET_VALUE:
+      g_value_set_double (value, target->value);
+      break;
+
+    case PROP_TARGET_TOGGLE:
+      g_value_set_boolean (value, target->toggle);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+binding_target_class_init (BindingTargetClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = binding_target_set_property;
+  gobject_class->get_property = binding_target_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_TARGET_BAR,
+                                   g_param_spec_int ("bar", "Bar", "Bar",
+                                                     -1, 100,
+                                                     0,
+                                                     G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_TARGET_VALUE,
+                                   g_param_spec_double ("value", "Value", "Value",
+                                                        -100.0, 200.0,
+                                                        0.0,
+                                                        G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_TARGET_TOGGLE,
+                                   g_param_spec_boolean ("toggle", "Toggle", "Toggle",
+                                                         FALSE,
+                                                         G_PARAM_READWRITE));
+}
+
+static void
+binding_target_init (BindingTarget *self)
+{
+}
+
+static gboolean
+munge_two_ints (GMultiBinding  *binding,
+                const GValue    from_values[],
+                GValue          to_values[],
+                gpointer        user_data)
+{
+  int v0, v1;
+
+  v0 = g_value_get_int (&from_values[0]);
+  v1 = g_value_get_int (&from_values[1]);
+
+  g_value_set_int (&to_values[0], v0 + v1);
+  g_value_set_int (&to_values[1], v0 - v1);
+}
+
+static void
+multibinding_basic (void)
+{
+  BindingSource *source0 = g_object_new (binding_source_get_type (), NULL);
+  BindingSource *source1 = g_object_new (binding_source_get_type (), NULL);
+  GObject *sources[2];
+  const char *source_props[2];
+  BindingTarget *target0 = g_object_new (binding_target_get_type (), NULL);
+  BindingTarget *target1 = g_object_new (binding_target_get_type (), NULL);
+  GObject *targets[2];
+  const char *target_props[2];
+  GMultiBinding *binding;
+
+  sources[0] = G_OBJECT (source0);
+  sources[1] = G_OBJECT (source1);
+  source_props[0] = "foo";
+  source_props[1] = "bar";
+  targets[0] = G_OBJECT (target0);
+  targets[1] = G_OBJECT (target1);
+  target_props[0] = "bar";
+  target_props[1] = "bar";
+  binding = g_object_multi_bind_property_v (2, sources, source_props,
+                                            2, targets, target_props,
+                                            munge_two_ints,
+                                            NULL, NULL);
+  g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding);
+
+  g_assert_cmpint (g_multi_binding_get_n_sources (binding), ==, 2);
+  g_assert (g_multi_binding_get_source (binding, 0) == sources[0]);
+  g_assert (g_multi_binding_get_source (binding, 1) == sources[1]);
+  g_assert_cmpstr (g_multi_binding_get_source_property (binding, 0), ==, source_props[0]);
+  g_assert_cmpstr (g_multi_binding_get_source_property (binding, 1), ==, source_props[1]);
+  g_assert_cmpint (g_multi_binding_get_n_targets (binding), ==, 2);
+  g_assert (g_multi_binding_get_target (binding, 0) == targets[0]);
+  g_assert (g_multi_binding_get_target (binding, 1) == targets[1]);
+  g_assert_cmpstr (g_multi_binding_get_target_property (binding, 0), ==, target_props[0]);
+  g_assert_cmpstr (g_multi_binding_get_target_property (binding, 1), ==, target_props[1]);
+
+  g_assert_cmpint (source0->foo, ==, 0);
+  g_assert_cmpint (source1->bar, ==, 0);
+  g_assert_cmpint (target0->bar, ==, 0);
+  g_assert_cmpint (target1->bar, ==, 0);
+
+  g_object_set (source0, "foo", 1, NULL);
+
+  g_assert_cmpint (source0->foo, ==, 1);
+  g_assert_cmpint (source1->bar, ==, 0);
+  g_assert_cmpint (target0->bar, ==, source0->foo + source1->bar);
+  g_assert_cmpint (target1->bar, ==, source0->foo - source1->bar);
+
+  g_object_set (source1, "foo", 1, NULL);
+
+  g_assert_cmpint (source0->foo, ==, 1);
+  g_assert_cmpint (source1->bar, ==, 0);
+  g_assert_cmpint (target0->bar, ==, source0->foo + source1->bar);
+  g_assert_cmpint (target1->bar, ==, source0->foo - source1->bar);
+
+  g_object_set (source0, "bar", 1, NULL);
+
+  g_assert_cmpint (source0->foo, ==, 1);
+  g_assert_cmpint (source1->bar, ==, 0);
+  g_assert_cmpint (target0->bar, ==, source0->foo + source1->bar);
+  g_assert_cmpint (target1->bar, ==, source0->foo - source1->bar);
+
+  g_object_set (source1, "bar", 1, NULL);
+
+  g_assert_cmpint (source0->foo, ==, 1);
+  g_assert_cmpint (source1->bar, ==, 1);
+  g_assert_cmpint (target0->bar, ==, source0->foo + source1->bar);
+  g_assert_cmpint (target1->bar, ==, source0->foo - source1->bar);
+
+  g_object_unref (source0);
+  g_object_unref (source1);
+  g_object_unref (target0);
+  g_object_unref (target1);
+  g_assert (binding == NULL);
+}
+
+int
+main (int argc, char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_bug_base ("http://bugzilla.gnome.org/";);
+
+  g_test_add_func ("/multibinding/basic", multibinding_basic);
+
+  return g_test_run ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]