[gtk+/composite-templates: 15/15] Added inline template creation support. Now builder creates new types dinamically when needed. that



commit 32de84474119d06533136f8b08e6b02ba4b53f13
Author: Juan Pablo Ugarte <juanpablougarte gmail com>
Date:   Thu Nov 15 16:36:35 2012 -0300

    Added inline template creation support. Now builder creates new types dinamically when needed.
    that is when it finds a template element without an asociated external object.

 gtk/gtkbuilder.c        |   18 +++++
 gtk/gtkbuilderparser.c  |  175 ++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtkbuilderprivate.h |    4 +
 gtk/gtkcontainer.c      |    2 +
 gtk/tests/builder.c     |   48 +++++++++++++
 5 files changed, 246 insertions(+), 1 deletions(-)
---
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index bcd2c09..27e3962 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -274,6 +274,8 @@ struct _GtkBuilderPrivate
   GSList *signals;
   gchar *filename;
   gchar *resource_prefix;
+
+  GType ignore_type;
 };
 
 G_DEFINE_TYPE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
@@ -628,6 +630,10 @@ _gtk_builder_construct (GtkBuilder *builder,
       return NULL;
     }
 
+  /* Safeguard to avoid recursion if we are building a new type with builder */
+  if (object_type == builder->priv->ignore_type)
+    return NULL;
+
   gtk_builder_get_parameters (builder, info->object_type,
                               info->id,
                               info->properties,
@@ -2081,3 +2087,15 @@ _gtk_builder_get_absolute_filename (GtkBuilder *builder, const gchar *string)
   
   return filename;
 }
+
+void
+_gtk_builder_set_ignore_type (GtkBuilder *builder, GType ignore_type)
+{
+  builder->priv->ignore_type = ignore_type;
+}
+
+GType
+_gtk_builder_get_ignore_type (GtkBuilder *builder)
+{
+  return builder->priv->ignore_type;
+}
diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c
index 1dc7a48..6ddd210 100644
--- a/gtk/gtkbuilderparser.c
+++ b/gtk/gtkbuilderparser.c
@@ -29,7 +29,7 @@
 #include "gtkversion.h"
 #include "gtktypebuiltins.h"
 #include "gtkintl.h"
-
+#include "gtkcontainer.h"
 
 static void free_property_info (PropertyInfo *info);
 static void free_object_info (ObjectInfo *info);
@@ -216,6 +216,13 @@ builder_construct (ParserData  *data,
   if (object_info->object)
     return object_info->object;
 
+  /* Safeguard to avoid recursion if we are building a new type with builder
+   * _gtk_builder_construct() also checks for this, but there is no need to
+   * reverse the property list if we are not going to build the object.
+   */
+  if (object_info->object_type == _gtk_builder_get_ignore_type (data->builder))
+    return NULL;
+  
   object_info->properties = g_slist_reverse (object_info->properties);
 
   object = _gtk_builder_construct (data->builder, object_info, error);
@@ -674,6 +681,150 @@ parse_signal (ParserData   *data,
   info->tag.name = element_name;
 }
 
+typedef struct
+{
+  const gchar *tmpl_class, *tmpl_id;
+
+  gboolean tmpl_found, done;
+  
+  GString *xml;
+} TemplateParseData;
+
+static void
+extract_template_start_element (GMarkupParseContext  *context,
+                                const gchar          *element_name,
+                                const gchar         **attribute_names,
+                                const gchar         **attribute_values,
+                                gpointer              user_data,
+                                GError              **error)
+{
+  TemplateParseData *state = user_data;
+  gint i;
+
+  if (state->done) return;
+  
+  if (g_strcmp0 (element_name, "template") == 0)
+    {
+      gboolean right_class = FALSE, right_id = FALSE;
+
+      for (i = 0; attribute_names[i]; i++)
+        {
+          if (!g_strcmp0 (attribute_names[i], "class"))
+            right_class = (g_strcmp0 (attribute_values[i], state->tmpl_class) == 0);
+	  else if (!g_strcmp0 (attribute_names[i], "id"))
+	    right_id = (g_strcmp0 (attribute_values[i], state->tmpl_id) == 0);
+        }
+
+      if (right_class && right_id)
+        state->tmpl_found = TRUE;
+    }
+
+  g_string_append_printf (state->xml, "<%s", element_name);
+  for (i = 0; attribute_names[i]; i++)
+    {
+      g_string_append_printf (state->xml, " %s=\"%s\"", 
+                              attribute_names[i], attribute_values[i]);
+    }
+  g_string_append_printf (state->xml, ">");
+}
+
+static void
+extract_template_end_element (GMarkupParseContext *context,
+                              const gchar         *element_name,
+                              gpointer             user_data,
+                              GError             **error)
+{
+  TemplateParseData *state = user_data;
+
+  if (state->done) return;
+  
+  g_string_append_printf (state->xml, "</%s>", element_name);
+
+  if (g_strcmp0 (element_name, "interface") == 0 && state->tmpl_found)
+    state->done = TRUE;
+}
+
+static void
+extract_template_text (GMarkupParseContext *context,
+                       const gchar         *text,
+                       gsize                text_len,
+                       gpointer             user_data,
+                       GError             **error)
+{
+  TemplateParseData *state = user_data;
+
+  if (state->done) return;
+  
+  g_string_append_len (state->xml, text, text_len);
+}
+
+static gchar *
+extract_template (const gchar *buffer, const gchar *class_name, const gchar *class_id)
+{
+  GMarkupParser parser = { extract_template_start_element, extract_template_end_element, extract_template_text };
+  TemplateParseData state = { class_name, class_id, FALSE, FALSE, g_string_new ("")};
+  GMarkupParseContext *context;
+
+  context = g_markup_parse_context_new (&parser,
+                                        G_MARKUP_TREAT_CDATA_AS_TEXT |
+                                        G_MARKUP_PREFIX_ERROR_POSITION,
+                                        &state, NULL);
+
+  g_markup_parse_context_parse (context, buffer, -1, NULL);
+  g_markup_parse_context_end_parse (context, NULL);
+  g_markup_parse_context_free (context);
+
+  if (state.done)
+    {
+      gchar *retval = state.xml->str;
+      g_string_free (state.xml, FALSE);
+      g_file_set_contents ("/tmp/a.dump", retval, -1, NULL);
+      return retval; 
+    }
+
+  g_string_free (state.xml, TRUE);
+  return NULL;
+}
+
+typedef struct
+{
+  GTypeInfo *info;
+  gchar *tmpl;
+  gchar *id;
+} TmplClassData;
+
+static void
+composite_template_derived_class_init (gpointer g_class, gpointer class_data)
+{
+  TmplClassData *data = class_data;
+  gtk_container_class_set_template_from_string (g_class, data->tmpl, data->id);
+}
+
+static GType
+create_inline_type (GType parent_type,
+                    const gchar *class_name,
+                    const gchar *class_id,
+                    const gchar *template_xml)
+{
+  TmplClassData *tmpl;
+  GTypeQuery query;
+  GTypeInfo *info;
+
+  g_type_query (parent_type, &query);
+
+  tmpl = g_new0 (TmplClassData, 1);
+  tmpl->info = info = g_new0 (GTypeInfo, 1);
+  tmpl->tmpl = extract_template (template_xml, class_name, class_id);
+  tmpl->id = g_strdup (class_id);
+
+  info->class_size = query.class_size;
+  info->class_init = composite_template_derived_class_init;
+  info->class_data = tmpl; /* Let it leak! */
+  info->instance_size = query.instance_size;
+
+  return g_type_register_static (parent_type, class_name, info, 0);
+}
+
 static void
 parse_template (ParserData   *data,
                 const gchar  *element_name,
@@ -767,6 +918,27 @@ parse_template (ParserData   *data,
 
       parser_add_object_id (data, object_info->id, error);
     }
+  else if (parent_class)
+    {
+      GType parent_type;
+
+      if (g_type_from_name (class_name))
+        {
+          error_generic (error, GTK_BUILDER_ERROR_TEMPLATE_CLASS_MISMATCH, data,
+                         element_name, "template class '%s' already registered", class_name);
+          return;
+        }
+
+      if (!(parent_type = gtk_builder_get_type_from_name (data->builder, parent_class)))
+        {
+          error_generic (error, GTK_BUILDER_ERROR_TEMPLATE_CLASS_MISMATCH, data,
+                         element_name, "invalid parent class type found '%s'", parent_class);
+          return;
+        }
+
+      /* Generate inline type */
+      create_inline_type (parent_type, class_name, id, data->buffer);
+    }
 }
 
 /* Called by GtkBuilder */
@@ -1308,6 +1480,7 @@ _gtk_builder_parser_parse_buffer (GtkBuilder   *builder,
   domain = gtk_builder_get_translation_domain (builder);
 
   data = g_new0 (ParserData, 1);
+  data->buffer = buffer;
   data->builder = builder;
   data->filename = filename;
   data->domain = g_strdup (domain);
diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h
index 987c5fc..025dfdf 100644
--- a/gtk/gtkbuilderprivate.h
+++ b/gtk/gtkbuilderprivate.h
@@ -91,6 +91,7 @@ typedef struct {
 } SubParser;
 
 typedef struct {
+  const gchar *buffer;
   const gchar *last_element;
   GtkBuilder *builder;
   gchar *domain;
@@ -164,4 +165,7 @@ void      _gtk_builder_menu_end   (ParserData  *parser_data);
 
 const gchar * _gtk_builder_object_get_name (GObject *object);
 
+void  _gtk_builder_set_ignore_type (GtkBuilder *builder, GType ignore_type);
+GType _gtk_builder_get_ignore_type (GtkBuilder *builder);
+
 #endif /* __GTK_BUILDER_PRIVATE_H__ */
diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c
index 7341f19..90b800c 100644
--- a/gtk/gtkcontainer.c
+++ b/gtk/gtkcontainer.c
@@ -1682,6 +1682,8 @@ gtk_container_constructor (GType                  type,
         
       builder = gtk_builder_new ();
       gtk_builder_expose_object (builder, cpriv->tmpl_id, object);
+      /* Safeguard to avoid recursion */
+      _gtk_builder_set_ignore_type (builder, type);
 
       if (cpriv->tmpl_type == TMPL_STRING)
         ret = gtk_builder_add_from_string (builder, cpriv->tmpl, cpriv->tmpl_len, &error);
diff --git a/gtk/tests/builder.c b/gtk/tests/builder.c
index c39da5a..7a516df 100644
--- a/gtk/tests/builder.c
+++ b/gtk/tests/builder.c
@@ -2845,6 +2845,53 @@ test_template (void)
   test_template_real (TRUE);
 }
 
+static void
+test_inline_template ()
+{
+  GError *error = NULL;
+  GtkBuilder *builder;
+  const gchar buffer[] =
+    "<interface>\n"
+    "  <template class=\"MyGtkGrid\" parent=\"GtkGrid\" id=\"mygrid_tmpl\">\n"
+    "    <property name=\"visible\">True</property>\n"
+    "    <child>\n"
+    "      <object class=\"GtkLabel\" id=\"gridlabel\">\n"
+    "        <property name=\"visible\">True</property>\n"
+    "      </object>\n"
+    "   </child>\n"
+    "  </template>\n"
+    "</interface>\n"
+    "<interface>\n"
+    "  <object class=\"GtkWindow\" id=\"window\">"
+    "    <child>\n"
+    "      <object class=\"MyGtkGrid\" id=\"mygrid\">\n"
+    "        <property name=\"visible\">True</property>\n"
+    "      </object>\n"
+    "   </child>\n"
+    "  </object>\n"
+    "</interface>\n";
+
+  builder = gtk_builder_new ();
+
+  /* make sure the type we are trying to register does not exist */
+  g_assert (!g_type_from_name ("MyGtkGrid"));
+
+  gtk_builder_add_from_string (builder, buffer, -1, &error);
+
+  if (error) g_warning ("%s", error->message);
+  g_assert (error == NULL);
+
+  /* Check if new type was registered on the fly! */
+  g_assert (g_type_from_name ("MyGtkGrid"));
+
+  g_assert (GTK_IS_WINDOW (gtk_builder_get_object (builder, "window")));
+
+  /* Check if inline derived child was built */
+  g_assert (GTK_IS_GRID (gtk_builder_get_object (builder, "mygrid")));
+
+  g_object_unref (builder);
+}
+
 static GObject *external_object = NULL, *external_object_swapped = NULL;
 
 void
@@ -2944,6 +2991,7 @@ main (int argc, char **argv)
   g_test_add_func ("/Builder/GMenu", test_gmenu);
   g_test_add_func ("/Builder/LevelBar", test_level_bar);
   g_test_add_func ("/Builder/Template", test_template);
+  g_test_add_func ("/Builder/Inline Template", test_inline_template);
   g_test_add_func ("/Builder/Expose Object", test_expose_object);
 
   return g_test_run();



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