[glade] glade-previewer: added support for widget templates
- From: Juan Pablo Ugarte <jpu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glade] glade-previewer: added support for widget templates
- Date: Thu, 3 Oct 2013 20:36:57 +0000 (UTC)
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]