[gtk+/composite-templates-new: 2/4] GtkBuilder: Add gtk_builder_extend_with_template()



commit b26d4c421df57bd6c143b5d87b9ab60485eb7fd5
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Wed Mar 20 16:33:52 2013 +0900

    GtkBuilder: Add gtk_builder_extend_with_template()
    
    This adds the definition of the <template> tag with some documentation
    on the variant of the format.
    
    gtk_builder_extend_with_template() is to be used while GtkContainer
    builds from composite templates but can be used manually. A couple
    of error codes are also added to handle a few new possible failure
    cases.

 gtk/gtk.symbols         |    1 +
 gtk/gtkbuilder.c        |  201 +++++++++++++++++++++++++++++++++++++++++++++--
 gtk/gtkbuilder.h        |   15 +++-
 gtk/gtkbuilderparser.c  |  130 ++++++++++++++++++++++++++++---
 gtk/gtkbuilderprivate.h |    5 +
 5 files changed, 335 insertions(+), 17 deletions(-)
---
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index cb8fdf1..aa85397 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -343,6 +343,7 @@ gtk_builder_connect_signals_full
 gtk_builder_error_get_type
 gtk_builder_error_quark
 gtk_builder_expose_object
+gtk_builder_extend_with_template
 gtk_builder_get_object
 gtk_builder_get_objects
 gtk_builder_get_translation_domain
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 6a7e886..74580a3 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -275,6 +275,7 @@ struct _GtkBuilderPrivate
   GSList *signals;
   gchar *filename;
   gchar *resource_prefix;
+  GType template_type;
 };
 
 G_DEFINE_TYPE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
@@ -459,8 +460,9 @@ gtk_builder_get_parameters (GtkBuilder  *builder,
                             GType        object_type,
                             const gchar *object_name,
                             GSList      *properties,
+                           GParamFlags  filter_flags,
                             GArray      **parameters,
-                            GArray      **construct_parameters)
+                            GArray      **filtered_parameters)
 {
   GSList *l;
   GParamSpec *pspec;
@@ -471,8 +473,10 @@ gtk_builder_get_parameters (GtkBuilder  *builder,
   oclass = g_type_class_ref (object_type);
   g_assert (oclass != NULL);
 
-  *parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
-  *construct_parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
+  if (parameters)
+    *parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
+  if (filtered_parameters)
+    *filtered_parameters = g_array_new (FALSE, FALSE, sizeof (GParameter));
 
   for (l = properties; l; l = l->next)
     {
@@ -530,10 +534,16 @@ gtk_builder_get_parameters (GtkBuilder  *builder,
           continue;
         }
 
-      if (pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY))
-        g_array_append_val (*construct_parameters, parameter);
+      if (pspec->flags & filter_flags)
+       {
+         if (filtered_parameters)
+           g_array_append_val (*filtered_parameters, parameter);
+       }
       else
-        g_array_append_val (*parameters, parameter);
+       {
+         if (parameters)
+           g_array_append_val (*parameters, parameter);
+       }
     }
 
   g_type_class_unref (oclass);
@@ -619,10 +629,22 @@ _gtk_builder_construct (GtkBuilder *builder,
                   info->class_name);
       return NULL;
     }
+  else if (builder->priv->template_type != 0 &&
+          g_type_is_a (object_type, builder->priv->template_type))
+    {
+      g_set_error (error,
+                  GTK_BUILDER_ERROR,
+                  GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED,
+                  "Refused to build object of type `%s' because it "
+                  "conforms to the template type `%s', avoiding infinite recursion.",
+                  info->class_name, g_type_name (builder->priv->template_type));
+      return NULL;
+    }
 
   gtk_builder_get_parameters (builder, object_type,
                               info->id,
                               info->properties,
+                             (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY),
                               &parameters,
                               &construct_parameters);
 
@@ -735,6 +757,61 @@ _gtk_builder_construct (GtkBuilder *builder,
 }
 
 void
+_gtk_builder_apply_properties (GtkBuilder *builder,
+                              ObjectInfo *info,
+                              GError **error)
+{
+  GArray *parameters;
+  GType object_type;
+  GtkBuildableIface *iface;
+  GtkBuildable *buildable;
+  gboolean custom_set_property;
+  gint i;
+
+  g_assert (info->object != NULL);
+  g_assert (info->class_name != NULL);
+  object_type = gtk_builder_get_type_from_name (builder, info->class_name);
+
+  /* Fetch all properties that are not construct-only */
+  gtk_builder_get_parameters (builder, object_type,
+                              info->id,
+                              info->properties,
+                             G_PARAM_CONSTRUCT_ONLY,
+                              &parameters, NULL);
+
+  custom_set_property = FALSE;
+  buildable = NULL;
+  iface = NULL;
+  if (GTK_IS_BUILDABLE (info->object))
+    {
+      buildable = GTK_BUILDABLE (info->object);
+      iface = GTK_BUILDABLE_GET_IFACE (info->object);
+      if (iface->set_buildable_property)
+        custom_set_property = TRUE;
+    }
+
+  for (i = 0; i < parameters->len; i++)
+    {
+      GParameter *param = &g_array_index (parameters, GParameter, i);
+      if (custom_set_property)
+        iface->set_buildable_property (buildable, builder, param->name, &param->value);
+      else
+        g_object_set_property (info->object, param->name, &param->value);
+
+#if G_ENABLE_DEBUG
+      if (gtk_get_debug_flags () & GTK_DEBUG_BUILDER)
+        {
+          gchar *str = g_strdup_value_contents ((const GValue*)&param->value);
+          g_print ("set %s: %s = %s\n", info->id, param->name, str);
+          g_free (str);
+        }
+#endif      
+      g_value_unset (&param->value);
+    }
+  g_array_free (parameters, TRUE);
+}
+
+void
 _gtk_builder_add (GtkBuilder *builder,
                   ChildInfo  *child_info)
 {
@@ -988,6 +1065,112 @@ gtk_builder_add_objects_from_file (GtkBuilder   *builder,
 }
 
 /**
+ * gtk_builder_extend_with_template:
+ * @builder: a #GtkBuilder
+ * @widget: the #GtkWidget to extend with the provided template XML
+ * @template_type: The #GType for which @widget is being extended.
+ * @buffer: the string to parse
+ * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
+ * @error: (allow-none): return location for an error, or %NULL
+ *
+ * Extends @widget with the provided template interface definition XML.
+ *
+ * Normally users don't need to call this function directly, but instead
+ * should use the automated facilities for defining composite container widgets
+ * such as gtk_container_class_set_template_from_resource() and friends.
+ *
+ * Unlike regular interface descriptions, this method will expect a
+ * &lt;template&gt; tag as a direct child of the toplevel &lt;interface&gt;
+ * tag. The &lt;template&gt; tag must specify the "class" attribute which
+ * must be the type name of @template_type. Optionally, the "parent" attribute
+ * may be specified to specify the direct parent type of @template_type, this
+ * is ignored by the builder but necessary for Glade to introspect what kind
+ * of properties and internal children exist for a given type when the actual
+ * type does not exist.
+ *
+ * The XML which is contained inside the &lt;template&gt; tag behaves as if
+ * it were added to the &lt;object&gt; tag defining @widget itself. You may set
+ * properties on @widget by inserting &lt;property&gt; tags into the &lt;template&gt; 
+ * tag, and also add &lt;child&gt; tags to add children and extend @widget in the
+ * normal way you would with &lt;object&gt; tags.
+ *
+ * Additionally, &lt;object&gt; tags can also be added before and
+ * after the initial &lt;template&gt; tag in the normal way, allowing
+ * one to define auxilary objects which might be referenced by other
+ * widgets declared as children of the &lt;template&gt; tag.
+ *
+ * <example>
+ * <title>A GtkBuilder Template Definition</title>
+ * <programlisting><![CDATA[
+ * <interface>
+ *   <template class="FooWidget" parent="GtkBox">
+ *     <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+ *     <property name="spacing">4</property>
+ *     <child>
+ *       <object class="GtkButton" id="hello_button">
+ *         <property name="label">Hello World</property>
+ *       </object>
+ *     </child>
+ *     <child>
+ *       <object class="GtkButton" id="goodbye_button">
+ *         <property name="label">Goodbye World</property>
+ *       </object>
+ *     </child>
+ *   </template>
+ * </interface>
+ * ]]></programlisting>
+ * </example>
+ *
+ * Upon errors 0 will be returned and @error will be assigned a
+ * #GError from the #GTK_BUILDER_ERROR or #G_MARKUP_ERROR domain.
+ *
+ * Returns: A positive value on success, 0 if an error occurred
+ *
+ * Since: 3.10
+ */
+guint
+gtk_builder_extend_with_template (GtkBuilder    *builder,
+                                 GtkWidget     *widget,
+                                 GType          template_type,
+                                 const gchar   *buffer,
+                                 gsize          length,
+                                 GError       **error)
+{
+  GError *tmp_error;
+
+  g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+  g_return_val_if_fail (g_type_name (template_type) != NULL, 0);
+  g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (widget), template_type), 0);
+  g_return_val_if_fail (buffer && buffer[0], 0);
+
+  tmp_error = NULL;
+
+  g_free (builder->priv->filename);
+  g_free (builder->priv->resource_prefix);
+  builder->priv->filename = g_strdup (".");
+  builder->priv->resource_prefix = NULL;
+  builder->priv->template_type = template_type;
+
+  gtk_builder_expose_object (builder, g_type_name (template_type), G_OBJECT (widget));
+  _gtk_builder_parser_parse_buffer (builder, "<input>",
+                                    buffer, length,
+                                    NULL,
+                                    &tmp_error);
+
+  /* In the wild case that this builder might be reused after */
+  builder->priv->template_type = 0;
+
+  if (tmp_error != NULL)
+    {
+      g_propagate_error (error, tmp_error);
+      return 0;
+    }
+
+  return 1;
+}
+
+/**
  * gtk_builder_add_from_resource:
  * @builder: a #GtkBuilder
  * @resource_path: the path of the resource file to parse
@@ -2147,6 +2330,12 @@ _gtk_builder_get_absolute_filename (GtkBuilder *builder, const gchar *string)
   return filename;
 }
 
+GType
+_gtk_builder_get_template_type (GtkBuilder *builder)
+{
+  return builder->priv->template_type;
+}
+
 /**
  * gtk_builder_add_callback_symbol:
  * @builder: a #GtkBuilder
diff --git a/gtk/gtkbuilder.h b/gtk/gtkbuilder.h
index 5b22eb1..af4a3d1 100644
--- a/gtk/gtkbuilder.h
+++ b/gtk/gtkbuilder.h
@@ -59,6 +59,10 @@ typedef struct _GtkBuilderPrivate GtkBuilderPrivate;
  * @GTK_BUILDER_ERROR_VERSION_MISMATCH: The input file requires a newer version
  *  of GTK+.
  * @GTK_BUILDER_ERROR_DUPLICATE_ID: An object id occurred twice.
+ * @GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED: An object type is of the same type or
+ *  derived from the type of the template type given to gtk_builder_extend_with_template().
+ *  This is refused in order to avoid infinite recursion.
+ * @GTK_BUILDER_ERROR_TEMPLATE_MISMATCH: The wrong template was used with gtk_builder_extend_with_template()
  *
  * Error codes that identify various errors that can occur while using
  * #GtkBuilder.
@@ -73,7 +77,9 @@ typedef enum
   GTK_BUILDER_ERROR_MISSING_PROPERTY_VALUE,
   GTK_BUILDER_ERROR_INVALID_VALUE,
   GTK_BUILDER_ERROR_VERSION_MISMATCH,
-  GTK_BUILDER_ERROR_DUPLICATE_ID
+  GTK_BUILDER_ERROR_DUPLICATE_ID,
+  GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED,
+  GTK_BUILDER_ERROR_TEMPLATE_MISMATCH
 } GtkBuilderError;
 
 GQuark gtk_builder_error_quark (void);
@@ -138,6 +144,13 @@ guint        gtk_builder_add_objects_from_string (GtkBuilder    *builder,
                                                   gsize          length,
                                                   gchar        **object_ids,
                                                   GError       **error);
+GDK_AVAILABLE_IN_3_10
+guint        gtk_builder_extend_with_template    (GtkBuilder    *builder,
+                                                 GtkWidget     *widget,
+                                                 GType          template_type,
+                                                 const gchar   *buffer,
+                                                 gsize          length,
+                                                 GError       **error);
 GObject*     gtk_builder_get_object              (GtkBuilder    *builder,
                                                   const gchar   *name);
 GSList*      gtk_builder_get_objects             (GtkBuilder    *builder);
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index 6344258..3e52c64 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -187,14 +187,27 @@ builder_construct (ParserData  *data,
 
   g_assert (object_info != NULL);
 
-  if (object_info->object)
+  if (object_info->object && object_info->applied_properties)
     return object_info->object;
 
   object_info->properties = g_slist_reverse (object_info->properties);
 
-  object = _gtk_builder_construct (data->builder, object_info, error);
-  if (!object)
-    return NULL;
+  if (object_info->object == NULL)
+    {
+      object = _gtk_builder_construct (data->builder, object_info, error);
+      if (!object)
+       return NULL;
+    }
+  else
+    {
+      /* We're building a template, the object is already set and
+       * we just want to resolve the properties at the right time
+       */
+      object = object_info->object;
+      _gtk_builder_apply_properties (data->builder, object_info, error);
+    }
+
+  object_info->applied_properties = TRUE;
 
   g_assert (G_IS_OBJECT (object));
 
@@ -412,6 +425,92 @@ parse_object (GMarkupParseContext  *context,
 }
 
 static void
+parse_template (GMarkupParseContext  *context,
+               ParserData           *data,
+               const gchar          *element_name,
+               const gchar         **names,
+               const gchar         **values,
+               GError              **error)
+{
+  ObjectInfo *object_info;
+  int i;
+  gchar *object_class = NULL;
+  gint line, line2;
+  GType template_type = _gtk_builder_get_template_type (data->builder);
+  GType parsed_type;
+
+  if (template_type == 0)
+    {
+      g_set_error (error,
+                  GTK_BUILDER_ERROR,
+                  GTK_BUILDER_ERROR_UNHANDLED_TAG,
+                  "Encountered template definition but not parsing a template.");
+      return;
+    }
+  else if (state_peek (data) != NULL)
+    {
+      g_set_error (error,
+                  GTK_BUILDER_ERROR,
+                  GTK_BUILDER_ERROR_UNHANDLED_TAG,
+                  "Encountered template definition that is not at the top level.");
+      return;
+    }
+
+  for (i = 0; names[i] != NULL; i++)
+    {
+      if (strcmp (names[i], "class") == 0)
+        object_class = g_strdup (values[i]);
+      else if (strcmp (names[i], "parent") == 0)
+        /* Ignore 'parent' attribute, however it's needed by Glade */;
+      else
+       {
+         error_invalid_attribute (data, element_name, names[i], error);
+         return;
+       }
+    }
+
+  if (!object_class)
+    {
+      error_missing_attribute (data, element_name, "class", error);
+      return;
+    }
+
+  parsed_type = g_type_from_name (object_class);
+  if (template_type != parsed_type)
+    {
+      g_set_error (error,
+                  GTK_BUILDER_ERROR,
+                  GTK_BUILDER_ERROR_TEMPLATE_MISMATCH,
+                  "Parsed template definition for type `%s', expected type `%s'.",
+                  object_class, g_type_name (template_type));
+      return;
+    }
+
+  ++data->cur_object_level;
+
+  object_info = g_slice_new0 (ObjectInfo);
+  object_info->class_name = object_class;
+  object_info->id = g_strdup (object_class);
+  object_info->object = gtk_builder_get_object (data->builder, object_class);
+  state_push (data, object_info);
+  object_info->tag.name = element_name;
+
+  g_markup_parse_context_get_position (context, &line, NULL);
+  line2 = GPOINTER_TO_INT (g_hash_table_lookup (data->object_ids, object_class));
+  if (line2 != 0)
+    {
+      g_set_error (error, GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_DUPLICATE_ID,
+                   _("Duplicate object ID '%s' on line %d (previously on line %d)"),
+                   object_class, line, line2);
+      return;
+    }
+
+  g_hash_table_insert (data->object_ids, g_strdup (object_class), GINT_TO_POINTER (line));
+}
+
+
+static void
 free_object_info (ObjectInfo *info)
 {
   /* Do not free the signal items, which GtkBuilder takes ownership of */
@@ -446,7 +545,9 @@ parse_child (ParserData   *data,
   guint i;
 
   object_info = state_peek_info (data, ObjectInfo);
-  if (!object_info || strcmp (object_info->tag.name, "object") != 0)
+  if (!object_info ||
+      !(strcmp (object_info->tag.name, "object") == 0 ||
+       strcmp (object_info->tag.name, "template") == 0))
     {
       error_invalid_tag (data, element_name, NULL, error);
       return;
@@ -493,7 +594,9 @@ parse_property (ParserData   *data,
   int i;
 
   object_info = state_peek_info (data, ObjectInfo);
-  if (!object_info || strcmp (object_info->tag.name, "object") != 0)
+  if (!object_info || 
+      !(strcmp (object_info->tag.name, "object") == 0 ||
+       strcmp (object_info->tag.name, "template") == 0))
     {
       error_invalid_tag (data, element_name, NULL, error);
       return;
@@ -566,7 +669,9 @@ parse_signal (ParserData   *data,
   int i;
 
   object_info = state_peek_info (data, ObjectInfo);
-  if (!object_info || strcmp (object_info->tag.name, "object") != 0)
+  if (!object_info ||
+      !(strcmp (object_info->tag.name, "object") == 0 ||
+       strcmp (object_info->tag.name, "template") == 0))
     {
       error_invalid_tag (data, element_name, NULL, error);
       return;
@@ -877,6 +982,8 @@ start_element (GMarkupParseContext *context,
     parse_requires (data, element_name, names, values, error);
   else if (strcmp (element_name, "object") == 0)
     parse_object (context, data, element_name, names, values, error);
+  else if (strcmp (element_name, "template") == 0)
+    parse_template (context, data, element_name, names, values, error);
   else if (data->requested_objects && !data->inside_requested_object)
     {
       /* If outside a requested object, simply ignore this tag */
@@ -972,7 +1079,8 @@ end_element (GMarkupParseContext *context,
     {
       _gtk_builder_menu_end (data);
     }
-  else if (strcmp (element_name, "object") == 0)
+  else if (strcmp (element_name, "object") == 0 ||
+          strcmp (element_name, "template") == 0)
     {
       ObjectInfo *object_info = state_pop_info (data, ObjectInfo);
       ChildInfo* child_info = state_peek_info (data, ChildInfo);
@@ -1012,7 +1120,8 @@ end_element (GMarkupParseContext *context,
       CommonInfo *info = state_peek_info (data, CommonInfo);
 
       /* Normal properties */
-      if (strcmp (info->tag.name, "object") == 0)
+      if (strcmp (info->tag.name, "object") == 0 ||
+         strcmp (info->tag.name, "template") == 0)
         {
           ObjectInfo *object_info = (ObjectInfo*)info;
 
@@ -1101,7 +1210,8 @@ text (GMarkupParseContext *context,
 static void
 free_info (CommonInfo *info)
 {
-  if (strcmp (info->tag.name, "object") == 0)
+  if (strcmp (info->tag.name, "object") == 0 ||
+      strcmp (info->tag.name, "template") == 0)
     free_object_info ((ObjectInfo *)info);
   else if (strcmp (info->tag.name, "child") == 0)
     free_child_info ((ChildInfo *)info);
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index 3667ed5..9c70636 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -38,6 +38,7 @@ typedef struct {
   GSList *signals;
   GObject *object;
   CommonInfo *parent;
+  gboolean applied_properties;
 } ObjectInfo;
 
 typedef struct {
@@ -121,6 +122,9 @@ void _gtk_builder_parser_parse_buffer (GtkBuilder *builder,
 GObject * _gtk_builder_construct (GtkBuilder *builder,
                                   ObjectInfo *info,
                                  GError    **error);
+void      _gtk_builder_apply_properties (GtkBuilder *builder,
+                                        ObjectInfo *info,
+                                        GError **error);
 void      _gtk_builder_add_object (GtkBuilder  *builder,
                                    const gchar *id,
                                    GObject     *object);
@@ -159,5 +163,6 @@ void      _gtk_builder_menu_start (ParserData   *parser_data,
                                    GError      **error);
 void      _gtk_builder_menu_end   (ParserData  *parser_data);
 
+GType     _gtk_builder_get_template_type (GtkBuilder *builder);
 
 #endif /* __GTK_BUILDER_PRIVATE_H__ */


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