[glade] Added intitial support for runtime creation and loading of composite templates.



commit 856a93ad0efe6608b9db0b967e0b7332111e68d6
Author: Juan Pablo Ugarte <juanpablougarte gmail com>
Date:   Tue Oct 2 16:12:16 2012 -0300

    Added intitial support for runtime creation and loading of composite templates.
    
    gladeui/glade-app.c: Load composite templates from G_USER_DIRECTORY_TEMPLATES
    
    gladeui/glade-project.[ch]
     o Added glade_project_dump_string()
     o Addes safe guards for NULL catalogs (composite template adaptors does not have a catalog)
    
    gladeui/glade-widget-adaptor.[ch]
     o Added template and template-path properties
     o Added glade_widget_adaptor_get_template() and
       glade_widget_adaptor_from_composite_template()
    
    gladeui/glade-composite-template.[ch]: Added support to load composite templates and export a widget as such.
    
    plugins/gtk+/glade-gtk.c, plugins/gtk+/gtk+.xml.in: added "Export as template" action

 gladeui/Makefile.am                |    2 +
 gladeui/glade-app.c                |    3 +
 gladeui/glade-composite-template.c |  260 ++++++++++++++++++++++++++++++++++
 gladeui/glade-composite-template.h |   42 ++++++
 gladeui/glade-project.c            |   35 +++++-
 gladeui/glade-project.h            |    1 +
 gladeui/glade-widget-adaptor.c     |  269 +++++++++++++++++++++++++++++-------
 gladeui/glade-widget-adaptor.h     |   22 ++-
 gladeui/glade.h                    |    1 +
 plugins/gtk+/glade-gtk.c           |   69 +++++++++
 plugins/gtk+/gtk+.xml.in           |    5 +-
 11 files changed, 651 insertions(+), 58 deletions(-)
---
diff --git a/gladeui/Makefile.am b/gladeui/Makefile.am
index 141aaed..288848d 100644
--- a/gladeui/Makefile.am
+++ b/gladeui/Makefile.am
@@ -53,6 +53,7 @@ libgladeui_2_la_SOURCES = \
 	glade-object-stub.c \
 	glade-xml-utils.c \
 	glade-catalog.c \
+	glade-composite-template.c \
 	glade-widget-adaptor.c \
 	glade-widget.c \
 	glade-property-class.c \
@@ -122,6 +123,7 @@ libgladeuiinclude_HEADERS = \
 	glade-design-view.h \
 	glade-widget.h \
 	glade-widget-adaptor.h \
+	glade-composite-template.h \
 	glade-property.h \
 	glade-property-class.h \
 	glade-utils.h \
diff --git a/gladeui/glade-app.c b/gladeui/glade-app.c
index fd1bedb..f5d0a3e 100644
--- a/gladeui/glade-app.c
+++ b/gladeui/glade-app.c
@@ -39,6 +39,7 @@
 #include "glade-design-layout.h"
 #include "glade-marshallers.h"
 #include "glade-accumulators.h"
+#include "glade-composite-template.h"
 
 #include <string.h>
 #include <glib.h>
@@ -348,6 +349,8 @@ glade_app_init (GladeApp *app)
 
   /* Load the configuration file */
   priv->config = g_key_file_ref (glade_app_get_config ());
+
+  glade_composite_template_load_directory (g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES));
 }
 
 static void
diff --git a/gladeui/glade-composite-template.c b/gladeui/glade-composite-template.c
new file mode 100644
index 0000000..80a331c
--- /dev/null
+++ b/gladeui/glade-composite-template.c
@@ -0,0 +1,260 @@
+/*
+ * glade-composite-template.c
+ *
+ * Copyright (C) 2012 Juan Pablo Ugarte
+ *
+ * Author: Juan Pablo Ugarte <juanpablougarte gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "glade-composite-template.h"
+#include "glade-app.h"
+#include "glade-utils.h"
+
+typedef struct
+{
+  gboolean right_id;
+  GType parent;
+  const gchar *type_name;
+} ParseData;
+
+static void
+start_element (GMarkupParseContext  *context,
+	       const gchar          *element_name,
+	       const gchar         **attribute_names,
+	       const gchar         **attribute_values,
+	       gpointer              user_data,
+	       GError              **error)
+{
+  ParseData *state = user_data;
+
+  if (g_strcmp0 (element_name, "template") == 0)
+    {
+      gint i;
+
+      for (i = 0; attribute_names[i]; i++)
+        {
+          if (!g_strcmp0 (attribute_names[i], "parent"))
+            state->parent = glade_util_get_type_from_name (attribute_values[i], FALSE);
+          else if (!g_strcmp0 (attribute_names[i], "class"))
+            state->type_name = g_intern_string (attribute_values[i]);
+	  else if (!g_strcmp0 (attribute_names[i], "id"))
+	    state->right_id = (g_strcmp0 (attribute_values[i], "this") == 0);
+        }
+    }
+}
+
+static gboolean
+parse_template (const gchar *template_str, GType *parent, const gchar **type_name)
+{
+  GMarkupParser parser = { start_element };
+  ParseData state = { FALSE, G_TYPE_INVALID, NULL };
+  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, template_str, -1, NULL);
+  g_markup_parse_context_end_parse (context, NULL);
+  g_markup_parse_context_free (context);
+  
+  if (!g_type_is_a (state.parent, GTK_TYPE_CONTAINER))
+    {
+      g_warning ("Composite templates should derive from GtkContainer");
+      return FALSE;
+    }
+  
+  if (parent) *parent = state.parent;
+  if (type_name) *type_name = state.type_name;
+
+  return state.right_id;
+}
+
+static void
+composite_template_derived_init (GTypeInstance *instance, gpointer g_class)
+{
+}
+
+static void
+composite_template_derived_class_init (gpointer g_class, gpointer class_data)
+{
+}
+
+static inline GType
+generate_type (GType parent, const gchar *type_name)
+{
+  GTypeQuery query;
+
+  g_type_query (parent, &query);
+
+  return g_type_register_static_simple (parent, type_name,
+                                        query.class_size,
+                                        composite_template_derived_class_init,
+                                        query.instance_size,
+                                        composite_template_derived_init,
+                                        0);
+}
+
+/* Public API */
+
+/**
+ * glade_composite_template_load_from_string:
+ * @template_xml: a #GtkBuilder UI description string
+ * 
+ * This function will create a new GType from the template UI description defined
+ * by @template_xml and its corresponding #GladeWidgetAdator
+ * 
+ * Returns: A newlly created and registered #GladeWidgetAdptor or NULL if @template_xml is malformed.
+ */
+GladeWidgetAdaptor *
+glade_composite_template_load_from_string (const gchar *template_xml)
+{
+  const gchar *type_name = NULL;
+  GType parent;
+
+  g_return_val_if_fail (template_xml != NULL, NULL);
+
+  if (parse_template (template_xml, &parent, &type_name))
+    {
+      GladeWidgetAdaptor *adaptor;
+      GType template_type;
+
+      /* Generate dummy template type */
+      template_type = generate_type (parent, type_name);
+      
+      /* Create adaptor for template */
+      adaptor = glade_widget_adaptor_from_composite_template (template_type,
+                                                              template_xml,
+                                                              type_name,
+                                                              NULL); /* TODO: generate icon name from parent icon plus some emblem */                        
+      /* Register adaptor */
+      glade_widget_adaptor_register (adaptor);
+
+      return adaptor;
+    }
+  else
+    g_warning ("Could not parse template");
+
+  return NULL;
+}
+
+/**
+ * glade_composite_template_load_from_file:
+ * @path: a filename to load
+ * 
+ * Loads a composite template from a file.
+ * See glade_composite_template_load_from_string() for details.
+ * 
+ * Returns: A newlly created and registered #GladeWidgetAdaptor or NULL.
+ */
+GladeWidgetAdaptor *
+glade_composite_template_load_from_file (const gchar *path)
+{
+  GladeWidgetAdaptor *adaptor;
+  GError *error = NULL;
+  gchar *contents;
+
+  g_return_val_if_fail (path != NULL, NULL);
+  
+  if (!g_file_get_contents (path, &contents, NULL, &error))
+    {
+      g_warning ("Could not load template `%s` %s", path, error->message);
+      g_error_free (error);
+      return NULL;
+    }
+  
+  if ((adaptor = glade_composite_template_load_from_string (contents)))
+    g_object_set (adaptor, "template-path", path, NULL);
+  
+  g_free (contents);
+
+  return adaptor;
+}
+
+/**
+ * glade_composite_template_load_directory:
+ * @directory: a directory path.
+ * 
+ * Loads every .ui composite template found in @directory
+ */
+void
+glade_composite_template_load_directory (const gchar *directory)
+{
+  GError *error = NULL;
+  const gchar *name;
+  GDir *dir;
+
+  g_return_if_fail (path != NULL);
+
+  if (!(dir = g_dir_open (directory, 0, &error)))
+    {
+      g_warning ("Could not open directory `%s` %s", directory, error->message);
+      g_error_free (error);
+      return;
+    }
+
+  while ((name = g_dir_read_name (dir)))
+    {
+      if (g_str_has_suffix (name, ".ui"))
+	{
+	  gchar *fullname = g_build_filename (directory, name, NULL);
+	  glade_composite_template_load_from_file (fullname);
+	  g_free (fullname);
+	}
+    }
+
+  g_dir_close (dir);
+}
+
+/**
+ * glade_composite_template_save_from_widget:
+ * @gwidget: a #GladeWidget
+ * @template_class: the name of the new composite template class
+ * @filename: a file name to save the template
+ * 
+ * Saves a copy of @gwidget as a composite template in @filename with @template_class
+ * as the class name
+ */
+void
+glade_composite_template_save_from_widget (GladeWidget *gwidget,
+                                           const gchar *template_class,
+                                           const gchar *filename)
+{
+  GladeProject *project;
+  gchar *template_xml;
+  GladeWidget *dup;
+  
+  g_return_if_fail (GLADE_IS_WIDGET (gwidget));
+  g_return_if_fail (template_class && filename);
+  g_return_if_fail (GTK_IS_CONTAINER (glade_widget_get_object (gwidget)));
+
+  project = glade_project_new ();
+  dup = glade_widget_dup (gwidget, TRUE);
+
+  /* make dupped widget a template */
+  glade_widget_set_name (dup, "this");
+  glade_widget_set_template_class (dup, template_class);
+  
+  glade_project_add_object (project, glade_widget_get_object (dup));
+  template_xml = glade_project_dump_string (project);
+
+  g_file_set_contents (filename, template_xml, -1, NULL);
+
+  g_free (template_xml);
+  g_object_unref (project);
+}
diff --git a/gladeui/glade-composite-template.h b/gladeui/glade-composite-template.h
new file mode 100644
index 0000000..557cea9
--- /dev/null
+++ b/gladeui/glade-composite-template.h
@@ -0,0 +1,42 @@
+/*
+ * glade-composite-template.h
+ *
+ * Copyright (C) 2012 Juan Pablo Ugarte
+ *
+ * Author: Juan Pablo Ugarte <juanpablougarte gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GLADE_COMPOSITE_TEMPLATE_H__
+#define __GLADE_COMPOSITE_TEMPLATE_H__
+
+#include <gladeui/glade-widget.h>
+
+G_BEGIN_DECLS
+
+GladeWidgetAdaptor *glade_composite_template_load_from_string (const gchar *template_xml);
+
+GladeWidgetAdaptor *glade_composite_template_load_from_file (const gchar *path);
+
+void glade_composite_template_load_directory (const gchar *directory);
+
+void glade_composite_template_save_from_widget (GladeWidget *gwidget,
+                                                const gchar *template_class,
+                                                const gchar *filename);
+
+G_END_DECLS
+
+#endif /* __GLADE_COMPOSITE_TEMPLATE_H__ */
diff --git a/gladeui/glade-project.c b/gladeui/glade-project.c
index 7b1cf02..e9aa181 100644
--- a/gladeui/glade-project.c
+++ b/gladeui/glade-project.c
@@ -1994,6 +1994,25 @@ glade_project_save (GladeProject *project, const gchar *path, GError **error)
 }
 
 /**
+ * glade_project_dump_string:
+ * @project: a #GladeProject
+ * 
+ * Returns: @project as a newlly allocated string
+ */
+gchar *
+glade_project_dump_string (GladeProject *project)
+{
+  GladeXmlContext *context;
+  gchar *retval;
+
+  context = glade_project_write (project);
+  retval = glade_xml_dump_from_context (context);
+  glade_xml_context_destroy (context);
+
+  return retval;
+}
+
+/**
  * glade_project_preview:
  * @project: a #GladeProject
  * @gwidget: a #GladeWidget
@@ -2122,6 +2141,13 @@ glade_project_verify_property_internal (GladeProject *project,
   adaptor      = glade_widget_adaptor_from_pspec (prop_adaptor, pspec);
 
   g_object_get (adaptor, "catalog", &catalog, NULL);
+
+  /* no need to check if there is no catalog because its a composite widget
+   * automagically loaded by libgladeui
+   */
+  if (catalog == NULL && glade_widget_adaptor_get_template (adaptor))
+    return;
+  
   glade_project_target_version_for_adaptor (project, adaptor,
                                             &target_major, &target_minor);
 
@@ -2207,6 +2233,10 @@ glade_project_verify_signal_internal (GladeWidget *widget,
   adaptor = glade_signal_class_get_adaptor (signal_class);
 
   g_object_get (adaptor, "catalog", &catalog, NULL);
+
+  if (catalog == NULL && glade_widget_adaptor_get_template (adaptor))
+    return;
+
   glade_project_target_version_for_adaptor (glade_widget_get_project (widget),
                                             adaptor,
                                             &target_major, &target_minor);
@@ -2427,8 +2457,11 @@ glade_project_verify_adaptor (GladeProject *project,
   for (adaptor_iter = adaptor; adaptor_iter && support_mask == GLADE_SUPPORT_OK;
        adaptor_iter = glade_widget_adaptor_get_parent_adaptor (adaptor_iter))
     {
-
       g_object_get (adaptor_iter, "catalog", &catalog, NULL);
+
+      if (catalog == NULL && glade_widget_adaptor_get_template (adaptor))
+	continue;
+
       glade_project_target_version_for_adaptor (project, adaptor_iter,
                                                 &target_major, &target_minor);
 
diff --git a/gladeui/glade-project.h b/gladeui/glade-project.h
index cdde53d..0910943 100644
--- a/gladeui/glade-project.h
+++ b/gladeui/glade-project.h
@@ -121,6 +121,7 @@ gboolean            glade_project_load_from_file      (GladeProject        *proj
 gboolean            glade_project_save                (GladeProject        *project, 
                                                        const gchar         *path, 
                                                        GError             **error);
+gchar              *glade_project_dump_string          (GladeProject       *project);
 void                glade_project_push_progress        (GladeProject       *project);
 gboolean            glade_project_load_cancelled       (GladeProject       *project);
 void                glade_project_cancel_load          (GladeProject       *project);
diff --git a/gladeui/glade-widget-adaptor.c b/gladeui/glade-widget-adaptor.c
index 4078dfb..309c67a 100644
--- a/gladeui/glade-widget-adaptor.c
+++ b/gladeui/glade-widget-adaptor.c
@@ -102,6 +102,8 @@ struct _GladeWidgetAdaptorPrivate
 				       * are special children (like notebook tab 
 				       * widgets for example).
 				       */
+  gchar       *template_xml;          /* The GtkBuilder template if this is a composite template class */
+  GFileMonitor *template_monitor;
 };
 
 struct _GladeChildPacking
@@ -135,7 +137,9 @@ enum
   PROP_CATALOG,
   PROP_BOOK,
   PROP_SPECIAL_TYPE,
-  PROP_CURSOR
+  PROP_CURSOR,
+  PROP_TEMPLATE,
+  PROP_TEMPLATE_PATH
 };
 
 typedef struct _GladeChildPacking GladeChildPacking;
@@ -380,7 +384,7 @@ gwa_clone_parent_properties (GladeWidgetAdaptor *adaptor, gboolean is_packing)
           parent_adaptor->priv->packing_props : parent_adaptor->priv->properties;
 
       /* Reset versioning in derived catalogs just once */
-      reset_version = strcmp (adaptor->priv->catalog, parent_adaptor->priv->catalog) != 0;
+      reset_version = g_strcmp0 (adaptor->priv->catalog, parent_adaptor->priv->catalog) != 0;
 
       for (list = proplist; list; list = list->next)
         {
@@ -533,8 +537,8 @@ gwa_inherit_signals (GladeWidgetAdaptor *adaptor)
               parent_signal = node->data;
 
               /* Reset versioning in derived catalogs just once */
-              if (strcmp (adaptor->priv->catalog,
-                          parent_adaptor->priv->catalog))
+              if (g_strcmp0 (adaptor->priv->catalog,
+                             parent_adaptor->priv->catalog))
 		glade_signal_class_set_since (signal, 0, 0);
               else
 		glade_signal_class_set_since (signal, 
@@ -624,7 +628,7 @@ glade_widget_adaptor_constructor (GType type,
 
   /* Reset version numbering if we're in a new catalog just once */
   if (parent_adaptor &&
-      strcmp (adaptor->priv->catalog, parent_adaptor->priv->catalog))
+      g_strcmp0 (adaptor->priv->catalog, parent_adaptor->priv->catalog))
     {
       GLADE_WIDGET_ADAPTOR_GET_CLASS (adaptor)->version_since_major =
           GLADE_WIDGET_ADAPTOR_GET_CLASS (adaptor)->version_since_minor = 0;
@@ -711,59 +715,48 @@ static void
 glade_widget_adaptor_finalize (GObject *object)
 {
   GladeWidgetAdaptor *adaptor = GLADE_WIDGET_ADAPTOR (object);
-
+  GladeWidgetAdaptorPrivate *priv = adaptor->priv;
+    
   /* Free properties and signals */
-  g_list_foreach (adaptor->priv->properties, (GFunc) glade_property_class_free, NULL);
-  g_list_free (adaptor->priv->properties);
+  g_list_foreach (priv->properties, (GFunc) glade_property_class_free, NULL);
+  g_list_free (priv->properties);
 
-  g_list_foreach (adaptor->priv->packing_props, (GFunc) glade_property_class_free,
+  g_list_foreach (priv->packing_props, (GFunc) glade_property_class_free,
                   NULL);
-  g_list_free (adaptor->priv->packing_props);
+  g_list_free (priv->packing_props);
 
   /* Be careful, this list holds GladeSignalClass* not GladeSignal,
    * thus g_free is enough as all members are const */
-  g_list_foreach (adaptor->priv->signals, (GFunc) g_free, NULL);
-  g_list_free (adaptor->priv->signals);
-
+  g_list_foreach (priv->signals, (GFunc) g_free, NULL);
+  g_list_free (priv->signals);
 
   /* Free child packings */
-  g_list_foreach (adaptor->priv->child_packings,
-                  (GFunc) gwa_child_packing_free, NULL);
-  g_list_free (adaptor->priv->child_packings);
-
-  if (adaptor->priv->book)
-    g_free (adaptor->priv->book);
-  if (adaptor->priv->catalog)
-    g_free (adaptor->priv->catalog);
-  if (adaptor->priv->special_child_type)
-    g_free (adaptor->priv->special_child_type);
-
-  if (adaptor->priv->cursor != NULL)
-    g_object_unref (adaptor->priv->cursor);
-
-  if (adaptor->priv->name)
-    g_free (adaptor->priv->name);
-  if (adaptor->priv->generic_name)
-    g_free (adaptor->priv->generic_name);
-  if (adaptor->priv->title)
-    g_free (adaptor->priv->title);
-  if (adaptor->priv->icon_name)
-    g_free (adaptor->priv->icon_name);
-  if (adaptor->priv->missing_icon)
-    g_free (adaptor->priv->missing_icon);
-
-  if (adaptor->priv->actions)
+  g_list_foreach (priv->child_packings, (GFunc) gwa_child_packing_free, NULL);
+  g_list_free (priv->child_packings);
+
+  g_free (priv->book);
+  g_free (priv->catalog);
+  g_free (priv->special_child_type);
+  g_clear_object (&priv->cursor);
+  g_free (priv->name);
+  g_free (priv->generic_name);
+  g_free (priv->title);
+  g_free (priv->icon_name);
+  g_free (priv->missing_icon);
+  g_free (priv->template_xml);
+
+  if (priv->actions)
     {
-      g_list_foreach (adaptor->priv->actions,
+      g_list_foreach (priv->actions,
                       (GFunc) glade_widget_action_class_free, NULL);
-      g_list_free (adaptor->priv->actions);
+      g_list_free (priv->actions);
     }
 
-  if (adaptor->priv->packing_actions)
+  if (priv->packing_actions)
     {
-      g_list_foreach (adaptor->priv->packing_actions,
+      g_list_foreach (priv->packing_actions,
                       (GFunc) glade_widget_action_class_free, NULL);
-      g_list_free (adaptor->priv->packing_actions);
+      g_list_free (priv->packing_actions);
     }
 
   gwa_internal_children_free (adaptor);
@@ -771,6 +764,103 @@ glade_widget_adaptor_finalize (GObject *object)
   G_OBJECT_CLASS (glade_widget_adaptor_parent_class)->finalize (object);
 }
 
+static inline void
+gwa_template_rebuild_objects (GladeWidgetAdaptor *adaptor, GType object_type)
+{
+  GList *l, *rebuild = NULL;
+
+  /* Iterate all projects */
+  for (l = glade_app_get_projects (); l; l = g_list_next (l))
+    {
+      GladeProject *project = l->data;
+      const GList *o;
+
+      /* Iterate all objects in the project */
+      for (o = glade_project_get_objects (project); o; o = g_list_next (o))
+	{
+	  GObject *obj = o->data;
+
+	  /* And rebuild widget if its template just changed */
+	  if (g_type_is_a (G_OBJECT_TYPE (obj), object_type))
+	    rebuild = g_list_prepend (rebuild, glade_widget_get_from_gobject (obj));
+	}
+    }
+
+  for (l = rebuild; l; l = g_list_next (l))
+    glade_widget_rebuild (l->data);
+
+  g_list_free (rebuild);
+}
+
+static inline void
+glade_widget_adaptor_set_template (GladeWidgetAdaptor *adaptor,
+                                   const gchar *template_xml)
+{
+  GladeWidgetAdaptorPrivate *priv = adaptor->priv;
+  GtkContainerClass *klass;
+  GType object_type;
+  
+  if (g_strcmp0 (priv->template_xml, template_xml) == 0)
+    return;
+
+  g_free (priv->template_xml);
+  priv->template_xml = g_strdup (template_xml);
+
+  /* Update container class template */
+  object_type = glade_widget_adaptor_get_object_type (adaptor);
+  klass = g_type_class_peek (object_type);
+  gtk_container_class_set_template_from_string (klass, priv->template_xml);
+  gwa_template_rebuild_objects (adaptor, object_type);
+}
+
+static void
+on_template_file_changed (GFileMonitor       *monitor,
+                          GFile              *file,
+                          GFile              *other_file,
+                          GFileMonitorEvent   event_type,
+                          GladeWidgetAdaptor *adaptor)
+{
+  gchar *contents = NULL;
+  gsize len;
+
+  if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+    return;
+
+  if (g_file_load_contents (file, NULL, &contents, &len, NULL, NULL))
+    {
+      g_object_set (adaptor, "template", contents, NULL);
+      g_free (contents);
+    }
+}
+
+static inline void
+glade_widget_adaptor_set_template_path (GladeWidgetAdaptor *adaptor,
+                                        const gchar *path)
+{
+  GladeWidgetAdaptorPrivate *priv = adaptor->priv;
+  GFile *file = g_file_new_for_path (path);
+  GError *error = NULL;
+
+  g_clear_object (&priv->template_monitor);
+  
+  /* Add watch for file */
+  priv->template_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
+
+  if (priv->template_monitor)
+    {
+      g_signal_connect (priv->template_monitor, "changed",
+                        G_CALLBACK (on_template_file_changed),
+                        adaptor);
+    }
+  else
+    {
+      g_warning ("Unable to monitor path `%s` %s", path, error->message);
+      g_error_free (error);
+    }
+  
+  g_object_unref (file);
+}
+
 static void
 glade_widget_adaptor_real_set_property (GObject *object,
                                         guint prop_id,
@@ -820,6 +910,12 @@ glade_widget_adaptor_real_set_property (GObject *object,
 	g_free (adaptor->priv->special_child_type);
 	adaptor->priv->special_child_type = g_value_dup_string (value);
         break;
+      case PROP_TEMPLATE:
+	glade_widget_adaptor_set_template (adaptor, g_value_get_string (value));
+        break;
+      case PROP_TEMPLATE_PATH:
+	glade_widget_adaptor_set_template_path (adaptor, g_value_get_string (value));
+	break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -866,6 +962,9 @@ glade_widget_adaptor_real_get_property (GObject *object,
       case PROP_CURSOR:
         g_value_set_pointer (value, adaptor->priv->cursor);
         break;
+      case PROP_TEMPLATE:
+        g_value_set_string (value, adaptor->priv->template_xml);
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -1455,6 +1554,20 @@ glade_widget_adaptor_class_init (GladeWidgetAdaptorClass *adaptor_class)
        ("cursor", _("Cursor"),
         _("A cursor for inserting widgets in the UI"), G_PARAM_READABLE));
 
+  g_object_class_install_property
+      (object_class, PROP_TEMPLATE,
+       g_param_spec_string
+       ("template", _("Template"),
+        _("Builder template of the class"),
+        NULL, G_PARAM_READWRITE));
+
+  g_object_class_install_property
+      (object_class, PROP_TEMPLATE_PATH,
+       g_param_spec_string
+       ("template-path", _("Template path"),
+        _("Builder template file path of the class, if set it will be used to monitor and update template property automatically"),
+        NULL, G_PARAM_WRITABLE));
+
   g_type_class_add_private (adaptor_class, sizeof (GladeWidgetAdaptorPrivate));
 }
 
@@ -1632,9 +1745,14 @@ static void
 gwa_derived_class_init (GladeWidgetAdaptorClass *adaptor_class,
                         GWADerivedClassData *data)
 {
-  GladeXmlNode *node = data->node;
-  GModule *module = data->module;
+  GladeXmlNode *node;
+  GModule *module;
+
+  if (data == NULL) return;
 
+  node = data->node;
+  module = data->module;
+  
   /* Load catalog symbols from module */
   if (module)
     gwa_extend_with_node_load_sym (adaptor_class, node, module);
@@ -1705,7 +1823,6 @@ gwa_derive_adaptor_for_type (GType object_type, GWADerivedClassData *data)
   return derived_type;
 }
 
-
 /*******************************************************************************
                                      API
  *******************************************************************************/
@@ -1781,6 +1898,14 @@ glade_widget_adaptor_get_signals (GladeWidgetAdaptor *adaptor)
   return adaptor->priv->signals;
 }
 
+G_CONST_RETURN gchar *
+glade_widget_adaptor_get_template (GladeWidgetAdaptor *adaptor)
+{
+  g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL);
+
+  return adaptor->priv->template_xml;
+}
+
 static void
 accum_adaptor (GType *type, GladeWidgetAdaptor *adaptor, GList **list)
 {
@@ -2601,6 +2726,54 @@ generate_deprecated_icon (const gchar *icon_name)
 }
 
 /**
+ * glade_widget_adaptor_from_composite_template:
+ * @template_type: a #GType
+ * @template_xml: composite template ui description
+ * @generic_name: the genereic name of the adaptor
+ * @icon_name: the icon name for the adaptor or NULL to fallback to the parent
+ *
+ * Dynamicaly creates the corresponding adaptor for the template type.
+ */
+GladeWidgetAdaptor *
+glade_widget_adaptor_from_composite_template (GType template_type,
+                                              const gchar *template_xml,
+                                              const gchar *generic_name,
+                                              const gchar *icon_name)
+{
+  gchar *adaptor_icon_name = NULL;
+  GladeWidgetAdaptor *adaptor;
+  GType adaptor_type;
+  const gchar *name;
+
+  g_return_val_if_fail (g_type_is_a (template_type, GTK_TYPE_CONTAINER), NULL);  
+  
+  adaptor_type = gwa_derive_adaptor_for_type (template_type, NULL);
+  name = g_type_name (template_type);
+
+  /* Fallback to parent icon */
+  if (!icon_name)
+    {
+      GladeWidgetAdaptor *parent = glade_widget_adaptor_get_parent_adaptor_by_type (template_type);
+      adaptor_icon_name = g_strdup ((parent && parent->priv->icon_name) ?
+                                    parent->priv->icon_name : DEFAULT_ICON_NAME);
+    }
+
+  adaptor = g_object_new (adaptor_type,
+                          "type", template_type,
+                          "template", template_xml,
+                          "name", name,
+                          "catalog",  NULL, /* yup NULL, it does not have a catalog */
+                          "generic-name", generic_name,
+                          "icon-name", icon_name ? icon_name : adaptor_icon_name,
+                          "title", name,
+                          NULL);
+
+  g_free (adaptor_icon_name);
+  
+  return adaptor;
+}
+
+/**
  * glade_widget_adaptor_from_catalog:
  * @catalog: A #GladeCatalog
  * @class_node: the #GladeXmlNode to load
diff --git a/gladeui/glade-widget-adaptor.h b/gladeui/glade-widget-adaptor.h
index ab33a53..6f07a16 100644
--- a/gladeui/glade-widget-adaptor.h
+++ b/gladeui/glade-widget-adaptor.h
@@ -185,8 +185,8 @@ typedef enum
  * Returns: A newly created #GladeWidget for the said adaptor.
  */
 typedef GladeWidget * (* GladeCreateWidgetFunc) (GladeWidgetAdaptor *adaptor,
-						 const gchar        *first_property_name,
-						 va_list             var_args);
+                                                 const gchar        *first_property_name,
+                                                 va_list             var_args);
 
 /**
  * GladeSetPropertyFunc:
@@ -201,9 +201,9 @@ typedef GladeWidget * (* GladeCreateWidgetFunc) (GladeWidgetAdaptor *adaptor,
  * Sets @value on @object for a given #GladePropertyClass
  */
 typedef void     (* GladeSetPropertyFunc)    (GladeWidgetAdaptor *adaptor,
-					      GObject            *object,
-					      const gchar        *property_name,
-					      const GValue       *value);
+                                              GObject            *object,
+                                              const gchar        *property_name,
+                                              const GValue       *value);
 
 /**
  * GladeGetPropertyFunc:
@@ -215,7 +215,7 @@ typedef void     (* GladeSetPropertyFunc)    (GladeWidgetAdaptor *adaptor,
  * Gets @value on @object for a given #GladePropertyClass
  */
 typedef void     (* GladeGetPropertyFunc)    (GladeWidgetAdaptor *adaptor,
-					      GObject            *object,
+                                              GObject            *object,
 					      const gchar        *property_name,
 					      GValue             *value);
 
@@ -686,12 +686,18 @@ G_CONST_RETURN gchar *glade_widget_adaptor_get_missing_icon (GladeWidgetAdaptor
 G_CONST_RETURN GList *glade_widget_adaptor_get_properties   (GladeWidgetAdaptor   *adaptor);
 G_CONST_RETURN GList *glade_widget_adaptor_get_packing_props(GladeWidgetAdaptor   *adaptor);
 G_CONST_RETURN GList *glade_widget_adaptor_get_signals      (GladeWidgetAdaptor   *adaptor);
+G_CONST_RETURN gchar *glade_widget_adaptor_get_template     (GladeWidgetAdaptor   *adaptor);
 
 GList                *glade_widget_adaptor_list_adaptors    (void);
 
 GladeWidgetAdaptor   *glade_widget_adaptor_from_catalog     (GladeCatalog         *catalog,
-							     GladeXmlNode         *class_node,
-							     GModule              *module);
+                                                             GladeXmlNode         *class_node,
+                                                             GModule              *module);
+
+GladeWidgetAdaptor   *glade_widget_adaptor_from_composite_template (GType template_type,
+                                                                    const gchar *template_xml,
+                                                                    const gchar *generic_name,
+                                                                    const gchar *icon_name);
 
 void                  glade_widget_adaptor_register         (GladeWidgetAdaptor   *adaptor);
  
diff --git a/gladeui/glade.h b/gladeui/glade.h
index ddfb178..04dc406 100644
--- a/gladeui/glade.h
+++ b/gladeui/glade.h
@@ -46,5 +46,6 @@
 #include <gladeui/glade-displayable-values.h>
 #include <gladeui/glade-cell-renderer-icon.h>
 #include <gladeui/glade-cursor.h>
+#include <gladeui/glade-composite-template.h>
 
 #endif /* __GLADE_H__ */
diff --git a/plugins/gtk+/glade-gtk.c b/plugins/gtk+/glade-gtk.c
index cb1ca4a..121bf2c 100644
--- a/plugins/gtk+/glade-gtk.c
+++ b/plugins/gtk+/glade-gtk.c
@@ -1367,6 +1367,75 @@ glade_gtk_container_get_property (GladeWidgetAdaptor *adaptor,
   else GWA_GET_CLASS (G_TYPE_OBJECT)->get_property (adaptor, object, id, value);
 }
 
+void
+glade_gtk_container_action_activate (GladeWidgetAdaptor *adaptor,
+                                     GObject *object,
+                                     const gchar *action_path)
+{
+  GladeWidget *gwidget = glade_widget_get_from_gobject (object);
+
+  if (strcmp (action_path, "template") == 0)
+    {
+      GtkFileFilter *filter = gtk_file_filter_new ();
+      gchar *template_class, *template_file;
+      GtkWidget *dialog, *help_label;
+      GtkFileChooser *chooser;
+
+      gtk_file_filter_add_pattern (filter, "*.ui");
+
+      dialog = gtk_file_chooser_dialog_new (_("Save Template"),
+                                            GTK_WINDOW (glade_app_get_window ()),
+                                            GTK_FILE_CHOOSER_ACTION_SAVE,
+                                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                            GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+                                            NULL);
+      chooser = GTK_FILE_CHOOSER (dialog);
+      gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+      gtk_file_chooser_set_filter (chooser, filter);
+      help_label = gtk_label_new (_("NOTE: the base name of the file will be used"
+                                    " as the class name so it should be in CamelCase"));
+      gtk_file_chooser_set_extra_widget (chooser, help_label);
+      gtk_widget_show (help_label);
+
+      template_class = g_strconcat ("My", G_OBJECT_TYPE_NAME (object), NULL);
+      template_file = g_strconcat (template_class, ".ui", NULL);
+      
+      gtk_file_chooser_set_current_folder (chooser, g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES));
+      gtk_file_chooser_set_current_name (chooser, template_file);
+      
+      if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+	{
+	  gchar *filename = gtk_file_chooser_get_filename (chooser);
+	  gchar *basename = g_path_get_basename (filename);
+	  gchar *dot = g_strrstr (basename, ".ui");
+
+	  /* Strip file extension to get the class name */
+	  if (dot)
+	    {
+	      *dot = '\0';
+	    }
+	  else
+	    {
+	      /* Or add it if it is not present */
+	      gchar *tmp = g_strconcat (filename, ".ui", NULL);
+	      g_free (filename);
+	      filename = tmp;
+	    }
+	  
+	  glade_composite_template_save_from_widget (gwidget, basename, filename);
+
+	  g_free (basename);
+	  g_free (filename);
+	}
+
+      g_free (template_class);
+      g_free (template_file);
+      gtk_widget_destroy (dialog);
+    }
+  else
+    GWA_GET_CLASS (GTK_TYPE_WIDGET)->action_activate (adaptor, object, action_path);
+}
+
 /* ----------------------------- GtkBox ------------------------------ */
 
 GladeWidget *
diff --git a/plugins/gtk+/gtk+.xml.in b/plugins/gtk+/gtk+.xml.in
index f4846a0..7028319 100644
--- a/plugins/gtk+/gtk+.xml.in
+++ b/plugins/gtk+/gtk+.xml.in
@@ -297,7 +297,10 @@ embedded in another object</_tooltip>
       <get-children-function>glade_gtk_container_get_children</get-children-function>
       <set-property-function>glade_gtk_container_set_property</set-property-function>
       <get-property-function>glade_gtk_container_get_property</get-property-function>
-
+      <action-activate-function>glade_gtk_container_action_activate</action-activate-function>
+      <actions>
+	<action id="template" _name="Export as template" stock="gtk-convert" important="True"/>
+      </actions>
       <properties>
 	<property id="glade-template-class" _name="Template Class" save="False" custom-layout="True">
 	  <parameter-spec>



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