1 /*
2 * gog-axis-color-map.c
3 *
4 * Copyright (C) 2012 Jean Brefort (jean.brefort@normalesup.org)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 */
21
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 #include <goffice/goffice-priv.h>
25
26 #include <gsf/gsf-input.h>
27 #include <gsf/gsf-output-gio.h>
28 #include <gsf/gsf-impl-utils.h>
29 #include <glib/gi18n-lib.h>
30 #include <string.h>
31
32 #ifdef GOFFICE_WITH_GTK
33 #include <gtk/gtk.h>
34 #endif
35 #include <sys/stat.h>
36
37 /**
38 * SECTION: gog-axis-color-map
39 * @short_description: map values to colors.
40 *
41 * Used to map color and pseudo-3d axes values to the actual color. The first
42 * color maps 0 and the last a positive integer returned by
43 * gog_axis_color_map_get_max(). For color axes, these integer values must
44 * themselves be mapped to the minimum and maximum of the axis (unless the
45 * axis is inverted). For pseudo-3d axes, successive colors are obtained for
46 * integer values, cycling to the first color when the colors number is not
47 * large enough.
48 **/
49 struct _GogAxisColorMap {
50 GObject base;
51 char *id, *name;
52 char *uri;
53 GHashTable *names;
54 GoResourceType type;
55 unsigned size; /* colors number */
56 unsigned allocated; /* only useful when editing */
57 unsigned *limits;
58 GOColor *colors;
59 };
60 typedef GObjectClass GogAxisColorMapClass;
61
62 static GObjectClass *parent_klass;
63
64 enum {
65 GOG_AXIS_COLOR_MAP_PROP_0,
66 GOG_AXIS_COLOR_MAP_PROP_TYPE
67 };
68
69 static void
gog_axis_color_map_set_property(GObject * gobject,guint param_id,GValue const * value,GParamSpec * pspec)70 gog_axis_color_map_set_property (GObject *gobject, guint param_id,
71 GValue const *value, GParamSpec *pspec)
72 {
73 GogAxisColorMap *map = GOG_AXIS_COLOR_MAP (gobject);
74
75 switch (param_id) {
76 case GOG_AXIS_COLOR_MAP_PROP_TYPE:
77 map->type = g_value_get_enum (value);
78 break;
79
80 default:
81 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
82 return; /* NOTE : RETURN */
83 }
84 }
85
86 static void
gog_axis_color_map_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)87 gog_axis_color_map_get_property (GObject *gobject, guint param_id,
88 GValue *value, GParamSpec *pspec)
89 {
90 GogAxisColorMap *map = GOG_AXIS_COLOR_MAP (gobject);
91
92 switch (param_id) {
93 case GOG_AXIS_COLOR_MAP_PROP_TYPE:
94 g_value_set_enum (value, map->type);
95 break;
96
97 default:
98 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
99 return; /* NOTE : RETURN */
100 }
101 }
102
103 static void
gog_axis_color_map_finalize(GObject * obj)104 gog_axis_color_map_finalize (GObject *obj)
105 {
106 GogAxisColorMap *map = GOG_AXIS_COLOR_MAP (obj);
107 g_free (map->id);
108 map->id = NULL;
109 g_free (map->name);
110 map->name = NULL;
111 g_free (map->uri);
112 g_free (map->limits);
113 map->limits = NULL;
114 g_free (map->colors);
115 map->colors = NULL;
116 if (map->names)
117 g_hash_table_destroy (map->names);
118 map->names = NULL;
119 parent_klass->finalize (obj);
120 }
121
122 static void
gog_axis_color_map_class_init(GObjectClass * gobject_klass)123 gog_axis_color_map_class_init (GObjectClass *gobject_klass)
124 {
125 parent_klass = g_type_class_peek_parent (gobject_klass);
126 /* GObjectClass */
127 gobject_klass->finalize = gog_axis_color_map_finalize;
128 gobject_klass->set_property = gog_axis_color_map_set_property;
129 gobject_klass->get_property = gog_axis_color_map_get_property;
130 g_object_class_install_property (gobject_klass, GOG_AXIS_COLOR_MAP_PROP_TYPE,
131 g_param_spec_enum ("resource-type",
132 _("Resource type"),
133 _("The resource type for the color map"),
134 go_resource_type_get_type (), GO_RESOURCE_INVALID,
135 GSF_PARAM_STATIC | G_PARAM_READWRITE |G_PARAM_CONSTRUCT_ONLY));
136 }
137
138 static void
gog_axis_color_map_init(GogAxisColorMap * map)139 gog_axis_color_map_init (GogAxisColorMap *map)
140 {
141 map->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
142 }
143
144 static void
save_name_cb(char const * lang,char const * name,GsfXMLOut * output)145 save_name_cb (char const *lang, char const *name, GsfXMLOut *output)
146 {
147 gsf_xml_out_start_element (output, "name");
148 if (strcmp (lang, "C"))
149 gsf_xml_out_add_cstr_unchecked (output, "xml:lang", lang);
150 gsf_xml_out_add_cstr (output, NULL, name);
151 gsf_xml_out_end_element (output);
152 }
153
154 static void gog_axis_color_map_save (GogAxisColorMap const *map);
155
156 static void
build_uri(GogAxisColorMap * map)157 build_uri (GogAxisColorMap *map)
158 {
159 char *filename, *full_name;
160 filename = g_strconcat (map->id, ".map", NULL);
161 full_name = g_build_filename (g_get_home_dir (), ".goffice", "colormaps", filename, NULL);
162 map->uri = go_filename_to_uri (full_name);
163 g_free (filename);
164 g_free (full_name);
165 }
166
167 static void
gog_axis_color_map_write(GOPersist const * gp,GsfXMLOut * output)168 gog_axis_color_map_write (GOPersist const *gp, GsfXMLOut *output)
169 {
170 unsigned i;
171 char *buf;
172 GogAxisColorMap *map;
173 g_return_if_fail (GOG_IS_AXIS_COLOR_MAP (gp));
174
175 map = GOG_AXIS_COLOR_MAP (gp);
176 if (output == NULL) {
177 g_return_if_fail (map->uri == NULL);
178 build_uri (map);
179 map->type = GO_RESOURCE_RW;
180 gog_axis_color_map_save (map);
181 return;
182 }
183 gsf_xml_out_add_cstr_unchecked (output, "id", map->id);
184 g_hash_table_foreach (map->names, (GHFunc) save_name_cb, output);
185 for (i = 0; i < map->size; i++) {
186 gsf_xml_out_start_element (output, "color-stop");
187 gsf_xml_out_add_uint (output, "bin", map->limits[i]);
188 buf = go_color_as_str (map->colors[i]);
189 gsf_xml_out_add_cstr_unchecked (output, "color", buf);
190 g_free (buf);
191 gsf_xml_out_end_element (output);
192 }
193 }
194
195 struct _color_stop {
196 unsigned bin;
197 GOColor color;
198 };
199
200 struct color_map_load_state {
201 GogAxisColorMap *map;
202 char *lang, *name;
203 unsigned name_lang_score;
204 char const * const *langs;
205 GSList *color_stops;
206 };
207
208 static void
color_stop_start(GsfXMLIn * xin,xmlChar const ** attrs)209 color_stop_start (GsfXMLIn *xin, xmlChar const **attrs)
210 {
211 struct color_map_load_state *state = (struct color_map_load_state *) xin->user_state;
212 GOColor color;
213 unsigned bin = 0;
214 char *end;
215 gboolean color_found = FALSE;
216 gboolean bin_found = FALSE;
217
218 if (state->map->name)
219 return;
220 for (; attrs != NULL && *attrs ; attrs += 2)
221 if (0 == strcmp (*attrs, "bin")) {
222 bin = strtoul (attrs[1], &end, 10);
223 if (*end == 0)
224 bin_found = TRUE;
225 } else if (0 == strcmp (*attrs, "color"))
226 color_found = go_color_from_str (attrs[1], &color);
227 if (color_found && bin_found) {
228 struct _color_stop* stop = g_new (struct _color_stop, 1);
229 stop->bin = bin;
230 stop->color = color;
231 state->color_stops = g_slist_append (state->color_stops, stop);
232 } else
233 g_warning ("[GogAxisColorMap]: Invalid color stop");
234 }
235
236 static void
map_start(GsfXMLIn * xin,xmlChar const ** attrs)237 map_start (GsfXMLIn *xin, xmlChar const **attrs)
238 {
239 struct color_map_load_state *state = (struct color_map_load_state *) xin->user_state;
240 if (state->map == NULL) {
241 state->map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, NULL);
242 for (; attrs && *attrs; attrs +=2)
243 if (!strcmp ((char const *) *attrs, "id")) {
244 state->map->id = g_strdup ((char const *) attrs[1]);
245 break;
246 }
247 }
248 }
249
250 static void
name_start(GsfXMLIn * xin,xmlChar const ** attrs)251 name_start (GsfXMLIn *xin, xmlChar const **attrs)
252 {
253 struct color_map_load_state *state = (struct color_map_load_state *) xin->user_state;
254 unsigned i;
255 if (state->map->name)
256 return;
257 for (i = 0; attrs != NULL && attrs[i] && attrs[i+1] ; i += 2)
258 if (0 == strcmp (attrs[i], "xml:lang"))
259 state->lang = g_strdup (attrs[i+1]);
260 }
261
262 static void
name_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)263 name_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
264 {
265 struct color_map_load_state *state = (struct color_map_load_state *) xin->user_state;
266 char *name = NULL;
267 if (state->map->name)
268 return;
269 if (xin->content->str == NULL)
270 return;
271 name = g_strdup (xin->content->str);
272 if (state->lang == NULL)
273 state->lang = g_strdup ("C");
274 if (state->name_lang_score > 0 && state->langs[0] != NULL) {
275 unsigned i;
276 for (i = 0; i < state->name_lang_score && state->langs[i] != NULL; i++) {
277 if (strcmp (state->langs[i], state->lang) == 0) {
278 g_free (state->name);
279 state->name = g_strdup (name);
280 state->name_lang_score = i;
281 }
282 }
283 }
284 g_hash_table_replace (state->map->names, state->lang, name);
285 state->lang = NULL;
286 }
287
288 static int
color_stops_cmp(struct _color_stop * first,struct _color_stop * second)289 color_stops_cmp (struct _color_stop *first, struct _color_stop *second)
290 {
291 return (first->bin < second->bin)? -1: (int) (first->bin - second->bin);
292 }
293
294 static GsfXMLInNode const color_map_dtd[] = {
295 GSF_XML_IN_NODE (THEME, THEME, -1, "GogAxisColorMap", GSF_XML_NO_CONTENT, map_start, NULL),
296 GSF_XML_IN_NODE (THEME, NAME, -1, "name", GSF_XML_CONTENT, name_start, name_end),
297 GSF_XML_IN_NODE (THEME, UNAME, -1, "_name", GSF_XML_CONTENT, name_start, name_end),
298 GSF_XML_IN_NODE (THEME, STOP, -1, "color-stop", GSF_XML_CONTENT, color_stop_start, NULL),
299 GSF_XML_IN_NODE_END
300 };
301 static GsfXMLInDoc *xml = NULL;
302
303 static void
gog_axis_color_map_save(GogAxisColorMap const * map)304 gog_axis_color_map_save (GogAxisColorMap const *map)
305 {
306 GsfOutput *output = gsf_output_gio_new_for_uri (map->uri, NULL);
307 GsfXMLOut *xml;
308 if (output == NULL) {
309 char *dir = go_dirname_from_uri (map->uri, TRUE);
310 int res = g_mkdir_with_parents (dir, 0777);
311 g_free (dir);
312 if (res < 0) {
313 g_warning ("[GogAxisColorMap]: Could not save color map to %s", map->uri);
314 return;
315 }
316 output = gsf_output_gio_new_for_uri (map->uri, NULL);
317 }
318 xml = gsf_xml_out_new (output);
319 gsf_xml_out_start_element (xml, "GogAxisColorMap");
320 gog_axis_color_map_write (GO_PERSIST (map), xml);
321 gsf_xml_out_end_element (xml);
322 g_object_unref (xml);
323 g_object_unref (output);
324 }
325
326 static void
color_map_loaded(struct color_map_load_state * state,char const * uri,gboolean delete_invalid)327 color_map_loaded (struct color_map_load_state *state, char const *uri, gboolean delete_invalid)
328 {
329 GSList *ptr;
330 if (!state->map || state->map->name)
331 return;
332 state->map->name = state->name;
333 /* populates the colors */
334 /* first sort the color list according to bins */
335 ptr = state->color_stops = g_slist_sort (state->color_stops, (GCompareFunc) color_stops_cmp);
336 if (((struct _color_stop *) ptr->data)->bin != 0) {
337 g_warning ("[GogAxisColorMap]: Invalid color map in %s", uri);
338 if (delete_invalid) {
339 g_object_unref (state->map);
340 state->map = NULL;
341 }
342 } else {
343 unsigned cur_bin, n = 0;
344 state->map->allocated = g_slist_length (state->color_stops);
345 state->map->limits = g_new (unsigned, state->map->allocated);
346 state->map->colors = g_new (GOColor, state->map->allocated);
347 while (ptr) {
348 cur_bin = state->map->limits[n] = ((struct _color_stop *) ptr->data)->bin;
349 state->map->colors[n++] = ((struct _color_stop *) ptr->data)->color;
350 do (ptr = ptr->next);
351 while (ptr && ((struct _color_stop *) ptr->data)->bin == cur_bin);
352 }
353 state->map->size = n; /* we drop duplicate bins */
354 if (state->map->id == NULL) {
355 if (state->map->uri) {
356 state->map->id = go_uuid ();
357 gog_axis_color_map_save (state->map);
358 } else {
359 g_warning ("[GogAxisColorMap]: Map without Id in %s", uri);
360 if (delete_invalid) {
361 g_object_unref (state->map);
362 state->map = NULL;
363 }
364 }
365 }
366 }
367 g_slist_free_full (state->color_stops, g_free);
368 g_free (state->lang);
369 }
370
371 static void
parse_done_cb(GsfXMLIn * xin,struct color_map_load_state * state)372 parse_done_cb (GsfXMLIn *xin, struct color_map_load_state *state)
373 {
374 color_map_loaded (state, gsf_input_name (gsf_xml_in_get_input (xin)), FALSE);
375 g_free (state);
376 }
377
378 static void
gog_axis_color_map_prep_sax(GOPersist * gp,GsfXMLIn * xin,xmlChar const ** attrs)379 gog_axis_color_map_prep_sax (GOPersist *gp, GsfXMLIn *xin, xmlChar const **attrs)
380 {
381 struct color_map_load_state *state;
382
383 g_return_if_fail (GOG_IS_AXIS_COLOR_MAP (gp));
384
385 state = g_new (struct color_map_load_state, 1);
386 state->map = GOG_AXIS_COLOR_MAP (gp);
387 state->name = NULL;
388 state->lang = NULL;
389 state->langs = g_get_language_names ();
390 state->name_lang_score = G_MAXINT;
391 state->color_stops = NULL;
392 if (!xml)
393 xml = gsf_xml_in_doc_new (color_map_dtd, NULL);
394 gsf_xml_in_push_state (xin, xml, state, (GsfXMLInExtDtor) parse_done_cb, attrs);
395 }
396
397 static void
gog_axis_color_map_persist_init(GOPersistClass * iface)398 gog_axis_color_map_persist_init (GOPersistClass *iface)
399 {
400 iface->prep_sax = gog_axis_color_map_prep_sax;
401 iface->sax_save = gog_axis_color_map_write;
402 }
403
404 GSF_CLASS_FULL (GogAxisColorMap, gog_axis_color_map,
405 NULL, NULL, gog_axis_color_map_class_init, NULL,
406 gog_axis_color_map_init, G_TYPE_OBJECT, 0,
407 GSF_INTERFACE (gog_axis_color_map_persist_init, GO_TYPE_PERSIST))
408
409 /**
410 * gog_axis_color_map_get_color:
411 * @map: a #GogAxisMap
412 * @x: the value to map
413 *
414 * Maps @x to a color.
415 * Returns: the found color.
416 **/
417 GOColor
gog_axis_color_map_get_color(GogAxisColorMap const * map,double x)418 gog_axis_color_map_get_color (GogAxisColorMap const *map, double x)
419 {
420 unsigned n = 1;
421 double t;
422 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), (GOColor) 0x00000000);
423 if (x < 0. || map->size == 0)
424 return (GOColor) 0x00000000;
425 if (map->size == 1)
426 return map->colors[0];
427 if (x > map->limits[map->size-1])
428 x -= floor (x / (map->limits[map->size-1] + 1)) * (map->limits[map->size-1] + 1);
429 while (n < map->size && x > map->limits[n] + 1e-10)
430 n++;
431 t = (x - map->limits[n-1]) / (map->limits[n] - map->limits[n-1]);
432 return GO_COLOR_INTERPOLATE (map->colors[n-1], map->colors[n], t);
433 }
434
435 /**
436 * gog_axis_color_map_get_max:
437 * @map: a #GogAxisMap
438 *
439 * Retrieves the value corresponding to the last color in the map. The first
440 * always corresponds to 0.
441 * Returns: the maximum value.
442 **/
443 unsigned
gog_axis_color_map_get_max(GogAxisColorMap const * map)444 gog_axis_color_map_get_max (GogAxisColorMap const *map)
445 {
446 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), 0);
447 return (map->size > 0)? map->limits[map->size-1]: 0;
448 }
449
450 /**
451 * gog_axis_color_map_get_id:
452 * @map: a #GogAxisMap
453 *
454 * Retrieves the color map name.
455 * Returns: (transfer none): the map name.
456 **/
457 char const *
gog_axis_color_map_get_id(GogAxisColorMap const * map)458 gog_axis_color_map_get_id (GogAxisColorMap const *map)
459 {
460 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), NULL);
461 return map->id;
462 }
463
464 /**
465 * gog_axis_color_map_get_name:
466 * @map: a #GogAxisMap
467 *
468 * Retrieves the color map localized name.
469 * Returns: (transfer none): the map name.
470 **/
471 char const *
gog_axis_color_map_get_name(GogAxisColorMap const * map)472 gog_axis_color_map_get_name (GogAxisColorMap const *map)
473 {
474 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), NULL);
475 return map->name;
476 }
477
478 /**
479 * gog_axis_color_map_get_resource_type:
480 * @map: a #GogAxisMap
481 *
482 * Retrieves the resource type for @map.
483 * Returns: the resource type.
484 **/
485 GoResourceType
gog_axis_color_map_get_resource_type(GogAxisColorMap const * map)486 gog_axis_color_map_get_resource_type (GogAxisColorMap const *map)
487 {
488 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), GO_RESOURCE_INVALID);
489 return map->type;
490 }
491
492 /**
493 * gog_axis_color_map_get_snapshot:
494 * @map: a #GogAxisMap
495 * @discrete: whether to use constant colors between each stop or a gradient.
496 * @horizontal: whether to get an horizontal or a vertical snapshot.
497 * @width: the pixbuf width.
498 * @height: the pixbuf height.
499 *
500 * Builds a snapshot of the color map.
501 * Returns: (transfer full): the new #GdkPixbuf.
502 **/
503 GdkPixbuf *
gog_axis_color_map_get_snapshot(GogAxisColorMap const * map,gboolean discrete,gboolean horizontal,unsigned width,unsigned height)504 gog_axis_color_map_get_snapshot (GogAxisColorMap const *map,
505 gboolean discrete, gboolean horizontal,
506 unsigned width, unsigned height)
507 {
508 cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 16, 16);
509 GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height);
510 cairo_t *cr = cairo_create (surface);
511 cairo_pattern_t *pattern;
512
513 g_return_val_if_fail (GOG_IS_AXIS_COLOR_MAP (map), NULL);
514 /* first fill with a "transparent" background */
515 cairo_rectangle (cr, 0., 0., 16., 16.);
516 cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (GO_COLOR_GREY(0x33)));
517 cairo_fill (cr);
518 cairo_rectangle (cr, 0., 8., 8., 8.);
519 cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (GO_COLOR_GREY(0x66)));
520 cairo_fill (cr);
521 cairo_rectangle (cr, 8., 0., 8., 8.);
522 cairo_fill (cr);
523 cairo_destroy (cr);
524 pattern = cairo_pattern_create_for_surface (surface);
525 cairo_surface_destroy (surface);
526 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
527 cr = cairo_create (surface);
528 cairo_rectangle (cr, 0., 0., width, height);
529 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
530 cairo_set_source (cr, pattern);
531 cairo_fill (cr);
532 cairo_pattern_destroy (pattern);
533 gog_axis_color_map_to_cairo (map, cr, discrete, horizontal, width, height);
534
535 go_cairo_convert_data_to_pixbuf (gdk_pixbuf_get_pixels (pixbuf),
536 cairo_image_surface_get_data (surface),
537 width, height, width * 4);
538 cairo_destroy (cr);
539 cairo_surface_destroy (surface);
540 return pixbuf;
541 }
542
543
544 /**
545 * gog_axis_color_map_to_cairo:
546 * @map: a #GogAxisMap
547 * @cr: a cairo context.
548 * @discrete: whether to use constant colors between each stop or a gradient.
549 * @horizontal: whether to get an horizontal or a vertical snapshot.
550 * @width: the rectangle width.
551 * @height: the rectangle height.
552 *
553 * When @discrete is larger than 1, it will be interpreted as the number of
554 * major ticks used. The number of colors will then be @discrete − 1.
555 * Draws a snapshot of the color map inside the rectangle.
556 **/
557 void
gog_axis_color_map_to_cairo(GogAxisColorMap const * map,cairo_t * cr,unsigned discrete,gboolean horizontal,double width,double height)558 gog_axis_color_map_to_cairo (GogAxisColorMap const *map, cairo_t *cr,
559 unsigned discrete, gboolean horizontal,
560 double width, double height)
561 {
562 unsigned i, max;
563
564 g_return_if_fail (GOG_IS_AXIS_COLOR_MAP (map));
565 max = gog_axis_color_map_get_max (map);
566 if (discrete) {
567 GOColor color;
568 double t0, maxt, step, start, scale = 1;
569 if (discrete > 1) {
570 if (discrete > max + 1) { /* we need to have at least two colors */
571 scale = (double) gog_axis_color_map_get_max (map) / (discrete - 2);
572 max = discrete - 2;
573 }
574 }
575 max++;
576 if (horizontal) {
577 maxt = width;
578 step = maxt / max;
579 start = 0;
580 } else {
581 maxt = height;
582 step = -maxt / max;
583 start = height;
584 }
585 for (i = 0; i < max; i++) {
586 t0 = start + i * step;
587 color = gog_axis_color_map_get_color (map, i * scale);
588 if (horizontal)
589 cairo_rectangle (cr, t0, 0., step, height);
590 else
591 cairo_rectangle (cr, 0., t0, width, step);
592 cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (color));
593 cairo_fill (cr);
594 }
595 } else {
596 cairo_pattern_t *pattern;
597 pattern = (horizontal)?
598 cairo_pattern_create_linear (0., 0., width, 0.):
599 cairo_pattern_create_linear (0., height, 0., 0.);
600 for (i = 0; i < map->size; i++) {
601 cairo_pattern_add_color_stop_rgba (pattern,
602 (double) map->limits[i] / (double) max,
603 GO_COLOR_TO_CAIRO (map->colors[i]));
604 }
605 cairo_rectangle (cr, 0., 0., width, height);
606 cairo_set_source (cr, pattern);
607 cairo_fill (cr);
608 cairo_pattern_destroy (pattern);
609 }
610 }
611
612 static void
gog_axis_color_map_set_name(GogAxisColorMap * map,char const * name)613 gog_axis_color_map_set_name (GogAxisColorMap *map, char const *name)
614 {
615 g_free (map->name);
616 g_hash_table_remove_all (map->names);
617 map->name = g_strdup (name);
618 g_hash_table_insert (map->names, g_strdup ("C"), g_strdup (name));
619 }
620
621 /**
622 * gog_axis_color_map_dup:
623 * @map: a #GogAxisColorMap
624 *
625 * Duplicates the color map.
626 * Returns: (transfer full): the new color map.
627 **/
628 GogAxisColorMap *
gog_axis_color_map_dup(GogAxisColorMap const * map)629 gog_axis_color_map_dup (GogAxisColorMap const *map)
630 {
631 unsigned i;
632 GogAxisColorMap *new_map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP,
633 "resource-type", GO_RESOURCE_RW,
634 NULL);
635 gog_axis_color_map_set_name (new_map, _("New map"));
636 new_map->id = go_uuid ();
637 build_uri (new_map);
638 new_map->size = new_map->allocated = map->size;
639 new_map->limits = g_new (unsigned, map->size);
640 new_map->colors = g_new (GOColor, map->size);
641 for (i = 0; i < map->size; i++) {
642 new_map->limits[i] = map->limits[i];
643 new_map->colors[i] = map->colors[i];
644 }
645 return new_map;
646 }
647
648 static GSList *color_maps;
649
650 /**
651 * gog_axis_color_map_registry_add:
652 * @map: a #GogAxisColorMap
653 *
654 * Keep a pointer to @map in graph color maps registry.
655 * This function does not add a reference to @map.
656 **/
657 static void
gog_axis_color_map_registry_add(GogAxisColorMap * map)658 gog_axis_color_map_registry_add (GogAxisColorMap *map)
659 {
660 g_return_if_fail (GOG_IS_AXIS_COLOR_MAP (map));
661
662 /* TODO: Check for duplicated names and for already
663 * registered map */
664
665 color_maps = g_slist_append (color_maps, map);
666 }
667
668 #ifdef GOFFICE_WITH_GTK
669
670 /**
671 * gog_axis_color_map_compare:
672 *
673 * Returns: TRUE if the maps are different.
674 **/
675 static gboolean
gog_axis_color_map_compare(GogAxisColorMap const * map1,GogAxisColorMap const * map2)676 gog_axis_color_map_compare (GogAxisColorMap const *map1, GogAxisColorMap const *map2)
677 {
678 unsigned i;
679 if (strcmp (map1->name, map2->name))
680 return TRUE;
681 if (map1->size != map2->size)
682 return TRUE;
683 for (i = 0; i < map1->size; i++)
684 if (map1->limits[i] != map2->limits[i] || map1->colors[i] != map2->colors[i])
685 return TRUE;
686 return FALSE;
687 }
688
689 struct color_map_edit_state {
690 GtkWidget *color_selector, *discrete, *continuous;
691 GtkBuilder *gui;
692 GogAxisColorMap *map, *orig;
693 unsigned bin;
694 };
695
696 static void
update_snapshots(struct color_map_edit_state * state)697 update_snapshots (struct color_map_edit_state *state)
698 {
699 GdkPixbuf *pixbuf;
700 pixbuf = gog_axis_color_map_get_snapshot (state->map, TRUE, TRUE, 200, 24);
701 gtk_image_set_from_pixbuf (GTK_IMAGE (state->discrete), pixbuf);
702 gtk_widget_show (state->discrete);
703 g_object_unref (pixbuf);
704 pixbuf = gog_axis_color_map_get_snapshot (state->map, FALSE, TRUE, 200, 24);
705 gtk_image_set_from_pixbuf (GTK_IMAGE (state->continuous), pixbuf);
706 g_object_unref (pixbuf);
707 gtk_widget_set_sensitive (go_gtk_builder_get_widget (state->gui, "save"),
708 gog_axis_color_map_compare (state->map, state->orig));
709 }
710
711 static void
erase_cb(struct color_map_edit_state * state)712 erase_cb (struct color_map_edit_state *state)
713 {
714 unsigned i;
715 for (i = 0; i < state->map->size && state->bin > state->map->limits[i]; i++);
716 state->map->size--;
717 memmove (state->map->limits + i, state->map->limits + i + 1,
718 sizeof (unsigned) * (state->map->size - i));
719 memmove (state->map->colors + i, state->map->colors + i + 1,
720 sizeof (GOColor) * (state->map->size - i));
721 gtk_widget_set_sensitive (go_gtk_builder_get_widget (state->gui, "erase"),
722 FALSE);
723 go_color_selector_set_color (GO_SELECTOR (state->color_selector),
724 state->map->colors[state->map->size - 1]);
725 /* update the snapshots */
726 update_snapshots (state);
727 }
728
729 static void
define_cb(struct color_map_edit_state * state)730 define_cb (struct color_map_edit_state *state)
731 {
732 unsigned i;
733 for (i = 0; i < state->map->size && state->bin > state->map->limits[i]; i++);
734 if (i < state->map->size && state->bin == state->map->limits[i]) {
735 state->map->colors[i] = go_color_selector_get_color (GO_SELECTOR (state->color_selector), NULL);
736 } else {
737 /* we need to insert the new color stop */
738 state->map->size++;
739 if (state->map->allocated < state->map->size) {
740 state->map->limits = g_renew (unsigned, state->map->limits,
741 state->map->size);
742 state->map->colors = g_renew (GOColor, state->map->colors,
743 state->map->size);
744 state->map->allocated = state->map->size;
745 }
746 if (i < state->map->size - 1) {
747 memmove (state->map->limits + i + 1, state->map->limits + i,
748 sizeof (unsigned) * (state->map->size - i - 1));
749 memmove (state->map->colors + i + 1, state->map->colors + i,
750 sizeof (GOColor) * (state->map->size - i - 1));
751 }
752 state->map->limits[i] = state->bin;
753 state->map->colors[i] = go_color_selector_get_color (GO_SELECTOR (state->color_selector), NULL);
754 gtk_widget_set_sensitive (go_gtk_builder_get_widget (state->gui, "erase"),
755 TRUE);
756 }
757 /* update the snapshots */
758 update_snapshots (state);
759 }
760
761 static void
bin_changed_cb(GtkSpinButton * btn,struct color_map_edit_state * state)762 bin_changed_cb (GtkSpinButton *btn, struct color_map_edit_state *state)
763 {
764 unsigned i;
765 state->bin = gtk_spin_button_get_value (btn);
766 for (i = 0; i < state->map->size && state->bin > state->map->limits[i]; i++);
767 if (i < state->map->size) {
768 gtk_widget_set_sensitive (go_gtk_builder_get_widget (state->gui, "erase"),
769 i > 0 && state->bin == state->map->limits[i]);
770 go_color_selector_set_color (GO_SELECTOR (state->color_selector),
771 gog_axis_color_map_get_color (state->map, i));
772 } else {
773 gtk_widget_set_sensitive (go_gtk_builder_get_widget (state->gui, "erase"),
774 FALSE);
775 go_color_selector_set_color (GO_SELECTOR (state->color_selector),
776 state->map->colors[state->map->size - 1]);
777 }
778 }
779
780 /**
781 * gog_axis_color_map_edit:
782 * @map: a #GogAxisColorMap or %NULL
783 * @cc: a #GOCmdContext or %NULL
784 *
785 * Opens a dialog to edit the color map. If @map is %NULL, creates a new one
786 * unless the user cancels the edition.
787 * Returns: (transfer none): the edited color map.
788 **/
789 GogAxisColorMap *
gog_axis_color_map_edit(GogAxisColorMap * map,GOCmdContext * cc)790 gog_axis_color_map_edit (GogAxisColorMap *map, GOCmdContext *cc)
791 {
792 GtkBuilder *gui = go_gtk_builder_load_internal ("res:go:graph/gog-axis-color-map-prefs.ui", GETTEXT_PACKAGE, cc);
793 GtkWidget *top_level = go_gtk_builder_get_widget (gui, "gog-axis-color-map-prefs"), *w;
794 unsigned response;
795 GdkPixbuf *pixbuf;
796 GtkGrid *grid = GTK_GRID (gtk_builder_get_object (gui, "grid"));
797 struct color_map_edit_state state;
798
799 if (map == NULL) {
800 GOColor color = 0; /* full transparent */
801 state.map = gog_axis_color_map_from_colors ("New map", 1, &color, GO_RESOURCE_RW);
802 state.map->id = go_uuid ();
803 build_uri (state.map);
804 state.orig = gog_axis_color_map_dup (state.map);
805 } else {
806 state.orig = map;
807 state.map = gog_axis_color_map_from_colors (map->name, map->size,
808 map->colors, map->type);
809 }
810
811 state.gui = gui;
812 state.bin = 0;
813 gtk_adjustment_set_upper (GTK_ADJUSTMENT (gtk_builder_get_object (gui, "stop-adj")), UINT_MAX);
814 pixbuf = gog_axis_color_map_get_snapshot (state.map, TRUE, TRUE, 200, 24);
815 state.discrete = gtk_image_new_from_pixbuf (pixbuf);
816 g_object_unref (pixbuf);
817 gtk_grid_attach (grid, state.discrete, 1, 5, 3, 1);
818 pixbuf = gog_axis_color_map_get_snapshot (state.map, FALSE, TRUE, 200, 24);
819 state.continuous = gtk_image_new_from_pixbuf (pixbuf);
820 g_object_unref (pixbuf);
821 gtk_grid_attach (grid, state.continuous, 1, 6, 3, 1);
822 w = go_gtk_builder_get_widget (gui, "erase");
823 gtk_widget_set_sensitive (w, FALSE);
824 state.color_selector = go_selector_new_color (state.map->colors[0], state.map->colors[0], "fill-color");
825 gtk_grid_attach (grid, state.color_selector, 3, 2, 1, 1);
826 gtk_entry_set_text (GTK_ENTRY (gtk_builder_get_object (gui, "name")), state.map->name);
827 gtk_widget_set_sensitive (go_gtk_builder_get_widget (gui, "save"), FALSE);
828 gtk_widget_show_all (GTK_WIDGET (grid));
829
830 /* set some signals */
831 g_signal_connect_swapped (w, "clicked", G_CALLBACK (erase_cb), &state);
832 g_signal_connect_swapped (gtk_builder_get_object (gui, "define"), "clicked",
833 G_CALLBACK (define_cb), &state);
834 g_signal_connect (gtk_builder_get_object (gui, "stop-btn"), "value-changed",
835 G_CALLBACK (bin_changed_cb), &state);
836 response = gtk_dialog_run (GTK_DIALOG (top_level));
837 if (map == NULL)
838 g_object_unref (state.orig);
839 if (response == 1) {
840 if (!map)
841 map = state.map;
842 else {
843 /* copy the colors to map */
844 unsigned i;
845 map->size = state.map->size;
846 if (map->size > map->allocated) {
847 map->limits = g_new (unsigned, map->size);
848 map->colors = g_new (GOColor, map->size);
849 map->allocated = map->size;
850 }
851 for (i = 0; i < map->size; i++) {
852 map->limits[i] = state.map->limits[i];
853 map->colors[i] = state.map->colors[i];
854 }
855 g_object_unref (state.map);
856 }
857 gog_axis_color_map_set_name (map, gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (gui, "name"))));
858 gog_axis_color_map_save (map);
859 gog_axis_color_map_registry_add (map);
860 } else {
861 if (map == NULL)
862 g_object_unref (state.map);
863 map = NULL;
864 }
865 gtk_widget_destroy (top_level);
866 g_object_unref (gui);
867 return map;
868 }
869 #endif
870
871 /**
872 * gog_axis_color_map_from_colors:
873 * @name: color map name
874 * @nb: colors number
875 * @colors: the colors
876 * @type: the resource type
877 *
878 * Creates a color map using @colors.
879 * Returns: (transfer full): the newly created color map.
880 **/
881 GogAxisColorMap *
gog_axis_color_map_from_colors(char const * name,unsigned nb,GOColor const * colors,GoResourceType type)882 gog_axis_color_map_from_colors (char const *name, unsigned nb,
883 GOColor const *colors, GoResourceType type)
884 {
885 unsigned i;
886 GogAxisColorMap *color_map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, NULL);
887 color_map->id = g_strdup (name);
888 gog_axis_color_map_set_name (color_map, name);
889 color_map->type = type;
890 color_map->size = color_map->allocated = nb;
891 color_map->limits = g_new (unsigned, nb);
892 color_map->colors = g_new (GOColor, nb);
893 for (i = 0; i < nb; i++) {
894 color_map->limits[i] = i;
895 color_map->colors[i] = colors[i];
896 }
897 return color_map;
898 }
899
900 static GogAxisColorMap *color_map = NULL;
901
902 GogAxisColorMap const *
_gog_axis_color_map_get_default()903 _gog_axis_color_map_get_default ()
904 {
905 return color_map;
906 }
907
908 static void
color_map_load_from_uri(char const * uri)909 color_map_load_from_uri (char const *uri)
910 {
911 struct color_map_load_state state;
912 GsfInput *input = go_file_open (uri, NULL);
913
914 if (input == NULL) {
915 g_warning ("[GogAxisColorMap]: Could not open %s", uri);
916 return;
917 }
918 state.map = NULL;
919 state.name = NULL;
920 state.lang = NULL;
921 state.langs = g_get_language_names ();
922 state.name_lang_score = G_MAXINT;
923 state.color_stops = NULL;
924 if (!xml)
925 xml = gsf_xml_in_doc_new (color_map_dtd, NULL);
926 if (!gsf_xml_in_doc_parse (xml, input, &state))
927 g_warning ("[GogAxisColorMap]: Could not parse %s", uri);
928 if (state.map != NULL) {
929 if (!go_file_access (uri, GO_W_OK)) {
930 state.map->uri = g_strdup (uri);
931 state.map->type = GO_RESOURCE_RW;
932 } else
933 state.map->type = GO_RESOURCE_RO;
934 color_map_loaded (&state, uri, TRUE);
935 if (state.map)
936 gog_axis_color_map_registry_add (state.map);
937 } else
938 g_free (state.name);
939 g_object_unref (input);
940 }
941
942 static void
color_maps_load_from_dir(char const * path)943 color_maps_load_from_dir (char const *path)
944 {
945 GDir *dir = g_dir_open (path, 0, NULL);
946 char const *d_name;
947
948 if (dir == NULL)
949 return;
950 while ((d_name = g_dir_read_name (dir)) != NULL) {
951 char *fullname = g_build_filename (path, d_name, NULL);
952 char *uri = go_filename_to_uri (fullname);
953 size_t n = strlen (uri);
954 /* checking for mime type does not look safe, so we check for the
955 extension. TRhings might fail later if the file does not describe a
956 valid color map */
957 if (n >= 4 && !strcmp (uri + n - 4, ".map"))
958 color_map_load_from_uri (uri);
959 g_free (uri);
960 g_free (fullname);
961 }
962 g_dir_close (dir);
963 }
964
965 /**
966 * GogAxisColorMapHandler:
967 * @map: a #GogAxisColorMap
968 * @user_data: user data
969 *
970 * Type of the callback to pass to gog_axis_color_map_foreach()
971 * to iterate through color maps.
972 **/
973
974 /**
975 * gog_axis_color_map_foreach:
976 * @handler: (scope call): a #GogAxisColorMapHandler
977 * @user_data: data to pass to @handler
978 *
979 * Executes @handler to each color map installed on the system or loaded from
980 * a document.
981 **/
982 void
gog_axis_color_map_foreach(GogAxisColorMapHandler handler,gpointer user_data)983 gog_axis_color_map_foreach (GogAxisColorMapHandler handler, gpointer user_data)
984 {
985 GSList *ptr;
986 for (ptr = color_maps; ptr; ptr = ptr->next)
987 handler ((GogAxisColorMap *) (ptr->data), user_data);
988 }
989
990 /**
991 * gog_axis_color_map_get_from_id:
992 * @id: the color map identifier to search for
993 *
994 * Retrieves the color map whose identifier is @id.
995 * Returns: (transfer none): the found color map.
996 **/
997 GogAxisColorMap const *
gog_axis_color_map_get_from_id(char const * id)998 gog_axis_color_map_get_from_id (char const *id)
999 {
1000 GSList *ptr;
1001 GogAxisColorMap *map;
1002 for (ptr = color_maps; ptr; ptr = ptr->next)
1003 if (!strcmp (((GogAxisColorMap *) (ptr->data))->id, id))
1004 return (GogAxisColorMap *) ptr->data;
1005 /* create an empty new one */
1006 map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, "resource-type", GO_RESOURCE_EXTERNAL, NULL);
1007 map->id = g_strdup (id);
1008 gog_axis_color_map_registry_add (map);
1009 return map;
1010 }
1011
1012 /**
1013 * gog_axis_color_map_delete:
1014 * @map: a #GogAxisColorMap
1015 *
1016 * Destroys the color map and remove it from the user directory and from the
1017 * database.
1018 * Returns: %TRUE on success.
1019 **/
1020 gboolean
gog_axis_color_map_delete(GogAxisColorMap * map)1021 gog_axis_color_map_delete (GogAxisColorMap *map)
1022 {
1023 GFile *file = g_file_new_for_uri (map->uri);
1024 gboolean res;
1025 if ((res = g_file_delete (file, NULL, NULL))) {
1026 color_maps = g_slist_remove (color_maps, map);
1027 g_object_unref (map);
1028 }
1029 g_object_unref (file);
1030 return res;
1031 }
1032
1033 void
_gog_axis_color_maps_init(void)1034 _gog_axis_color_maps_init (void)
1035 {
1036 char *path;
1037
1038 /* Default color map */
1039 color_map = g_object_new (GOG_TYPE_AXIS_COLOR_MAP, "resource-type", GO_RESOURCE_NATIVE, NULL);
1040 color_map->id = g_strdup ("Default");
1041 color_map->name = g_strdup (N_("Default"));
1042 color_map->size = color_map->allocated = 5;
1043 color_map->limits = g_new (unsigned, 5);
1044 color_map->colors = g_new (GOColor, 5);
1045 color_map->limits[0] = 0;
1046 color_map->colors[0] = GO_COLOR_BLUE;
1047 color_map->limits[1] = 1;
1048 color_map->colors[1] = GO_COLOR_FROM_RGB (0, 0xff, 0xff);
1049 color_map->limits[2] = 2;
1050 color_map->colors[2] = GO_COLOR_GREEN;
1051 color_map->limits[3] = 4;
1052 color_map->colors[3] = GO_COLOR_YELLOW;
1053 color_map->limits[4] = 6;
1054 color_map->colors[4] = GO_COLOR_RED;
1055
1056 /* Now load registered color maps */
1057 path = g_build_filename (go_sys_data_dir (), "colormaps", NULL);
1058 color_maps_load_from_dir (path);
1059 g_free (path);
1060
1061 path = g_build_filename (g_get_home_dir (), ".goffice", "colormaps", NULL);
1062 color_maps_load_from_dir (path);
1063 g_free (path);
1064 }
1065
1066 void
_gog_axis_color_maps_shutdown(void)1067 _gog_axis_color_maps_shutdown (void)
1068 {
1069 g_object_unref (color_map);
1070 g_slist_free_full (color_maps, g_object_unref);
1071 if (xml)
1072 gsf_xml_in_doc_free (xml);
1073 }
1074