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 <GogTheme> 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