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