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