Commit 856a93ad authored by Juan Pablo Ugarte's avatar Juan Pablo Ugarte
Browse files

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
parent 451dd402
......@@ -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 \
......
......@@ -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
......
/*
* 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);
}
/*
* 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__ */
......@@ -1993,6 +1993,25 @@ glade_project_save (GladeProject *project, const gchar *path, GError **error)
return ret > 0;
}
/**
* 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
......@@ -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);
......
......@@ -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);
......
</
......@@ -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,