[glade] glade-previewer: added support for widget templates



commit 2150c3c80dd7ac141fef925b9c828686676c862e
Author: Juan Pablo Ugarte <juanpablougarte gmail com>
Date:   Thu Oct 3 16:52:31 2013 -0300

    glade-previewer: added support for widget templates

 gladeui/Makefile.am              |    4 +-
 gladeui/glade-preview-template.c |  267 ++++++++++++++++++++++++++++++++++++++
 gladeui/glade-preview-template.h |   36 +++++
 gladeui/glade-preview.c          |   16 ++-
 gladeui/glade-previewer.c        |  115 ++++++++++++-----
 5 files changed, 398 insertions(+), 40 deletions(-)
---
diff --git a/gladeui/Makefile.am b/gladeui/Makefile.am
index e635ed4..c1e391a 100644
--- a/gladeui/Makefile.am
+++ b/gladeui/Makefile.am
@@ -24,7 +24,7 @@ glade_previewer_LDADD = libgladeui-2.la $(GTK_MAC_LIBS)
 glade_previewer_SOURCES = \
        glade-previewer.c \
        glade-preview-window.c \
-       glade-preview-window.h
+       glade-preview-template.c
 
 if NATIVE_WIN32
 glade_previewer_LDADD += glade-win32-res.o
@@ -192,6 +192,8 @@ noinst_HEADERS = \
        glade-popup.h \
        glade-preview.h \
        glade-preview-tokens.h \
+       glade-preview-window.h \
+       glade-preview-template.h \
        glade-project-properties.h \
        gladeui-resources.h \
        glade-drag.h \
diff --git a/gladeui/glade-preview-template.c b/gladeui/glade-preview-template.c
new file mode 100644
index 0000000..48ee9d4
--- /dev/null
+++ b/gladeui/glade-preview-template.c
@@ -0,0 +1,267 @@
+/*
+ * glade-preview-template.c
+ *
+ * Copyright (C) 2013 Juan Pablo Ugarte
+   *
+ * Author: Juan Pablo Ugarte <juanpablougarte gmail com>
+ *
+ * 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.1 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include "glade-utils.h"
+#include "glade-preview-template.h"
+
+typedef struct
+{
+  GTypeInfo info;
+  GString *template_string;
+  GBytes *template_data;
+  gint count;
+} TypeData;
+
+/* We need to call gtk_widget_init_template() in the instance init for the
+* template to work.
+*/
+static void
+template_init (GTypeInstance *instance, gpointer g_class)
+{  
+  gtk_widget_init_template (GTK_WIDGET (instance));
+}
+
+/* Need to associate the class with a template */
+static void
+template_class_init (gpointer g_class, gpointer user_data)
+{
+  TypeData *data = user_data;
+  gtk_widget_class_set_template (g_class, data->template_data);
+}
+
+static GQuark type_data_quark = 0;
+
+static GType
+template_generate_type (const gchar *name,
+                        const gchar *parent_name,
+                        GString *template_string)
+{
+  GType parent_type, retval;
+  gchar *real_name = NULL;
+  GTypeQuery query;
+  TypeData *data;
+
+  g_return_val_if_fail (name != NULL, 0);
+  g_return_val_if_fail (parent_name != NULL, 0);
+
+  parent_type = glade_util_get_type_from_name (parent_name, FALSE);
+  g_return_val_if_fail (parent_type != 0, 0);
+
+  if ((retval = g_type_from_name (name)) &&
+      (data = g_type_get_qdata (retval, type_data_quark)))
+    {
+      /* Type already registered! reuse TypeData
+       * 
+       * If the template and parent class are the same there is no need to
+       * register a new type
+       */
+      if (g_type_parent (retval) == parent_type &&
+          template_string->len == data->template_string->len && 
+          g_strcmp0 (template_string->str, data->template_string->str) == 0)
+        return retval;
+
+      real_name = g_strdup_printf ("GladePreviewTemplate_%s_%d", name, data->count);
+    }
+  else
+    {
+      /* We only allocate a TypeData struct once for each type class */
+      data = g_new0 (TypeData, 1);
+    }
+
+  g_type_query (parent_type, &query);
+  g_return_val_if_fail (query.type != 0, 0);
+
+  /* Free old template string */
+  if (data->template_string)
+    g_string_free (data->template_string, TRUE);
+
+  /* And old bytes reference to template string */
+  if (data->template_data)
+    g_bytes_unref (data->template_data);
+
+  /* Take ownership, will be freed next time we want to create this type */
+  data->template_string = template_string;
+
+  data->info.class_size = query.class_size;
+  data->info.instance_size = query.instance_size;
+  data->info.class_init = template_class_init;
+  data->info.instance_init = template_init;
+  data->info.class_data = data;
+  data->template_data = g_bytes_new_static (template_string->str, template_string->len);
+
+  retval = g_type_register_static (parent_type, real_name ? real_name : name, &data->info, 0);
+
+  /* bind TypeData struct with GType */
+  if (!data->count)
+    g_type_set_qdata (retval, type_data_quark, data);
+
+  data->count++;
+
+  g_free (real_name);
+
+  return retval;
+}
+
+typedef struct
+{
+  gboolean is_template;
+  GString *xml;
+  gchar *klass, *parent;
+  gint indent;
+} 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;
+  gboolean is_template = FALSE;
+  gint i;
+
+  g_string_append_printf (state->xml, "<%s", element_name);
+
+  if (g_strcmp0 (element_name, "template") == 0)
+    state->is_template = is_template = TRUE;
+
+  for (i = 0; attribute_names[i]; i++)
+    {
+      gchar *escaped_value = g_markup_escape_text (attribute_values[i], -1);
+
+      if (is_template)
+        {
+          if (!g_strcmp0 (attribute_names[i], "class"))
+            {
+              TypeData *data;
+              GType type;
+
+              state->klass = g_strdup (attribute_values[i]);
+
+              /* If we are in a template then we need to replace the class with
+               * the fake name template_generate_type() will use to register
+                 * a new class
+               */
+              if ((type = g_type_from_name (state->klass)) &&
+                  (data = g_type_get_qdata (type, type_data_quark)))
+                {
+                  g_free (escaped_value);
+                  escaped_value = g_strdup_printf ("GladePreviewTemplate_%s_%d", state->klass, data->count);
+                }
+            }
+          else if (!g_strcmp0 (attribute_names[i], "parent"))
+            state->parent = g_strdup (attribute_values[i]);
+        }
+
+      g_string_append_printf (state->xml, " %s=\"%s\"",
+                              attribute_names[i], escaped_value);
+      g_free (escaped_value);
+    }
+
+  g_string_append (state->xml, ">");
+}
+
+static void
+end_element (GMarkupParseContext *context,
+             const gchar         *element_name,
+             gpointer             user_data,
+             GError             **error)
+{
+  ParseData *state = user_data;
+  g_string_append_printf (state->xml, "</%s>", element_name);
+}
+
+static void
+text (GMarkupParseContext *context,
+      const gchar         *text,
+      gsize                text_len,
+      gpointer             user_data,
+      GError             **error)
+{
+  ParseData *state = user_data;
+  g_string_append_len (state->xml, text, text_len);
+}
+
+static void
+passthrough (GMarkupParseContext *context,
+             const gchar         *text,
+             gsize                text_len,
+             gpointer             user_data,
+             GError             **error)
+{
+  ParseData *state = user_data;
+  g_string_append_len (state->xml, text, text_len);
+  g_string_append_c (state->xml, '\n');
+}
+
+GObject *
+glade_preview_template_object_new (const gchar *template_data, gsize len)
+{
+  GMarkupParser parser = { start_element, end_element, text, passthrough};
+  ParseData state = { FALSE, NULL};
+  GMarkupParseContext *context;
+  GObject *object = NULL;
+
+  if (type_data_quark == 0)
+    type_data_quark = g_quark_from_string ("glade-preview-type-data");
+
+  if (len == -1)
+    len = strlen (template_data);
+
+  /* Preallocate enough for the string plus the new fake template name */
+  state.xml = g_string_sized_new (len + 32);
+
+  context = g_markup_parse_context_new (&parser,
+                                        G_MARKUP_TREAT_CDATA_AS_TEXT |
+                                        G_MARKUP_PREFIX_ERROR_POSITION,
+                                        &state, NULL);
+
+  if (g_markup_parse_context_parse (context, template_data, len, NULL) &&
+      g_markup_parse_context_end_parse (context, NULL) && 
+      state.is_template)
+    {
+      GType template_type = template_generate_type (state.klass,
+                                                    state.parent,
+                                                    state.xml);
+      if (template_type)
+        object = g_object_new (template_type, NULL);
+      else
+        {
+          /* on success template_generate_type() takes ownership of xml */
+          g_string_free (state.xml, TRUE);
+        }
+    }
+  else
+    g_string_free (state.xml, TRUE);
+
+  g_free (state.klass);
+  g_free (state.parent);
+  g_markup_parse_context_free (context);
+
+  return object ? g_object_ref_sink (object) : NULL;
+}
diff --git a/gladeui/glade-preview-template.h b/gladeui/glade-preview-template.h
new file mode 100644
index 0000000..9c5fda9
--- /dev/null
+++ b/gladeui/glade-preview-template.h
@@ -0,0 +1,36 @@
+/*
+ * glade-preview-template.h
+ *
+ * Copyright (C) 2013 Juan Pablo Ugarte
+   *
+ * Author: Juan Pablo Ugarte <juanpablougarte gmail com>
+ *
+ * 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.1 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _GLADE_PREVIEW_TEMPLATE_H_
+#define _GLADE_PREVIEW_TEMPLATE_H_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+GObject *
+glade_preview_template_object_new (const gchar *template_data, gsize len);
+
+G_END_DECLS
+
+#endif /* _GLADE_PREVIEW_TEMPLATE_H_ */
diff --git a/gladeui/glade-preview.c b/gladeui/glade-preview.c
index 0d1acf8..028163f 100644
--- a/gladeui/glade-preview.c
+++ b/gladeui/glade-preview.c
@@ -201,7 +201,7 @@ glade_preview_launch (GladeWidget *widget, const gchar *buffer)
 {
   GPid pid;
   GError *error = NULL;
-  gchar *argv[9], *executable;
+  gchar *argv[10], *executable;
   gint child_stdin;
   gsize bytes_written;
   GIOChannel *output;
@@ -209,6 +209,7 @@ glade_preview_launch (GladeWidget *widget, const gchar *buffer)
   const gchar *css_provider, *filename;
   GladeProject *project;
   gchar *name;
+  gint i;
 
   g_return_val_if_fail (GLADE_IS_WIDGET (widget), NULL);
 
@@ -224,14 +225,19 @@ glade_preview_launch (GladeWidget *widget, const gchar *buffer)
   argv[3] = (gchar *) glade_widget_get_name (widget);
   argv[4] = "--filename";
   argv[5] = (filename) ? (gchar *) filename : name;
-  argv[6] = NULL;
+
+  i = 5;
+  if (glade_project_get_template (project))
+    argv[++i] = "--template";
+    
+  argv[++i] = NULL;
 
   css_provider = glade_project_get_css_provider_path (glade_widget_get_project (widget));
   if (css_provider)
     {
-      argv[6] = "--css";
-      argv[7] = (gchar *) css_provider;
-      argv[8] = NULL;
+      argv[i] = "--css";
+      argv[++i] = (gchar *) css_provider;
+      argv[++i] = NULL;
     }
   
   if (g_spawn_async_with_pipes (NULL,
diff --git a/gladeui/glade-previewer.c b/gladeui/glade-previewer.c
index 30f1726..cd52f79 100644
--- a/gladeui/glade-previewer.c
+++ b/gladeui/glade-previewer.c
@@ -29,18 +29,20 @@
 #include <glib/gstdio.h>
 
 #include "glade-preview-window.h"
+#include "glade-preview-template.h"
 #include "glade-preview-tokens.h"
 
 typedef struct
 {
   GladePreviewWindow *window;
   gchar *file_name, *toplevel;
+  gboolean is_template;
 } GladePreviewer;
 
-static GtkWidget *
+static GObject *
 get_toplevel (GtkBuilder *builder, gchar *name)
 {
-  GtkWidget *toplevel = NULL;
+  GObject *toplevel = NULL;
   GObject *object;
 
   if (name == NULL)
@@ -56,9 +58,9 @@ get_toplevel (GtkBuilder *builder, gchar *name)
             continue;
 
           if (toplevel == NULL)
-            toplevel = GTK_WIDGET (obj);
+            toplevel = obj;
           else if (GTK_IS_WINDOW (obj))
-            toplevel = GTK_WIDGET (obj);
+            toplevel = obj;
         }
 
       g_slist_free (objects);
@@ -84,19 +86,17 @@ get_toplevel (GtkBuilder *builder, gchar *name)
           exit (1);
         }
 
-      toplevel = GTK_WIDGET (object);
+      toplevel = object;
     }
 
   return g_object_ref_sink (toplevel);
 }
 
-static GtkWidget *
-get_toplevel_from_string (GladePreviewer *app, gchar *name, gchar *string)
+static GObject *
+get_toplevel_from_string (GladePreviewer *app, gchar *name, gchar *string, gsize size)
 {
-  GtkBuilder *builder = gtk_builder_new ();
-  GError *error = NULL;
-  GtkWidget *retval;
-  gchar *wd;
+  gchar *wd = NULL;
+  GObject *retval;
 
   /* We need to change the working directory so builder get a chance to load resources */
   if (app->file_name)
@@ -106,18 +106,42 @@ get_toplevel_from_string (GladePreviewer *app, gchar *name, gchar *string)
       g_chdir (dirname);
       g_free (dirname);
     }
-  else
-    wd = NULL;
 
-  if (!gtk_builder_add_from_string (builder, string, -1, &error))
+  /* We use template flag as a hint since the user can turn on and off template
+   * while the preview is live.
+   */
+  retval = (app->is_template) ? glade_preview_template_object_new (string, size) : NULL;
+
+  if (!retval)
     {
-      g_printerr (_("Couldn't load builder definition: %s"), error->message);
-      g_error_free (error);
-      exit (1);
-    }
+      GtkBuilder *builder = gtk_builder_new ();
+      GError *error = NULL;
 
-  retval = get_toplevel (builder, name);
-  g_object_unref (builder);
+      /* We do not know if its a template yet */
+      app->is_template = FALSE;
+
+      if (gtk_builder_add_from_string (builder, string, size, &error))
+        retval = get_toplevel (builder, name);
+      else
+        {
+          if (error->code == GTK_BUILDER_ERROR_UNHANDLED_TAG &&
+              (retval = glade_preview_template_object_new (string, size)))
+            {
+              /* At this point we know it is a template, so keep a hint for next time */
+              app->is_template = TRUE;
+            }
+          else
+            {
+              gchar *message = g_strdup_printf (_("Couldn't load builder definition: %s"), error->message);
+              glade_preview_window_set_message (app->window, GTK_MESSAGE_ERROR, message);
+              g_free (message);
+            }
+
+          g_error_free (error);
+        }
+
+      g_object_unref (builder);
+    }
 
   /* restore directory */
   if (wd)
@@ -214,7 +238,7 @@ static gboolean
 on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data)
 {
   GladePreviewer *app = data;
-  GtkWidget *new_widget;
+  GObject *new_widget;
   gchar *buffer;
 
   buffer = read_buffer (source);
@@ -241,7 +265,7 @@ on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data)
           return FALSE;
         }
 
-      new_widget = get_toplevel_from_string (app, split_buffer[0], split_buffer[1]);
+      new_widget = get_toplevel_from_string (app, split_buffer[0], split_buffer[1], -1);
       glade_previewer_window_set_title (GTK_WINDOW (app->window), app->file_name,
                                         split_buffer[0]);
 
@@ -249,14 +273,17 @@ on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data)
     }
   else
     {
-      new_widget = get_toplevel_from_string (app, app->toplevel, buffer);
+      new_widget = get_toplevel_from_string (app, app->toplevel, buffer, -1);
       glade_previewer_window_set_title (GTK_WINDOW (app->window), app->file_name, app->toplevel);
     }
 
-  glade_preview_window_set_widget (app->window, new_widget);
+  if (new_widget)
+    {
+      glade_preview_window_set_widget (app->window, GTK_WIDGET (new_widget));
+      gtk_widget_show (GTK_WIDGET (new_widget));
+    }
 
   gtk_window_present (GTK_WINDOW (app->window));
-  gtk_widget_show (new_widget);
   
   g_free (buffer);
   
@@ -339,6 +366,7 @@ glade_previewer_stack_key_press_event (GtkWidget *window, GdkEventKey *event, Gt
 static gboolean listen = FALSE;
 static gboolean version = FALSE;
 static gboolean slideshow = FALSE;
+static gboolean template = FALSE;
 static gchar *file_name = NULL;
 static gchar *toplevel_name = NULL;
 static gchar *css_file_name = NULL;
@@ -347,6 +375,7 @@ static gchar *screenshot_file_name = NULL;
 static GOptionEntry option_entries[] =
 {
     {"filename", 'f', 0, G_OPTION_ARG_FILENAME, &file_name, N_("Name of the file to preview"), "FILENAME"},
+    {"template", 0, 0, G_OPTION_ARG_NONE, &template, N_("Creates dummy widget class to load a template"), 
NULL},
     {"toplevel", 't', 0, G_OPTION_ARG_STRING, &toplevel_name, N_("Name of the toplevel to preview"), 
"TOPLEVELNAME"},
     {"screenshot", 0, 0, G_OPTION_ARG_FILENAME, &screenshot_file_name, N_("File name to save a screenshot"), 
NULL},
     {"css", 0, 0, G_OPTION_ARG_FILENAME, &css_file_name, N_("CSS file to use"), NULL},
@@ -362,6 +391,7 @@ main (int argc, char **argv)
   GladePreviewer *app;
   GOptionContext *context;
   GError *error = NULL;
+  GObject *toplevel = NULL;
 
 #ifdef ENABLE_NLS
   setlocale (LC_ALL, "");
@@ -414,11 +444,23 @@ main (int argc, char **argv)
       GIOChannel *input = g_io_channel_unix_new (fileno (stdin));
 #endif
 
+      app->is_template = template;
+
       g_io_add_watch (input, G_IO_IN | G_IO_HUP, on_data_incoming, app);
 
       gtk_main ();
     }
-  else if (app->file_name)
+  else if (template)
+    {
+      gchar *contents = NULL;
+      gsize size;
+
+      if (g_file_get_contents (file_name, &contents, &size, NULL))
+        toplevel = get_toplevel_from_string (app, NULL, contents, size);
+
+      g_free (contents);
+    }
+  else if (file_name)
     {
       GtkBuilder *builder = gtk_builder_new ();
       GError *error = NULL;
@@ -474,19 +516,24 @@ main (int argc, char **argv)
         }
       else
         {
-          GtkWidget *widget = get_toplevel (builder, toplevel_name);
-          glade_preview_window_set_widget (app->window, widget);
-          gtk_widget_show (widget);
-
-          if (screenshot_file_name)
-            glade_preview_window_screenshot (app->window, TRUE, screenshot_file_name);
-          else
-            gtk_main ();
+          toplevel = get_toplevel (builder, toplevel_name);
         }
 
       g_object_unref (builder);
     }
 
+  if (toplevel)
+    {
+      glade_preview_window_set_widget (app->window, GTK_WIDGET (toplevel));
+      g_object_unref (toplevel);
+      gtk_widget_show (GTK_WIDGET (toplevel));
+
+      if (screenshot_file_name)
+        glade_preview_window_screenshot (app->window, TRUE, screenshot_file_name);
+      else
+        gtk_main ();
+    }
+
   /* free unused resources */
   g_free (file_name);
   g_free (toplevel_name);


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