[gtk+/gtkbuilder-gbinding: 145/145] Introduce gtk_builder_create_bindings[_full](), add transformation function support



commit 173bd1618dbeff02805535791336d16d243812f0
Author: Denis Washington <denisw online de>
Date:   Sat Jul 23 10:56:22 2011 +0200

    Introduce gtk_builder_create_bindings[_full](), add transformation function support
    
    Property bindings are not created implicitly by gtk_builder_add*()
    anymore but by explicitly calling gtk_builder_create_bindings() or
    gtk_builder_create_bindings_full(), which are analog to the
    gtk_builder_signals*() functions. This change is needed for the
    new support for transformation functions, which need to be located
    like signal handlers (via GModule or a custom function). A transormation
    function can be specified by adding a "transform-func" attribute to
    a <binding> tag with the function name.
    
    Also, updated the GtkBuilder documentation to reflect the changes
    and adjusted the test case (there is also a new test for transformation
    functions).

 gtk/gtkbuilder.c        |  185 ++++++++++++++++++++++++++++++++++++++---------
 gtk/gtkbuilder.h        |   15 ++++
 gtk/gtkbuilderparser.c  |   17 +++++
 gtk/gtkbuilderprivate.h |    7 ++-
 gtk/tests/builder.c     |   69 ++++++++++++++++++
 5 files changed, 258 insertions(+), 35 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index ae5ff26..77eebbb 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -93,7 +93,8 @@
  *                      internal-child 	    #IMPLIED >
  * <!ATTLIST binding    to                  #REQUIRED
  *                      from                #REQUIRED
- *                      source              #REQUIRED >
+ *                      source              #REQUIRED
+ *                      transform-func      #IMPLIED>
  * ]]></programlisting>
  * <para>
  * The toplevel element is &lt;interface&gt;. It optionally takes a "domain"
@@ -160,16 +161,22 @@
  * construct-only property.
  *
  * It is also possible to define the value of a property by binding it to
- * another property with the &lt;binding&gt; element. This causes the value
+ * another property with a &lt;binding&gt; element. This causes the value
  * of the property specified with the "to" attribute to be whatever the
  * property "from" of object "source" is set to, even if that property
- * is later set to another value. See the documentation of GLib's GBinding
- * documentation for more details about the property binding mechanism.
+ * is later set to another value. If present, "transform-func" specifies a
+ * transformation function that converts the source property value before
+ * it is passed to the target property. GTK+'s default method for finding
+ * such a function is using g_module_symbol(); to overridde this behavior,
+ * a custom #GtkBuilderBindingFunc can be passed to
+ * gtk_builder_create_bindings_full(). For more information about the
+ * property binding mechanism, see #GBinding (which GTK+ uses internally).
  *
  * Signal handlers are set up with the &lt;signal&gt; element. The "name"
  * attribute specifies the name of the signal, and the "handler" attribute
- * specifies the function to connect to the signal. By default, GTK+ tries to
- * find the handler using g_module_symbol(), but this can be changed by passing
+ * specifies the function to connect to the signal. Like in the case of
+ * transformation functions, GTK+ tries to find the handler using
+ * g_module_symbol() by default, but this can be changed by passing
  * a custom #GtkBuilderConnectFunc to gtk_builder_connect_signals_full(). The
  * remaining attributes, "after", "swapped" and "object", have the same meaning
  * as the corresponding parameters of the g_signal_connect_object() or
@@ -879,37 +886,10 @@ gtk_builder_apply_delayed_properties (GtkBuilder *builder)
   g_slist_free (props);
 }
 
-static void
-gtk_builder_create_bindings (GtkBuilder *builder)
-{
-  GSList *l;
-
-  for (l = builder->priv->bindings; l; l = l->next)
-    {
-      BindingInfo *binding = (BindingInfo*)l->data;
-      GObject *target, *source;
-
-      target = gtk_builder_lookup_object (builder, binding->object_name);
-      g_assert (target != NULL);
-
-      source = gtk_builder_lookup_object (builder, binding->source);
-      if (source)
-	{
-	  g_object_bind_property (source, binding->from,
-				  target, binding->to,
-				  G_BINDING_SYNC_CREATE);
-	}
-    }
-
-  g_slist_free (builder->priv->bindings);
-  builder->priv->bindings = NULL;
-}
-
 void
 _gtk_builder_finish (GtkBuilder *builder)
 {
   gtk_builder_apply_delayed_properties (builder);
-  gtk_builder_create_bindings (builder);
 }
 
 /**
@@ -1413,6 +1393,145 @@ gtk_builder_connect_signals_full (GtkBuilder            *builder,
   builder->priv->signals = NULL;
 }
 
+static void
+gtk_builder_create_bindings_default (GtkBuilder    *builder,
+                                     GObject       *source,
+                                     const gchar   *source_property,
+                                     GObject       *target,
+                                     const gchar   *target_property,
+                                     const gchar   *transform_func,
+                                     GBindingFlags  flags,
+                                     gpointer       user_data)
+{
+  GBindingTransformFunc func = NULL;
+  connect_args *args = (connect_args*)user_data;
+  
+  if (transform_func &&
+      !g_module_symbol (args->module, transform_func, (gpointer)&func))
+    {
+      g_warning ("Could not find binding transformation function '%s'",
+                 transform_func);
+      return;
+    }
+
+  g_object_bind_property_full (source, source_property,
+                               target, target_property,
+                               flags | G_BINDING_SYNC_CREATE,
+                               func, NULL,
+                               args->data, NULL);
+}
+
+/**
+ * gtk_builder_create_bindings:
+ * @builder: a #GtkBuilder
+ * @user_data: a pointer to a structure sent in as user data to all signals
+ *
+ * This function establishes all property bindings defined in the interface
+ * description. It uses g_object_bind_property_full() with the
+ * #G_BINDING_SYNC_CREATE flag flag for this purpose. Like
+ * gtk_builder_connect_signals(), #GModule is used to match any
+ * transformation function names given in the interface description with
+ * symbols exported through the application's symbol table.  To overide
+ * this behavior with a different binding creation method, use
+ * gtk_builder_create_bindings_full() with a custom #GtkBuilderBindingFunc
+ * instead.
+ * 
+ * Note that this function requires #GModule to be supported on the platform
+ * to work. It can only be called once, subsequent calls will do nothing.
+ *
+ * When compiling applications for Windows, you must declare transformation
+ * functions with #G_MODULE_EXPORT, or they will not be put in the symbol
+ * table. On Linux and Unices, this is not necessary; applications should
+ * instead be compiled with the -Wl,--export-dynamic CFLAGS, and linked
+ * against gmodule-export-2.0.
+ */
+void
+gtk_builder_create_bindings (GtkBuilder *builder,
+                             gpointer    user_data)
+{
+  /* Reuse connect_args from gtk_builder_connect_signals */
+  connect_args *args;
+  
+  g_return_if_fail (GTK_IS_BUILDER (builder));
+  
+  if (!g_module_supported ())
+    g_error ("gtk_builder_create_bindings() requires working GModule");
+
+  args = g_slice_new0 (connect_args);
+  args->module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+  args->data = user_data;
+  
+  gtk_builder_create_bindings_full (builder,
+                                    gtk_builder_create_bindings_default,
+                                    args);
+  g_module_close (args->module);
+
+  g_slice_free (connect_args, args);
+}
+
+/**
+ * GtkBuilderBindingFunc:
+ * @builder: a #GtkBuilder
+ * @source: the binding source object
+ * @source_property: the binding source property
+ * @target: the binding target object
+ * @target_property: the binding target property
+ * @flags: #GBindingFlags to use
+ * @user_data: user data
+ *
+ * The signature of a function used to create property bindings, used by the
+ * gtk_builder_create_bindings() and gtk_builder_create_bindings_full()
+ * methods.  Like #GtkBuilderConnectFunc, its main use case is bindings for
+ * interpreted programming languages.
+ *
+ * Since: ?
+ */
+
+/**
+ * gtk_builder_create_bindings_full:
+ * @builder: a #GtkBuilder
+ * @func: (scope call): the function used to create the bindings
+ * @user_data: arbitrary data that will be passed to the binding function
+ *
+ * Calls @func for every property binding defined in the interface description
+ * in order to create it. Like gtk_builder_connect_signals_full(), this
+ * function is mainly thought as a version of gtk_builder_create_bindings()
+ * suitable for interpreted language bindings, but has other uses too.
+ */
+void
+gtk_builder_create_bindings_full (GtkBuilder            *builder,
+                                  GtkBuilderBindingFunc  func,
+                                  gpointer               user_data)
+{
+  GSList *l;
+
+  builder->priv->bindings = g_slist_reverse (builder->priv->bindings);
+  for (l = builder->priv->bindings; l; l = l->next)
+    {
+      BindingInfo *binding = (BindingInfo*)l->data;
+      GObject *target, *source;
+
+      target = gtk_builder_lookup_object (builder, binding->object_name);
+      g_assert (target != NULL);
+
+      source = gtk_builder_lookup_object (builder, binding->source);
+      if (!source)
+        {
+          g_warning ("Could not lookup source object %s for binding to "
+                     "property %s of object %s",
+                     binding->source, binding->to, binding->object_name);
+          continue;
+        }
+      
+        func (builder, source, binding->from, target, binding->to,
+              binding->transform_func, 0, user_data);
+    }
+
+  g_slist_foreach (builder->priv->bindings, (GFunc)_free_binding_info, NULL);
+  g_slist_free (builder->priv->bindings);
+  builder->priv->bindings = NULL;
+}
+
 /**
  * gtk_builder_value_from_string:
  * @builder: a #GtkBuilder
diff --git a/gtk/gtkbuilder.h b/gtk/gtkbuilder.h
index cdbd06f..a39cbc6 100644
--- a/gtk/gtkbuilder.h
+++ b/gtk/gtkbuilder.h
@@ -113,6 +113,15 @@ typedef void (*GtkBuilderConnectFunc) (GtkBuilder    *builder,
 				       GConnectFlags  flags,
 				       gpointer       user_data);
 
+typedef void (*GtkBuilderBindingFunc) (GtkBuilder    *builder,
+				       GObject       *source,
+				       const gchar   *source_property,
+				       GObject       *target,
+				       const gchar   *target_property,
+                                       const gchar   *transform_func,
+				       GBindingFlags  flags,
+				       gpointer       user_data);
+
 GType        gtk_builder_get_type                (void) G_GNUC_CONST;
 GtkBuilder*  gtk_builder_new                     (void);
 
@@ -140,6 +149,12 @@ void         gtk_builder_connect_signals         (GtkBuilder    *builder,
 void         gtk_builder_connect_signals_full    (GtkBuilder    *builder,
                                                   GtkBuilderConnectFunc func,
 						  gpointer       user_data);
+void         gtk_builder_create_bindings         (GtkBuilder    *builder,
+						  gpointer       user_data);
+void         gtk_builder_create_bindings_full    (GtkBuilder    *builder,
+						  GtkBuilderBindingFunc func,
+						  gpointer       user_data);
+
 void         gtk_builder_set_translation_domain  (GtkBuilder   	*builder,
                                                   const gchar  	*domain);
 const gchar* gtk_builder_get_translation_domain  (GtkBuilder   	*builder);
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index aefff55..8017818 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -544,6 +544,7 @@ parse_binding (ParserData   *data,
   const gchar *to = NULL;
   const gchar *from = NULL;
   const gchar *source = NULL;
+  const gchar *transform_func = NULL;
   ObjectInfo *object_info;
   int i;
   
@@ -562,6 +563,8 @@ parse_binding (ParserData   *data,
 	from = values[i];
       else if (strcmp (names[i], "source") == 0)
 	source = values[i];
+      else if (strcmp (names[i], "transform-func") == 0)
+	transform_func = values[i];
       else
 	{
 	  error_invalid_attribute (data, element_name, names[i], error);
@@ -589,6 +592,8 @@ parse_binding (ParserData   *data,
   info->to = g_strdup (to);
   info->from = g_strdup (from);
   info->source = g_strdup (source);
+  if (transform_func)
+    info->transform_func = g_strdup (transform_func);
   state_push (data, info);
 
   info->tag.name = element_name;
@@ -696,6 +701,18 @@ _free_signal_info (SignalInfo *info,
 }
 
 void
+_free_binding_info (BindingInfo *info,
+                    gpointer user_data)
+{
+  g_free (info->object_name);
+  g_free (info->to);
+  g_free (info->from);
+  g_free (info->source);
+  g_free (info->transform_func);
+  g_slice_free (BindingInfo, info);
+}
+
+void
 _free_requires_info (RequiresInfo *info,
 		     gpointer user_data)
 {
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index cb75c94..931b811 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -68,6 +68,7 @@ typedef struct {
   gchar *to;
   gchar *from;
   gchar *source;
+  gchar *transform_func;
 } BindingInfo;
 
 typedef struct {
@@ -133,8 +134,10 @@ void      _gtk_builder_add_signals (GtkBuilder *builder,
 void      _gtk_builder_add_bindings (GtkBuilder  *builder,
 				     GSList      *bindings);
 void      _gtk_builder_finish (GtkBuilder *builder);
-void _free_signal_info (SignalInfo *info,
-                        gpointer user_data);
+void _free_signal_info  (SignalInfo *info,
+                         gpointer user_data);
+void _free_binding_info (BindingInfo *info,
+                         gpointer user_data);
 
 /* Internal API which might be made public at some point */
 gboolean _gtk_builder_boolean_from_string (const gchar  *string,
diff --git a/gtk/tests/builder.c b/gtk/tests/builder.c
index 2fde26c..80bc917 100644
--- a/gtk/tests/builder.c
+++ b/gtk/tests/builder.c
@@ -2601,6 +2601,7 @@ test_property_bindings (void)
   GObject *checkbutton, *button, *window;
   
   builder = builder_new_from_string (buffer, -1, NULL);
+  gtk_builder_create_bindings (builder, NULL);
   
   checkbutton = gtk_builder_get_object (builder, "checkbutton");
   g_assert (checkbutton != NULL);
@@ -2620,6 +2621,73 @@ test_property_bindings (void)
   g_object_unref (builder);
 }
 
+gboolean
+reverse_func (GBinding *binding,
+              const GValue *source_value,
+              GValue *target_value,
+              gpointer user_data)
+{
+  gchar *tmp;
+
+  g_assert_cmpstr ((const gchar *)user_data, ==, "user_data");
+  
+  tmp = g_strdup (g_value_get_string (source_value));
+  g_strreverse (tmp);
+  g_value_set_string (target_value, tmp);
+  g_free (tmp);
+
+  return TRUE;
+}
+
+static void
+test_transform_funcs (void)
+{
+  const gchar *buffer =
+    "<interface>"
+    "  <object class=\"GtkWindow\" id=\"window\">"
+    "    <child>"
+    "      <object class=\"GtkVBox\" id=\"vbox\">"
+    "        <property name=\"visible\">True</property>"
+    "        <property name=\"orientation\">vertical</property>"
+    "        <child>"
+    "          <object class=\"GtkEntry\" id=\"entry\">"
+    "            <property name=\"text\">GTK+</property>"
+    "          </object>"
+    "        </child>"
+    "        <child>"
+    "          <object class=\"GtkButton\" id=\"button\">"
+    "            <binding to=\"label\" from=\"text\" source=\"entry\" transform-func=\"reverse_func\"/>"
+    "          </object>"
+    "        </child>"
+    "      </object>"
+    "    </child>"
+    "  </object>"
+    "</interface>";
+
+  GtkBuilder *builder;
+  GObject *entry, *button, *window;
+  
+  builder = builder_new_from_string (buffer, -1, NULL);
+  gtk_builder_create_bindings (builder, "user_data");
+  
+  entry = gtk_builder_get_object (builder, "entry");
+  g_assert (entry != NULL);
+  g_assert (GTK_IS_ENTRY (entry));
+  g_assert (strcmp (gtk_entry_get_text (GTK_ENTRY (entry)), "GTK+") == 0);
+
+  button = gtk_builder_get_object (builder, "button");
+  g_assert (button != NULL);
+  g_assert (GTK_IS_BUTTON (button));
+  g_assert (strcmp (gtk_button_get_label (GTK_BUTTON (button)), "+KTG") == 0);
+
+  gtk_entry_set_text (GTK_ENTRY (entry), "rocks");
+  g_assert (strcmp (gtk_button_get_label (GTK_BUTTON (button)), "skcor") == 0);
+  
+  window = gtk_builder_get_object (builder, "window");
+  gtk_widget_destroy (GTK_WIDGET (window));
+  g_object_unref (builder);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -2667,6 +2735,7 @@ main (int argc, char **argv)
   g_test_add_func ("/Builder/MessageArea", test_message_area);
   g_test_add_func ("/Builder/MessageDialog", test_message_dialog);
   g_test_add_func ("/Builder/Property Bindings", test_property_bindings);
+  g_test_add_func ("/Builder/Transformation Functions", test_transform_funcs);  
 
   return g_test_run();
 }



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