1 
2 /*
3  * gnm-so-line.c: Lines, arrows, arcs
4  *
5  * Copyright (C) 2004-2007 Jody Goldberg (jody@gnome.org)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <gnm-so-line.h>
26 #include <sheet-object-impl.h>
27 #include <gutils.h>
28 #include <xml-sax.h>
29 
30 #include <goffice/goffice.h>
31 #include <gsf/gsf-impl-utils.h>
32 #include <glib/gi18n-lib.h>
33 #include <string.h>
34 
35 #define CXML2C(s) ((char const *)(s))
36 
37 static inline gboolean
attr_eq(const xmlChar * a,const char * s)38 attr_eq (const xmlChar *a, const char *s)
39 {
40 	return !strcmp (CXML2C (a), s);
41 }
42 
43 #define GNM_SO_LINE(o)		(G_TYPE_CHECK_INSTANCE_CAST((o), GNM_SO_LINE_TYPE, GnmSOLine))
44 #define GNM_SO_LINE_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST ((k),   GNM_SO_LINE_TYPE, GnmSOLineClass))
45 
46 /*****************************************************************************/
47 
48 typedef struct {
49 	SheetObject base;
50 
51 	GOStyle *style;
52 	GOArrow	  start_arrow, end_arrow;
53 } GnmSOLine;
54 typedef SheetObjectClass GnmSOLineClass;
55 
56 static SheetObjectClass *gnm_so_line_parent_class;
57 
58 #ifdef GNM_WITH_GTK
59 static void
so_line_view_set_bounds(SheetObjectView * sov,double const * coords,gboolean visible)60 so_line_view_set_bounds (SheetObjectView *sov, double const *coords, gboolean visible)
61 {
62 	GocItem	*view = GOC_ITEM (sov);
63 	GocItem *item = sheet_object_view_get_item (sov);
64 	SheetObject	*so = sheet_object_view_get_so (sov);
65 	GOStyleLine const *style = &GNM_SO_LINE (so)->style->line;
66 	double scale = goc_canvas_get_pixels_per_unit (view->canvas);
67 
68 	sheet_object_direction_set (so, coords);
69 
70 	if (visible &&
71 	    style->color != 0 && style->width >= 0 && style->dash_type != GO_LINE_NONE) {
72 		goc_item_set (item,
73 		              "x0", coords[0] / scale,
74 		              "y0", coords[1] / scale,
75 		              "x1", coords[2] / scale,
76 		              "y1", coords[3] / scale,
77 		              NULL);
78 		goc_item_show (view);
79 	} else
80 		goc_item_hide (view);
81 }
82 
83 static void
so_line_goc_view_class_init(SheetObjectViewClass * sov_klass)84 so_line_goc_view_class_init (SheetObjectViewClass *sov_klass)
85 {
86 	sov_klass->set_bounds	= so_line_view_set_bounds;
87 }
88 typedef SheetObjectView		LineGocView;
89 typedef SheetObjectViewClass	LineGocViewClass;
90 static GSF_CLASS (LineGocView, so_line_goc_view,
91 	so_line_goc_view_class_init, NULL,
92 	GNM_SO_VIEW_TYPE)
93 
94 #endif /* GNM_WITH_GTK */
95 enum {
96 	SOL_PROP_0,
97 	SOL_PROP_STYLE,
98 	SOL_PROP_START_ARROW,
99 	SOL_PROP_END_ARROW
100 };
101 
102 static GOStyle *
sol_default_style(void)103 sol_default_style (void)
104 {
105 	GOStyle *res = go_style_new ();
106 	res->interesting_fields = GO_STYLE_LINE;
107 	res->line.width   = 0; /* hairline */
108 	res->line.color   = GO_COLOR_BLACK;
109 	res->line.dash_type = GO_LINE_SOLID; /* anything but 0 */
110 	return res;
111 }
112 
113 #ifdef GNM_WITH_GTK
114 #include <sheet-control-gui.h>
115 #include <dialogs/dialogs.h>
116 #include <gnumeric-simple-canvas.h>
117 #include <gnm-pane.h>
118 
119 static void
gnm_so_line_user_config(SheetObject * so,SheetControl * sc)120 gnm_so_line_user_config (SheetObject *so, SheetControl *sc)
121 {
122 	dialog_so_styled (scg_wbcg (GNM_SCG (sc)), G_OBJECT (so),
123 			  sol_default_style (),
124 			  _("Line/Arrow Properties"), SO_STYLED_LINE);
125 }
126 
127 static void
cb_gnm_so_line_changed(GnmSOLine const * sol,G_GNUC_UNUSED GParamSpec * pspec,GocItem * item)128 cb_gnm_so_line_changed (GnmSOLine const *sol,
129 			G_GNUC_UNUSED GParamSpec *pspec,
130 			GocItem *item)
131 {
132 	item = sheet_object_view_get_item (GNM_SO_VIEW (item));
133 	goc_item_set (item,
134 		      "start-arrow", &sol->start_arrow,
135 		      "end-arrow", &sol->end_arrow,
136 		      "style", sol->style,
137 		      NULL);
138 }
139 
140 static SheetObjectView *
gnm_so_line_new_view(SheetObject * so,SheetObjectViewContainer * container)141 gnm_so_line_new_view (SheetObject *so, SheetObjectViewContainer *container)
142 {
143 	GnmSOLine const *sol = GNM_SO_LINE (so);
144 	GocItem *item = goc_item_new (
145 		gnm_pane_object_group (GNM_PANE (container)),
146 		so_line_goc_view_get_type (),
147 		NULL);
148 	goc_item_new (GOC_GROUP (item), GOC_TYPE_LINE, NULL);
149 	cb_gnm_so_line_changed (sol, NULL, item);
150 	g_signal_connect_object (G_OBJECT (sol),
151 		"notify", G_CALLBACK (cb_gnm_so_line_changed),
152 		item, 0);
153 	return gnm_pane_object_register (so, item, TRUE);
154 }
155 
156 #endif /* GNM_WITH_GTK */
157 
158 static void
draw_arrow(const GOArrow * arrow,cairo_t * cr,double * x,double * y,double phi)159 draw_arrow (const GOArrow *arrow, cairo_t *cr,
160 	    double *x, double *y, double phi)
161 {
162 	double dx, dy;
163 
164 	cairo_save (cr);
165 	cairo_translate (cr, *x, *y);
166 	go_arrow_draw (arrow, cr, &dx, &dy, phi);
167 	*x += dx;
168 	*y += dy;
169 	cairo_restore (cr);
170 }
171 
172 static void
gnm_so_line_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)173 gnm_so_line_draw_cairo (SheetObject const *so, cairo_t *cr,
174 			double width, double height)
175 {
176 	GnmSOLine *sol = GNM_SO_LINE (so);
177 	GOStyle const *style = sol->style;
178 	double x1, y1, x2, y2;
179 	double phi;
180 
181 	if (style->line.color == 0 ||
182 	    style->line.width < 0 ||
183 	    style->line.dash_type == GO_LINE_NONE)
184 		return;
185 
186 	if ((so->anchor.base.direction & GOD_ANCHOR_DIR_H_MASK) == GOD_ANCHOR_DIR_RIGHT) {
187 		x1 = 0.;
188 		x2 = width;
189 	} else {
190 		x1 = width;
191 		x2 = 0.;
192 	}
193 
194 	if ((so->anchor.base.direction & GOD_ANCHOR_DIR_V_MASK) == GOD_ANCHOR_DIR_DOWN) {
195 		y1 = 0.;
196 		y2 = height;
197 	} else {
198 		y1 = height;
199 		y2 = 0.;
200 	}
201 
202 	cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (style->line.color));
203 
204 	phi = atan2 (y2 - y1, x2 - x1) - M_PI_2;
205 	draw_arrow (&sol->start_arrow, cr, &x1, &y1, phi + M_PI);
206 	draw_arrow (&sol->end_arrow, cr, &x2, &y2, phi);
207 
208 	cairo_move_to (cr, x1, y1);
209 	cairo_line_to (cr, x2, y2);
210 	if (go_style_set_cairo_line (style, cr))
211 		cairo_stroke (cr);
212 	else
213 		cairo_new_path (cr);
214 }
215 
216 static void
write_xml_sax_arrow(GOArrow const * arrow,const char * prefix,GsfXMLOut * output)217 write_xml_sax_arrow (GOArrow const *arrow, const char *prefix,
218 		     GsfXMLOut *output)
219 {
220 	const char *typename = go_arrow_type_as_str (arrow->typ);
221 	char *attr;
222 
223 	if (!typename || arrow->typ == GO_ARROW_NONE)
224 		return;
225 
226 	attr = g_strconcat (prefix, "ArrowType", NULL);
227 	gsf_xml_out_add_cstr (output, attr, typename);
228 	g_free (attr);
229 
230 	attr = g_strconcat (prefix, "ArrowShapeA", NULL);
231 	go_xml_out_add_double (output, attr, arrow->a);
232 	g_free (attr);
233 
234 	attr = g_strconcat (prefix, "ArrowShapeB", NULL);
235 	go_xml_out_add_double (output, attr, arrow->b);
236 	g_free (attr);
237 
238 	attr = g_strconcat (prefix, "ArrowShapeC", NULL);
239 	go_xml_out_add_double (output, attr, arrow->c);
240 	g_free (attr);
241 }
242 
243 static void
gnm_so_line_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)244 gnm_so_line_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
245 			   GnmConventions const *convs)
246 {
247 	GnmSOLine const *sol = GNM_SO_LINE (so);
248 
249 	gsf_xml_out_add_int (output, "Type", 1);
250 	write_xml_sax_arrow (&sol->start_arrow, "Start", output);
251 	write_xml_sax_arrow (&sol->end_arrow, "End", output);
252 
253 	gsf_xml_out_start_element (output, "Style");
254 	go_persist_sax_save (GO_PERSIST (sol->style), output);
255 	gsf_xml_out_end_element (output); /* </Style> */
256 }
257 
258 static void
sol_sax_style(GsfXMLIn * xin,xmlChar const ** attrs)259 sol_sax_style (GsfXMLIn *xin, xmlChar const **attrs)
260 {
261 	SheetObject *so = gnm_xml_in_cur_obj (xin);
262 	GnmSOLine *sol = GNM_SO_LINE (so);
263 	go_persist_prep_sax (GO_PERSIST (sol->style), xin, attrs);
264 }
265 
266 
267 static gboolean
read_xml_sax_arrow(xmlChar const ** attrs,const char * prefix,GOArrow * arrow)268 read_xml_sax_arrow (xmlChar const **attrs, const char *prefix,
269 		    GOArrow *arrow)
270 {
271 	size_t plen = strlen (prefix);
272 	const char *attr = CXML2C (attrs[0]);
273 	const char *val = CXML2C (attrs[1]);
274 
275 	if (strncmp (attr, prefix, plen) != 0)
276 		return FALSE;
277 	attr += plen;
278 
279 	if (strcmp (attr, "ArrowType") == 0) {
280 		arrow->typ = go_arrow_type_from_str (val);
281 	} else if (strcmp (attr, "ArrowShapeA") == 0) {
282 		arrow->a = go_strtod (val, NULL);
283 	} else if (strcmp (attr, "ArrowShapeB") == 0) {
284 		arrow->b = go_strtod (val, NULL);
285 	} else if (strcmp (attr, "ArrowShapeC") == 0) {
286 		arrow->c = go_strtod (val, NULL);
287 	} else
288 		return FALSE;
289 
290 	return TRUE;
291 }
292 
293 static void
gnm_so_line_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)294 gnm_so_line_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
295 			     xmlChar const **attrs,
296 			     GnmConventions const *convs)
297 {
298 	static GsfXMLInNode const dtd[] = {
299 	  GSF_XML_IN_NODE (STYLE, STYLE, -1, "Style",	GSF_XML_NO_CONTENT, &sol_sax_style, NULL),
300 	  GSF_XML_IN_NODE_END
301 	};
302 	static GsfXMLInDoc *doc = NULL;
303 	GnmSOLine *sol = GNM_SO_LINE (so);
304 	gboolean old_format = FALSE;
305 	double tmp, arrow_a = -1., arrow_b = -1., arrow_c = -1.;
306 	int type = 0;
307 
308 	if (NULL == doc) {
309 		doc = gsf_xml_in_doc_new (dtd, NULL);
310 		gnm_xml_in_doc_dispose_on_exit (&doc);
311 	}
312 	gsf_xml_in_push_state (xin, doc, NULL, NULL, attrs);
313 
314 	go_arrow_clear (&sol->start_arrow);
315 	go_arrow_clear (&sol->end_arrow);
316 
317 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
318 		/* Old 1.0 and 1.2 */
319 		if (gnm_xml_attr_double (attrs, "Width", &tmp)) {
320 			old_format = TRUE;
321 			sol->style->line.width = tmp;
322 		} else if (attr_eq (attrs[0], "FillColor")) {
323 			go_color_from_str (CXML2C (attrs[1]), &sol->style->line.color);
324 			old_format = TRUE;
325 		} else if (gnm_xml_attr_int (attrs, "Type", &type)) {
326 		} else if (gnm_xml_attr_double (attrs, "ArrowShapeA", &arrow_a) ||
327 			   gnm_xml_attr_double (attrs, "ArrowShapeB", &arrow_b) ||
328 			   gnm_xml_attr_double (attrs, "ArrowShapeC", &arrow_c))
329 			old_format = TRUE;
330 		else if (read_xml_sax_arrow (attrs, "Start", &sol->start_arrow) ||
331 			 read_xml_sax_arrow (attrs, "End", &sol->end_arrow))
332 			; /* Nothing */
333 	}
334 
335 	/* 2 == arrow */
336 	if (old_format &&
337 	    type == 2 &&
338 	    arrow_a >= 0. && arrow_b >= 0. && arrow_c >= 0.)
339 		go_arrow_init_kite (&sol->end_arrow,
340 				    arrow_a, arrow_b, arrow_c);
341 }
342 
343 static void
gnm_so_line_copy(SheetObject * dst,SheetObject const * src)344 gnm_so_line_copy (SheetObject *dst, SheetObject const *src)
345 {
346 	GnmSOLine const *sol = GNM_SO_LINE (src);
347 	GnmSOLine   *new_sol = GNM_SO_LINE (dst);
348 
349 	g_object_unref (new_sol->style);
350 	new_sol->style = go_style_dup (sol->style);
351 	new_sol->start_arrow = sol->start_arrow;
352 	new_sol->end_arrow = sol->end_arrow;
353 }
354 
355 static void
gnm_so_line_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)356 gnm_so_line_set_property (GObject *obj, guint param_id,
357 			  GValue const *value, GParamSpec *pspec)
358 {
359 	GnmSOLine *sol = GNM_SO_LINE (obj);
360 	switch (param_id) {
361 	case SOL_PROP_STYLE: {
362 		GOStyle *style = go_style_dup (g_value_get_object (value));
363 		style->interesting_fields = GO_STYLE_LINE;
364 		g_object_unref (sol->style);
365 		sol->style = style;
366 		break;
367 	}
368 	case SOL_PROP_START_ARROW:
369 		sol->start_arrow = *((GOArrow *)g_value_peek_pointer (value));
370 		break;
371 	case SOL_PROP_END_ARROW:
372 		sol->end_arrow = *((GOArrow* )g_value_peek_pointer (value));
373 		break;
374 	default:
375 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
376 		return;
377 	}
378 }
379 
380 static void
gnm_so_line_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)381 gnm_so_line_get_property (GObject *obj, guint param_id,
382 			  GValue  *value,  GParamSpec *pspec)
383 {
384 	GnmSOLine *sol = GNM_SO_LINE (obj);
385 	switch (param_id) {
386 	case SOL_PROP_STYLE:
387 		g_value_set_object (value, sol->style);
388 		break;
389 	case SOL_PROP_START_ARROW:
390 		g_value_set_boxed (value, &sol->start_arrow);
391 		break;
392 	case SOL_PROP_END_ARROW:
393 		g_value_set_boxed (value, &sol->end_arrow);
394 		break;
395 	default:
396 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
397 		break;
398 	}
399 }
400 
401 static void
gnm_so_line_finalize(GObject * object)402 gnm_so_line_finalize (GObject *object)
403 {
404 	GnmSOLine *sol = GNM_SO_LINE (object);
405 	g_clear_object (&sol->style);
406 	G_OBJECT_CLASS (gnm_so_line_parent_class)->finalize (object);
407 }
408 
409 static void
gnm_so_line_class_init(GObjectClass * gobject_class)410 gnm_so_line_class_init (GObjectClass *gobject_class)
411 {
412 	SheetObjectClass *so_class  = GNM_SO_CLASS (gobject_class);
413 
414 	gnm_so_line_parent_class = g_type_class_peek_parent (gobject_class);
415 
416 	gobject_class->finalize		= gnm_so_line_finalize;
417 	gobject_class->set_property	= gnm_so_line_set_property;
418 	gobject_class->get_property	= gnm_so_line_get_property;
419 	so_class->write_xml_sax		= gnm_so_line_write_xml_sax;
420 	so_class->prep_sax_parser	= gnm_so_line_prep_sax_parser;
421 	so_class->copy			= gnm_so_line_copy;
422 	so_class->rubber_band_directly  = TRUE;
423 	so_class->xml_export_name	= "SheetObjectGraphic";
424 
425 #ifdef GNM_WITH_GTK
426 	so_class->draw_cairo	= gnm_so_line_draw_cairo;
427 	so_class->user_config		= gnm_so_line_user_config;
428 	so_class->new_view		= gnm_so_line_new_view;
429 #endif /* GNM_WITH_GTK */
430 
431         g_object_class_install_property (gobject_class, SOL_PROP_STYLE,
432                  g_param_spec_object ("style", NULL, NULL, GO_TYPE_STYLE,
433 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
434         g_object_class_install_property (gobject_class, SOL_PROP_START_ARROW,
435                  g_param_spec_boxed ("start-arrow", NULL, NULL,
436 				     GO_ARROW_TYPE,
437 				     GSF_PARAM_STATIC | G_PARAM_READWRITE));
438         g_object_class_install_property (gobject_class, SOL_PROP_END_ARROW,
439                  g_param_spec_boxed ("end-arrow", NULL, NULL,
440 				     GO_ARROW_TYPE,
441 				     GSF_PARAM_STATIC | G_PARAM_READWRITE));
442 }
443 
444 static void
gnm_so_line_init(GObject * obj)445 gnm_so_line_init (GObject *obj)
446 {
447 	GnmSOLine *sol  = GNM_SO_LINE (obj);
448 	sol->style = sol_default_style ();
449 	go_arrow_clear (&sol->start_arrow);
450 	go_arrow_clear (&sol->end_arrow);
451 	GNM_SO (obj)->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
452 }
453 
454 GSF_CLASS (GnmSOLine, gnm_so_line,
455 	   gnm_so_line_class_init, gnm_so_line_init,
456 	   GNM_SO_TYPE)
457