1 /*
2  * gog-theme.c :
3  *
4  * Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org)
5  * Copyright (C) 2010 Jean Brefort (jean.brefort@normalesup.org)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 
23 #include <goffice/goffice-config.h>
24 #include <goffice/goffice-priv.h>
25 #include <goffice/graph/gog-theme.h>
26 #include <gsf/gsf-input-gio.h>
27 #include <gsf/gsf-output-gio.h>
28 
29 #include <gsf/gsf-impl-utils.h>
30 #include <glib/gi18n-lib.h>
31 #include <string.h>
32 
33 /**
34  * SECTION:gog-theme
35  * @short_description: a list of default styles to apply to appropriate graph elements.
36  *
37  * The library provides two hard coded themes, "Default", and "Guppi". Other themes
38  * are described in files, some of which might be distributed with the library.
39  *
40  * A file defining a theme is an xml file with a &lt;GogTheme&gt; root node.
41  * The root element needs to have an Id which should be unique. The uuid command
42  * provides such Ids.
43  *
44  * The contents must be: _name|name+, _description?|description*, GOStyle+.
45  *
46  * _name and name nodes:
47  *
48  * The _name node should be used for themes distributed with goffice, localized
49  * names will be in *.po files and only the default name for "C" locale needs to
50  * be there. Other files need at least one name node for the default name, and
51  * might have some translated names with an appropriate "xml:lang" attribute.
52  *
53  * _description and description nodes:
54  *
55  * These work just like name nodes. The difference is that no description node is
56  * mandatory. A theme can work perfectly without a description.
57  *
58  * GOStyle nodes:
59  *
60  * These nodes actually define the theme. Attributes and contents are:
61  * node attributes: class, role.
62  * node contents: (line|outline)?, fill?, marker?, font?, text_layout?
63  *
64  * The attributes define the target for the style. You might have a class and
65  * a role attribute, or just one of them. If the role attribute is given, the class attribute,
66  * if given, represents the class of the parent object.
67  * A GOStyle node with no class or role will be used
68  * as default when another style is missing. If several such nodes exist, the
69  * last one will be used. If no default style exists, the "Default" theme is applied for
70  * missing nodes.
71  * The list of GOStyle nodes might be:
72  * <table style="border-spacing:1cm 2mm">
73  * <thead><tr><td>class</td><td>role</td><td>contents</td><td>comments</td></tr></thead>
74  * <tr><td>GogGraph</td><td></td><td>outline, fill</td></tr>
75  * <tr><td>GogGraph</td><td>Title</td><td>outline, fill, font, text_layout</td><td>The graph title</td></tr>
76  * <tr><td>GogChart</td><td></td><td>outline, fill</td></tr>
77  * <tr><td>GogChart</td><td>Title</td><td>outline, fill, font, text_layout</td><td>The chart title</td></tr>
78  * <tr><td>GogLegend</td><td></td><td>outline, fill, font</td></tr>
79  * <tr><td>GogAxis</td><td></td><td>line, font, text_layout</td></tr>
80  * <tr><td>GogAxisLine</td><td></td><td>line, font</td></tr>
81  * <tr><td>GogGrid</td><td></td><td>outline, fill</td><td>GogGrid is actually the back plane</td></tr>
82  * <tr><td> </td><td>MajorGrid</td><td>line, fill</td></tr>
83  * <tr><td> </td><td>MinorGrid</td><td>line, fill</td></tr>
84  * <tr><td>GogLabel</td><td></td><td>outline, fill, font, text_layout</td></tr>
85  * <tr><td>GogSeries</td><td></td><td>line, fill, marker</td><td>One is needed for each entry in the palette</td></tr>
86  * <tr><td>GogTrendLine</td><td></td><td>line</td></tr>
87  * <tr><td>GogRegEqn</td><td></td><td>outline, fill, font, text_layout</td></tr>
88  * <tr><td>GogSeriesLabels</td><td></td><td>outline, fill, font, text_layout</td></tr>
89  * <tr><td>GogRegEqn</td><td></td><td>outline, fill, font, text_layout</td></tr>
90  * <tr><td>GogSeriesLabel</td><td></td><td>outline, fill, font, text_layout</td></tr>
91  * <tr><td>GogDataLabel</td><td></td><td>outline, fill, font, text_layout</td></tr>
92  * <tr><td>GogEquation</td><td></td><td>outline, fill, font, text_layout</td></tr>
93  * </table>
94  *
95  * The line and outline nodes are actually the same so using line in place of outline is
96  * not an issue. A color is specified either using the format RR:GG::BB:AA or a string as defined
97  * in rgb.txt, so black can be specified as "black" or "00:00:00:FF".
98  *
99  * "line" or "outline" node:
100  *
101  * <table style="border-spacing:1cm 2mm">
102  * <thead><tr><td>attribute</td><td>value</td></tr><td>comments</td></thead>
103  * <tr><td>dash</td><td>one of "none", "solid", "s-dot", "s-dash-dot", "s-dash-dot-dot", "dash-dot-dot-dot", "dot",
104  * "s-dash", "dash", "l-dash", "dash-dot", or "dash-dot-dot"</td></tr>
105  * <tr><td>color</td><td>any color as described above</td></tr>
106  * <tr><td>width</td><td>float</td><td>not always taken into account</td></tr>
107  * </table>
108  *
109  * "fill" node
110  *
111  * contents: (pattern|gradient)?
112  * <table style="border-spacing:1cm 2mm">
113  * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
114  * <tr><td>type</td><td>one of "none", "pattern", or "gradient"</td></tr>
115  * </table>
116  *
117  * "pattern" node
118  *
119  * Should be included in the fill node if type is pattern.
120  * <table style="border-spacing:1cm 2mm">
121  * <thead><tr><td>attribute</td><td>value</td></tr></thead>
122  * <tr><td>type</td><td>one of "solid", "grey75", "grey50", "grey25", "grey12.5",
123  * "grey6.25", "horiz", "vert", "rev-diag", "diag", "diag-cross", "thick-diag-cross",
124  * "thin-horiz", "thin-vert", "thin-rev-diag", "thin-diag", "thin-horiz-cross",
125  * "thin-diag-cross", "foreground-solid", "small-circles","semi-circles", "thatch",
126  * "large-circles", or "bricks"</td></tr>
127  * <tr><td>fore</td><td>any color as described above</td></tr>
128  * <tr><td>back</td><td>any color as described above</td></tr>
129  * </table>
130  *
131  * "gradient" node
132  *
133  * Should be included in the fill node if type is gradient.
134  * <table style="border-spacing:1cm 2mm">
135  * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
136  * <tr><td>direction</td><td>one of "n-s", "s-n", "n-s-mirrored", "s-n-mirrored",
137  * "w-e", "e-w", "w-e-mirrored", "e-w-mirrored", "nw-se", "se-nw", "nw-se-mirrored",
138  * "se-nw-mirrored", "ne-sw", "sw-ne", "sw-ne-mirrored", or "ne-sw-mirrored" </td><td></td></tr>
139  * <tr><td>start_color</td><td>any color as described above</td><td></td></tr>
140  * <tr><td>brightness</td><td>float</td><td>meaningful only for monocolor gradients</td></tr>
141  * <tr><td>end_color</td><td>any color as described above</td><td>meaningful only for bicolor gradients</td></tr>
142  * </table>
143  *
144  * "marker" node
145  *
146  * <table style="border-spacing:1cm 2mm">
147  * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
148  * <tr><td>shape</td><td>one of "none", "square", "diamond", "triangle-down",
149  * "triangle-up", "triangle-right", "triangle-left", "circle", "x", "cross",
150  * "asterisk", "bar", "half-bar", "butterfly", "hourglass", or "lefthalf-bar"</td><td></td></tr>
151  * <tr><td>fill-color</td><td>any color as described above</td><td></td></tr>
152  * <tr><td>outline-color</td><td>any color as described above</td><td></td></tr>
153  * <tr><td>size</td><td>float</td><td>not always taken into account</td></tr>
154  * </table>
155  *
156  * "font" node
157  * <table style="border-spacing:1cm 2mm">
158  * <thead><tr><td>attribute</td><td>value</td></tr></thead>
159  * <tr><td>color</td><td>any color as described above</td></tr>
160  * <tr><td>font</td><td>a string describing the font such as "Sans 10"</td></tr>
161  * </table>
162  *
163  * "text_layout" node
164  * <table style="border-spacing:1cm 2mm">
165  * <thead><tr><td>attribute</td><td>value</td><td>comments</td></tr></thead>
166  * <tr><td>angle</td><td>float</td><td>expressed in degrees</td></tr>
167  * </table>
168  **/
169 
170 typedef void (*GogThemeStyleMap) (GOStyle *style, unsigned ind, GogTheme const *theme);
171 
172 typedef struct {
173 	/* If role is non-null, klass_name specifies the container class,
174 	 * If role is null, klass_name specifies object type */
175 	char 		 *klass_name;
176 	char 		 *role_id;
177 	GOStyle   	 *style;
178 	GogThemeStyleMap  map;
179 } GogThemeElement;
180 
181 struct _GogTheme {
182 	GObject	base;
183 
184 	char		*id;
185 	char 		*name;
186 	char 		*description;
187 	char		*uri;
188 	GoResourceType type;
189 	GHashTable	*names;
190 	GHashTable	*descs;
191 	GHashTable	*elem_hash_by_role;
192 	GHashTable	*elem_hash_by_class;
193 	GHashTable	*class_aliases;
194 	GOStyle	*default_style;
195 	GPtrArray	*palette;
196 	GogAxisColorMap *cm, *dcm; /* cm: color map for color axis, dcm stands for discrete color map */
197 	gboolean built_color_map; /* TRUE if color map is built from the palette */
198 	gboolean writeable; /* TRUE if theme can be edited */
199 	char		*path; /* file path if any */
200 };
201 
202 typedef GObjectClass GogThemeClass;
203 
204 static GObjectClass *parent_klass;
205 
206 static GSList	    *themes;
207 static GogTheme	    *default_theme = NULL;
208 static GHashTable   *global_class_aliases = NULL;
209 
210 enum {
211 	GOG_THEME_PROP_0,
212 	GOG_THEME_PROP_TYPE
213 };
214 
215 static void
gog_theme_set_property(GObject * gobject,guint param_id,GValue const * value,GParamSpec * pspec)216 gog_theme_set_property (GObject *gobject, guint param_id,
217                                  GValue const *value, GParamSpec *pspec)
218 {
219 	GogTheme *theme = GOG_THEME (gobject);
220 
221 	switch (param_id) {
222 	case GOG_THEME_PROP_TYPE:
223 		theme->type = g_value_get_enum (value);
224 		break;
225 
226 	default:
227 		G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
228 		return; /* NOTE : RETURN */
229 	}
230 }
231 
232 static void
gog_theme_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)233 gog_theme_get_property (GObject *gobject, guint param_id,
234                         GValue *value, GParamSpec *pspec)
235 {
236 	GogTheme *theme = GOG_THEME (gobject);
237 
238 	switch (param_id) {
239 	case GOG_THEME_PROP_TYPE:
240 		g_value_set_enum (value, theme->type);
241 		break;
242 
243 	default:
244 		G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
245 		return; /* NOTE : RETURN */
246 	}
247 }
248 
249 static void
gog_theme_element_free(GogThemeElement * elem)250 gog_theme_element_free (GogThemeElement *elem)
251 {
252 	g_object_unref (elem->style);
253 	g_free (elem->klass_name);
254 	g_free (elem->role_id);
255 	g_free (elem);
256 }
257 
258 static guint
gog_theme_element_hash(GogThemeElement const * elem)259 gog_theme_element_hash (GogThemeElement const *elem)
260 {
261 	return g_str_hash (elem->role_id);
262 }
263 
264 static gboolean
gog_theme_element_eq(GogThemeElement const * a,GogThemeElement const * b)265 gog_theme_element_eq (GogThemeElement const *a, GogThemeElement const *b)
266 {
267 	if (!g_str_equal (a->role_id, b->role_id))
268 		return FALSE;
269 	if (a->klass_name == NULL)
270 		return b->klass_name == NULL;
271 	if (b->klass_name == NULL)
272 		return FALSE;
273 	return g_str_equal (a->klass_name, b->klass_name);
274 }
275 
276 static void
gog_theme_finalize(GObject * obj)277 gog_theme_finalize (GObject *obj)
278 {
279 	GogTheme *theme = GOG_THEME (obj);
280 
281 	themes = g_slist_remove (themes, theme);
282 
283 	g_free (theme->id);
284 	g_free (theme->name);
285 	g_free (theme->uri);
286 	g_free (theme->description);
287 	if (theme->names)
288 		g_hash_table_destroy (theme->names);
289 	if (theme->descs)
290 		g_hash_table_destroy (theme->descs);
291 	if (theme->elem_hash_by_role)
292 		g_hash_table_destroy (theme->elem_hash_by_role);
293 	if (theme->elem_hash_by_class)
294 		g_hash_table_destroy (theme->elem_hash_by_class);
295 	if (theme->class_aliases)
296 		g_hash_table_destroy (theme->class_aliases);
297 	if (theme->palette) {
298 		unsigned i;
299 		for (i = 0; i < theme->palette->len; i++)
300 			g_object_unref (g_ptr_array_index (theme->palette, i));
301 		g_ptr_array_free (theme->palette, TRUE);
302 	}
303 	if (theme->cm)
304 		g_object_unref (theme->cm);
305 	if (theme->dcm)
306 		g_object_unref (theme->dcm);
307 
308 	(parent_klass->finalize) (obj);
309 }
310 
311 static void
gog_theme_class_init(GogThemeClass * klass)312 gog_theme_class_init (GogThemeClass *klass)
313 {
314 	GObjectClass *gobject_klass   = (GObjectClass *) klass;
315 
316 	parent_klass = g_type_class_peek_parent (klass);
317 	gobject_klass->finalize	    = gog_theme_finalize;
318 	gobject_klass->set_property = gog_theme_set_property;
319 	gobject_klass->get_property = gog_theme_get_property;
320 	g_object_class_install_property (gobject_klass, GOG_THEME_PROP_TYPE,
321 		g_param_spec_enum ("resource-type",
322 			_("Resource type"),
323 			_("The resource type for the theme"),
324 			go_resource_type_get_type (), GO_RESOURCE_INVALID,
325 		        GSF_PARAM_STATIC | G_PARAM_READWRITE |G_PARAM_CONSTRUCT_ONLY));
326 }
327 
328 static void
gog_theme_init(GogTheme * theme)329 gog_theme_init (GogTheme *theme)
330 {
331 	theme->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
332 	theme->descs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
333 	theme->elem_hash_by_role =
334 		g_hash_table_new_full ((GHashFunc) gog_theme_element_hash,
335 				       (GCompareFunc) gog_theme_element_eq,
336 					NULL, (GDestroyNotify) gog_theme_element_free);
337 
338 	theme->elem_hash_by_class =
339 		g_hash_table_new_full (g_str_hash, g_str_equal,
340 				       NULL, (GDestroyNotify) gog_theme_element_free);
341 
342 	theme->class_aliases =
343 		g_hash_table_new (g_str_hash, g_str_equal);
344 }
345 
346 /***************
347  * Output code *
348  ***************/
349 
350 static void
save_name_cb(char const * lang,char const * name,GsfXMLOut * output)351 save_name_cb (char const *lang, char const *name, GsfXMLOut *output)
352 {
353 	gsf_xml_out_start_element (output, "name");
354 	if (strcmp (lang, "C"))
355 		gsf_xml_out_add_cstr_unchecked (output, "xml:lang", lang);
356 	gsf_xml_out_add_cstr_unchecked (output, NULL, name);
357 	gsf_xml_out_end_element (output);
358 }
359 
360 static void
save_desc_cb(char const * lang,char const * name,GsfXMLOut * output)361 save_desc_cb (char const *lang, char const *name, GsfXMLOut *output)
362 {
363 	gsf_xml_out_start_element (output, "description");
364 	if (strcmp (lang, "C"))
365 		gsf_xml_out_add_cstr_unchecked (output, "xml:lang", lang);
366 	gsf_xml_out_add_cstr_unchecked (output, NULL, name);
367 	gsf_xml_out_end_element (output);
368 }
369 
370 static void
save_elem_cb(G_GNUC_UNUSED char const * key,GogThemeElement * elt,GsfXMLOut * output)371 save_elem_cb (G_GNUC_UNUSED char const *key, GogThemeElement *elt, GsfXMLOut *output)
372 {
373 	if (elt->klass_name && !strcmp (elt->klass_name, "GogSeries"))
374 		return;
375 	gsf_xml_out_start_element (output, "GOStyle");
376 	if (elt->klass_name)
377 		gsf_xml_out_add_cstr_unchecked (output, "class", elt->klass_name);
378 	if (elt->role_id)
379 		gsf_xml_out_add_cstr_unchecked (output, "role", elt->role_id);
380 	go_persist_sax_save (GO_PERSIST (elt->style), output);
381 	gsf_xml_out_end_element (output);
382 }
383 
384 static void
save_series_style_cb(GOPersist * gp,GsfXMLOut * output)385 save_series_style_cb (GOPersist *gp, GsfXMLOut *output)
386 {
387 	gsf_xml_out_start_element (output, "GOStyle");
388 	gsf_xml_out_add_cstr_unchecked (output, "class", "GogSeries");
389 	go_persist_sax_save (gp, output);
390 	gsf_xml_out_end_element (output);
391 }
392 
393 static void gog_theme_save (GogTheme const *theme);
394 
395 static void
gog_theme_build_uri(GogTheme * theme)396 gog_theme_build_uri (GogTheme *theme)
397 {
398 	char *filename, *full_name;
399 	filename = g_strconcat (theme->id, ".theme", NULL);
400 	full_name = g_build_filename (g_get_home_dir (), ".goffice", "themes", filename, NULL);
401 	theme->uri = go_filename_to_uri (full_name);
402 	g_free (filename);
403 	g_free (full_name);
404 }
405 
406 static void
gog_theme_sax_save(GOPersist const * gp,GsfXMLOut * output)407 gog_theme_sax_save (GOPersist const *gp, GsfXMLOut *output)
408 {
409 	GogTheme *theme;
410 
411 	g_return_if_fail (GOG_IS_THEME (gp));
412 
413 	theme = GOG_THEME (gp);
414 	if (output == NULL) {
415 		g_return_if_fail (theme->uri == NULL);
416 		gog_theme_build_uri (theme);
417 		theme->type = GO_RESOURCE_RW;
418 		gog_theme_save (theme);
419 		return;
420 	}
421 	gsf_xml_out_add_cstr_unchecked (output, "id", theme->id);
422 	g_hash_table_foreach (theme->names, (GHFunc) save_name_cb, output);
423 	g_hash_table_foreach (theme->descs, (GHFunc) save_desc_cb, output);
424 	g_hash_table_foreach (theme->elem_hash_by_class, (GHFunc) save_elem_cb, output);
425 	g_hash_table_foreach (theme->elem_hash_by_role, (GHFunc) save_elem_cb, output);
426 	if (theme->palette)
427 		g_ptr_array_foreach (theme->palette, (GFunc) save_series_style_cb, output);
428 	if (theme->cm && gog_axis_color_map_get_resource_type (theme->cm) == GO_RESOURCE_CHILD) {
429 		gsf_xml_out_start_element (output, "GogAxisColorMap");
430 		gsf_xml_out_add_cstr_unchecked (output, "type", (theme->cm == theme->dcm)? "both": "gradient");
431 		go_persist_sax_save (GO_PERSIST (theme->cm), output);
432 		gsf_xml_out_end_element (output);
433 	}
434 	if (theme->dcm && theme->dcm != theme->cm && gog_axis_color_map_get_resource_type (theme->dcm) == GO_RESOURCE_CHILD) {
435 		gsf_xml_out_start_element (output, "GogAxisColorMap");
436 		gsf_xml_out_add_cstr_unchecked (output, "type", "discrete");
437 		go_persist_sax_save (GO_PERSIST (theme->dcm), output);
438 		gsf_xml_out_end_element (output);
439 	}
440 }
441 
442 static void
gog_theme_save(GogTheme const * theme)443 gog_theme_save (GogTheme const *theme)
444 {
445 	GsfOutput *output = gsf_output_gio_new_for_uri (theme->uri, NULL);
446 	GsfXMLOut *xml = gsf_xml_out_new (output);
447 	gsf_xml_out_start_element (xml, "GogTheme");
448 	gog_theme_sax_save (GO_PERSIST (theme), xml);
449 	gsf_xml_out_end_element (xml);
450 	g_object_unref (xml);
451 	g_object_unref (output);
452 }
453 
454 /**
455  * gog_theme_save_to_home_dir:
456  * @theme: the #GogTheme to save
457  *
458  * Writes the theme to the user directory so that it becomes permanently
459  * available.
460  **/
461 void
gog_theme_save_to_home_dir(GogTheme * theme)462 gog_theme_save_to_home_dir (GogTheme *theme)
463 {
464 	g_return_if_fail (GOG_IS_THEME (theme) && theme->type == GO_RESOURCE_EXTERNAL && theme->uri == NULL);
465 	gog_theme_build_uri (theme);
466 	gog_theme_save (theme);
467 	theme->type = GO_RESOURCE_RW;
468 }
469 
470 /**************
471  * Input code *
472  **************/
473 
474 struct theme_load_state {
475 	GogTheme *theme;
476 	char *name, *desc, *lang;
477 	unsigned name_lang_score;
478 	unsigned desc_lang_score;
479 	char const * const *langs;
480 	GSList *garbage;
481 };
482 
483 static void
theme_start(GsfXMLIn * xin,xmlChar const ** attrs)484 theme_start (GsfXMLIn *xin, xmlChar const **attrs)
485 {
486 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
487 	if (state->theme ==  NULL) {
488 		state->theme = g_object_new (GOG_TYPE_THEME, NULL);
489 		for (; attrs && *attrs; attrs +=2)
490 			if (!strcmp ((char const *) *attrs, "id")) {
491 				state->theme->id = g_strdup ((char const *) attrs[1]);
492 				break;
493 			}
494 	}
495 }
496 
497 static void
name_start(GsfXMLIn * xin,xmlChar const ** attrs)498 name_start (GsfXMLIn *xin, xmlChar const **attrs)
499 {
500 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
501 	unsigned i;
502 	if (state->theme->name) /* the theme has already been loaded from elsewhere */
503 		return;
504 	for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2)
505 		if (0 == strcmp (attrs[i], "xml:lang"))
506 			state->lang = g_strdup (attrs[i+1]);
507 }
508 
509 static void
name_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)510 name_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
511 {
512 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
513 	char *name = NULL;
514 	if (state->theme->name) /* the theme has already been loaded from elsewhere */
515 		return;
516 	if (xin->content->str == NULL)
517 		return;
518 	name = g_strdup (xin->content->str);
519 	if (state->lang == NULL)
520 	state->lang = g_strdup ("C");
521 	if (state->name_lang_score > 0 && state->langs[0] != NULL) {
522 		unsigned i;
523 		for (i = 0; i < state->name_lang_score && state->langs[i] != NULL; i++) {
524 			if (strcmp (state->langs[i], state->lang) == 0) {
525 				g_free (state->name);
526 				state->name = g_strdup (name);
527 				state->name_lang_score = i;
528 			}
529 		}
530 	}
531 	g_hash_table_replace (state->theme->names, state->lang, name);
532 	state->lang = NULL;
533 }
534 
535 static void
desc_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)536 desc_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
537 {
538 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
539 	char *desc = NULL;
540 	if (state->theme->name) /* the theme has already been loaded from elsewhere */
541 		return;
542 	if (xin->content->str == NULL)
543 		return;
544 	desc = g_strdup (xin->content->str);
545 	if (state->lang == NULL)
546 	state->lang = g_strdup ("C");
547 	if (state->desc_lang_score > 0 && state->langs[0] != NULL) {
548 		unsigned i;
549 		for (i = 0; i < state->desc_lang_score && state->langs[i] != NULL; i++) {
550 			if (strcmp (state->langs[i], state->lang) == 0) {
551 				g_free (state->desc);
552 				state->desc = g_strdup (desc);
553 				state->desc_lang_score = i;
554 			}
555 		}
556 	}
557 	g_hash_table_replace (state->theme->descs, state->lang, desc);
558 	state->lang = NULL;
559 }
560 
561 enum { /* used in theme editor to display the right snapshot */
562 	SNAPSHOT_GRAPH,
563 	SNAPSHOT_TRENDLINE,
564 	SNAPSHOT_SERIES,
565 	SNAPSHOT_SERIESLABELS,
566 	SNAPSHOT_SERIESLINES,
567 #ifdef GOFFICE_WITH_LASEM
568 	SNAPSHOT_EQUATION,
569 #endif
570 };
571 
572 typedef struct {
573 	char const *klass_name;
574 	char const *role_id;
575 	char const *label;
576 	GOStyleFlag flags;
577 	unsigned snapshot;
578 } GogThemeRoles;
579 
580 static GogThemeRoles roles[] = {
581 	{"GogGraph", NULL, N_("Graph"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH},
582 	{"GogGraph", "Title", N_("Graph title"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH},
583 	{"GogChart", NULL, N_("Chart"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH},
584 	{"GogChart", "Title", N_("Chart title"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH},
585 	{"GogLegend", NULL, N_("Legend"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT, SNAPSHOT_GRAPH},
586 	{"GogAxis", NULL, N_("Axis"), GO_STYLE_LINE | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH},
587 	{"GogAxisLine", NULL, N_("Axis line"), GO_STYLE_LINE | GO_STYLE_FONT, SNAPSHOT_GRAPH},
588 	{"GogGrid", NULL, N_("Backplane"), GO_STYLE_FILL | GO_STYLE_OUTLINE, SNAPSHOT_GRAPH},
589 	{"GogGrid", "MajorGrid", N_("Major grid"), GO_STYLE_LINE | GO_STYLE_FILL, SNAPSHOT_GRAPH},
590 	{"GogGrid", "MinorGrid", N_("Minor grid"), GO_STYLE_LINE | GO_STYLE_FILL, SNAPSHOT_GRAPH},
591 	{"GogLabel", NULL, N_("Label"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_GRAPH},
592 	{"GogTrendLine", NULL, N_("Trend line"), GO_STYLE_LINE, SNAPSHOT_TRENDLINE},
593 	{"GogRegEqn", NULL, N_("Regression equation"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_TRENDLINE},
594 #ifdef GOFFICE_WITH_LASEM
595 	{"GogEquation", NULL, N_("Equation"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_EQUATION},
596 #endif
597 	{"GogSeriesLine", NULL, N_("Series lines"), GO_STYLE_LINE, SNAPSHOT_SERIESLINES},
598 	{"GogSeries", NULL, N_("Series"), GO_STYLE_LINE | GO_STYLE_FILL | GO_STYLE_MARKER, SNAPSHOT_SERIES},
599 	{"GogSeriesLabels", NULL, N_("Series labels"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_SERIESLABELS},
600 	{"GogDataLabel", NULL, N_("Data label"), GO_STYLE_OUTLINE | GO_STYLE_FILL | GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT, SNAPSHOT_SERIESLABELS},
601 	{"GogColorScale", NULL, N_("Color scale"), GO_STYLE_OUTLINE, SNAPSHOT_GRAPH}
602 };
603 
604 static void
gog_theme_add_element(GogTheme * theme,GOStyle * style,GogThemeStyleMap map,char * klass_name,char * role_id)605 gog_theme_add_element (GogTheme *theme, GOStyle *style,
606 		       GogThemeStyleMap	map,
607 		       char *klass_name, char *role_id)
608 {
609 	GogThemeElement *elem;
610 	unsigned i;
611 
612 	g_return_if_fail (theme != NULL);
613 
614 	elem = g_new0 (GogThemeElement, 1);
615 	elem->klass_name = klass_name;
616 	elem->role_id  = role_id;
617 	elem->style = style;
618 	elem->map   = map;
619 
620 	/* sets the needed insteresting_fields in style */
621 	for (i = 0; i < G_N_ELEMENTS (roles); i++) {
622 		if ((klass_name && roles[i].klass_name && strcmp (klass_name, roles[i].klass_name))
623 		    || (klass_name != NULL && roles[i].klass_name == NULL))
624 			continue;
625 		if ((role_id && roles[i].role_id && strcmp (role_id, roles[i].role_id))
626 		    || (role_id == NULL && roles[i].role_id != NULL) ||
627 		    (role_id != NULL && roles[i].role_id == NULL))
628 			continue;
629 		style->interesting_fields = roles[i].flags;
630 		break;
631 	}
632 	if (i == G_N_ELEMENTS (roles))
633 		g_warning ("[GogTheme]: unregistered style class=%s role=%s\n",klass_name, role_id);
634 	/* Never put an element into both by_role_id & by_class_name */
635 	if (role_id != NULL) {
636 		if (g_hash_table_lookup (theme->elem_hash_by_role, (gpointer)elem) == NULL)
637 			g_hash_table_insert (theme->elem_hash_by_role,
638 				(gpointer)elem, elem);
639 		else {
640 			g_object_unref (style);
641 			g_free (elem);
642 		}
643 	} else if (klass_name != NULL) {
644 		if (g_hash_table_lookup (theme->elem_hash_by_class, (gpointer)klass_name) == NULL)
645 			g_hash_table_insert (theme->elem_hash_by_class,
646 				(gpointer)klass_name, elem);
647 		else {
648 			g_object_unref (style);
649 			g_free (elem);
650 		}
651 
652 	} else {
653 		if (theme->default_style)
654 			g_object_unref (theme->default_style);
655 		theme->default_style = style;
656 		g_free (elem);
657 	}
658 }
659 
660 static void
elem_start(GsfXMLIn * xin,G_GNUC_UNUSED xmlChar const ** attrs)661 elem_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
662 {
663 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
664 	char *role = NULL, *class_name = NULL;
665 	GOStyle *style;
666 	unsigned i;
667 
668 	if (state->theme == NULL)
669 		return;
670 	if (state->theme->name) { /* the theme has already been loaded from elsewhere */
671 		style = go_style_new (); /* still load the style to avoid warnings */
672 		go_persist_prep_sax (GO_PERSIST (style), xin, attrs);
673 		state->garbage = g_slist_prepend (state->garbage, style);
674 		return;
675 	}
676 	for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2)
677 		if (0 == strcmp (attrs[i], "class"))
678 			class_name = g_strdup (attrs[i+1]);
679 		else if (0 == strcmp (attrs[i], "role"))
680 			role = g_strdup (attrs[i+1]);
681 	style = go_style_new ();
682 	go_style_clear_auto (style);
683 	go_persist_prep_sax (GO_PERSIST (style), xin, attrs);
684 
685 	if (class_name && !strcmp (class_name, "GogSeries")) {
686 		if (state->theme->palette ==  NULL)
687 			state->theme->palette = g_ptr_array_new ();
688 		g_ptr_array_add (state->theme->palette, style);
689 		g_free (class_name);
690 	} else
691 		gog_theme_add_element (state->theme, style, NULL, class_name, role);
692 }
693 
694 static void
color_map_start(GsfXMLIn * xin,G_GNUC_UNUSED xmlChar const ** attrs)695 color_map_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
696 {
697 	struct theme_load_state	*state = (struct theme_load_state *) xin->user_state;
698 	GogAxisColorMap *map;
699 	if (state->theme == NULL)
700 		return;
701 	if (state->theme->name) /* the theme has already been loaded from elsewhere */
702 		return;
703 	map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, "resource-type", GO_RESOURCE_CHILD, NULL);
704 	for (; attrs && *attrs; attrs += 2)
705 		if (!strcmp ((char const *) *attrs, "type")) {
706 			if (strcmp ((char const *) attrs[1], "discrete")) {
707 				if (state->theme->cm != NULL) {
708 					g_warning ("[GogTheme]: extra GogAxisColorMap found.");
709 					g_object_unref (map);
710 					return;
711 				}
712 				state->theme->cm =  map;
713 				if (strcmp ((char const *) attrs[1], "both"))
714 					return;
715 				g_object_ref (map);
716 			}
717 			if (state->theme->dcm != NULL) {
718 				g_warning ("[GogTheme]: extra GogAxisColorMap found.");
719 				g_object_unref (map);
720 				return;
721 			}
722 			state->theme->dcm = map;
723 		}
724 }
725 
726 static GsfXMLInNode const theme_dtd[] = {
727 	GSF_XML_IN_NODE (THEME, THEME, -1, "GogTheme", GSF_XML_NO_CONTENT, theme_start, NULL),
728 		GSF_XML_IN_NODE (THEME, NAME, -1, "name", GSF_XML_CONTENT, name_start, name_end),
729 		GSF_XML_IN_NODE (THEME, UNAME, -1, "_name", GSF_XML_CONTENT, name_start, name_end),
730 		GSF_XML_IN_NODE (THEME, DESCRIPTION, -1, "description", GSF_XML_CONTENT, name_start, desc_end),
731 		GSF_XML_IN_NODE (THEME, UDESCRIPTION, -1, "_description", GSF_XML_CONTENT, name_start, desc_end),
732 		GSF_XML_IN_NODE (THEME, STYLE, -1, "GOStyle", GSF_XML_NO_CONTENT, elem_start, NULL),
733 		GSF_XML_IN_NODE (THEME, COLORMAP, -1, "GogAxisColormap", GSF_XML_NO_CONTENT, color_map_start, NULL),
734 	GSF_XML_IN_NODE_END
735 };
736 static GsfXMLInDoc *xml = NULL;
737 
738 static void map_area_series_solid_palette (GOStyle *, unsigned , GogTheme const *);
739 static void map_area_series_solid_default (GOStyle *, unsigned , GogTheme const *);
740 static void
theme_loaded(struct theme_load_state * state)741 theme_loaded (struct theme_load_state *state)
742 {
743 	/* initialize a dummy GogSeries style */
744 	GOStyle *style = go_style_new ();
745 	go_style_clear_auto (style);
746 	style->line.dash_type = GO_LINE_SOLID;
747 	style->line.width = 0; /* hairline */
748 	style->line.color = GO_COLOR_BLACK;
749 	style->fill.type = GO_STYLE_FILL_PATTERN;
750 	if (state->theme->palette && state->theme->palette->len > 0)
751 		gog_theme_add_element (state->theme, style,
752 			   map_area_series_solid_palette, g_strdup ("GogSeries"), NULL);
753 	else
754 		gog_theme_add_element (state->theme, style,
755 			   map_area_series_solid_default, g_strdup ("GogSeries"), NULL);
756 	if (state->theme->dcm == NULL) {
757 		if (state->theme->palette) {
758 			GOColor *colors = g_new (GOColor, state->theme->palette->len);
759 			unsigned i;
760 			for (i = 0; i < state->theme->palette->len; i++)
761 				colors[i] = GO_STYLE (g_ptr_array_index (state->theme->palette, i))->fill.pattern.back;
762 			state->theme->dcm = gog_axis_color_map_from_colors ("Default",
763 				                                                state->theme->palette->len,
764 				                                                colors,
765 				                                                GO_RESOURCE_GENERATED);
766 			g_free (colors);
767 		} else {
768 			GogTheme *theme = gog_theme_registry_lookup ("Default");
769 			state->theme->dcm = g_object_ref (theme->dcm);
770 		}
771 	}
772 	state->theme->name = state->name;
773 	state->theme->description = state->desc;
774 }
775 
776 static void
parse_done_cb(GsfXMLIn * xin,struct theme_load_state * state)777 parse_done_cb (GsfXMLIn *xin, struct theme_load_state *state)
778 {
779 	if (state->theme->name == NULL)
780 		theme_loaded (state);
781 	else {
782 		g_free (state->name);
783 		g_free (state->desc);
784 	}
785 	g_free (state->lang);
786 	g_slist_free_full (state->garbage, g_object_unref);
787 	g_free (state);
788 }
789 
790 static void
gog_theme_prep_sax(GOPersist * gp,GsfXMLIn * xin,xmlChar const ** attrs)791 gog_theme_prep_sax (GOPersist *gp, GsfXMLIn *xin, xmlChar const **attrs)
792 {
793 	struct theme_load_state *state;
794 
795 	g_return_if_fail (GOG_IS_THEME (gp));
796 
797 	state = g_new (struct theme_load_state, 1);
798 	state->theme = GOG_THEME (gp);
799 	state->name = NULL;
800 	state->desc = NULL;
801 	state->lang = NULL;
802 	state->langs = g_get_language_names ();
803 	state->name_lang_score = G_MAXINT;
804 	state->desc_lang_score = G_MAXINT;
805 	state->garbage = NULL;
806 	if (!xml)
807 		xml = gsf_xml_in_doc_new (theme_dtd, NULL);
808 	gsf_xml_in_push_state (xin, xml, state, (GsfXMLInExtDtor) parse_done_cb, attrs);
809 }
810 
811 static void
gog_theme_persist_init(GOPersistClass * iface)812 gog_theme_persist_init (GOPersistClass *iface)
813 {
814 	iface->prep_sax = gog_theme_prep_sax;
815 	iface->sax_save = gog_theme_sax_save;
816 }
817 
818 GSF_CLASS_FULL (GogTheme, gog_theme,
819                 NULL, NULL, gog_theme_class_init, NULL,
820                 gog_theme_init, G_TYPE_OBJECT, 0,
821                 GSF_INTERFACE (gog_theme_persist_init, GO_TYPE_PERSIST))
822 
823 
824 static GogThemeElement *
gog_theme_find_element(GogTheme const * theme,GogObject const * obj)825 gog_theme_find_element (GogTheme const *theme, GogObject const *obj)
826 {
827 	GogThemeElement *elem = NULL;
828 	GObjectClass *klass = NULL;	/* make gcc happy */
829 	char const *name;
830 
831 	if (theme == NULL)
832 		theme = default_theme;
833 	g_return_val_if_fail (theme != NULL, NULL);
834 
835 	/* FIXME: Restore hash search caching. */
836 
837 	/* Search by role */
838 	if (elem == NULL && obj->role != NULL && obj->parent != NULL) {
839 		GogThemeElement key;
840 
841 		/* Search by specific role */
842 		key.role_id = (char *) obj->role->id;
843 		key.klass_name = (char *) G_OBJECT_TYPE_NAME (obj->parent);
844 		elem = g_hash_table_lookup (theme->elem_hash_by_role, &key);
845 	}
846 
847 	if (elem == NULL && obj->role != NULL) {
848 		GogThemeElement key;
849 
850 		/* Search by generic role */
851 		key.role_id = (char *) obj->role->id;
852 			key.klass_name = NULL;
853 			elem = g_hash_table_lookup (theme->elem_hash_by_role, &key);
854 	}
855 
856 	/* Still not found ? Search by object type */
857 	if (elem == NULL) {
858 		klass = G_OBJECT_GET_CLASS (obj);
859 		do {
860 			name = G_OBJECT_CLASS_NAME (klass);
861 
862 			elem = g_hash_table_lookup (	/* Is the type known ? */
863 				theme->elem_hash_by_class, name);
864 			if (elem == NULL) {		/* Is this a local alias ? */
865 				name = g_hash_table_lookup (
866 					theme->class_aliases, name);
867 				if (name != NULL)
868 					elem = g_hash_table_lookup (
869 						theme->elem_hash_by_class, name);
870 			}
871 			if (elem == NULL &&
872 			    global_class_aliases != NULL) { /* Is this a global alias */
873 				name = g_hash_table_lookup (
874 					global_class_aliases, G_OBJECT_CLASS_NAME (klass));
875 				if (name != NULL)
876 					elem = g_hash_table_lookup (
877 						theme->elem_hash_by_class, name);
878 			}
879 		} while (elem == NULL &&
880 			 (klass = g_type_class_peek_parent (klass)) != NULL);
881 	}
882 
883 	/* still not found, use default theme */
884 	if (elem == NULL && theme != default_theme)
885 		elem = gog_theme_find_element (default_theme, obj);
886 
887 	return elem;
888 }
889 
890 /**
891  * gog_theme_fillin_style :
892  * @theme: #GogTheme
893  * @style: #GOStyle to initialize
894  * @obj: #GogObject The object associated with @style
895  * @ind: an optional index
896  * @relevant_fields: GOStyleFlag
897  *
898  * Fill in the auto aspects of @style based on @theme 's element for objects of
899  * type/role similar to @obj with index @ind.  If @relevant_fields is GO_STYLE_ALL,
900  * fillin the entire style, not just the auto portions included in @relevant_fields.
901  **/
902 void
gog_theme_fillin_style(GogTheme const * theme,GOStyle * style,GogObject const * obj,int ind,GOStyleFlag relevant_fields)903 gog_theme_fillin_style (GogTheme const *theme,
904 			GOStyle *style,
905 			GogObject const *obj,
906 			int ind,
907 			GOStyleFlag relevant_fields)
908 {
909 	GogThemeElement *elem = gog_theme_find_element (theme, obj);
910 
911 	g_return_if_fail (elem != NULL);
912 
913 	if (relevant_fields == GO_STYLE_ALL) {
914 		go_style_assign (style, elem->style);
915 		go_style_force_auto (style);
916 	} else
917 		go_style_apply_theme (style, elem->style, relevant_fields);
918 
919 /* FIXME FIXME FIXME we should handle the applicability here not in the map */
920 	/* ensure only relevant fields are changed */
921 	if (ind >= 0 && elem->map) {
922 		/* ensure only relevant fields are changed */
923 		GOStyleFlag flags = style->disable_theming;
924 		style->disable_theming = GO_STYLE_ALL ^ relevant_fields;
925 		(elem->map) (style, ind, theme);
926 		style->disable_theming = flags;
927 	}
928 }
929 
930 static GogTheme *
gog_theme_new(char const * name,GoResourceType type)931 gog_theme_new (char const *name, GoResourceType type)
932 {
933 	GogTheme *theme = g_object_new (GOG_TYPE_THEME, NULL);
934 	theme->name = g_strdup (_(name));
935 	if (type == GO_RESOURCE_NATIVE)
936 		theme->id = g_strdup (name);
937 	theme->type = type;
938 	return theme;
939 }
940 
941 #if 0
942 void
943 gog_theme_register_file (char const *name, char const *file)
944 {
945 	GogTheme *theme = gog_theme_new (name);
946 	theme->load_from_file = g_strdup (file);
947 }
948 
949 static void
950 gog_theme_add_alias (GogTheme *theme, char const *from, char const *to)
951 {
952 	g_hash_table_insert (theme->class_aliases, (gpointer)from, (gpointer)to);
953 }
954 #endif
955 
956 /**
957  * gog_theme_get_id:
958  * @theme: a #GogTheme
959  *
960  * Retrieves the theme Id.
961  * Returns: the GogTheme Id.
962  **/
963 char const *
gog_theme_get_id(GogTheme const * theme)964 gog_theme_get_id (GogTheme const *theme)
965 {
966 	g_return_val_if_fail (GOG_IS_THEME (theme), "");
967 	return theme->id;
968 }
969 
970 /**
971  * gog_theme_get_name:
972  * @theme: a #GogTheme
973  *
974  * Returns: the GogTheme name.
975  **/
976 
977 char const *
gog_theme_get_name(GogTheme const * theme)978 gog_theme_get_name (GogTheme const *theme)
979 {
980 	g_return_val_if_fail (GOG_IS_THEME (theme), "");
981 	return theme->name;
982 }
983 
984 /**
985  * gog_theme_get_descrition:
986  * @theme: a #GogTheme
987  *
988  * Returns: the localized GogTheme decription.
989  **/
990 
991 char const *
gog_theme_get_description(GogTheme const * theme)992 gog_theme_get_description (GogTheme const *theme)
993 {
994 	g_return_val_if_fail (GOG_IS_THEME (theme), "");
995 	return theme->description;
996 }
997 
998 /**
999  * gog_themep_get_resource_type:
1000  * @theme: a #GogTheme
1001  *
1002  * Retrieves the resource type for @theme.
1003  * Returns: the resource type.
1004  **/
1005 GoResourceType
gog_theme_get_resource_type(GogTheme const * theme)1006 gog_theme_get_resource_type (GogTheme const *theme)
1007 {
1008 	g_return_val_if_fail (GOG_IS_THEME (theme), GO_RESOURCE_INVALID);
1009 	return theme->type;
1010 }
1011 
1012 /**************************************************************************/
1013 
1014 static void
map_marker(GOStyleMark * mark,unsigned shape,unsigned palette_index,GOColor const * palette)1015 map_marker (GOStyleMark *mark, unsigned shape, unsigned palette_index,
1016 	    GOColor const *palette)
1017 {
1018 	static GOMarkerShape const shape_palette [] = {
1019 		GO_MARKER_DIAMOND,	GO_MARKER_SQUARE,
1020 		GO_MARKER_TRIANGLE_UP,	GO_MARKER_X,
1021 		GO_MARKER_ASTERISK,	GO_MARKER_CIRCLE,
1022 		GO_MARKER_CROSS,	GO_MARKER_HALF_BAR,
1023 		GO_MARKER_BAR
1024 	};
1025 
1026 	if (shape >= G_N_ELEMENTS (shape_palette))
1027 		shape %= G_N_ELEMENTS (shape_palette);
1028 
1029 	if (mark->auto_shape)
1030 		go_marker_set_shape (mark->mark, shape_palette [shape]);
1031 	if (mark->auto_outline_color)
1032 		go_marker_set_outline_color (mark->mark,
1033 			palette [palette_index]);
1034 	if (mark->auto_fill_color)
1035 		go_marker_set_fill_color (mark->mark, palette [palette_index]);
1036 }
1037 
1038 static GOColor const default_palette [] = {
1039 	0x9c9cffff, 0x9c3163ff, 0xffffceff, 0xceffffff, 0x630063ff,
1040 	0xff8080ff, 0x0063ceff, 0xceceffff, 0x000080ff, 0xff00ffff,
1041 	0xffff00ff, 0x00ffffff, 0x800080ff, 0x800000ff, 0x008080ff,
1042 	0x0000ffff, 0x00ceffff, 0xceffffff, 0xceffceff, 0xffff9cff,
1043 	0x9cceffff, 0xff9cceff, 0xce9cffff, 0xffce9cff, 0x3163ffff,
1044 	0x31ceceff, 0x9cce00ff, 0xffce00ff, 0xff9c00ff, 0xff6300ff,
1045 	0x63639cff, 0x949494ff, 0x003163ff, 0x319c63ff, 0x003100ff,
1046 	0x313100ff, 0x9c3100ff, 0x9c3163ff, 0x31319cff, 0x313131ff,
1047 	0xffffffff, 0xff0000ff, 0x00ff00ff, 0x0000ffff, 0xffff00ff,
1048 	0xff00ffff, 0x00ffffff, 0x800000ff, 0x008000ff, 0x000080ff,
1049 	0x808000ff, 0x800080ff, 0x008080ff, 0xc6c6c6ff, 0x808080ff
1050 };
1051 
1052 static void
map_area_series_solid_default(GOStyle * style,unsigned ind,G_GNUC_UNUSED GogTheme const * theme)1053 map_area_series_solid_default (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme)
1054 {
1055 	unsigned palette_index = ind;
1056 	if (palette_index >= G_N_ELEMENTS (default_palette))
1057 		palette_index %= G_N_ELEMENTS (default_palette);
1058 	if (style->fill.auto_back) {
1059 		style->fill.pattern.back = default_palette [palette_index];
1060 
1061 		/* force the brightness to reinterpolate */
1062 		if (style->fill.type == GO_STYLE_FILL_GRADIENT &&
1063 		    style->fill.gradient.brightness >= 0)
1064 			go_style_set_fill_brightness (style,
1065 				style->fill.gradient.brightness);
1066 	}
1067 
1068 	palette_index += 8;
1069 	if (palette_index >= G_N_ELEMENTS (default_palette))
1070 		palette_index -= G_N_ELEMENTS (default_palette);
1071 	if (style->line.auto_color && !(style->disable_theming & GO_STYLE_LINE))
1072 		style->line.color = default_palette [palette_index];
1073 	if (!(style->disable_theming & GO_STYLE_MARKER))
1074 		map_marker (&style->marker, ind, palette_index, default_palette);
1075 }
1076 
1077 static GOColor const guppi_palette[] = {
1078 	0xff3000ff, 0x80ff00ff, 0x00ffcfff, 0x2000ffff,
1079 	0xff008fff, 0xffbf00ff, 0x00ff10ff, 0x009fffff,
1080 	0xaf00ffff, 0xff0000ff, 0xafff00ff, 0x00ff9fff,
1081 	0x0010ffff, 0xff00bfff, 0xff8f00ff, 0x20ff00ff,
1082 	0x00cfffff, 0x8000ffff, 0xff0030ff, 0xdfff00ff,
1083 	0x00ff70ff, 0x0040ffff, 0xff00efff, 0xff6000ff,
1084 	0x50ff00ff, 0x00ffffff, 0x5000ffff, 0xff0060ff,
1085 	0xffef00ff, 0x00ff40ff, 0x0070ffff, 0xdf00ffff
1086 };
1087 
1088 static void
map_area_series_solid_guppi(GOStyle * style,unsigned ind,G_GNUC_UNUSED GogTheme const * theme)1089 map_area_series_solid_guppi (GOStyle *style, unsigned ind, G_GNUC_UNUSED GogTheme const *theme)
1090 {
1091 	unsigned palette_index = ind;
1092 	if (palette_index >= G_N_ELEMENTS (guppi_palette))
1093 		palette_index %= G_N_ELEMENTS (guppi_palette);
1094 	if (style->fill.auto_back) {
1095 		style->fill.pattern.back = guppi_palette [palette_index];
1096 		if (style->fill.type == GO_STYLE_FILL_GRADIENT &&
1097 		    style->fill.gradient.brightness >= 0)
1098 			/* force the brightness to reinterpolate */
1099 			go_style_set_fill_brightness (style,
1100 				style->fill.gradient.brightness);
1101 	}
1102 	if (style->line.auto_color && !(style->disable_theming & GO_STYLE_LINE))
1103 		style->line.color = guppi_palette [palette_index];
1104 	if (!(style->disable_theming & GO_STYLE_MARKER))
1105 		map_marker (&style->marker, ind, palette_index, guppi_palette);
1106 }
1107 
1108 static void
map_area_series_solid_palette(GOStyle * style,unsigned ind,GogTheme const * theme)1109 map_area_series_solid_palette (GOStyle *style, unsigned ind, GogTheme const *theme)
1110 {
1111 	GOStyle *src;
1112 	if (theme->palette->len == 0)
1113 		src = theme->default_style;
1114 	else {
1115 		ind %= theme->palette->len;
1116 		src = g_ptr_array_index (theme->palette, ind);
1117 	}
1118 	if (src)
1119 		go_style_apply_theme (style, src, style->interesting_fields);
1120 }
1121 
1122 /**************************************************************************/
1123 
1124 /**
1125  * gog_theme_registry_add:
1126  * @theme: a #GogTheme
1127  * @is_default: bool
1128  *
1129  * Keep a pointer to @theme in graph theme registry.
1130  * This function does not add a reference to @theme.
1131  **/
1132 static void
gog_theme_registry_add(GogTheme * theme,gboolean is_default)1133 gog_theme_registry_add (GogTheme *theme, gboolean is_default)
1134 {
1135 	g_return_if_fail (GOG_IS_THEME (theme));
1136 
1137 	/* TODO: Check for duplicated names and for already
1138 	 * registered themes */
1139 
1140 	if (is_default) {
1141 		g_object_ref (theme);
1142 		if (default_theme != NULL)
1143 			g_object_unref (default_theme);
1144 		default_theme = theme;
1145 	}
1146 
1147 	themes = g_slist_append (themes, theme);
1148 }
1149 
1150 /**
1151  * gog_theme_registry_lookup:
1152  * @name: a theme name
1153  *
1154  * Returns: (transfer none): a #GogTheme from theme registry.
1155  **/
1156 GogTheme *
gog_theme_registry_lookup(char const * name)1157 gog_theme_registry_lookup (char const *name)
1158 {
1159 	GSList *ptr;
1160 	GogTheme *theme = default_theme;
1161 
1162 	if (name != NULL) {
1163 		for (ptr = themes ; ptr != NULL ; ptr = ptr->next) {
1164 			theme = ptr->data;
1165 			if (!strcmp (theme->id, name))
1166 				return theme;
1167 		}
1168 		if (strlen (name) != 36 || name[8] != '-' || name[13] != '-' || name[18] !='-' || name[23] != '-') {
1169 			/* name does not seem to be an uuid, migth be the theme name (needed for compatibility) */
1170 			char const *found_name;
1171 			for (ptr = themes ; ptr != NULL ; ptr = ptr->next) {
1172 				theme = ptr->data;
1173 				found_name = g_hash_table_lookup (theme->names, "C");
1174 				if (found_name && !strcmp (found_name, name))
1175 					return theme;
1176 			}
1177 		}
1178 		/* create an empty theme */
1179 		theme = g_object_new (GOG_TYPE_THEME, "resource-type", GO_RESOURCE_EXTERNAL, NULL);
1180 		theme->id = g_strdup (name);
1181 		gog_theme_registry_add (theme, FALSE);
1182 	}
1183 	return theme;
1184 }
1185 
1186 /**
1187  * gog_theme_registry_get_theme_names:
1188  *
1189  * Returns: (element-type utf8) (transfer container): a newly allocated theme name list from theme registry.
1190  **/
1191 GSList *
gog_theme_registry_get_theme_names(void)1192 gog_theme_registry_get_theme_names (void)
1193 {
1194 	GogTheme *theme;
1195 	GSList *names = NULL;
1196 	GSList *ptr;
1197 
1198 	for (ptr = themes; ptr != NULL; ptr = ptr->next) {
1199 		theme = ptr->data;
1200 		names = g_slist_append (names, theme->id);
1201 	}
1202 
1203 	return names;
1204 }
1205 
1206 /**************************************************************************/
1207 
1208 static void
build_predefined_themes(void)1209 build_predefined_themes (void)
1210 {
1211 	GogTheme *theme;
1212 	GOStyle *style;
1213 
1214 	if (NULL == global_class_aliases) {
1215 		global_class_aliases = g_hash_table_new (
1216 			g_str_hash, g_str_equal);
1217 		g_hash_table_insert (global_class_aliases,
1218 			(gpointer)"GogSeriesElement", (gpointer)"GogSeries");
1219 		g_hash_table_insert (global_class_aliases,
1220 			(gpointer)"GogSeriesLines", (gpointer)"GogSeries");
1221 		g_hash_table_insert (global_class_aliases,
1222 			(gpointer)"GogSeriesLabels", (gpointer)"GogLabel");
1223 		g_hash_table_insert (global_class_aliases,
1224 			(gpointer)"GogDataLabel", (gpointer)"GogLabel");
1225 #ifdef GOFFICE_WITH_LASEM
1226 		g_hash_table_insert (global_class_aliases,
1227 			(gpointer)"GogEquation", (gpointer)"GogLabel");
1228 #endif
1229 	}
1230 
1231 	/* An MS Excel-ish theme */
1232 	theme = gog_theme_new (N_("Default"), GO_RESOURCE_NATIVE);
1233 	theme->description = g_strdup (_("An MS Excel like theme"));
1234 	gog_theme_registry_add (theme, TRUE);
1235 
1236 	/* graph */
1237 	style = go_style_new ();
1238 	go_style_clear_auto (style);
1239 	style->line.dash_type = GO_LINE_NONE;
1240 	style->line.width = 0;
1241 	style->line.color = GO_COLOR_BLACK;
1242 	style->fill.type = GO_STYLE_FILL_NONE;
1243 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1244 	gog_theme_add_element (theme, style, NULL, g_strdup ("GogGraph"), NULL);
1245 
1246 	/* chart */
1247 	style = go_style_new ();
1248 	go_style_clear_auto (style);
1249 	style->line.dash_type = GO_LINE_SOLID;
1250 	style->line.width = 0; /* hairline */
1251 	style->line.color = GO_COLOR_BLACK;
1252 	style->fill.type = GO_STYLE_FILL_PATTERN;
1253 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1254 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogChart"), NULL);
1255 
1256 	/* Legend */
1257 	style = go_style_new ();
1258 	go_style_clear_auto (style);
1259 	style->line.dash_type = GO_LINE_SOLID;
1260 	style->line.width = 0; /* hairline */
1261 	style->line.color = GO_COLOR_BLACK;
1262 	style->fill.type = GO_STYLE_FILL_PATTERN;
1263 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1264 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogLegend"), NULL);
1265 
1266 	/* Axis */
1267 	style = go_style_new ();
1268 	go_style_clear_auto (style);
1269 	style->line.width = 0; /* hairline */
1270 	style->line.color = GO_COLOR_BLACK;
1271 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogAxis"), NULL);
1272 
1273 	/* AxisLine */
1274 	style = go_style_new ();
1275 	go_style_clear_auto (style);
1276 	style->line.width = 0; /* hairline */
1277 	style->line.color = GO_COLOR_BLACK;
1278 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogAxisLine"), NULL);
1279 
1280 	/* Grid */
1281 	style = go_style_new ();
1282 	go_style_clear_auto (style);
1283 	style->fill.type  = GO_STYLE_FILL_PATTERN;
1284 	style->line.dash_type = GO_LINE_SOLID;
1285 	style->line.width = 1.;
1286 	style->line.color = 0X848284ff;
1287 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0xd0));
1288 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogGrid"), NULL);
1289 
1290 	/* GridLine */
1291 	style = go_style_new ();
1292 	go_style_clear_auto (style);
1293 	style->line.dash_type = GO_LINE_SOLID;
1294 	style->line.width = 0.4;
1295 	style->line.color = GO_COLOR_BLACK;
1296 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0));
1297 	style->fill.type = GO_STYLE_FILL_NONE;
1298 	gog_theme_add_element (theme, style, NULL, NULL,  g_strdup ("MajorGrid"));
1299 	style = go_style_new ();
1300 	go_style_clear_auto (style);
1301 	style->line.dash_type = GO_LINE_SOLID;
1302 	style->line.width = 0.2;
1303 	style->line.color = GO_COLOR_BLACK;
1304 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0));
1305 	style->fill.type = GO_STYLE_FILL_NONE;
1306 	gog_theme_add_element (theme, style, NULL, NULL,  g_strdup ("MinorGrid"));
1307 
1308 	/* Series */
1309 	style = go_style_new ();
1310 	go_style_clear_auto (style);
1311 	style->line.dash_type = GO_LINE_SOLID;
1312 	style->line.width = 0; /* hairline */
1313 	style->line.color = GO_COLOR_BLACK;
1314 	style->fill.type = GO_STYLE_FILL_PATTERN;
1315 	/* FIXME : not really true, will want to split area from line */
1316 	gog_theme_add_element (theme, style,
1317 		map_area_series_solid_default,  g_strdup ("GogSeries"), NULL);
1318 
1319 	/* Chart titles */
1320 	style = go_style_new ();
1321 	go_style_clear_auto (style);
1322 	style->line.dash_type = GO_LINE_NONE;
1323 	style->line.width = 0.;
1324 	style->line.color = GO_COLOR_BLACK;
1325 	style->fill.type = GO_STYLE_FILL_NONE;
1326 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1327 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans Bold 12"));
1328 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogChart"), g_strdup ("Title"));
1329 
1330 	/* labels */
1331 	style = go_style_new ();
1332 	go_style_clear_auto (style);
1333 	style->line.dash_type = GO_LINE_NONE;
1334 	style->line.width = 0.;
1335 	style->line.color = GO_COLOR_BLACK;
1336 	style->fill.type = GO_STYLE_FILL_NONE;
1337 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1338 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10"));
1339 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogLabel"), NULL);
1340 
1341 	/* regression curves */
1342 	style = go_style_new ();
1343 	go_style_clear_auto (style);
1344 	style->line.dash_type = GO_LINE_SOLID;
1345 	style->line.width = 1;
1346 	style->line.color = GO_COLOR_BLACK;
1347 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0x00, 0x00, 0x00, 0x20));
1348 	style->fill.type = GO_STYLE_FILL_NONE;
1349 	gog_theme_add_element (theme, style,
1350 		NULL,  g_strdup ("GogTrendLine"), NULL);
1351 
1352 	/* regression equations */
1353 	style = go_style_new ();
1354 	go_style_clear_auto (style);
1355 	style->line.dash_type = GO_LINE_SOLID;
1356 	style->line.width = 0; /* hairline */
1357 	style->line.color = GO_COLOR_BLACK;
1358 	style->fill.type = GO_STYLE_FILL_PATTERN;
1359 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1360 	gog_theme_add_element (theme, style,
1361 		NULL,  g_strdup ("GogRegEqn"), NULL);
1362 
1363 	/* series labels */
1364 	style = go_style_new ();
1365 	go_style_clear_auto (style);
1366 	style->line.dash_type = GO_LINE_NONE;
1367 	style->line.width = 0; /* none */
1368 	style->line.color = GO_COLOR_BLACK;
1369 	style->fill.type = GO_STYLE_FILL_NONE;
1370 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1371 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
1372 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogSeriesLabels"), NULL);
1373 
1374 	/* data label */
1375 	style = go_style_new ();
1376 	style->line.dash_type = GO_LINE_NONE;
1377 	style->line.width = 0; /* none */
1378 	style->line.color = GO_COLOR_BLACK;
1379 	style->fill.type = GO_STYLE_FILL_NONE;
1380 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1381 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
1382 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogDataLabel"), NULL);
1383 
1384 	/* Color scale */
1385 	style = go_style_new ();
1386 	go_style_clear_auto (style);
1387 	style->line.dash_type = GO_LINE_SOLID;
1388 	style->line.width = 0; /* hairline */
1389 	style->line.color = GO_COLOR_BLACK;
1390 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogColorScale"), NULL);
1391 
1392 #ifdef GOFFICE_WITH_LASEM
1393 	/* Equations */
1394 	style = go_style_new ();
1395 	go_style_clear_auto (style);
1396 	style->line.dash_type = GO_LINE_NONE;
1397 	style->line.width = 0; /* hairline */
1398 	style->line.color = GO_COLOR_BLACK;
1399 	style->fill.type = GO_STYLE_FILL_NONE;
1400 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1401 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10"));
1402 	gog_theme_add_element (theme, style,
1403 		NULL,  g_strdup ("GogEquation"), NULL);
1404 #endif
1405 	/* builds the default discrete color map */
1406 	theme->dcm = gog_axis_color_map_from_colors (N_("Theme"),
1407 	                                             G_N_ELEMENTS (default_palette),
1408 	                                             default_palette, GO_RESOURCE_GENERATED);
1409 
1410 /* Guppi */
1411 	theme = gog_theme_new (N_("Guppi"), GO_RESOURCE_NATIVE);
1412 	theme->description = g_strdup (_("Guppi theme"));
1413 	gog_theme_registry_add (theme, FALSE);
1414 
1415 	/* graph */
1416 	style = go_style_new ();
1417 	go_style_clear_auto (style);
1418 	style->line.dash_type = GO_LINE_NONE;
1419 	style->line.width = 0;
1420 	style->line.color = GO_COLOR_BLACK;
1421 	style->fill.type = GO_STYLE_FILL_GRADIENT;
1422 	style->fill.gradient.dir   = GO_GRADIENT_N_TO_S;
1423 	style->fill.pattern.fore = GO_COLOR_BLUE;
1424 	style->fill.pattern.back = GO_COLOR_BLACK;
1425 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogGraph"), NULL);
1426 
1427 	/* chart */
1428 	style = go_style_new ();
1429 	go_style_clear_auto (style);
1430 	style->line.dash_type = GO_LINE_SOLID;
1431 	style->line.width = 0; /* hairline */
1432 	style->line.color = GO_COLOR_BLACK;
1433 	style->fill.type = GO_STYLE_FILL_PATTERN;
1434 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1435 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogChart"), NULL);
1436 
1437 	/* legend */
1438 	style = go_style_new ();
1439 	go_style_clear_auto (style);
1440 	style->line.dash_type = GO_LINE_SOLID;
1441 	style->line.width = 0; /* hairline */
1442 	style->line.color = GO_COLOR_BLACK;
1443 	style->fill.type = GO_STYLE_FILL_PATTERN;
1444 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1445 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogLegend"), NULL);
1446 
1447 	/* Axis */
1448 	style = go_style_new ();
1449 	go_style_clear_auto (style);
1450 	style->line.dash_type = GO_LINE_SOLID;
1451 	style->line.width = 0.; /* hairline */
1452 	style->line.color = GO_COLOR_GREY (0x20);
1453 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogAxis"), NULL);
1454 
1455 	/* AxisLine */
1456 	style = go_style_new ();
1457 	go_style_clear_auto (style);
1458 	style->line.dash_type = GO_LINE_SOLID;
1459 	style->line.width = 0.; /* hairline */
1460 	style->line.color = GO_COLOR_GREY (0x20);
1461 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogAxisLine"), NULL);
1462 
1463 	/* Grid */
1464 	style = go_style_new ();
1465 	go_style_clear_auto (style);
1466 	style->fill.type  = GO_STYLE_FILL_PATTERN;
1467 	style->line.dash_type = GO_LINE_NONE;
1468 	style->line.color = GO_COLOR_BLACK;
1469 	style->line.width = 0;
1470 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0xd0));
1471 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogGrid"), NULL);
1472 
1473 	/* GridLine */
1474 	style = go_style_new ();
1475 	go_style_clear_auto (style);
1476 	style->line.dash_type = GO_LINE_SOLID;
1477 	style->line.width = 0.; /* hairline */
1478 	style->line.color = GO_COLOR_GREY (0x96);
1479 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0));
1480 	style->fill.type = GO_STYLE_FILL_NONE;
1481 	gog_theme_add_element (theme, style, NULL, NULL,  g_strdup ("MajorGrid"));
1482 	style = go_style_new ();
1483 	go_style_clear_auto (style);
1484 	style->line.dash_type = GO_LINE_SOLID;
1485 	style->line.width = 0.; /* hairline */
1486 	style->line.color = GO_COLOR_GREY (0xC0);
1487 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0xE0, 0xE0, 0xE0, 0xE0));
1488 	style->fill.type = GO_STYLE_FILL_NONE;
1489 	gog_theme_add_element (theme, style, NULL, NULL,  g_strdup ("MinorGrid"));
1490 
1491 	/* series */
1492 	style = go_style_new ();
1493 	go_style_clear_auto (style);
1494 	style->line.dash_type = GO_LINE_SOLID;
1495 	style->line.width = 0.; /* hairline */
1496 	style->line.color = GO_COLOR_BLACK;
1497 	style->fill.type = GO_STYLE_FILL_PATTERN;
1498 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_GREY (0x20));
1499 	/* FIXME : not really true, will want to split area from line */
1500 	gog_theme_add_element (theme, style,
1501 		map_area_series_solid_guppi,  g_strdup ("GogSeries"), NULL);
1502 
1503 	/* labels */
1504 	style = go_style_new ();
1505 	go_style_clear_auto (style);
1506 	style->line.dash_type = GO_LINE_NONE;
1507 	style->line.width = 0; /* none */
1508 	style->line.color = GO_COLOR_BLACK;
1509 	style->fill.type = GO_STYLE_FILL_NONE;
1510 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1511 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogLabel"), NULL);
1512 
1513 	/* trend lines */
1514 	style = go_style_new ();
1515 	go_style_clear_auto (style);
1516 	style->line.dash_type = GO_LINE_SOLID;
1517 	style->line.width = 1;
1518 	style->line.color = GO_COLOR_BLACK;
1519 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_FROM_RGBA (0x00, 0x00, 0x00, 0x20));
1520 	style->fill.type = GO_STYLE_FILL_NONE;
1521 	gog_theme_add_element (theme, style,
1522 		NULL,  g_strdup ("GogTrendLine"), NULL);
1523 
1524 	/* regression equations */
1525 	style = go_style_new ();
1526 	go_style_clear_auto (style);
1527 	style->line.dash_type = GO_LINE_SOLID;
1528 	style->line.width = 0; /* hairline */
1529 	style->line.color = GO_COLOR_BLACK;
1530 	style->fill.type = GO_STYLE_FILL_PATTERN;
1531 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1532 	gog_theme_add_element (theme, style,
1533 		NULL,  g_strdup ("GogRegEqn"), NULL);
1534 
1535 	/* series labels */
1536 	style = go_style_new ();
1537 	go_style_clear_auto (style);
1538 	style->line.dash_type = GO_LINE_NONE;
1539 	style->line.width = 0; /* none */
1540 	style->line.color = GO_COLOR_BLACK;
1541 	style->fill.type = GO_STYLE_FILL_NONE;
1542 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1543 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
1544 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogSeriesLabels"), NULL);
1545 
1546 	/* data label */
1547 	style = go_style_new ();
1548 	go_style_clear_auto (style);
1549 	style->line.dash_type = GO_LINE_NONE;
1550 	style->line.width = 0; /* none */
1551 	style->line.color = GO_COLOR_BLACK;
1552 	style->fill.type = GO_STYLE_FILL_NONE;
1553 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1554 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
1555 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogDataLabel"), NULL);
1556 
1557 	/* Color scale */
1558 	style = go_style_new ();
1559 	go_style_clear_auto (style);
1560 	style->line.dash_type = GO_LINE_SOLID;
1561 	style->line.width = 0; /* hairline */
1562 	style->line.color = GO_COLOR_BLACK;
1563 	gog_theme_add_element (theme, style, NULL,  g_strdup ("GogColorScale"), NULL);
1564 
1565 #ifdef GOFFICE_WITH_LASEM
1566 	/* Equations */
1567 	style = go_style_new ();
1568 	go_style_clear_auto (style);
1569 	style->line.dash_type = GO_LINE_NONE;
1570 	style->line.width = 0; /* hairline */
1571 	style->line.color = GO_COLOR_BLACK;
1572 	style->fill.type = GO_STYLE_FILL_NONE;
1573 	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
1574 	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 10"));
1575 	gog_theme_add_element (theme, style,
1576 		NULL,  g_strdup ("GogEquation"), NULL);
1577 #endif
1578 
1579 	theme->dcm = gog_axis_color_map_from_colors (N_("Theme"),
1580 	                                             G_N_ELEMENTS (guppi_palette),
1581 	                                             guppi_palette, GO_RESOURCE_GENERATED);
1582 }
1583 
1584 static void
theme_load_from_uri(char const * uri)1585 theme_load_from_uri (char const *uri)
1586 {
1587 	struct theme_load_state	state;
1588 	GsfInput *input = go_file_open (uri, NULL);
1589 
1590 	if (input == NULL) {
1591 		g_warning ("[GogTheme]: Could not open %s", uri);
1592 		return;
1593 	}
1594 	state.theme = NULL;
1595 	state.desc = state.lang = state.name = NULL;
1596 	state.langs = g_get_language_names ();
1597 	state.name_lang_score = state.desc_lang_score = G_MAXINT;
1598 	if (!xml)
1599 		xml = gsf_xml_in_doc_new (theme_dtd, NULL);
1600 	if (!gsf_xml_in_doc_parse (xml, input, &state))
1601 		g_warning ("[GogTheme]: Could not parse %s", uri);
1602 	if (state.theme != NULL) {
1603 		if (!go_file_access (uri, GO_W_OK)) {
1604 			state.theme->uri = g_strdup (uri);
1605 			if (state.theme->id == NULL) {
1606 				state.theme->id = go_uuid ();
1607 				gog_theme_save (state.theme);
1608 			}
1609 			state.theme->type = GO_RESOURCE_RW;
1610 		} else {
1611 			if (state.theme->id == NULL) {
1612 				/* just duplicate the name, anyway, this should never occur */
1613 				char *name = g_hash_table_lookup (state.theme->names, "C");
1614 				if (name)
1615 					state.theme->id = g_strdup (name);
1616 				else
1617 					g_warning ("[GogTheme]: Theme with no Id in %s", uri);
1618 			}
1619 			state.theme->type = GO_RESOURCE_RO;
1620 		}
1621 		theme_loaded (&state);
1622 		gog_theme_registry_add (state.theme, FALSE);
1623 	} else {
1624 		g_free (state.name);
1625 		g_free (state.desc);
1626 	}
1627 	g_free (state.lang);
1628 	g_object_unref (input);
1629 }
1630 
1631 static void
themes_load_from_dir(char const * path)1632 themes_load_from_dir (char const *path)
1633 {
1634 	GDir *dir = g_dir_open (path, 0, NULL);
1635 	char const *d_name;
1636 
1637 	if (dir == NULL)
1638 		return;
1639 	while ((d_name = g_dir_read_name (dir)) != NULL) {
1640 		char *fullname = g_build_filename (path, d_name, NULL);
1641 		char *uri = go_filename_to_uri (fullname);
1642 		char *mime_type = go_get_mime_type (uri);
1643 		if (!strcmp (mime_type, "application/x-theme"))
1644 			theme_load_from_uri (uri);
1645 		g_free (mime_type);
1646 		g_free (uri);
1647 		g_free (fullname);
1648 	}
1649 	g_dir_close (dir);
1650 }
1651 
1652 void
_gog_themes_init(void)1653 _gog_themes_init (void)
1654 {
1655 	char *path;
1656 
1657 	_gog_axis_color_maps_init ();
1658 	build_predefined_themes ();
1659 
1660 	/* Load themes from file */
1661 	path = g_build_filename (go_sys_data_dir (), "themes", NULL);
1662 	themes_load_from_dir (path);
1663 	g_free (path);
1664 
1665 	path = g_build_filename (g_get_home_dir (), ".goffice", "themes", NULL);
1666 	themes_load_from_dir (path);
1667 	g_free (path);
1668 }
1669 
1670 void
_gog_themes_shutdown(void)1671 _gog_themes_shutdown (void)
1672 {
1673 	if (default_theme != NULL) {
1674 		g_object_unref (default_theme);
1675 		default_theme = NULL;
1676 	}
1677 
1678 	g_slist_free_full (g_slist_copy (themes), g_object_unref);
1679 	g_slist_free (themes);
1680 	g_hash_table_destroy (global_class_aliases);
1681 	_gog_axis_color_maps_shutdown ();
1682 	themes = NULL;
1683 	if (xml)
1684 		gsf_xml_in_doc_free (xml);
1685 }
1686 
1687 /**
1688  * gog_theme_get_color_map:
1689  * @theme: #GogTheme
1690  * @discrete: whether the map is for a discrete axis.
1691  *
1692  * Retrieves the themed color map. Each theme has a discrete color map and a
1693  * continuous one.
1694  * Returns: (transfer none): the requested color map.
1695  **/
1696 GogAxisColorMap const *
gog_theme_get_color_map(GogTheme const * theme,gboolean discrete)1697 gog_theme_get_color_map (GogTheme const *theme, gboolean discrete)
1698 {
1699 	g_return_val_if_fail (GOG_IS_THEME (theme), NULL);
1700 	if (discrete)
1701 		return theme->dcm;
1702 	else
1703 		return (theme->cm)? theme->cm: _gog_axis_color_map_get_default ();
1704 	return NULL;
1705 }
1706 
1707 /**
1708  * gog_theme_delete:
1709  * @theme: a #GogTheme
1710  *
1711  * Destroys the theme and remove it from the user directory and from the
1712  * database.
1713  * Returns: %TRUE on success.
1714  **/
1715 gboolean
gog_theme_delete(GogTheme * theme)1716 gog_theme_delete (GogTheme *theme)
1717 {
1718 	GFile *file = g_file_new_for_uri (theme->uri);
1719 	gboolean res;
1720 	if ((res = g_file_delete (file, NULL, NULL))) {
1721 		themes = g_slist_remove (themes, theme);
1722 		g_object_unref (theme);
1723 	}
1724 	g_object_unref (file);
1725 	return res;
1726 }
1727 
1728 /*****************
1729  * Theme edition *
1730  *****************/
1731 
1732 /**
1733  * gog_theme_foreach:
1734  * @handler: (scope call): a #GFunc using a theme as first argument
1735  * @user_data: data to pass to @handler
1736  *
1737  * Executes @handler to each theme installed on the system, including built-in
1738  * themes.
1739  **/
1740 void
gog_theme_foreach(GFunc handler,gpointer user_data)1741 gog_theme_foreach (GFunc handler, gpointer user_data)
1742 {
1743 	g_slist_foreach (themes, handler, user_data);
1744 }
1745 
1746 static void
gog_theme_set_name(GogTheme * theme,char const * name)1747 gog_theme_set_name (GogTheme *theme, char const *name)
1748 {
1749 	g_return_if_fail (GOG_IS_THEME (theme));
1750 	g_free (theme->name);
1751 	g_hash_table_remove_all (theme->names);
1752 	theme->name = g_strdup (name);
1753 	g_hash_table_insert (theme->names, g_strdup ("C"), g_strdup (name));
1754 }
1755 static void
gog_theme_set_description(GogTheme * theme,char const * desc)1756 gog_theme_set_description (GogTheme *theme, char const *desc)
1757 {
1758 	g_return_if_fail (GOG_IS_THEME (theme));
1759 	g_free (theme->description);
1760 	g_hash_table_remove_all (theme->descs);
1761 	theme->description = g_strdup (desc);
1762 	g_hash_table_insert (theme->descs, g_strdup ("C"), g_strdup (desc));
1763 }
1764 
1765 /**
1766  * gog_theme_dup:
1767  * @theme: a #GogTheme
1768  *
1769  * Duplicates @theme with a new Id.
1770  * Returns: (transfer full): the new theme.
1771  **/
1772 GogTheme*
gog_theme_dup(GogTheme * theme)1773 gog_theme_dup (GogTheme *theme)
1774 {
1775 	GogTheme *new_theme;
1776 	char *desc, *name;
1777 
1778 	g_return_val_if_fail (GOG_IS_THEME (theme), NULL);
1779 	new_theme = g_object_new (GOG_TYPE_THEME,
1780 	                          "resource-type", GO_RESOURCE_RW,
1781 	                          NULL);
1782 	new_theme->id = go_uuid ();
1783 	gog_theme_build_uri (new_theme);
1784 	gog_theme_set_name (new_theme, "New theme");
1785 	name = g_hash_table_lookup (theme->names, "C");
1786 	desc = g_strdup_printf ("New theme base on %s", name);
1787 	gog_theme_set_description (new_theme, desc);
1788 	g_free (desc);
1789 	/* duplicate the styles */
1790 	/* duplicate the color maps */
1791 	if (theme->cm) {
1792 		new_theme->cm = gog_axis_color_map_dup (theme->cm);
1793 		g_object_set (G_OBJECT (new_theme->cm),
1794 			          "resource-type", GO_RESOURCE_CHILD,
1795 			          NULL);
1796 	}
1797 	if (theme->dcm &&
1798 	    gog_axis_color_map_get_resource_type (theme->dcm) == GO_RESOURCE_CHILD) {
1799 			new_theme->dcm = gog_axis_color_map_dup (theme->dcm);
1800 			g_object_set (G_OBJECT (new_theme->dcm),
1801 					      "resource-type", GO_RESOURCE_CHILD,
1802 					      NULL);
1803 		}
1804 	return new_theme;
1805 }
1806 
1807 #ifdef GOFFICE_WITH_GTK
1808 
1809 struct theme_edit_state {
1810 	GtkBuilder *gui;
1811 	GogTheme *theme;
1812 };
1813 
1814 static void
create_toggled_cb(GtkListStore * list,char const * path)1815 create_toggled_cb (GtkListStore *list, char const *path)
1816 {
1817 	GtkTreeIter iter;
1818 	gboolean set;
1819 	gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (list), &iter, path);
1820 	gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, 1, &set, -1);
1821 	gtk_list_store_set (list, &iter, 1, !set, -1);
1822 }
1823 
1824 /**
1825  * gog_theme_edit:
1826  * @theme: the #GogTheme to edit or %NULL to create a new one.
1827  * @cc: a #GOCmdContext or %NULL.
1828  *
1829  * Displays a dialog box to edit the theme. This can be done only for
1830  * locally installed themes that are writeable.
1831  * Returns: (transfer none): The edited theme or %NULL if the edition has
1832  * been cancelled.
1833  **/
1834 GogTheme *
gog_theme_edit(GogTheme * theme,GOCmdContext * cc)1835 gog_theme_edit (GogTheme *theme, GOCmdContext *cc)
1836 {
1837 	struct theme_edit_state state;
1838 	GtkWidget *w;
1839 
1840 	if (!GOG_IS_THEME (theme)) {
1841 		/* display a dialog box to select used roles and series number */
1842 		GtkBuilder *gui = go_gtk_builder_load_internal ("res:go:graph/new-theme-prefs.ui", GETTEXT_PACKAGE, cc);
1843 		int response;
1844 		GtkWidget *w;
1845 		GtkListStore *l = GTK_LIST_STORE (gtk_builder_get_object (gui, "classes-list"));
1846 		GtkTreeView *tv = GTK_TREE_VIEW (gtk_builder_get_object (gui, "classes-tree"));
1847 		GtkCellRenderer *renderer;
1848 		GtkTreeViewColumn *column;
1849 		GtkTreeIter iter;
1850 		unsigned i;
1851 
1852 		renderer = gtk_cell_renderer_text_new ();
1853 		column = gtk_tree_view_column_new_with_attributes (_("Class"), renderer, "text", 0, NULL);
1854 		gtk_tree_view_append_column (tv, column);
1855 		renderer = gtk_cell_renderer_toggle_new ();
1856 		column = gtk_tree_view_column_new_with_attributes (_("Create"), renderer, "active", 1, NULL);
1857 		gtk_tree_view_append_column (tv, column);
1858 		for (i = 0; i < G_N_ELEMENTS (roles); i++) {
1859 			if (!strcmp (roles[i].klass_name, "Series"))
1860 				continue;
1861 			gtk_list_store_append (l, &iter);
1862 			gtk_list_store_set (l, &iter, 0, _(roles[i].label), 1, FALSE, 2, i, -1);
1863 		}
1864 		g_signal_connect_swapped (renderer, "toggled", G_CALLBACK (create_toggled_cb), l);
1865 
1866 		w = go_gtk_builder_get_widget (gui, "new-theme-prefs");
1867 		response = gtk_dialog_run (GTK_DIALOG (w));
1868 		gtk_widget_destroy (w);
1869 		g_object_unref (gui);
1870 		if (response == 1) {
1871 			theme = gog_theme_new (_("New theme"), FALSE);
1872 			theme->id = go_uuid ();
1873 			theme->type = GO_RESOURCE_RW;
1874 		} else
1875 			return NULL;
1876 	}
1877 
1878 	state.theme = theme;
1879 	state.gui = go_gtk_builder_load_internal ("res:go:graph/gog-theme-editor.ui", GETTEXT_PACKAGE, cc);
1880 
1881 	w = go_gtk_builder_get_widget (state.gui, "gog-theme-editor");
1882 	if (gtk_dialog_run (GTK_DIALOG (w))) {
1883 	}
1884 	gtk_widget_destroy (w);
1885 	g_object_unref (state.gui);
1886 	return NULL;
1887 }
1888 
1889 #endif
1890 
1891