[goffice] Allow themes defined as xml files. [#531070]



commit 709fb74e47298672e76e61239838342c1c12ff60
Author: Jean Brefort <jean brefort normalesup org>
Date:   Thu Aug 12 11:13:11 2010 +0200

    Allow themes defined as xml files. [#531070]

 ChangeLog                               |   19 ++
 NEWS                                    |    1 +
 docs/reference/goffice-0.8-sections.txt |   17 +-
 goffice/graph/gog-graph-prefs.ui        |   20 +-
 goffice/graph/gog-graph.c               |   37 ++--
 goffice/graph/gog-plot-engine.c         |   53 ----
 goffice/graph/gog-styled-object.c       |    2 +-
 goffice/graph/gog-theme.c               |  434 ++++++++++++++++++++++++++++---
 goffice/graph/gog-theme.h               |    5 +-
 9 files changed, 463 insertions(+), 125 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index d6c4c12..e4806b1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,24 @@
 2010-08-12  Jean Brefort  <jean brefort normalesup org>
 
+	* docs/reference/goffice-0.8-sections.txt: update.
+	* goffice/graph/gog-graph-prefs.ui: allow for themes defined in xml files.
+	[#531070]
+	* goffice/graph/gog-graph.c (cb_theme_changed),
+	(gog_graph_populate_editor): ditto.
+	* goffice/graph/gog-plot-engine.c (_gog_plugin_services_init): ditto.
+	* goffice/graph/gog-styled-object.c (gog_styled_object_init_style): ditto.
+	* goffice/graph/gog-theme.c (gog_theme_finalize),
+	(gog_theme_find_element), (gog_theme_fillin_style),
+	(gog_theme_add_element), (gog_theme_get_local_name),
+	(gog_theme_get_description), (map_area_series_solid_default),
+	(map_area_series_solid_guppi), (map_area_series_solid_palette),
+	(build_predefined_themes), (name_start), (name_end), (desc_end),
+	(elem_start), (theme_load_from_uri), (themes_load_from_dir),
+	(_gog_themes_init): ditto.
+	* goffice/graph/gog-theme.h: ditto.
+
+2010-08-12  Jean Brefort  <jean brefort normalesup org>
+
 	* goffice/graph/gog-error-bar.c (gog_error_bar_prefs): fix memory leak.
 	* plugins/plot_distrib/gog-boxplot.c (gog_box_plot_pref),
 	(gog_box_plot_populate_editor): fix memory leak and criticals. [#626665]
diff --git a/NEWS b/NEWS
index adef122..ae2791c 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ Jean:
 	* Don't crash when loading a corrupted chart. [#626206, #626263, #626305]
 	* Fix blur in zoomed charts.
 	* Fix memory leak and criticals in graph editor. [#626665]
+	* Allow themes defined as xml files. [#531070]
 
 --------------------------------------------------------------------------
 goffice 0.8.8:
diff --git a/docs/reference/goffice-0.8-sections.txt b/docs/reference/goffice-0.8-sections.txt
index 13dc5e3..fa49341 100644
--- a/docs/reference/goffice-0.8-sections.txt
+++ b/docs/reference/goffice-0.8-sections.txt
@@ -835,11 +835,10 @@ GOG_THEME
 GOG_IS_THEME
 GogTheme
 gog_theme_get_type
-gog_theme_new
-gog_theme_new_from_file
 gog_theme_get_name
+gog_theme_get_local_name
+gog_theme_get_description
 gog_theme_fillin_style
-gog_theme_registry_add
 gog_theme_registry_lookup
 gog_theme_registry_get_theme_names
 </SECTION>
@@ -3026,6 +3025,18 @@ goc_line_get_type
 </SECTION>
 
 <SECTION>
+<FILE>goc-path</FILE>
+<TITLE>GocPath</TITLE>
+GocPath
+GocPathClass
+<SUBSECTION Standard>
+GOC_PATH
+GOC_IS_PATH
+GOC_TYPE_PATH
+goc_path_get_type
+</SECTION>
+
+<SECTION>
 <FILE>goc-pixbuf</FILE>
 <TITLE>GocPixbuf</TITLE>
 GocPixbuf
diff --git a/goffice/graph/gog-graph-prefs.ui b/goffice/graph/gog-graph-prefs.ui
index 5e24866..bdfddbb 100644
--- a/goffice/graph/gog-graph-prefs.ui
+++ b/goffice/graph/gog-graph-prefs.ui
@@ -1,17 +1,7 @@
 <?xml version="1.0"?>
 <interface>
+  <!-- interface-requires gtk+ 2.12 -->
   <!-- interface-naming-policy toplevel-contextual -->
-  <object class="GtkListStore" id="model1">
-    <columns>
-      <!-- column-name gchararray -->
-      <column type="gchararray"/>
-    </columns>
-    <data>
-      <row>
-        <col id="0" translatable="yes">Default</col>
-      </row>
-    </data>
-  </object>
   <object class="GtkTable" id="gog_graph_prefs">
     <property name="visible">True</property>
     <property name="border_width">12</property>
@@ -99,4 +89,12 @@
       </packing>
     </child>
   </object>
+  <object class="GtkListStore" id="model1">
+    <columns>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+      <!-- column-name theme -->
+      <column type="GObject"/>
+    </columns>
+  </object>
 </interface>
diff --git a/goffice/graph/gog-graph.c b/goffice/graph/gog-graph.c
index bdaa876..072af7d 100644
--- a/goffice/graph/gog-graph.c
+++ b/goffice/graph/gog-graph.c
@@ -166,20 +166,17 @@ gog_graph_type_name (GogObject const *gobj)
 static void
 cb_theme_changed (GtkComboBox *combo, GogGraph *graph)
 {
-	GSList *theme_names;
-	char const *name;
-	int index = gtk_combo_box_get_active (combo);
-
-	theme_names = gog_theme_registry_get_theme_names ();
-	if (theme_names == NULL)
-		return;
-
-	name = g_slist_nth_data (theme_names, index);
-	g_slist_free (theme_names);
-	if (name == NULL)
-		return;
-
-	gog_graph_set_theme (graph, gog_theme_registry_lookup (name));
+	GtkTreeIter iter;
+
+	if (gtk_combo_box_get_active_iter (combo, &iter)) {
+		GtkTreeModel *model = GTK_TREE_MODEL (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)));
+		GogTheme *theme = NULL;
+		gtk_tree_model_get (model, &iter, 1, &theme, -1);
+		if (theme != NULL) {
+			gog_graph_set_theme (graph, theme);
+			g_object_unref (theme);
+		}
+	}
 }
 
 static void
@@ -211,16 +208,24 @@ gog_graph_populate_editor (GogObject *gobj,
 		GtkWidget *box;
 		GtkWidget *combo;
 		GSList *ptr;
+		GtkListStore *model;
+		GtkTreeIter iter;
+		GogTheme *theme;
 		char const *graph_theme_name;
 		int count, index = 0;
 
 		graph_theme_name = gog_theme_get_name (graph->theme);
 		combo = go_gtk_builder_get_widget (gui, "theme_combo");
-		gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo))));
+		model = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo)));
 
 		count = 0;
 		for (ptr = theme_names; ptr != NULL; ptr = ptr->next) {
-			gtk_combo_box_append_text (GTK_COMBO_BOX (combo), _(ptr->data));
+			theme = gog_theme_registry_lookup (ptr->data);
+			gtk_list_store_append (model, &iter);
+			gtk_list_store_set (model, &iter,
+			                    0, gog_theme_get_local_name (theme),
+			                    1, theme,
+			                    -1);
 			if (strcmp (ptr->data, graph_theme_name) == 0)
 				index = count;
 			count++;
diff --git a/goffice/graph/gog-plot-engine.c b/goffice/graph/gog-plot-engine.c
index afab7e9..adf20fa 100644
--- a/goffice/graph/gog-plot-engine.c
+++ b/goffice/graph/gog-plot-engine.c
@@ -354,58 +354,6 @@ GSF_CLASS (GogPlotTypeService, gog_plot_type_service,
            GO_TYPE_PLUGIN_SERVICE_SIMPLE)
 
 /***************************************************************************/
-/* Use a plugin service to define themes */
-
-#define GOG_TYPE_THEME_SERVICE  (gog_theme_service_get_type ())
-#define GOG_THEME_SERVICE(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_THEME_SERVICE, GogThemeService))
-#define GOG_IS_THEME_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_THEME_SERVICE))
-
-GType gog_theme_service_get_type (void);
-
-typedef GOPluginServiceSimple GogThemeService;
-typedef GOPluginServiceSimpleClass GogThemeServiceClass;
-
-static void
-gog_theme_service_read_xml (GOPluginService *service, xmlNode *tree, GOErrorInfo **ret_error)
-{
-	GogTheme *theme;
-	xmlNode *ptr;
-	char    *path;
-
-	for (ptr = tree->xmlChildrenNode; ptr != NULL; ptr = ptr->next)
-		if (0 == xmlStrcmp (ptr->name, "file") &&
-		    NULL != (path = xmlNodeGetContent (ptr))) {
-			if (!g_path_is_absolute (path)) {
-				char const *dir = go_plugin_get_dir_name (
-					go_plugin_service_get_plugin (service));
-				char *tmp = g_build_filename (dir, path, NULL);
-				g_free (path);
-				path = tmp;
-			}
-			theme = gog_theme_new_from_file (go_plugin_service_get_description (service),
-							 path);
-			gog_theme_registry_add (theme, FALSE);
-		}
-}
-
-static char *
-gog_theme_service_get_description (GOPluginService *service)
-{
-	return g_strdup (_("Chart Theme"));
-}
-
-static void
-gog_theme_service_class_init (GOPluginServiceClass *ps_class)
-{
-	ps_class->read_xml	  = gog_theme_service_read_xml;
-	ps_class->get_description = gog_theme_service_get_description;
-}
-
-GSF_CLASS (GogThemeService, gog_theme_service,
-           gog_theme_service_class_init, NULL,
-           GO_TYPE_PLUGIN_SERVICE_SIMPLE)
-
-/***************************************************************************/
 /* Support regression curves engines in plugins */
 
 #define GOG_TYPE_TREND_LINE_ENGINE_SERVICE  (gog_trend_line_engine_service_get_type ())
@@ -665,7 +613,6 @@ _gog_plugin_services_init (void)
 {
 	go_plugin_service_define ("plot_engine", &gog_plot_engine_service_get_type);
 	go_plugin_service_define ("plot_type",   &gog_plot_type_service_get_type);
-	go_plugin_service_define ("chart_theme",  &gog_theme_service_get_type);
 	go_plugin_service_define ("trendline_engine", &gog_trend_line_engine_service_get_type);
 	go_plugin_service_define ("trendline_type", &gog_trend_line_service_get_type);
 }
diff --git a/goffice/graph/gog-styled-object.c b/goffice/graph/gog-styled-object.c
index 624a1fb..b32f5fc 100644
--- a/goffice/graph/gog-styled-object.c
+++ b/goffice/graph/gog-styled-object.c
@@ -168,7 +168,7 @@ gog_styled_object_init_style (GogStyledObject *gso, GOStyle *style)
 {
 	style->interesting_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL; /* default */
 	gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)),
-		style, GOG_OBJECT (gso), 0, FALSE);
+		style, GOG_OBJECT (gso), 0, style->interesting_fields);
 }
 
 static void
diff --git a/goffice/graph/gog-theme.c b/goffice/graph/gog-theme.c
index 426addf..037b658 100644
--- a/goffice/graph/gog-theme.c
+++ b/goffice/graph/gog-theme.c
@@ -3,6 +3,7 @@
  * gog-theme.c :
  *
  * Copyright (C) 2003-2004 Jody Goldberg (jody gnome org)
+ * Copyright (C) 2010 Jean Brefort (jean brefort normalesup org)
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of version 2 of the GNU General Public
@@ -20,18 +21,149 @@
  */
 
 #include <goffice/goffice-config.h>
+#include <goffice/goffice-priv.h>
 #include <goffice/graph/gog-theme.h>
 #include <goffice/graph/gog-object.h>
 #include <goffice/utils/go-color.h>
 #include <goffice/utils/go-gradient.h>
 #include <goffice/utils/go-units.h>
 #include <goffice/utils/go-marker.h>
+#include <gsf/gsf-input-gio.h>
 
 #include <gsf/gsf-impl-utils.h>
 #include <glib/gi18n-lib.h>
 #include <string.h>
 
-typedef void (*GogThemeStyleMap) (GOStyle *style, unsigned ind);
+/**
+ * SECTION:gog-theme
+ * @short_description: a list of default styles to apply to appropriate graph elements.
+ * 
+ * The library provides two hard coded themes, "Default", and "Guppi". Other themes
+ * are described in files, some of which might be distributed with the library.
+ * 
+ * A file defining a theme is an xml file with a &lt;GogTheme&gt; root node. The contents
+ * must be: _name|name+, _description?|description*, GOStyle+.
+ * 
+ * _name and name nodes:
+ *
+ * The _name node should be used for themes distributed with goffice, localized
+ * names will be in *.po files and only the default name for "C" locale needs to
+ * be there. Other files need at least one name node for the default name, and
+ * might have some translated names with an appropriate "xml:lang" attribute.
+ * 
+ * _description and description nodes:
+ *
+ * These work just like name nodes. The difference is that no description node is
+ * mandatory. A theme can work perfectly without a description.
+ *
+ * GOStyle nodes:
+ *
+ * These nodes actually define the theme. Attributes and contents are:
+ * attributes: class, role.
+ * contents: (line|outline)?, fill?, marker?, font?, text_layout?
+ *
+ * The attributes define the target for the style. You might have a class and
+ * a role attribute, or just one of them. If the role attribute is given, the class attribute,
+ * if given, represents the class of the parent object.
+ * A GOStyle node with no class or role will be used
+ * as default when another style is missing. If several such nodes exist, the
+ * last one will be used. If no default style exists, the "Default" theme is applied for
+ * missing nodes.
+ * The list of GOStyle nodes might be:
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>class</td><td>role</td><td>contents</td><td>comments</td></tr></thead>
+ * <tr><td>GogGraph</td><td></td><td>outline, fill</td></tr>
+ * <tr><td>GogGraph</td><td>Title</td><td>outline, fill, font, text_layout</td><td>The graph title</td></tr>
+ * <tr><td>GogChart</td><td></td><td>outline, fill</td></tr>
+ * <tr><td>GogChart</td><td>Title</td><td>outline, fill, font, text_layout</td><td>The chart title</td></tr>
+ * <tr><td>GogLegend</td><td></td><td>outline, fill, font</td></tr>
+ * <tr><td>GogAxis</td><td></td><td>line, font, text_layout</td></tr>
+ * <tr><td>GogAxisLine</td><td></td><td>line, font</td></tr>
+ * <tr><td>GogGrid</td><td></td><td>outline, fill</td><td>GogGrid is actually the back plane</td></tr>
+ * <tr><td> </td><td>MajorGrid</td><td>line, fill</td></tr>
+ * <tr><td> </td><td>MinorGrid</td><td>line, fill</td></tr>
+ * <tr><td>GogLabel</td><td></td><td>outline, fill, font, text_layout</td></tr>
+ * <tr><td>GogSeries</td><td></td><td>line, fill, marker</td><td>One is needed for each entry in the palette</td></tr>
+ * <tr><td>GogTrendLine</td><td></td><td>line, fill</td></tr>
+ * <tr><td>GogReqEqn</td><td></td><td>line, fill, font, text_layout</td></tr>
+ * </table>
+ *
+ * The line and outline nodes are actually the same so using line in place of outline is
+ * not an issue. A color is specified either using the format RR:GG::BB:AA or a string as defined
+ * in rgb.txt, so black can be specified as "black" or "00:00:00:FF".
+ *
+ * "line" or "outline" node:
+ *
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td></tr><td>comments</td></thead>
+ * <tr><td>dash</td><td>one of "none", "solid", "s-dot", "s-dash-dot", "s-dash-dot-dot", "dash-dot-dot-dot", "dot",
+ * "s-dash", "dash", "l-dash", "dash-dot", or "dash-dot-dot"</td></tr>
+ * <tr><td>color</td><td>any color as described above</td></tr>
+ * <tr><td>width</td><td>float</td><td>not always taken into account</td></tr>
+ * </table>
+ *
+ * "fill" node
+ *
+ * contents: (pattern|gradient)?
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
+ * <tr><td>type</td><td>one of "none", "pattern", or "gradient"</td></tr>
+ * </table>
+ *
+ * "pattern" node
+ *
+ * Should be included in the fill node if type is pattern.
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td></tr></thead>
+ * <tr><td>type</td><td>one of "solid", "grey75", "grey50", "grey25", "grey12.5",
+ * "grey6.25", "horiz", "vert", "rev-diag", "diag", "diag-cross", "thick-diag-cross",
+ * "thin-horiz", "thin-vert", "thin-rev-diag", "thin-diag", "thin-horiz-cross",
+ * "thin-diag-cross", "foreground-solid", "small-circles","semi-circles", "thatch",
+ * "large-circles", or "bricks"</td></tr>
+ * <tr><td>fore</td><td>any color as described above</td></tr>
+ * <tr><td>back</td><td>any color as described above</td></tr>
+ * </table>
+ *
+ * "gradient" node
+ *
+ * Should be included in the fill node if type is gradient.
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
+ * <tr><td>direction</td><td>one of "n-s", "s-n", "n-s-mirrored", "s-n-mirrored",
+ * "w-e", "e-w", "w-e-mirrored", "e-w-mirrored", "nw-se", "se-nw", "nw-se-mirrored",
+ * "se-nw-mirrored", "ne-sw", "sw-ne", "sw-ne-mirrored", or "ne-sw-mirrored" </td><td></td></tr>
+ * <tr><td>start_color</td><td>any color as described above</td><td></td></tr>
+ * <tr><td>brightness</td><td>float</td><td>meaningful only for monocolor gradients</td></tr>
+ * <tr><td>end_color</td><td>any color as described above</td><td>meaningful only for bicolor gradients</td></tr>
+ * </table>
+ *
+ * "marker" node
+ *
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
+ * <tr><td>shape</td><td>one of "none", "square", "diamond", "triangle-down",
+ * "triangle-up", "triangle-right", "triangle-left", "circle", "x", "cross",
+ * "asterisk", "bar", "half-bar", "butterfly", "hourglass", or "lefthalf-bar"</td><td></td></tr>
+ * <tr><td>fill-color</td><td>any color as described above</td><td></td></tr>
+ * <tr><td>outline-color</td><td>any color as described above</td><td></td></tr>
+ * <tr><td>size</td><td>float</td><td>not always taken into account</td></tr>
+ * </table>
+ *
+ * "font" node
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td></tr></thead>
+ * <tr><td>color</td><td>any color as described above</td></tr>
+ * <tr><td>font</td><td>a string describing the font such as "Sans 10"</td></tr>
+ * </table>
+ *
+ * "text_layout" node
+ * <table style="border-spacing:1cm 2mm">
+ * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
+ * <tr><td>angle</td><td>float</td><td>expressed in degrees</td></tr>
+ * </table>
+ **/
+
+typedef void (*GogThemeStyleMap) (GOStyle *style, unsigned ind, GogTheme const *theme);
 
 typedef struct {
 	/* If role is non-null, klass_name specifies the container class,
@@ -46,11 +178,13 @@ struct _GogTheme {
 	GObject	base;
 
 	char 		*name;
-	char 		*load_from_file; /* optionally NULL */
+	char 		*local_name;
+	char 		*description;
 	GHashTable	*elem_hash_by_role;
 	GHashTable	*elem_hash_by_class;
 	GHashTable	*class_aliases;
 	GOStyle	*default_style;
+	GArray		*palette;
 };
 
 typedef GObjectClass GogThemeClass;
@@ -90,15 +224,22 @@ static void
 gog_theme_finalize (GObject *obj)
 {
 	GogTheme *theme = GOG_THEME (obj);
+	unsigned i;
 
 	g_free (theme->name); theme->name = NULL;
-	g_free (theme->load_from_file); theme->load_from_file = NULL;
+	g_free (theme->local_name); theme->local_name = NULL;
+	g_free (theme->description); theme->description = NULL;
 	if (theme->elem_hash_by_role)
 		g_hash_table_destroy (theme->elem_hash_by_role);
 	if (theme->elem_hash_by_class)
 		g_hash_table_destroy (theme->elem_hash_by_class);
 	if (theme->class_aliases)
 		g_hash_table_destroy (theme->class_aliases);
+	if (theme->palette) {
+		for (i = 0; i < theme->palette->len; i++)
+			g_object_unref (g_array_index (theme->palette, GObject*, i));
+		g_array_unref (theme->palette);
+	}
 
 	(parent_klass->finalize) (obj);
 }
@@ -132,6 +273,7 @@ GSF_CLASS (GogTheme, gog_theme,
 	   gog_theme_class_init, gog_theme_init,
 	   G_TYPE_OBJECT)
 
+
 static GogThemeElement *
 gog_theme_find_element (GogTheme const *theme, GogObject const *obj)
 {
@@ -143,11 +285,6 @@ gog_theme_find_element (GogTheme const *theme, GogObject const *obj)
 		theme = default_theme;
 	g_return_val_if_fail (theme != NULL, NULL);
 
-	if (theme->load_from_file != NULL) {
-		/* FIXME: Parse some XML */
-		g_warning ("[GogTheme] Theme from XML file not implemented");
-	}
-
 	/* FIXME: Restore hash search caching. */
 
 	/* Search by role */
@@ -174,6 +311,7 @@ gog_theme_find_element (GogTheme const *theme, GogObject const *obj)
 		klass = G_OBJECT_GET_CLASS (obj);
 		do {
 			name = G_OBJECT_CLASS_NAME (klass);
+
 			elem = g_hash_table_lookup (	/* Is the type known ? */
 				theme->elem_hash_by_class, name);
 			if (elem == NULL) {		/* Is this a local alias ? */
@@ -195,6 +333,10 @@ gog_theme_find_element (GogTheme const *theme, GogObject const *obj)
 			 (klass = g_type_class_peek_parent (klass)) != NULL);
 	}
 
+	/* still not found, use default theme */
+	if (elem == NULL && theme != default_theme)
+		elem = gog_theme_find_element (default_theme, obj);
+
 	return elem;
 }
 
@@ -232,12 +374,12 @@ gog_theme_fillin_style (GogTheme const *theme,
 		/* ensure only relevant fields are changed */
 		GOStyleFlag flags = style->disable_theming;
 		style->disable_theming = GO_STYLE_ALL ^ relevant_fields;
-		(elem->map) (style, ind);
+		(elem->map) (style, ind, theme);
 		style->disable_theming = flags;
 	}
 }
 
-GogTheme *
+static GogTheme *
 gog_theme_new (char const *name)
 {
 	GogTheme *theme = g_object_new (GOG_TYPE_THEME, NULL);
@@ -245,14 +387,6 @@ gog_theme_new (char const *name)
 	return theme;
 }
 
-GogTheme *
-gog_theme_new_from_file (char const *name, char const *file)
-{
-	GogTheme *theme = gog_theme_new (name);
-	theme->load_from_file = g_strdup (file);
-	return theme;
-}
-
 #if 0
 void
 gog_theme_register_file (char const *name, char const *file)
@@ -284,13 +418,24 @@ gog_theme_add_element (GogTheme *theme, GOStyle *style,
 	elem->map   = map;
 
 	/* Never put an element into both by_role_id & by_class_name */
-	if (role_id != NULL)
-		g_hash_table_insert (theme->elem_hash_by_role,
-			(gpointer)elem, elem);
-	else if (klass_name != NULL)
-		g_hash_table_insert (theme->elem_hash_by_class,
-			(gpointer)klass_name, elem);
-	else {
+	if (role_id != NULL) {
+		if (g_hash_table_lookup (theme->elem_hash_by_role, (gpointer)elem) == NULL)
+			g_hash_table_insert (theme->elem_hash_by_role,
+				(gpointer)elem, elem);
+		else {
+			g_object_unref (style);
+			g_free (elem);
+		}
+	} else if (klass_name != NULL) {
+		if (g_hash_table_lookup (theme->elem_hash_by_class, (gpointer)klass_name) == NULL)
+			g_hash_table_insert (theme->elem_hash_by_class,
+				(gpointer)klass_name, elem);
+		else {
+			g_object_unref (style);
+			g_free (elem);
+		}
+
+	} else {
 		if (theme->default_style)
 			g_object_unref (theme->default_style);
 		theme->default_style = style;
@@ -298,6 +443,13 @@ gog_theme_add_element (GogTheme *theme, GOStyle *style,
 	}
 }
 
+/**
+ * gog_theme_get_name:
+ * @theme: a #GogTheme
+ *
+ * Returns: the GogTheme name.
+ **/
+
 char const *
 gog_theme_get_name (GogTheme const *theme)
 {
@@ -305,6 +457,34 @@ gog_theme_get_name (GogTheme const *theme)
 	return theme->name;
 }
 
+/**
+ * gog_theme_get_local_name:
+ * @theme: a #GogTheme
+ *
+ * Returns: the localized GogTheme name.
+ **/
+
+char const *
+gog_theme_get_local_name (GogTheme const *theme)
+{
+	g_return_val_if_fail (GOG_IS_THEME (theme), "");
+	return (theme->local_name)? theme->local_name: _(theme->name);
+}
+
+/**
+ * gog_theme_get_descrition:
+ * @theme: a #GogTheme
+ *
+ * Returns: the localized GogTheme decription.
+ **/
+
+char const *
+gog_theme_get_description (GogTheme const *theme)
+{
+	g_return_val_if_fail (GOG_IS_THEME (theme), "");
+	return theme->description;
+}
+
 /**************************************************************************/
 
 static void
@@ -341,7 +521,7 @@ map_marker (GOStyleMark *mark, unsigned shape, unsigned palette_index,
 }
 
 static void
-map_area_series_solid_default (GOStyle *style, unsigned ind)
+map_area_series_solid_default (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme)
 {
 	static GOColor const palette [] = {
 		0x9c9cffff, 0x9c3163ff, 0xffffceff, 0xceffffff, 0x630063ff,
@@ -380,7 +560,7 @@ map_area_series_solid_default (GOStyle *style, unsigned ind)
 }
 
 static void
-map_area_series_solid_guppi (GOStyle *style, unsigned ind)
+map_area_series_solid_guppi (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme)
 {
 	static GOColor const palette[] = {
 		0xff3000ff, 0x80ff00ff, 0x00ffcfff, 0x2000ffff,
@@ -410,6 +590,20 @@ map_area_series_solid_guppi (GOStyle *style, unsigned ind)
 		map_marker (&style->marker, ind, palette_index, palette);
 }
 
+static void
+map_area_series_solid_palette (GOStyle *style, unsigned ind, GogTheme const *theme)
+{
+	GOStyle *src;
+	if (theme->palette->len == 0)
+		src = theme->default_style;
+	else {
+		ind %= theme->palette->len;
+		src = g_array_index (theme->palette, GOStyle*, ind);
+	}
+	if (src)
+		go_style_apply_theme (style, src, style->interesting_fields);
+}
+
 /**************************************************************************/
 
 /**
@@ -420,7 +614,7 @@ map_area_series_solid_guppi (GOStyle *style, unsigned ind)
  * Keep a pointer to @theme in graph theme registry.
  * This function does not add a reference to @theme.
  **/
-void
+static void
 gog_theme_registry_add (GogTheme *theme, gboolean is_default)
 {
 	g_return_if_fail (GOG_IS_THEME (theme));
@@ -483,8 +677,7 @@ gog_theme_registry_get_theme_names (void)
 
 /**************************************************************************/
 
-void
-_gog_themes_init (void)
+static void build_predefined_themes (void)
 {
 	GogTheme *theme;
 	GOStyle *style;
@@ -494,6 +687,8 @@ _gog_themes_init (void)
 			g_str_hash, g_str_equal);
 		g_hash_table_insert (global_class_aliases,
 			(gpointer)"GogSeriesElement", (gpointer)"GogSeries");
+		g_hash_table_insert (global_class_aliases,
+			(gpointer)"GogSeriesLine", (gpointer)"GogSeries");
 	}
 
 	/* An MS Excel-ish theme */
@@ -627,11 +822,6 @@ _gog_themes_init (void)
 		NULL, "GogEquation", NULL);
 #endif
 
-	/* series lines */
-	style = go_style_new ();
-	gog_theme_add_element (theme, style,
-		map_area_series_solid_default, "GogSeriesLines", NULL);
-
 /* Guppi */
 	theme = gog_theme_new (N_("Guppi"));
 	gog_theme_registry_add (theme, FALSE);
@@ -756,11 +946,179 @@ _gog_themes_init (void)
 	gog_theme_add_element (theme, style,
 		NULL, "GogEquation", NULL);
 #endif
+}
+
+struct theme_load_state {
+	GogTheme *theme;
+	char *desc, *lang, *local_name;
+	unsigned name_lang_score;
+	unsigned desc_lang_score;
+	char const * const *langs;
+};
 
-	/* series lines */
+static void
+name_start (GsfXMLIn *xin, xmlChar const **attrs)
+{
+	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
+	unsigned i;
+	for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2)
+		if (0 == strcmp (attrs[i], "xml:lang"))
+			state->lang = g_strdup (attrs[i+1]);
+}
+
+static void
+name_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
+{
+	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
+	char *name = NULL;
+	if (xin->content->str == NULL)
+		return;
+	name = g_strdup (xin->content->str);
+	if (state->lang == NULL) {
+		GOStyle *style;
+		state->theme = gog_theme_new (name);
+		state->theme->palette = g_array_new (FALSE, FALSE, sizeof (GOStyle*));
+		/* initialize a dummy GogSeries style */
+		style = go_style_new ();
+		style->line.dash_type = GO_LINE_SOLID;
+		style->line.width = 0; /* hairline */
+		style->line.color = GO_COLOR_BLACK;
+		style->fill.type = GO_STYLE_FILL_NONE;
+		gog_theme_add_element (state->theme, style,
+			map_area_series_solid_palette, "GogSeries", NULL);
+	} else if (state->name_lang_score > 0 && state->langs[0] != NULL) {
+		unsigned i;
+		for (i = 0; i < state->name_lang_score && state->langs[i] != NULL; i++) {
+			if (strcmp (state->langs[i], state->lang) == 0) {
+				g_free (state->local_name);
+				state->local_name = g_strdup (name);
+				state->name_lang_score = i;
+			}
+		}
+	}
+	g_free (state->lang);
+	state->lang = NULL;
+}
+
+static void
+desc_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
+{
+	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
+	if (state->lang == NULL) {
+		if (state->desc == NULL)
+			state->desc = g_strdup (xin->content->str);
+	} else if (state->desc_lang_score > 0 && state->langs[0] != NULL) {
+		unsigned i;
+		for (i = 0; i < state->desc_lang_score && state->langs[i] != NULL; i++) {
+			if (strcmp (state->langs[i], state->lang) == 0) {
+				g_free (state->desc);
+				state->desc = g_strdup (xin->content->str);
+				state->desc_lang_score = i;
+			}
+		}
+	}
+	g_free (state->lang);
+	state->lang = NULL;
+}
+
+static void
+elem_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
+{
+	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state; 
+	char const *role = NULL, *class_name = NULL;
+	GOStyle *style;
+	unsigned i;
+
+	if (state->theme == NULL)
+		return;
+	for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2)
+		if (0 == strcmp (attrs[i], "class"))
+			class_name = g_strdup (attrs[i+1]);
+		else if (0 == strcmp (attrs[i], "role"))
+			role = g_strdup (attrs[i+1]);
 	style = go_style_new ();
-	gog_theme_add_element (theme, style,
-		NULL, "GogSeriesLines", NULL);
+	go_persist_prep_sax (GO_PERSIST (style), xin, attrs);
+
+	if (class_name && !strcmp (class_name, "GogSeries"))
+		state->theme->palette = g_array_append_val (state->theme->palette, style);
+	else
+		gog_theme_add_element (state->theme, style, NULL, class_name, role);
+}
+
+static void
+theme_load_from_uri (char const *uri)
+{
+	static GsfXMLInNode const theme_dtd[] = {
+		GSF_XML_IN_NODE (THEME, THEME, -1, "GogTheme", GSF_XML_NO_CONTENT, NULL, NULL),
+			GSF_XML_IN_NODE (THEME, NAME, -1, "name", GSF_XML_CONTENT, name_start, name_end),
+			GSF_XML_IN_NODE (THEME, UNAME, -1, "_name", GSF_XML_CONTENT, name_start, name_end),
+			GSF_XML_IN_NODE (THEME, DESCRIPTION, -1, "description", GSF_XML_CONTENT, name_start, desc_end),
+			GSF_XML_IN_NODE (THEME, UDESCRIPTION, -1, "_description", GSF_XML_CONTENT, name_start, desc_end),
+			GSF_XML_IN_NODE (THEME, STYLE, -1, "GOStyle", GSF_XML_NO_CONTENT, elem_start, NULL),
+		GSF_XML_IN_NODE_END
+	};
+	struct theme_load_state	state;
+	GsfXMLInDoc *xml;
+	GsfInput *input = go_file_open (uri, NULL);
+
+	if (input == NULL)
+		g_warning ("[GogTheme]: Could not open %s", uri);
+	state.theme = NULL;
+	state.desc = state.lang = state.local_name = NULL;
+	state.langs = g_get_language_names ();
+	state.name_lang_score = state.desc_lang_score = G_MAXINT;
+	xml = gsf_xml_in_doc_new (theme_dtd, NULL);
+	if (!gsf_xml_in_doc_parse (xml, input, &state))
+		g_warning ("[GogTheme]: Could not parse %s", uri);
+	if (state.theme != NULL) {
+		state.theme->local_name = state.local_name;
+		state.theme->description = state.desc;
+		gog_theme_registry_add (state.theme, FALSE);
+	} else {
+		g_free (state.local_name);
+		g_free (state.desc);
+	}
+	g_free (state.lang);
+	gsf_xml_in_doc_free (xml);
+	g_object_unref (input);
+}
+
+static void
+themes_load_from_dir (char const *path)
+{
+	GDir *dir = g_dir_open (path, 0, NULL);
+	char const *d_name;
+	char *uri, *mime_type;
+
+	if (dir == NULL)
+		return;
+	while ((d_name = g_dir_read_name (dir)) != NULL) {
+		uri = g_strconcat ("file://", path, "/", d_name, NULL);
+		mime_type = go_get_mime_type (uri);
+		if (!strcmp (mime_type, "application/x-theme"))
+			theme_load_from_uri (uri);
+		g_free (mime_type);
+		g_free (uri);
+	}
+	g_dir_close (dir);
+}
+
+void
+_gog_themes_init (void)
+{
+	char *path;
+	char const *home;
+
+	build_predefined_themes ();
+
+	/* Load themes from file */
+	path = g_build_filename (go_sys_data_dir (), "themes", NULL);
+	themes_load_from_dir (path);
+	g_free (path);
+	home = getenv ("HOME");
+	path = g_strconcat (home, "/.goffice/themes", NULL);
+	themes_load_from_dir (path);
+	g_free (path);
 }
 
 void
diff --git a/goffice/graph/gog-theme.h b/goffice/graph/gog-theme.h
index 5ff8bdb..be7644f 100644
--- a/goffice/graph/gog-theme.h
+++ b/goffice/graph/gog-theme.h
@@ -31,14 +31,13 @@ G_BEGIN_DECLS
 
 GType gog_theme_get_type (void);
 
-GogTheme   *gog_theme_new        	(char const *name);
-GogTheme   *gog_theme_new_from_file     (char const *name, char const *file);
 char const *gog_theme_get_name 		(GogTheme const *theme);
+char const *gog_theme_get_local_name 	(GogTheme const *theme);
+char const *gog_theme_get_description	(GogTheme const *theme);
 void 	    gog_theme_fillin_style    	(GogTheme const *theme, GOStyle *style,
 				         GogObject const *obj, int ind,
 				         GOStyleFlag relevant_fields);
 
-void	    gog_theme_registry_add		(GogTheme *theme, gboolean is_default);
 GogTheme   *gog_theme_registry_lookup 		(char const *name);
 GSList	   *gog_theme_registry_get_theme_names	(void);
 



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