1 /*
2  * goc-line.c :
3  *
4  * Copyright (C) 2009 Jean Brefort (jean.brefort@normalesup.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 #include <gsf/gsf-impl-utils.h>
25 #include <glib/gi18n-lib.h>
26 
27 /**
28  * SECTION:goc-line
29  * @short_description: Simple line.
30  *
31  * #GocLine implements simple line drawing in the canvas. The line can have
32  * arrows at the start and/or at the end.
33 **/
34 
35 static GocItemClass *parent_class;
36 
37 enum {
38 	LINE_PROP_0,
39 	LINE_PROP_X0,
40 	LINE_PROP_Y0,
41 	LINE_PROP_X1,
42 	LINE_PROP_Y1,
43 	LINE_PROP_START_ARROW,
44 	LINE_PROP_END_ARROW
45 };
46 
47 static void
goc_line_set_property(GObject * gobject,guint param_id,GValue const * value,GParamSpec * pspec)48 goc_line_set_property (GObject *gobject, guint param_id,
49 				    GValue const *value, GParamSpec *pspec)
50 {
51 	GocLine *line = GOC_LINE (gobject);
52 
53 	switch (param_id) {
54 	case LINE_PROP_X0:
55 		line->startx = g_value_get_double (value);
56 		break;
57 
58 	case LINE_PROP_Y0:
59 		line->starty = g_value_get_double (value);
60 		break;
61 
62 	case LINE_PROP_X1:
63 		line->endx = g_value_get_double (value);
64 		break;
65 
66 	case LINE_PROP_Y1:
67 		line->endy = g_value_get_double (value);
68 		break;
69 
70 	case LINE_PROP_START_ARROW:
71 		line->start_arrow = *((GOArrow *)g_value_peek_pointer (value));
72 		break;
73 
74 	case LINE_PROP_END_ARROW:
75 		line->end_arrow = *((GOArrow *)g_value_peek_pointer (value));
76 		break;
77 
78 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
79 		return; /* NOTE : RETURN */
80 	}
81 	goc_item_bounds_changed (GOC_ITEM (gobject));
82 }
83 
84 static void
goc_line_get_property(GObject * gobject,guint param_id,GValue * value,GParamSpec * pspec)85 goc_line_get_property (GObject *gobject, guint param_id,
86 				    GValue *value, GParamSpec *pspec)
87 {
88 	GocLine *line = GOC_LINE (gobject);
89 
90 	switch (param_id) {
91 	case LINE_PROP_X0:
92 		g_value_set_double (value, line->startx);
93 		break;
94 
95 	case LINE_PROP_Y0:
96 		g_value_set_double (value, line->starty);
97 		break;
98 
99 	case LINE_PROP_X1:
100 		g_value_set_double (value, line->endx);
101 		break;
102 
103 	case LINE_PROP_Y1:
104 		g_value_set_double (value, line->endy);
105 		break;
106 
107 	case LINE_PROP_START_ARROW:
108 		g_value_set_boxed (value, &line->start_arrow);
109 		break;
110 
111 	case LINE_PROP_END_ARROW:
112 		g_value_set_boxed (value, &line->end_arrow);
113 		break;
114 
115 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec);
116 		return; /* NOTE : RETURN */
117 	}
118 }
119 
120 static void
handle_arrow_bounds(GOArrow const * arrow,GocItem * item)121 handle_arrow_bounds (GOArrow const *arrow, GocItem *item)
122 {
123 	/*
124 	 * Do not calculate things precisely, just add enough room
125 	 * in all directions.
126 	 */
127 
128 	switch (arrow->typ) {
129 	case GO_ARROW_NONE:
130 		break;
131 	case GO_ARROW_KITE: {
132 		double d = hypot (arrow->b, arrow->c);
133 		item->x0 -= d;
134 		item->x1 += d;
135 		item->y0 -= d;
136 		item->y1 += d;
137 		break;
138 	}
139 	case GO_ARROW_OVAL: {
140 		double d = MAX (arrow->a, arrow->b);
141 		item->x0 -= d;
142 		item->x1 += d;
143 		item->y0 -= d;
144 		item->y1 += d;
145 		break;
146 	}
147 	default:
148 		g_assert_not_reached ();
149 	}
150 }
151 
152 static void
goc_line_update_bounds(GocItem * item)153 goc_line_update_bounds (GocItem *item)
154 {
155 	GocLine *line = GOC_LINE (item);
156 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (item));
157 	double extra_width = style->line.width /2.;
158 	/* FIXME: take transform into account */
159 	if (extra_width <= 0.)
160 		extra_width = .5;
161 	if (style->line.cap == CAIRO_LINE_CAP_SQUARE)
162 		extra_width *= 1.5; /* 1.4142 should be enough */
163 	if (line->startx < line->endx) {
164 		item->x0 = line->startx - extra_width;
165 		item->x1 = line->endx + extra_width;
166 	} else {
167 		item->x0 = line->endx - extra_width;
168 		item->x1 = line->startx + extra_width;
169 	}
170 	if (line->starty < line->endy) {
171 		item->y0 = line->starty - extra_width;
172 		item->y1 = line->endy + extra_width;
173 	} else {
174 		item->y0 = line->endy - extra_width;
175 		item->y1 = line->starty + extra_width;
176 	}
177 
178 	handle_arrow_bounds (&line->start_arrow, item);
179 	handle_arrow_bounds (&line->end_arrow, item);
180 }
181 
182 static double
goc_line_distance(GocItem * item,double x,double y,GocItem ** near_item)183 goc_line_distance (GocItem *item, double x, double y, GocItem **near_item)
184 {
185 	GocLine *line = GOC_LINE (item);
186 	double dx, dy, l, t;
187 	GOStyle *style;
188 	dx = line->endx - line->startx;
189 	dy = line->endy - line->starty;
190 	l = hypot (dx, dy);
191 	x -= line->startx;
192 	y -= line->starty;
193 	t = (x * dx + y * dy) / l;
194 	y = (-x * dy + y * dx) / l;
195 	*near_item = item;
196 	if (t < 0.)
197 		return hypot (t, y); /* that might be not fully exact,
198 	 but we don't need a large precision */
199 	if (t > l)
200 		return hypot (t - l, y);
201 	style = go_styled_object_get_style (GO_STYLED_OBJECT (item));
202 	t = fabs (y) - ((style->line.width > 5)?  style->line.width/ 2.: 2.5);
203 	/* FIXME: do we need to take the arrow end into account? */
204 	return (t > 0.)? t: 0.;
205 }
206 
207 static void
draw_arrow(GOArrow const * arrow,cairo_t * cr,GOStyle * style,double * endx,double * endy,double phi)208 draw_arrow (GOArrow const *arrow, cairo_t *cr, GOStyle *style,
209 	    double *endx, double *endy, double phi)
210 {
211 	double dx, dy;
212 
213 	if (arrow->typ == GO_ARROW_NONE)
214 		return;
215 
216 	cairo_save (cr);
217 	cairo_translate (cr, *endx, *endy);
218 	cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (style->line.color));
219 	go_arrow_draw (arrow, cr, &dx, &dy, phi - M_PI / 2);
220 	*endx += dx;
221 	*endy += dy;
222 	cairo_restore (cr);
223 }
224 
225 static void
goc_line_draw(GocItem const * item,cairo_t * cr)226 goc_line_draw (GocItem const *item, cairo_t *cr)
227 {
228 	GocLine *line = GOC_LINE (item);
229 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (item));
230 	double sign = (item->canvas && goc_canvas_get_direction (item->canvas) == GOC_DIRECTION_RTL)? -1: 1;
231 	double endx = (line->endx - line->startx) * sign, endy = line->endy - line->starty;
232 	double hoffs, voffs = ceil (style->line.width);
233 	double startx = 0, starty = 0;
234 	double phi;
235 
236 	if (line->startx == line->endx && line->starty == line->endy)
237 		return;
238 
239 	if (voffs <= 0.)
240 		voffs = 1.;
241 
242 	hoffs = ((int) voffs & 1)? .5: 0.;
243 	voffs = (line->starty == line->endy)? hoffs: 0.;
244 	if (line->startx != line->endx)
245 	                hoffs = 0.;
246 
247 	cairo_save (cr);
248 	_goc_item_transform (item, cr, TRUE);
249 	goc_group_cairo_transform (item->parent, cr,
250 				   hoffs + floor (line->startx),
251 				   voffs + floor (line->starty));
252 
253 	endx = (endx > 0.)? ceil (endx): floor (endx);
254 	endy = (endy > 0.)? ceil (endy): floor (endy);
255 
256 	phi = atan2 (endy, endx);
257 	draw_arrow (&line->start_arrow, cr, style,
258 		    &startx, &starty, phi + M_PI);
259 	draw_arrow (&line->end_arrow, cr, style,
260 		    &endx, &endy, phi);
261 
262         if ((endx != 0. || endy!= 0.) &&
263 	    go_styled_object_set_cairo_line (GO_STYLED_OBJECT (item), cr)) {
264 		/* try to avoid horizontal and vertical lines between two pixels */
265 		cairo_move_to (cr, startx, starty);
266 		cairo_line_to (cr, endx, endy);
267 		cairo_stroke (cr);
268 	}
269 	cairo_restore (cr);
270 }
271 
272 static void
goc_line_copy(GocItem * dest,GocItem * source)273 goc_line_copy (GocItem *dest, GocItem *source)
274 {
275 	GocLine *src = GOC_LINE (source), *dst = GOC_LINE (dest);
276 
277 	dst->startx = src->startx;
278 	dst->starty = src->starty;
279 	dst->endx = src->endx;
280 	dst->endy = src->endy;
281 	dst->start_arrow = src->start_arrow;
282 	dst->end_arrow = src->end_arrow;
283 	parent_class->copy (dest, source);
284 }
285 
286 static void
goc_line_init_style(G_GNUC_UNUSED GocStyledItem * item,GOStyle * style)287 goc_line_init_style (G_GNUC_UNUSED GocStyledItem *item, GOStyle *style)
288 {
289 	style->interesting_fields = GO_STYLE_LINE;
290 	if (style->line.auto_dash)
291 		style->line.dash_type = GO_LINE_SOLID;
292 	if (style->line.auto_color)
293 		style->line.color = GO_COLOR_BLACK;
294 	if (style->line.auto_fore)
295 		style->line.fore  = 0;
296 }
297 
298 static void
goc_line_class_init(GocItemClass * item_klass)299 goc_line_class_init (GocItemClass *item_klass)
300 {
301 	GObjectClass *obj_klass = (GObjectClass *) item_klass;
302 	GocStyledItemClass *gsi_klass = (GocStyledItemClass *) item_klass;
303 	parent_class = g_type_class_peek_parent (item_klass);
304 
305 	gsi_klass->init_style = goc_line_init_style;
306 
307 	obj_klass->get_property = goc_line_get_property;
308 	obj_klass->set_property = goc_line_set_property;
309 	g_object_class_install_property (obj_klass, LINE_PROP_X0,
310 		g_param_spec_double ("x0",
311 			_("x0"),
312 			_("The line start x coordinate"),
313 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
314 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
315 	g_object_class_install_property (obj_klass, LINE_PROP_Y0,
316 		g_param_spec_double ("y0",
317 			_("y0"),
318 			_("The line start y coordinate"),
319 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
320 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
321 	g_object_class_install_property (obj_klass, LINE_PROP_X1,
322 		g_param_spec_double ("x1",
323 			_("x1"),
324 			_("The line end x coordinate"),
325 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
326 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
327 	g_object_class_install_property (obj_klass, LINE_PROP_Y1,
328 		g_param_spec_double ("y1",
329 			_("y1"),
330 			_("The line end y coordinate"),
331 			-G_MAXDOUBLE, G_MAXDOUBLE, 0.,
332 			GSF_PARAM_STATIC | G_PARAM_READWRITE));
333         g_object_class_install_property (obj_klass, LINE_PROP_START_ARROW,
334                  g_param_spec_boxed ("start-arrow",
335 				     _("Start Arrow"),
336 				     _("Arrow for line's start"),
337 				     GO_ARROW_TYPE,
338 				     GSF_PARAM_STATIC | G_PARAM_READWRITE));
339         g_object_class_install_property (obj_klass, LINE_PROP_END_ARROW,
340                  g_param_spec_boxed ("end-arrow",
341 				     _("End Arrow"),
342 				     _("Arrow for line's end"),
343 				     GO_ARROW_TYPE,
344 				     GSF_PARAM_STATIC | G_PARAM_READWRITE));
345 
346 	item_klass->update_bounds = goc_line_update_bounds;
347 	item_klass->distance = goc_line_distance;
348 	item_klass->draw = goc_line_draw;
349 	item_klass->copy = goc_line_copy;
350 }
351 
352 GSF_CLASS (GocLine, goc_line,
353 	   goc_line_class_init, NULL,
354 	   GOC_TYPE_STYLED_ITEM)
355 
356 G_END_DECLS
357