GETENV: check for proper UTF-8.
[gnumeric.git] / src / gnm-so-filled.c
blob36732a1d52830083450cf87cb42788ff30b5750b
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * gnm-so-filled.c: Boxes, Ovals and Polygons
6 * Copyright (C) 2004-2006 Jody Goldberg (jody@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
24 #include <gnumeric-config.h>
25 #include "gnumeric.h"
26 #include "application.h"
27 #include "gnm-so-filled.h"
28 #include "sheet-object-impl.h"
29 #include "sheet.h"
30 #include "gutils.h"
31 #include "xml-sax.h"
33 #include <goffice/goffice.h>
34 #include <gsf/gsf-impl-utils.h>
35 #include <glib/gi18n-lib.h>
36 #include <string.h>
37 #include <math.h>
39 #define CXML2C(s) ((char const *)(s))
41 static inline gboolean
42 attr_eq (const xmlChar *a, const char *s)
44 return !strcmp (CXML2C (a), s);
47 #define GNM_SO_FILLED(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GNM_SO_FILLED_TYPE, GnmSOFilled))
48 #define GNM_SO_FILLED_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SO_FILLED_TYPE, GnmSOFilledClass))
50 typedef struct {
51 SheetObject base;
53 GOStyle *style;
54 gboolean is_oval;
56 char *text;
57 PangoAttrList *markup;
58 struct {
59 double top, bottom, left, right;
60 } margin_pts;
61 } GnmSOFilled;
62 typedef SheetObjectClass GnmSOFilledClass;
64 #ifdef GNM_WITH_GTK
65 #include <goffice/goffice.h>
67 typedef struct {
68 SheetObjectView base;
69 GocItem *bg, *text;
70 } FilledItemView;
72 static void
73 so_filled_view_set_bounds (SheetObjectView *sov, double const *coords, gboolean visible)
75 GocItem *view = GOC_ITEM (sov);
76 FilledItemView *fiv = (FilledItemView *) sov;
77 double scale = goc_canvas_get_pixels_per_unit (view->canvas);
79 if (visible) {
80 SheetObject *so = sheet_object_view_get_so (sov);
81 GnmSOFilled *sof = GNM_SO_FILLED (so);
82 double w = fabs (coords [2] - coords [0]) / scale;
83 double h = fabs (coords [3] - coords [1]) / scale;
85 goc_item_set (view,
86 "x", MIN (coords [0], coords [2]) / scale,
87 "y", MIN (coords [1], coords [3]) / scale,
88 NULL);
90 goc_item_set (GOC_ITEM (fiv->bg),
91 "width", w, "height", h,
92 NULL);
95 if (fiv->text != NULL && GOC_IS_ITEM (fiv->text)) {
96 w -= (sof->margin_pts.left + sof->margin_pts.right)
97 / scale;
98 w = MAX (w, DBL_MIN);
100 h -= (sof->margin_pts.top + sof->margin_pts.bottom)
101 / scale;
102 h = MAX (h, DBL_MIN);
104 if (sof->is_oval)
105 goc_item_set (GOC_ITEM (fiv->text),
106 "x", w / 2.,
107 "y", h / 2.,
108 NULL);
110 goc_item_set (GOC_ITEM (fiv->text),
111 "clip-height", h,
112 "clip-width", w,
113 "wrap-width", w,
114 NULL);
117 goc_item_show (view);
118 } else
119 goc_item_hide (view);
122 static void
123 so_filled_item_view_class_init (SheetObjectViewClass *sov_klass)
125 sov_klass->set_bounds = so_filled_view_set_bounds;
128 typedef SheetObjectViewClass FilledItemViewClass;
129 static GSF_CLASS (FilledItemView, so_filled_item_view,
130 so_filled_item_view_class_init, NULL,
131 GNM_SO_VIEW_TYPE)
133 #endif /* GNM_WITH_GTK */
135 /*****************************************************************************/
137 static SheetObjectClass *gnm_so_filled_parent_class;
139 enum {
140 SOF_PROP_0,
141 SOF_PROP_STYLE,
142 SOF_PROP_IS_OVAL,
143 SOF_PROP_TEXT,
144 SOF_PROP_MARKUP,
145 SOF_PROP_DOCUMENT
148 static GOStyle *
149 sof_default_style (void)
151 GOStyle *res = go_style_new ();
152 res->interesting_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL;
153 res->line.width = 0; /* hairline */
154 res->line.color = GO_COLOR_BLACK;
155 res->line.dash_type = GO_LINE_SOLID; /* anything but 0 */
156 res->fill.type = GO_STYLE_FILL_PATTERN;
157 go_pattern_set_solid (&res->fill.pattern, GO_COLOR_WHITE);
158 return res;
161 /*****************************************************************************/
162 #ifdef GNM_WITH_GTK
163 #include <sheet-control-gui.h>
164 #include <dialogs/dialogs.h>
165 #include <gnumeric-simple-canvas.h>
166 #include <gnm-pane.h>
167 #include <goffice/canvas/goc-rectangle.h>
168 #include <goffice/canvas/goc-ellipse.h>
169 #include <goffice/canvas/goc-text.h>
171 static void
172 gnm_so_filled_user_config (SheetObject *so, SheetControl *sc)
174 dialog_so_styled (scg_wbcg (GNM_SCG (sc)), G_OBJECT (so),
175 sof_default_style (),
176 _("Filled Object Properties"),
177 SO_STYLED_TEXT);
180 static void
181 cb_gnm_so_filled_style_changed (GocItem *background, GnmSOFilled const *sof)
183 GOStyle const *style = sof->style;
185 goc_item_set (background, "style", style, NULL);
188 static void
189 cb_gnm_so_filled_changed (GnmSOFilled const *sof,
190 G_GNUC_UNUSED GParamSpec *pspec,
191 FilledItemView *group)
193 cb_gnm_so_filled_style_changed (GOC_ITEM (group->bg), sof);
195 if (sof->text != NULL) {
196 /* set a font, a very bad solution, but will do until we move to GOString */
197 PangoFontDescription *desc = pango_font_description_from_string ("Sans 10");
198 GOStyle *style;
199 double w, h;
200 double scale = goc_canvas_get_pixels_per_unit (GOC_ITEM (group)->canvas);
201 g_object_get (group->bg, "width", &w, "height", &h, NULL);
202 w -= (sof->margin_pts.left + sof->margin_pts.right)
203 / scale;
204 w = MAX (w, DBL_MIN);
206 h -= (sof->margin_pts.top + sof->margin_pts.bottom)
207 / scale;
208 h = MAX (h, DBL_MIN);
209 if (group->text == NULL) {
210 if (sof->is_oval) {
211 group->text = goc_item_new (GOC_GROUP (group), GOC_TYPE_TEXT,
212 "anchor", GO_ANCHOR_CENTER,
213 "clip", TRUE,
214 "x", w / 2.,
215 "y", h / 2.,
216 "attributes", sof->markup,
217 NULL);
218 } else
219 group->text = goc_item_new (GOC_GROUP (group), GOC_TYPE_TEXT,
220 "anchor", GO_ANCHOR_NW,
221 "clip", TRUE,
222 "x", sof->margin_pts.left,
223 "y", sof->margin_pts.top,
224 "attributes", sof->markup,
225 NULL);
227 style = go_styled_object_get_style (GO_STYLED_OBJECT (group->text));
228 go_style_set_font_desc (style, desc);
229 goc_item_set (group->text,
230 "text", sof->text,
231 "attributes", sof->markup,
232 "clip-height", h,
233 "clip-width", w,
234 "wrap-width", w,
235 NULL);
236 } else if (group->text != NULL) {
237 g_object_unref (group->text);
238 group->text = NULL;
242 static SheetObjectView *
243 gnm_so_filled_new_view (SheetObject *so, SheetObjectViewContainer *container)
245 GnmSOFilled *sof = GNM_SO_FILLED (so);
246 FilledItemView *group = (FilledItemView *) goc_item_new (
247 gnm_pane_object_group (GNM_PANE (container)),
248 so_filled_item_view_get_type (),
249 NULL);
251 group->bg = goc_item_new (GOC_GROUP (group),
252 sof->is_oval ? GOC_TYPE_ELLIPSE : GOC_TYPE_RECTANGLE,
253 "x", 0., "y", 0.,
254 NULL);
255 cb_gnm_so_filled_changed (sof, NULL, group);
256 g_signal_connect_object (sof,
257 "notify", G_CALLBACK (cb_gnm_so_filled_changed),
258 group, 0);
259 return gnm_pane_object_register (so, GOC_ITEM (group), TRUE);
262 #endif /* GNM_WITH_GTK */
264 static void
265 gnm_so_filled_draw_cairo (SheetObject const *so, cairo_t *cr,
266 double width, double height)
268 GnmSOFilled *sof = GNM_SO_FILLED (so);
269 GOStyle const *style = sof->style;
271 cairo_new_path (cr);
272 if (sof->is_oval) {
273 cairo_save (cr);
274 cairo_scale (cr, width, height);
275 cairo_arc (cr, .5, .5, .5, 0., 2 * M_PI);
276 cairo_restore (cr);
277 } else {
278 cairo_move_to (cr, 0., 0.);
279 cairo_line_to (cr, width, 0.);
280 cairo_line_to (cr, width, height);
281 cairo_line_to (cr, 0., height);
282 cairo_line_to (cr, 0., 0.);
283 cairo_close_path (cr);
285 /* Fill the shape */
286 go_style_fill (style, cr, TRUE);
287 /* Draw the line */
288 if (go_style_set_cairo_line (style, cr))
289 cairo_stroke (cr);
290 else
291 cairo_new_path (cr);
292 /* Draw the text. */
293 if (sof->text != NULL && *(sof->text) != '\0') {
294 PangoLayout *pl = pango_cairo_create_layout (cr);
295 double const scale_h = 72. / gnm_app_display_dpi_get (TRUE);
296 double const scale_v = 72. / gnm_app_display_dpi_get (FALSE);
297 double pl_height = (height - sof->margin_pts.top
298 - sof->margin_pts.bottom) * PANGO_SCALE
299 / scale_v;
300 double pl_width = (width - sof->margin_pts.left
301 - sof->margin_pts.right) * PANGO_SCALE
302 / scale_h;
303 /* set a font, a very bad solution, but will do until we move to GOString */
304 PangoFontDescription *desc = pango_font_description_from_string ("Sans 10");
305 pango_layout_set_font_description (pl, desc);
306 pango_layout_set_text (pl, sof->text, -1);
307 pango_layout_set_attributes (pl, sof->markup);
308 pango_layout_set_width (pl, pl_width);
309 pango_layout_set_height (pl, pl_height);
310 cairo_save (cr);
311 if (sof->is_oval) {
312 PangoRectangle r;
313 pango_layout_get_extents (pl, NULL, &r);
314 cairo_move_to (cr,
315 (width - r.width / PANGO_SCALE * scale_h) / 2.,
316 (height - r.height / PANGO_SCALE * scale_v) / 2.);
317 } else
318 cairo_move_to (cr, sof->margin_pts.left,
319 sof->margin_pts.top);
320 cairo_scale (cr, scale_h, scale_v);
321 cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (style->font.color));
322 pango_cairo_show_layout (cr, pl);
323 cairo_new_path (cr);
324 cairo_restore (cr);
325 g_object_unref (pl);
326 pango_font_description_free (desc);
330 static void
331 gnm_so_filled_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
332 GnmConventions const *convs)
334 GnmSOFilled const *sof = GNM_SO_FILLED (so);
335 GOStyle const *style = sof->style;
336 gsf_xml_out_add_int (output, "Type", sof->is_oval ? 102 : 101);
338 if (sof->text != NULL && sof->text[0] != '\0') {
339 gsf_xml_out_add_cstr (output, "Label", sof->text);
340 if (sof->markup != NULL) {
341 GOFormat *fmt = go_format_new_markup (sof->markup, TRUE);
342 gsf_xml_out_add_cstr (output, "LabelFormat",
343 go_format_as_XL (fmt));
344 go_format_unref (fmt);
348 gsf_xml_out_start_element (output, "Style");
349 go_persist_sax_save (GO_PERSIST (style), output);
350 gsf_xml_out_end_element (output); /* </Style> */
353 static void
354 sof_sax_style (GsfXMLIn *xin, xmlChar const **attrs)
356 SheetObject *so = gnm_xml_in_cur_obj (xin);
357 GnmSOFilled *sof = GNM_SO_FILLED (so);
358 go_persist_prep_sax (GO_PERSIST (sof->style), xin, attrs);
361 static void
362 gnm_so_filled_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
363 xmlChar const **attrs,
364 GnmConventions const *convs)
366 static GsfXMLInNode const dtd[] = {
367 GSF_XML_IN_NODE (STYLE, STYLE, -1, "Style", GSF_XML_NO_CONTENT, &sof_sax_style, NULL),
368 GSF_XML_IN_NODE_END
370 static GsfXMLInDoc *doc = NULL;
371 GnmSOFilled *sof = GNM_SO_FILLED (so);
372 double tmp;
373 int type;
375 if (NULL == doc) {
376 doc = gsf_xml_in_doc_new (dtd, NULL);
377 gnm_xml_in_doc_dispose_on_exit (&doc);
379 gsf_xml_in_push_state (xin, doc, NULL, NULL, attrs);
381 for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
382 if (attr_eq (attrs[0], "Label"))
383 g_object_set (G_OBJECT (sof), "text", attrs[1], NULL);
384 else if (attr_eq (attrs[0], "LabelFormat")) {
385 GOFormat * fmt = go_format_new_from_XL (attrs[1]);
386 if (go_format_is_markup (fmt))
387 g_object_set (G_OBJECT (sof),
388 "markup", go_format_get_markup (fmt),
389 NULL);
390 go_format_unref (fmt);
391 } else if (gnm_xml_attr_int (attrs, "Type", &type))
392 sof->is_oval = (type == 102);
394 /* Old 1.0 and 1.2 */
395 else if (gnm_xml_attr_double (attrs, "Width", &tmp))
396 sof->style->line.width = tmp;
397 else if (attr_eq (attrs[0], "OutlineColor"))
398 go_color_from_str (CXML2C (attrs[1]), &sof->style->line.color);
399 else if (attr_eq (attrs[0], "FillColor"))
400 go_color_from_str (CXML2C (attrs[1]), &sof->style->fill.pattern.back);
403 static void
404 gnm_so_filled_copy (SheetObject *dst, SheetObject const *src)
406 GnmSOFilled const *sof = GNM_SO_FILLED (src);
407 GnmSOFilled *new_sof = GNM_SO_FILLED (dst);
409 g_object_unref (new_sof->style);
410 new_sof->is_oval = sof->is_oval;
411 new_sof->style = go_style_dup (sof->style);
412 new_sof->text = g_strdup (sof->text);
413 new_sof->margin_pts.top = sof->margin_pts.top ;
414 new_sof->margin_pts.bottom = sof->margin_pts.bottom;
415 new_sof->margin_pts.left = sof->margin_pts.left;
416 new_sof->margin_pts.right = sof->margin_pts.right;
417 if (NULL != (new_sof->markup = sof->markup))
418 pango_attr_list_ref (new_sof->markup);
421 static void
422 gnm_so_filled_set_property (GObject *obj, guint param_id,
423 GValue const *value, GParamSpec *pspec)
425 GnmSOFilled *sof = GNM_SO_FILLED (obj);
426 char const * str;
428 switch (param_id) {
429 case SOF_PROP_STYLE: {
430 GOStyle *style = go_style_dup (g_value_get_object (value));
431 style->interesting_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL;
432 g_object_unref (sof->style);
433 sof->style = style;
434 break;
436 case SOF_PROP_IS_OVAL:
437 sof->is_oval = g_value_get_boolean (value);
438 break;
439 case SOF_PROP_TEXT:
440 g_free (sof->text);
441 str = g_value_get_string (value);
442 sof->text = g_strdup (str == NULL ? "" : str);
443 break;
444 case SOF_PROP_MARKUP:
445 if (sof->markup != NULL)
446 pango_attr_list_unref (sof->markup);
447 sof->markup = g_value_peek_pointer (value);
448 if (sof->markup != NULL)
449 pango_attr_list_ref (sof->markup);
450 break;
452 default:
453 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
454 return;
458 static void
459 gnm_so_filled_get_property (GObject *obj, guint param_id,
460 GValue *value, GParamSpec *pspec)
462 GnmSOFilled *sof = GNM_SO_FILLED (obj);
463 switch (param_id) {
464 case SOF_PROP_STYLE:
465 g_value_set_object (value, sof->style);
466 break;
467 case SOF_PROP_IS_OVAL:
468 g_value_set_boolean (value, sof->is_oval);
469 break;
470 case SOF_PROP_TEXT :
471 g_value_set_string (value, sof->text);
472 break;
473 case SOF_PROP_MARKUP :
474 g_value_set_boxed (value, sof->markup);
475 break;
476 case SOF_PROP_DOCUMENT:
477 g_value_set_object (value, sheet_object_get_sheet (GNM_SO (obj))->workbook);
478 break;
479 default :
480 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
481 break;
485 static void
486 gnm_so_filled_finalize (GObject *object)
488 GnmSOFilled *sof = GNM_SO_FILLED (object);
490 g_clear_object (&sof->style);
492 g_free (sof->text);
493 sof->text = NULL;
495 if (NULL != sof->markup) {
496 pango_attr_list_unref (sof->markup);
497 sof->markup = NULL;
500 G_OBJECT_CLASS (gnm_so_filled_parent_class)->finalize (object);
503 static void
504 gnm_so_filled_class_init (GObjectClass *gobject_class)
506 SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
508 gnm_so_filled_parent_class = g_type_class_peek_parent (gobject_class);
510 gobject_class->finalize = gnm_so_filled_finalize;
511 gobject_class->set_property = gnm_so_filled_set_property;
512 gobject_class->get_property = gnm_so_filled_get_property;
513 so_class->write_xml_sax = gnm_so_filled_write_xml_sax;
514 so_class->prep_sax_parser = gnm_so_filled_prep_sax_parser;
515 so_class->copy = gnm_so_filled_copy;
516 so_class->rubber_band_directly = TRUE;
517 so_class->xml_export_name = "SheetObjectFilled";
519 #ifdef GNM_WITH_GTK
520 so_class->new_view = gnm_so_filled_new_view;
521 so_class->user_config = gnm_so_filled_user_config;
522 #endif /* GNM_WITH_GTK */
524 so_class->draw_cairo = gnm_so_filled_draw_cairo;
526 g_object_class_install_property (gobject_class, SOF_PROP_STYLE,
527 g_param_spec_object ("style", NULL, NULL, GO_TYPE_STYLE,
528 GSF_PARAM_STATIC | G_PARAM_READWRITE));
529 g_object_class_install_property (gobject_class, SOF_PROP_IS_OVAL,
530 g_param_spec_boolean ("is-oval", NULL, NULL, FALSE,
531 GSF_PARAM_STATIC | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
532 g_object_class_install_property (gobject_class, SOF_PROP_TEXT,
533 g_param_spec_string ("text", NULL, NULL, NULL,
534 GSF_PARAM_STATIC | G_PARAM_READWRITE));
535 g_object_class_install_property (gobject_class, SOF_PROP_MARKUP,
536 g_param_spec_boxed ("markup", NULL, NULL, PANGO_TYPE_ATTR_LIST,
537 GSF_PARAM_STATIC | G_PARAM_READWRITE));
538 g_object_class_install_property (gobject_class, SOF_PROP_DOCUMENT,
539 g_param_spec_object ("document", NULL, NULL, GO_TYPE_DOC,
540 GSF_PARAM_STATIC | G_PARAM_READABLE));
543 static void
544 gnm_so_filled_init (GObject *obj)
546 GnmSOFilled *sof = GNM_SO_FILLED (obj);
547 sof->style = sof_default_style ();
548 sof->markup = NULL;
549 sof->margin_pts.top = sof->margin_pts.bottom = 3;
550 sof->margin_pts.left = sof->margin_pts.right = 5;
551 GNM_SO (obj)->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
554 GSF_CLASS (GnmSOFilled, gnm_so_filled,
555 gnm_so_filled_class_init, gnm_so_filled_init,
556 GNM_SO_TYPE)