1 /*
2  * gog-color-scale.h
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 
25 #include <gsf/gsf-impl-utils.h>
26 #include <glib/gi18n-lib.h>
27 
28 #ifdef GOFFICE_WITH_GTK
29 #include <gtk/gtk.h>
30 #endif
31 
32 /**
33  * SECTION: gog-color-scale
34  * @short_description: Displays the color scale used by an axis.
35  *
36  * A color scale has two parts: an axis, and a rectangle filled by the colors
37  * corresponding to the axis scale. It can be displayed horizontally or
38  * vertically.
39  **/
40 struct _GogColorScale {
41 	GogStyledObject base;
42 	GogAxis *color_axis; /* the color or pseudo-3d axis */
43 	gboolean horizontal;
44 	gboolean axis_at_low;	/* axis position on low coordinates side */
45 	double width; /* will actually be height of the colored rectangle if
46 	 			   * horizontal */
47 	int tick_size;
48 };
49 typedef GogStyledObjectClass GogColorScaleClass;
50 
51 static GObjectClass *parent_klass;
52 static GType gog_color_scale_view_get_type (void);
53 
54 static void
gog_color_scale_init_style(GogStyledObject * gso,GOStyle * style)55 gog_color_scale_init_style (GogStyledObject *gso, GOStyle *style)
56 {
57 	style->interesting_fields = GO_STYLE_LINE | GO_STYLE_FILL |
58 	                        GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT;
59 	gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)),
60 	                        style, GOG_OBJECT (gso), 0,
61 	                        GO_STYLE_LINE | GO_STYLE_FILL |
62 	                        GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT);
63 }
64 
65 static void
gog_color_scale_set_orientation(GogColorScale * scale,gboolean horizontal)66 gog_color_scale_set_orientation (GogColorScale *scale, gboolean horizontal)
67 {
68 	GogObjectPosition pos;
69 	GogObject *gobj = GOG_OBJECT (scale);
70 	GSList *l, *ptr;
71 	GOStyle *style;
72 	scale->horizontal = horizontal;
73 	/* if the position is automatic, try to adjust accordingly */
74 	pos = gog_object_get_position_flags (gobj, GOG_POSITION_MANUAL);
75 	if (!pos) {
76 		gboolean changed = TRUE;
77 		pos = gog_object_get_position_flags (gobj, GOG_POSITION_COMPASS);
78 		switch (pos) {
79 		case GOG_POSITION_N:
80 			pos = GOG_POSITION_W;
81 			break;
82 		case GOG_POSITION_S:
83 			pos = GOG_POSITION_E;
84 			break;
85 		case GOG_POSITION_E:
86 			pos = GOG_POSITION_S;
87 			break;
88 		case GOG_POSITION_W:
89 			pos = GOG_POSITION_N;
90 			break;
91 		default:
92 			changed = FALSE;
93 		}
94 		if (changed)
95 			gog_object_set_position_flags (gobj, pos, GOG_POSITION_COMPASS);
96 	}
97 	pos = gog_object_get_position_flags (gobj, GOG_POSITION_ANY_MANUAL_SIZE);
98 	if (pos) {
99 		GogViewAllocation alloc;
100 		double buf;
101 		switch (pos) {
102 		case GOG_POSITION_MANUAL_W:
103 			gog_object_set_position_flags (gobj, GOG_POSITION_MANUAL_H,
104 				                           GOG_POSITION_ANY_MANUAL_SIZE);
105 			break;
106 		case GOG_POSITION_MANUAL_W_ABS:
107 			gog_object_set_position_flags (gobj, GOG_POSITION_MANUAL_H_ABS,
108 				                           GOG_POSITION_ANY_MANUAL_SIZE);
109 			break;
110 		case GOG_POSITION_MANUAL_H:
111 			gog_object_set_position_flags (gobj, GOG_POSITION_MANUAL_W,
112 				                           GOG_POSITION_ANY_MANUAL_SIZE);
113 			break;
114 		case GOG_POSITION_MANUAL_H_ABS:
115 			gog_object_set_position_flags (gobj, GOG_POSITION_MANUAL_W_ABS,
116 				                           GOG_POSITION_ANY_MANUAL_SIZE);
117 			break;
118 		default:
119 			break;
120 		}
121 		gog_object_get_manual_position (gobj, &alloc);
122 		buf = alloc.w;
123 		alloc.w = alloc.h;
124 		alloc.h = buf;
125        	gog_object_set_manual_position (gobj, &alloc);
126 	}
127 	l = gog_object_get_children (gobj, NULL);
128 	for (ptr = l; ptr; ptr = ptr->next) {
129 		if (GO_IS_STYLED_OBJECT (ptr->data)) {
130 			style = go_style_dup (go_styled_object_get_style (ptr->data));
131 			go_styled_object_set_style (ptr->data, style);
132 			g_object_unref (style);
133 		}
134 	}
135 	g_slist_free (l);
136 }
137 
138 #ifdef GOFFICE_WITH_GTK
139 
140 static void
position_fill_cb(GtkComboBoxText * box,GogColorScale * scale)141 position_fill_cb (GtkComboBoxText *box, GogColorScale *scale)
142 {
143 	if (scale->horizontal) {
144 		gtk_combo_box_text_append_text (box, _("Top"));
145 		gtk_combo_box_text_append_text (box, _("Bottom"));
146 	} else {
147 		gtk_combo_box_text_append_text (box, _("Left"));
148 		gtk_combo_box_text_append_text (box, _("Right"));
149 	}
150 	gtk_combo_box_set_active (GTK_COMBO_BOX (box), (scale->axis_at_low)? 0: 1);
151 }
152 
153 static void
position_changed_cb(GtkComboBox * box,GogColorScale * scale)154 position_changed_cb (GtkComboBox *box, GogColorScale *scale)
155 {
156 	scale->axis_at_low = gtk_combo_box_get_active (box) == 0;
157 	gog_object_emit_changed (GOG_OBJECT (scale), FALSE);
158 }
159 
160 static void
direction_changed_cb(GtkComboBox * box,GogColorScale * scale)161 direction_changed_cb (GtkComboBox *box, GogColorScale *scale)
162 {
163 	GtkComboBoxText *text;
164 	gog_color_scale_set_orientation (scale, gtk_combo_box_get_active (box) == 0);
165 	g_signal_emit_by_name (scale, "update-editor");
166 	text = GTK_COMBO_BOX_TEXT (g_object_get_data (G_OBJECT (box), "position"));
167 	g_signal_handlers_block_by_func (text, position_changed_cb, scale);
168 	gtk_list_store_clear (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (text))));
169 	position_fill_cb (text, scale);
170 	g_signal_handlers_unblock_by_func (text, position_changed_cb, scale);
171 }
172 
173 static void
width_changed_cb(GtkSpinButton * btn,GogColorScale * scale)174 width_changed_cb (GtkSpinButton *btn, GogColorScale *scale)
175 {
176 	scale->width = gtk_spin_button_get_value_as_int (btn);
177 	gog_object_emit_changed (GOG_OBJECT (scale), TRUE);
178 }
179 
180 static void
gog_color_scale_populate_editor(GogObject * gobj,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)181 gog_color_scale_populate_editor (GogObject *gobj,
182 			   GOEditor *editor,
183 			   G_GNUC_UNUSED GogDataAllocator *dalloc,
184 			   GOCmdContext *cc)
185 {
186 	GogColorScale *scale = GOG_COLOR_SCALE (gobj);
187 	GtkBuilder *gui;
188 	GtkWidget *w, *w_;
189 
190 	gui = go_gtk_builder_load_internal ("res:go:graph/gog-color-scale-prefs.ui", GETTEXT_PACKAGE, cc);
191 	if (gui == NULL)
192 		return;
193 
194 	w = go_gtk_builder_get_widget (gui, "direction-btn");
195 	gtk_combo_box_set_active (GTK_COMBO_BOX (w), (scale->horizontal)? 0: 1);
196 	g_signal_connect (w, "changed", G_CALLBACK (direction_changed_cb), scale);
197 
198 	w_ = go_gtk_builder_get_widget (gui, "position-btn");
199 	g_object_set_data (G_OBJECT (w), "position", w_);
200 	position_fill_cb (GTK_COMBO_BOX_TEXT (w_), scale);
201 	g_signal_connect (w_, "changed", G_CALLBACK (position_changed_cb), scale);
202 
203 	w = go_gtk_builder_get_widget (gui, "width-btn");
204 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), scale->width);
205 	g_signal_connect (w, "changed", G_CALLBACK (width_changed_cb), scale);
206 
207 	go_editor_add_page (editor, go_gtk_builder_get_widget (gui, "color-scale-prefs"), _("Details"));
208 	g_object_unref (gui);
209 	((GogObjectClass *) parent_klass)->populate_editor (gobj, editor, dalloc, cc);
210 }
211 #endif
212 
213 static GogManualSizeMode
gog_color_scale_get_manual_size_mode(GogObject * obj)214 gog_color_scale_get_manual_size_mode (GogObject *obj)
215 {
216 	return GOG_COLOR_SCALE (obj)->horizontal? GOG_MANUAL_SIZE_WIDTH: GOG_MANUAL_SIZE_HEIGHT;
217 }
218 
219 enum {
220 	COLOR_SCALE_PROP_0,
221 	COLOR_SCALE_PROP_HORIZONTAL,
222 	COLOR_SCALE_PROP_WIDTH,
223 	COLOR_SCALE_PROP_AXIS,
224 	COLOR_SCALE_PROP_TICK_SIZE_PTS
225 };
226 
227 static void
gog_color_scale_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)228 gog_color_scale_set_property (GObject *obj, guint param_id,
229 		       GValue const *value, GParamSpec *pspec)
230 {
231 	GogColorScale *scale = GOG_COLOR_SCALE (obj);
232 
233 	switch (param_id) {
234 	case COLOR_SCALE_PROP_HORIZONTAL:
235 		gog_color_scale_set_orientation (scale, g_value_get_boolean (value));
236 		break;
237 	case COLOR_SCALE_PROP_WIDTH:
238 		scale->width = g_value_get_double (value);
239 		break;
240 	case COLOR_SCALE_PROP_AXIS: {
241 		GogChart *chart = GOG_CHART (gog_object_get_parent (GOG_OBJECT (obj)));
242 		GSList *ptr;
243 		char const *buf = g_value_get_string (value);
244 		GogAxisType	 type;
245 		int id;
246 		GogAxis *axis = NULL;
247 		if (!strncmp (buf, "color", 5)) {
248 			type = GOG_AXIS_COLOR;
249 			buf += 5;
250 		} else if (!strncmp (buf, "3d", 2)) {
251 			type = GOG_AXIS_PSEUDO_3D;
252 			buf += 2;
253 		} else {
254 			unsigned index;
255 			/* kludge: old file searching for 3D in all known locales */
256 			type = (strstr (buf, "3D") != NULL || strstr (buf , "3d") !=NULL ||
257 					strstr (buf, "3Д") != NULL || strstr (buf, "3Δ") != NULL ||
258 					strstr (buf, "ترويسات متعلقة بالمحور") != NULL)?
259 					GOG_AXIS_PSEUDO_3D: GOG_AXIS_COLOR;
260 			index = strlen (buf) - 1;
261 			while (index > 0 && buf[index] <= '9' && buf[index] >= '0')
262 				index--;
263 			buf += index + 1;
264 		}
265 		id = atoi (buf);
266 		if (id < 1) /* this should not happen */
267 				id = 1;
268 		for (ptr = gog_chart_get_axes (chart, type); ptr && ptr->data; ptr = ptr->next) {
269 			if ((unsigned ) id == gog_object_get_id (GOG_OBJECT (ptr->data))) {
270 			    axis = GOG_AXIS (ptr->data);
271 				break;
272 			}
273 		}
274 		gog_color_scale_set_axis (scale, axis);
275 		break;
276 	}
277 	case COLOR_SCALE_PROP_TICK_SIZE_PTS:
278 		scale->tick_size = g_value_get_int (value);
279 		break;
280 
281 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
282 		 return; /* NOTE : RETURN */
283 	}
284 
285 	gog_object_emit_changed (GOG_OBJECT (obj), TRUE);
286 }
287 
288 static void
gog_color_scale_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)289 gog_color_scale_get_property (GObject *obj, guint param_id,
290 		       GValue *value, GParamSpec *pspec)
291 {
292 	GogColorScale *scale = GOG_COLOR_SCALE (obj);
293 
294 	switch (param_id) {
295 	case COLOR_SCALE_PROP_HORIZONTAL:
296 		g_value_set_boolean (value, scale->horizontal);
297 		break;
298 	case COLOR_SCALE_PROP_WIDTH:
299 		g_value_set_double (value, scale->width);
300 		break;
301 	case COLOR_SCALE_PROP_AXIS: {
302 		char buf[16];
303 		if (gog_axis_get_atype (scale->color_axis) == GOG_AXIS_COLOR)
304 			snprintf (buf, 16, "color%u", gog_object_get_id (GOG_OBJECT (scale->color_axis)));
305 		else
306 			snprintf (buf, 16, "3d%u", gog_object_get_id (GOG_OBJECT (scale->color_axis)));
307 		g_value_set_string (value, buf);
308 		break;
309 	}
310 	case COLOR_SCALE_PROP_TICK_SIZE_PTS:
311 		g_value_set_int (value, scale->tick_size);
312 		break;
313 
314 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
315 		 return; /* NOTE : RETURN */
316 	}
317 }
318 
319 static void
gog_color_scale_class_init(GObjectClass * gobject_klass)320 gog_color_scale_class_init (GObjectClass *gobject_klass)
321 {
322 	GogObjectClass *gog_klass = (GogObjectClass *) gobject_klass;
323 	GogStyledObjectClass *style_klass = (GogStyledObjectClass *) gog_klass;
324 	static GogObjectRole const roles[] = {
325 		{ N_("Label"), "GogLabel", 0,
326 		  GOG_POSITION_SPECIAL|GOG_POSITION_ANY_MANUAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
327 		  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } }
328 	};
329 
330 	parent_klass = g_type_class_peek_parent (gobject_klass);
331 	/* GObjectClass */
332 	gobject_klass->get_property = gog_color_scale_get_property;
333 	gobject_klass->set_property = gog_color_scale_set_property;
334 	g_object_class_install_property (gobject_klass, COLOR_SCALE_PROP_HORIZONTAL,
335 		g_param_spec_boolean ("horizontal", _("Horizontal"),
336 			_("Whether to display the scale horizontally"),
337 			FALSE,
338 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
339 	g_object_class_install_property (gobject_klass, COLOR_SCALE_PROP_WIDTH,
340 		g_param_spec_double ("width", _("Width"),
341 			_("Color scale thickness."),
342 			1., 255., 10.,
343 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
344 	g_object_class_install_property (gobject_klass, COLOR_SCALE_PROP_AXIS,
345 		g_param_spec_string ("axis",
346 			_("Axis"),
347 			_("Reference to the color or pseudo-3d axis"),
348 			NULL,
349 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
350 	g_object_class_install_property (gobject_klass, COLOR_SCALE_PROP_TICK_SIZE_PTS,
351 		g_param_spec_int ("tick-size-pts",
352 			_("Tick size"),
353 			_("Size of the tick marks, in points"),
354 			0, 20, 4,
355 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
356 
357 	gog_klass->get_manual_size_mode = gog_color_scale_get_manual_size_mode;
358 	gog_klass->view_type	= gog_color_scale_view_get_type ();
359 #ifdef GOFFICE_WITH_GTK
360 	gog_klass->populate_editor = gog_color_scale_populate_editor;
361 #endif
362 	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
363 	style_klass->init_style = gog_color_scale_init_style;
364 }
365 
366 static void
gog_color_scale_init(GogColorScale * scale)367 gog_color_scale_init (GogColorScale *scale)
368 {
369 	scale->width = 10;
370 	scale->tick_size = 4;
371 }
372 
GSF_CLASS(GogColorScale,gog_color_scale,gog_color_scale_class_init,gog_color_scale_init,GOG_TYPE_STYLED_OBJECT)373 GSF_CLASS (GogColorScale, gog_color_scale,
374 	   gog_color_scale_class_init, gog_color_scale_init,
375 	   GOG_TYPE_STYLED_OBJECT)
376 
377 /**
378  * gog_color_scale_get_axis:
379  * @scale: #GogColorScale
380  *
381  * Gets the axis mapping to the colors and associated with @scale
382  * Returns: (transfer none): the associated axis.
383  **/
384 GogAxis *
385 gog_color_scale_get_axis (GogColorScale *scale)
386 {
387 	g_return_val_if_fail (GOG_IS_COLOR_SCALE (scale), NULL);
388 	return scale->color_axis;
389 }
390 
391 /**
392  * gog_color_scale_set_axis:
393  * @scale: #GogColorScale
394  * @axis: a color or pseudo-3d axis
395  *
396  * Associates the axis with @scale.
397  **/
398 void
gog_color_scale_set_axis(GogColorScale * scale,GogAxis * axis)399 gog_color_scale_set_axis (GogColorScale *scale, GogAxis *axis)
400 {
401 	g_return_if_fail (GOG_IS_COLOR_SCALE (scale));
402 	if (scale->color_axis == axis)
403 		return;
404 	if (scale->color_axis != NULL)
405 		_gog_axis_set_color_scale (scale->color_axis, NULL);
406 	scale->color_axis = axis;
407 	if (axis)
408 		_gog_axis_set_color_scale (axis, scale);
409 }
410 
411 /************************************************************************/
412 
413 typedef struct {
414 	GogView	base;
415 	GogViewAllocation scale_area;
416 } GogColorScaleView;
417 typedef GogViewClass	GogColorScaleViewClass;
418 
419 static GogViewClass *view_parent_class;
420 
421 #define GOG_TYPE_COLOR_SCALE_VIEW	(gog_color_scale_view_get_type ())
422 #define GOG_COLOR_SCALE_VIEW(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_COLOR_SCALE_VIEW, GogColorScaleView))
423 #define GOG_IS_COLOR_SCALE_VIEW(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_COLOR_SCALE_VIEW))
424 
425 static void
gog_color_scale_view_size_request(GogView * v,GogViewRequisition const * available,GogViewRequisition * req)426 gog_color_scale_view_size_request (GogView *v,
427                                    GogViewRequisition const *available,
428                                    GogViewRequisition *req)
429 {
430 	GogColorScale *scale = GOG_COLOR_SCALE (v->model);
431 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (scale));
432 	double line_width, tick_size, label_padding, max_width = 0., max_height = 0.;
433 	double extra_h = 0., extra_w = 0.;
434 	unsigned nb_ticks, i;
435 	GogAxisTick *ticks;
436 	GOGeometryAABR txt_aabr;
437 	GOGeometryOBR txt_obr;
438 	GSList *ptr;
439 	GogView *child;
440 	GogObjectPosition pos;
441 	GogViewRequisition child_req;
442 
443 	gog_renderer_push_style (v->renderer, style);
444 	gog_renderer_get_text_OBR (v->renderer, "0", TRUE, &txt_obr, -1.);
445 	label_padding = txt_obr.h * .15;
446 	nb_ticks = gog_axis_get_ticks (scale->color_axis, &ticks);
447 	for (i = 0; i < nb_ticks; i++)
448 		if (ticks[i].type == GOG_AXIS_TICK_MAJOR) {
449 			gog_renderer_get_gostring_AABR (v->renderer, ticks[i].str, &txt_aabr, 0.);
450 			if (txt_aabr.w > max_width)
451 				max_width = txt_aabr.w;
452 			if (txt_aabr.h > max_height)
453 				max_height = txt_aabr.h;
454 		}
455 
456 	if (go_style_is_line_visible (style)) {
457 		line_width = gog_renderer_line_size (v->renderer, style->line.width);
458 		tick_size = gog_renderer_pt2r (v->renderer, scale->tick_size);
459 	} else
460 		line_width = tick_size = 0.;
461 	if (scale->horizontal) {
462 		req->w = available->w;
463 		req->h = gog_renderer_pt2r (v->renderer, scale->width)
464 				 + 2 * line_width + tick_size + label_padding + max_height;
465 	} else {
466 		req->h = available->h;
467 		req->w = gog_renderer_pt2r (v->renderer, scale->width)
468 				 + 2 * line_width + tick_size + label_padding + max_width;
469 	}
470 	gog_renderer_pop_style (v->renderer);
471 	for (ptr = v->children; ptr != NULL ; ptr = ptr->next) {
472 		child = ptr->data;
473 		pos = child->model->position;
474 		if (!(pos & GOG_POSITION_MANUAL)) {
475 			gog_view_size_request (child, available, &child_req);
476 			if (scale->horizontal)
477 				extra_h += child_req.h;
478 			else
479 				extra_w += child_req.w;
480 		} else {
481 		}
482 	}
483 	req->w += extra_w;
484 	req->h += extra_h;
485 }
486 
487 static void
gog_color_scale_view_size_allocate(GogView * view,GogViewAllocation const * bbox)488 gog_color_scale_view_size_allocate (GogView *view, GogViewAllocation const *bbox)
489 {
490 	GogColorScale *scale = GOG_COLOR_SCALE (view->model);
491 	GSList *ptr;
492 	GogView *child;
493 	GogObjectPosition pos = view->model->position, child_pos;
494 	GogViewAllocation child_alloc, res = *bbox;
495 	GogViewRequisition available, req;
496 	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
497 		child = ptr->data;
498 		child_pos = child->model->position;
499 		available.w = res.w;
500 		available.h = res.h;
501 		if (child_pos & GOG_POSITION_MANUAL) {
502 			gog_view_size_request (child, &available, &req);
503 			child_alloc = gog_object_get_manual_allocation (gog_view_get_model (child),
504 			                                                bbox, &req);
505 		} else if (child_pos == GOG_POSITION_SPECIAL) {
506 			gog_view_size_request (child, &available, &req);
507 			if (scale->horizontal) {
508 				if (pos & GOG_POSITION_N) {
509 					child_alloc.y = res.y;
510 					res.y += req.h;
511 				} else {
512 					child_alloc.y = res.y + res.h - req.h;
513 				}
514 				res.h -= req.h;
515 				child_alloc.x = res.x + (res.w - req.w) / 2.;
516 			} else {
517 				if (pos & GOG_POSITION_W) {
518 					child_alloc.x = res.x;
519 					res.x += req.w;
520 				} else {
521 					child_alloc.x = res.x + res.w - req.w;
522 				}
523 				res.w -= req.w;
524 				child_alloc.y = res.y + (res.h - req.h) / 2.;
525 				child_alloc.w = req.w;
526 				child_alloc.h = req.h;
527 			}
528 			child_alloc.w = req.w;
529 			child_alloc.h = req.h;
530 		} else {
531 			g_warning ("[GogColorScaleView::size_allocate] unexpected position %x for child %p of %p",
532 				   pos, child, view);
533 			continue;
534 		}
535 		gog_view_size_allocate (child, &child_alloc);
536 	}
537 	view->residual = res;
538 }
539 
540 static void
gog_color_scale_view_render(GogView * view,GogViewAllocation const * bbox)541 gog_color_scale_view_render (GogView *view, GogViewAllocation const *bbox)
542 {
543 	GogColorScale *scale = GOG_COLOR_SCALE (view->model);
544 	unsigned nb_ticks, nb_maj_ticks = 0, i, j, l;
545 	GogAxisTick *ticks;
546 	GogViewAllocation scale_area, label_pos;
547 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (scale));
548 	double line_width, tick_size, label_padding, width, pos, start, stop;
549 	GOGeometryOBR txt_obr;
550 	GOGeometryOBR *obrs = NULL;
551 	GogAxisMap *map;
552 	GOPath *path;
553 	gboolean is_line_visible;
554 	double min, max, first, last = 0., hf = 0., hl = 0.;
555 	GOAnchorType anchor;
556 	gboolean discrete = gog_axis_get_atype (scale->color_axis) == GOG_AXIS_PSEUDO_3D;
557 	GogAxisColorMap const *cmap = gog_axis_get_color_map (scale->color_axis);
558 
559 	gog_renderer_push_style (view->renderer, style);
560 	nb_ticks = gog_axis_get_ticks (scale->color_axis, &ticks);
561 	for (i = 0; i < nb_ticks; i++)
562 		if (ticks[i].type == GOG_AXIS_TICK_MAJOR)
563 			nb_maj_ticks++;
564 	gog_renderer_get_text_OBR (view->renderer, "0", TRUE, &txt_obr, -1.);
565 	label_padding = txt_obr.h * .15;
566 	width = gog_renderer_pt2r (view->renderer, scale->width);
567 	obrs = g_new0 (GOGeometryOBR, nb_maj_ticks);
568 	gog_axis_get_bounds (scale->color_axis, &min, &max);
569 	first = min - 1.;
570 	/* evaluate labels size, and find first and last label */
571 	for (i = 0, j = 0; i < nb_ticks; i++)
572 		if (ticks[i].type == GOG_AXIS_TICK_MAJOR) {
573 			gog_renderer_get_gostring_OBR (view->renderer, ticks[i].str, obrs + j, -1.);
574 			if (first < min) {
575 				first = last = ticks[i].position;
576 				hf = hl = scale->horizontal? obrs[j].w: obrs[j].h;
577 			} else if (ticks[i].position > last) {
578 				last = ticks[i].position;
579 				hl = scale->horizontal? obrs[j].w: obrs[j].h;
580 			} else if (ticks[i].position < first) {
581 				first = ticks[i].position;
582 				hf = scale->horizontal? obrs[j].w: obrs[j].h;
583 			}
584 			j++;
585 		}
586 	hf /= 2.;
587 	hl /= 2.;
588 
589 	/* evaluate color scale area */
590 	if ((is_line_visible = go_style_is_line_visible (style))) {
591 		line_width = gog_renderer_line_size (view->renderer, style->line.width);
592 		tick_size = gog_renderer_pt2r (view->renderer, scale->tick_size);
593 	} else
594 		line_width = tick_size = 0.;
595 	if (min == max || first == last) {
596 		/* avoid infinites and nans later, this might happen if no data are available */
597 		if (min < first)
598 			first = min;
599 		else
600 			min = first;
601 		if (max > last)
602 			last = max;
603 		else
604 			max = last;
605 	}
606 	if (scale->horizontal) {
607 		scale_area.x = view->residual.x;
608 		/* we make sure that we have enough room to display the last and first labels */
609 		pos = (scale_area.w = view->residual.w) - hf - hl;
610 		stop = hl - pos * (max - last) / (max - min);
611 		start = hf - pos * (first - min) / (max - min);
612 		if (start < 0)
613 			start = 0.;
614 		if (stop < 0.)
615 			stop = 0.;
616 		/* we might actually remove slightly more than needed */
617 		scale_area.w -= start + stop;
618 		scale_area.x += gog_axis_is_inverted (scale->color_axis)? stop: start;
619 		scale_area.y = (scale->axis_at_low)?
620 			view->residual.y + view->residual.h - width - 2 * line_width:
621 			view->residual.y;
622 		scale_area.h = width + 2 * line_width;
623 	} else {
624 		scale_area.x = (scale->axis_at_low)?
625 			view->residual.x + view->residual.w - width - 2 * line_width:
626 			view->residual.x;
627 		scale_area.w = width + 2 * line_width;
628 		scale_area.y = view->residual.y;
629 		/* we make sure that we have enough room to display the last and first labels */
630 		pos = (scale_area.h = view->residual.h) - hf -hl;
631 		stop = hl - pos * (max - last) / (max - min);
632 		start = hf - pos * (first - min) / (max - min);
633 		if (start < 0)
634 			start = 0.;
635 		if (stop < 0.)
636 			stop = 0.;
637 		/* we might actually remove slightly more than needed */
638 		scale_area.h -= start + stop;
639 		scale_area.y += gog_axis_is_inverted (scale->color_axis)? start: stop;
640 	}
641 
642 	gog_renderer_stroke_rectangle (view->renderer, &scale_area);
643 	scale_area.x += line_width / 2.;
644 	scale_area.w -= line_width;
645 	scale_area.y += line_width / 2.;
646 	scale_area.h -= line_width;
647 	if (scale->horizontal)
648 		map = gog_axis_map_new (scale->color_axis, scale_area.x, scale_area.w);
649 	else
650 		map = gog_axis_map_new (scale->color_axis, scale_area.y + scale_area.h, -scale_area.h);
651 	if (discrete) {
652 		GOStyle *dstyle;
653 		double *limits, epsilon, sc, *x, *w;
654 		/* some of the code was copied from gog-contour.c to be consistent */
655 		i = j = 0;
656 		epsilon = (max - min) / nb_ticks * 1e-10; /* should avoid rounding errors */
657 		while (ticks[i].type != GOG_AXIS_TICK_MAJOR)
658 			i++;
659 		if (ticks[i].position - min > epsilon) {
660 			limits = g_new (double, nb_maj_ticks + 2);
661 			limits[j++] = min;
662 		} else
663 			limits = g_new (double, nb_maj_ticks + 1);
664 		for (; i < nb_ticks; i++)
665 			if (ticks[i].type == GOG_AXIS_TICK_MAJOR)
666 				limits[j++] = ticks[i].position;
667 		if (j == 0 || max - limits[j - 1] > epsilon)
668 			limits[j] = max;
669 		else
670 			j--;
671 		sc = (j > gog_axis_color_map_get_max (cmap) && j > 1)? (double) gog_axis_color_map_get_max (cmap) / (j - 1): 1.;
672 		dstyle = go_style_dup (style);
673 		dstyle->interesting_fields = GO_STYLE_FILL | GO_STYLE_OUTLINE;
674 		dstyle->fill.type = GO_STYLE_FILL_PATTERN;
675 		dstyle->fill.pattern.pattern = GO_PATTERN_SOLID;
676 		gog_renderer_push_style (view->renderer, dstyle);
677 		/* using label_pos as drawing rectangle */
678 		if (scale->horizontal) {
679 			x = &label_pos.x;
680 			w = &label_pos.w;
681 			label_pos.y = scale_area.y;
682 			label_pos.h = scale_area.h;
683 		} else {
684 			x = &label_pos.y;
685 			w = &label_pos.h;
686 			label_pos.x = scale_area.x;
687 			label_pos.w = scale_area.w;
688 		}
689 		if (gog_axis_is_inverted (scale->color_axis)) {
690 			for (i = 0; i < j; i++) {
691 				dstyle->fill.pattern.back = (j < 2)? GO_COLOR_WHITE: gog_axis_color_map_get_color (cmap, i * sc);
692 				*x = gog_axis_map_to_view (map, limits[j - i - 1]);
693 				*w = gog_axis_map_to_view (map, limits[j - i]) - *x;
694 				gog_renderer_fill_rectangle (view->renderer, &label_pos);
695 			}
696 			if (limits[i - j] - min > epsilon) {
697 				dstyle->fill.pattern.back = (j < 2)? GO_COLOR_WHITE: gog_axis_color_map_get_color (cmap, i * sc);
698 				*x = gog_axis_map_to_view (map, min);
699 				*w = gog_axis_map_to_view (map, limits[i - j]) - *x;
700 				gog_renderer_fill_rectangle (view->renderer, &label_pos);
701 			}
702 		} else {
703 			if (epsilon < limits[0] - min) {
704 				dstyle->fill.pattern.back = (j < 2)? GO_COLOR_WHITE: gog_axis_color_map_get_color (cmap, 0);
705 				*x = gog_axis_map_to_view (map, min);
706 				*w = gog_axis_map_to_view (map, limits[0]) - *x;
707 				gog_renderer_fill_rectangle (view->renderer, &label_pos);
708 				i = 1;
709 				j++;
710 			} else
711 				i = 0;
712 			for (; i < j; i++) {
713 				dstyle->fill.pattern.back = (j < 2)? GO_COLOR_WHITE: gog_axis_color_map_get_color (cmap, i * sc);
714 				*x = gog_axis_map_to_view (map, limits[i]);
715 				*w = gog_axis_map_to_view (map, limits[i + 1]) - *x;
716 				gog_renderer_fill_rectangle (view->renderer, &label_pos);
717 			}
718 		}
719 		gog_renderer_pop_style (view->renderer);
720 		g_free (limits);
721 		g_object_unref (dstyle);
722 	} else
723 		gog_renderer_draw_color_map (view->renderer, cmap,
724 		                             FALSE, scale->horizontal, &scale_area);
725 	/* draw ticks */
726 	if (scale->horizontal) {
727 		if (scale->axis_at_low) {
728 			stop = scale_area.y - line_width;
729 			start = stop - tick_size;
730 			anchor = GO_ANCHOR_SOUTH;
731 			if (discrete)
732 				stop += scale_area.h;
733 		} else {
734 			stop = scale_area.y + scale_area.h + line_width;
735 			start = stop + tick_size;
736 			anchor = GO_ANCHOR_NORTH;
737 			if (discrete)
738 				stop -= scale_area.h;
739 		}
740 		j = l = 0;
741 		for (i = 0; i < nb_ticks; i++)
742 			/* Only major ticks are displayed, at least for now */
743 			if (ticks[i].type == GOG_AXIS_TICK_MAJOR) {
744 				pos = gog_axis_map_to_view (map, ticks[i].position);
745 				if (is_line_visible) {
746 					path = go_path_new ();
747 					go_path_move_to (path, pos, start);
748 					go_path_line_to (path, pos, stop);
749 					gog_renderer_stroke_shape (view->renderer, path);
750 					go_path_free (path);
751 				}
752 				label_pos.x = pos;
753 				label_pos.y = start + ((scale->axis_at_low)? -label_padding: label_padding);
754 				obrs[j].x = label_pos.x - obrs[j].w / 2.;
755 				obrs[j].y = pos;
756 				if (j == 0 || !go_geometry_test_OBR_overlap (obrs + j, obrs + l)) {
757 					gog_renderer_draw_gostring (view->renderer, ticks[i].str,
758 						                        &label_pos, anchor,
759 							                    GO_JUSTIFY_CENTER, -1.);
760 					l = j;
761 				}
762 				j++;
763 			}
764 	} else {
765 		if (scale->axis_at_low) {
766 			stop = scale_area.x - line_width;
767 			start = stop - tick_size;
768 			anchor = GO_ANCHOR_EAST;
769 			if (discrete)
770 				stop += scale_area.w;
771 		} else {
772 			stop = scale_area.x + scale_area.w + line_width;
773 			start = stop + tick_size;
774 			anchor = GO_ANCHOR_WEST;
775 			if (discrete)
776 				stop -= scale_area.w;
777 		}
778 		j = l = 0;
779 		for (i = 0; i < nb_ticks; i++)
780 			/* Only major ticks are displayed, at least for now */
781 			if (ticks[i].type == GOG_AXIS_TICK_MAJOR) {
782 				pos = gog_axis_map_to_view (map, ticks[i].position);
783 				if (is_line_visible) {
784 					path = go_path_new ();
785 					go_path_move_to (path, start, pos);
786 					go_path_line_to (path, stop, pos);
787 					gog_renderer_stroke_shape (view->renderer, path);
788 					go_path_free (path);
789 				}
790 				label_pos.x = start + ((scale->axis_at_low)? -label_padding: label_padding);
791 				label_pos.y = pos;
792 				obrs[j].x = label_pos.x;
793 				obrs[j].y = pos - obrs[j].h / 2.;
794 				if (j == 0 || !go_geometry_test_OBR_overlap (obrs + j, obrs + l)) {
795 					gog_renderer_draw_gostring (view->renderer, ticks[i].str,
796 						                        &label_pos, anchor,
797 							                    GO_JUSTIFY_CENTER, -1.);
798 					l = j;
799 				}
800 				j++;
801 			}
802 	}
803 	g_free (obrs);
804 	gog_axis_map_free (map);
805 	gog_renderer_pop_style (view->renderer);
806 	view_parent_class->render (view, bbox);
807 }
808 
809 static void
gog_color_scale_view_class_init(GogColorScaleViewClass * gview_klass)810 gog_color_scale_view_class_init (GogColorScaleViewClass *gview_klass)
811 {
812 	GogViewClass *view_klass    = (GogViewClass *) gview_klass;
813 
814 	view_parent_class = g_type_class_peek_parent (gview_klass);
815 	view_klass->size_request = gog_color_scale_view_size_request;
816 	view_klass->render = gog_color_scale_view_render;
817 	view_klass->size_allocate = gog_color_scale_view_size_allocate;
818 }
819 
820 static GSF_CLASS (GogColorScaleView, gog_color_scale_view,
821 	   gog_color_scale_view_class_init, NULL,
822 	   GOG_TYPE_VIEW)
823