2006-12-05 David Lodge <dave@cirt.net>
[dia.git] / objects / standard / line.c
blob776e811e154e6374671a1ad897ac76e3d7f2a819
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 * Copyright (C) 2002 David Hoover
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
24 #include <assert.h>
25 #include <math.h>
27 #include "intl.h"
28 #include "object.h"
29 #include "connection.h"
30 #include "connectionpoint.h"
31 #include "diarenderer.h"
32 #include "attributes.h"
33 #include "widgets.h"
34 #include "arrows.h"
35 #include "connpoint_line.h"
36 #include "properties.h"
38 #include "tool-icons.h"
40 #define DEFAULT_WIDTH 0.25
42 typedef struct _LineProperties LineProperties;
44 typedef struct _Line {
45 Connection connection;
47 ConnPointLine *cpl;
49 Color line_color;
50 real line_width;
51 LineStyle line_style;
52 Arrow start_arrow, end_arrow;
53 real dashlength;
54 real absolute_start_gap, absolute_end_gap;
55 } Line;
57 struct _LineProperties {
58 real absolute_start_gap, absolute_end_gap;
61 static LineProperties default_properties;
63 static ObjectChange* line_move_handle(Line *line, Handle *handle,
64 Point *to, ConnectionPoint *cp,
65 HandleMoveReason reason,
66 ModifierKeys modifiers);
67 static ObjectChange* line_move(Line *line, Point *to);
68 static void line_select(Line *line, Point *clicked_point,
69 DiaRenderer *interactive_renderer);
70 static void line_draw(Line *line, DiaRenderer *renderer);
71 static DiaObject *line_create(Point *startpoint,
72 void *user_data,
73 Handle **handle1,
74 Handle **handle2);
75 static real line_distance_from(Line *line, Point *point);
76 static void line_update_data(Line *line);
77 static void line_destroy(Line *line);
78 static DiaObject *line_copy(Line *line);
80 static PropDescription *line_describe_props(Line *line);
81 static void line_get_props(Line *line, GPtrArray *props);
82 static void line_set_props(Line *line, GPtrArray *props);
84 static void line_save(Line *line, ObjectNode obj_node, const char *filename);
85 static DiaObject *line_load(ObjectNode obj_node, int version, const char *filename);
86 static DiaMenu *line_get_object_menu(Line *line, Point *clickedpoint);
88 void Line_adjust_for_absolute_gap(Line *line, Point *gap_endpoints);
90 static ObjectTypeOps line_type_ops =
92 (CreateFunc) line_create,
93 (LoadFunc) line_load,
94 (SaveFunc) line_save,
95 (GetDefaultsFunc) NULL,
96 (ApplyDefaultsFunc) NULL
99 DiaObjectType line_type =
101 "Standard - Line", /* name */
102 0, /* version */
103 (char **) line_icon, /* pixmap */
104 &line_type_ops /* ops */
107 DiaObjectType *_line_type = (DiaObjectType *) &line_type;
109 static ObjectOps line_ops = {
110 (DestroyFunc) line_destroy,
111 (DrawFunc) line_draw,
112 (DistanceFunc) line_distance_from,
113 (SelectFunc) line_select,
114 (CopyFunc) line_copy,
115 (MoveFunc) line_move,
116 (MoveHandleFunc) line_move_handle,
117 (GetPropertiesFunc) object_create_props_dialog,
118 (ApplyPropertiesFunc) object_apply_props_from_dialog,
119 (ObjectMenuFunc) line_get_object_menu,
120 (DescribePropsFunc) line_describe_props,
121 (GetPropsFunc) line_get_props,
122 (SetPropsFunc) line_set_props
125 static PropNumData gap_range = { -G_MAXFLOAT, G_MAXFLOAT, 0.1};
127 static PropDescription line_props[] = {
128 OBJECT_COMMON_PROPERTIES,
129 PROP_STD_LINE_WIDTH,
130 PROP_STD_LINE_COLOUR,
131 PROP_STD_LINE_STYLE,
132 PROP_FRAME_BEGIN("arrows",PROP_FLAG_STANDARD,N_("Arrows")),
133 PROP_STD_START_ARROW,
134 PROP_STD_END_ARROW,
135 PROP_FRAME_END("arrows",PROP_FLAG_STANDARD),
136 { "start_point", PROP_TYPE_POINT, 0,
137 N_("Start point"), NULL },
138 { "end_point", PROP_TYPE_POINT, 0,
139 N_("End point"), NULL },
141 PROP_FRAME_BEGIN("gaps",0,N_("Line gaps")),
142 { "absolute_start_gap", PROP_TYPE_REAL, PROP_FLAG_VISIBLE,
143 N_("Absolute start gap"), NULL, &gap_range },
144 { "absolute_end_gap", PROP_TYPE_REAL, PROP_FLAG_VISIBLE,
145 N_("Absolute end gap"), NULL, &gap_range },
146 PROP_FRAME_END("gaps",0),
148 PROP_DESC_END
151 static PropDescription *
152 line_describe_props(Line *line)
154 if (line_props[0].quark == 0)
155 prop_desc_list_calculate_quarks(line_props);
156 return line_props;
159 static PropOffset line_offsets[] = {
160 OBJECT_COMMON_PROPERTIES_OFFSETS,
161 { "line_width", PROP_TYPE_REAL, offsetof(Line, line_width) },
162 { "line_colour", PROP_TYPE_COLOUR, offsetof(Line, line_color) },
163 { "line_style", PROP_TYPE_LINESTYLE,
164 offsetof(Line, line_style), offsetof(Line, dashlength) },
165 { "start_arrow", PROP_TYPE_ARROW, offsetof(Line, start_arrow) },
166 { "end_arrow", PROP_TYPE_ARROW, offsetof(Line, end_arrow) },
167 { "start_point", PROP_TYPE_POINT, offsetof(Connection, endpoints[0]) },
168 { "end_point", PROP_TYPE_POINT, offsetof(Connection, endpoints[1]) },
169 { "absolute_start_gap", PROP_TYPE_REAL, offsetof(Line, absolute_start_gap) },
170 { "absolute_end_gap", PROP_TYPE_REAL, offsetof(Line, absolute_end_gap) },
171 { NULL, 0, 0 }
174 static void
175 line_get_props(Line *line, GPtrArray *props)
177 object_get_props_from_offsets(&line->connection.object,
178 line_offsets, props);
181 static void
182 line_set_props(Line *line, GPtrArray *props)
184 object_set_props_from_offsets(&line->connection.object,
185 line_offsets, props);
186 line_update_data(line);
189 static void
190 line_init_defaults() {
191 static int defaults_initialized = 0;
193 if (!defaults_initialized) {
194 default_properties.absolute_start_gap = 0.0;
195 default_properties.absolute_end_gap = 0.0;
196 defaults_initialized = 1;
200 static ObjectChange *
201 line_add_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
203 ObjectChange *oc;
204 oc = connpointline_add_point(((Line *)obj)->cpl,clicked);
205 line_update_data((Line *)obj);
206 return oc;
209 static ObjectChange *
210 line_remove_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
212 ObjectChange *oc;
213 oc = connpointline_remove_point(((Line *)obj)->cpl,clicked);
214 line_update_data((Line *)obj);
215 return oc;
218 static DiaMenuItem object_menu_items[] = {
219 { N_("Add connection point"), line_add_connpoint_callback, NULL, 1 },
220 { N_("Delete connection point"), line_remove_connpoint_callback,
221 NULL, 1 },
224 static DiaMenu object_menu = {
225 N_("Line"),
226 sizeof(object_menu_items)/sizeof(DiaMenuItem),
227 object_menu_items,
228 NULL
231 static DiaMenu *
232 line_get_object_menu(Line *line, Point *clickedpoint)
234 ConnPointLine *cpl;
236 cpl = line->cpl;
237 /* Set entries sensitive/selected etc here */
238 object_menu_items[0].active =
239 connpointline_can_add_point(cpl, clickedpoint);
240 object_menu_items[1].active =
241 connpointline_can_remove_point(cpl,clickedpoint);
242 return &object_menu;
246 About start_gap, end_gap, auto/absolute
248 Place positive reals (try 1.0, for instance) in absolute
249 to create gaps on line end/start.
250 If auto_gap is false, these gaps are of that length.
251 If a gap is negative, the line extends past the handle.
252 If auto_gap is true, the gap length is computed so it touches
253 the connected object edge then the absolute gap is added
256 Point
257 calculate_object_edge(Point *objmid, Point *end, DiaObject *obj)
259 #define MAXITER 25
260 #ifdef TRACE_DIST
261 Point trace[MAXITER];
262 real disttrace[MAXITER];
263 #endif
264 Point mid1, mid2, mid3;
265 real dist;
266 int i = 0;
268 mid1 = *objmid;
269 mid2.x = (objmid->x+end->x)/2;
270 mid2.y = (objmid->y+end->y)/2;
271 mid3 = *end;
273 /* If the other end is inside the object */
274 dist = obj->ops->distance_from(obj, &mid3);
275 if (dist < 0.001) return mid1;
278 do {
279 dist = obj->ops->distance_from(obj, &mid2);
280 if (dist < 0.0000001) {
281 mid1 = mid2;
282 } else {
283 mid3 = mid2;
285 mid2.x = (mid1.x + mid3.x)/2;
286 mid2.y = (mid1.y + mid3.y)/2;
287 #ifdef TRACE_DIST
288 trace[i] = mid2;
289 disttrace[i] = dist;
290 #endif
291 i++;
292 } while (i < MAXITER && (dist < 0.0000001 || dist > 0.001));
294 #ifdef TRACE_DIST
295 if (i == MAXITER) {
296 for (i = 0; i < MAXITER; i++) {
297 printf("%d: %f, %f: %f\n", i, trace[i].x, trace[i].y, disttrace[i]);
299 printf("i = %d, dist = %f\n", i, dist);
301 #endif
303 return mid2;
307 /** Calculate the absolute gap -- this gap is 'transient', in that
308 * the actual end of the line is not moved, but it is made to look like
309 * it is shorter.
311 void
312 line_adjust_for_absolute_gap(Line *line, Point *gap_endpoints)
314 Point endpoints[2];
315 real line_length;
317 endpoints[0] = line->connection.endpoints[0];
318 endpoints[1] = line->connection.endpoints[1];
320 line_length = distance_point_point(&endpoints[0], &endpoints[1]);
322 /* puts new 0 to x% of 0->1 */
323 point_convex(&gap_endpoints[0], &endpoints[0], &endpoints[1],
324 1 - line->absolute_start_gap/line_length);
326 /* puts new 1 to x% of 1->0 */
327 point_convex(&gap_endpoints[1], &endpoints[1], &endpoints[0],
328 1 - line->absolute_end_gap/line_length);
331 static real
332 line_distance_from(Line *line, Point *point)
334 Point *endpoints;
336 endpoints = &line->connection.endpoints[0];
338 if (line->absolute_start_gap || line->absolute_end_gap ) {
339 Point gap_endpoints[2]; /* Visible endpoints of line */
341 line_adjust_for_absolute_gap(line, gap_endpoints);
342 return distance_line_point( &gap_endpoints[0], &gap_endpoints[1],
343 line->line_width, point);
344 } else {
345 return distance_line_point( &endpoints[0], &endpoints[1],
346 line->line_width, point);
350 static void
351 line_select(Line *line, Point *clicked_point,
352 DiaRenderer *interactive_renderer)
354 connection_update_handles(&line->connection);
357 static ObjectChange*
358 line_move_handle(Line *line, Handle *handle,
359 Point *to, ConnectionPoint *cp,
360 HandleMoveReason reason, ModifierKeys modifiers)
362 assert(line!=NULL);
363 assert(handle!=NULL);
364 assert(to!=NULL);
366 connection_move_handle(&line->connection, handle->id, to, cp, reason, modifiers);
368 line_update_data(line);
370 return NULL;
373 static ObjectChange*
374 line_move(Line *line, Point *to)
376 Point start_to_end;
377 Point *endpoints = &line->connection.endpoints[0];
379 start_to_end = endpoints[1];
380 point_sub(&start_to_end, &endpoints[0]);
382 endpoints[1] = endpoints[0] = *to;
383 point_add(&endpoints[1], &start_to_end);
385 line_update_data(line);
387 return NULL;
390 static void
391 line_draw(Line *line, DiaRenderer *renderer)
393 Point gap_endpoints[2];
395 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
397 assert(line != NULL);
398 assert(renderer != NULL);
400 renderer_ops->set_linewidth(renderer, line->line_width);
401 renderer_ops->set_linestyle(renderer, line->line_style);
402 renderer_ops->set_dashlength(renderer, line->dashlength);
403 renderer_ops->set_linecaps(renderer, LINECAPS_BUTT);
405 if (line->absolute_start_gap || line->absolute_end_gap ) {
406 line_adjust_for_absolute_gap(line, gap_endpoints);
408 renderer_ops->draw_line_with_arrows(renderer,
409 &gap_endpoints[0], &gap_endpoints[1],
410 line->line_width,
411 &line->line_color,
412 &line->start_arrow,
413 &line->end_arrow);
414 } else {
415 renderer_ops->draw_line_with_arrows(renderer,
416 &line->connection.endpoints[0],
417 &line->connection.endpoints[1],
418 line->line_width,
419 &line->line_color,
420 &line->start_arrow,
421 &line->end_arrow);
426 static DiaObject *
427 line_create(Point *startpoint,
428 void *user_data,
429 Handle **handle1,
430 Handle **handle2)
432 Line *line;
433 Connection *conn;
434 DiaObject *obj;
435 Point defaultlen = { 1.0, 1.0 };
437 line_init_defaults();
439 line = g_malloc0(sizeof(Line));
441 line->line_width = attributes_get_default_linewidth();
442 line->line_color = attributes_get_foreground();
443 line->absolute_start_gap = default_properties.absolute_start_gap;
444 line->absolute_end_gap = default_properties.absolute_end_gap;
446 conn = &line->connection;
447 conn->endpoints[0] = *startpoint;
448 conn->endpoints[1] = *startpoint;
449 point_add(&conn->endpoints[1], &defaultlen);
451 obj = &conn->object;
453 obj->type = &line_type;
454 obj->ops = &line_ops;
456 connection_init(conn, 2, 0);
458 line->cpl = connpointline_create(obj,1);
460 attributes_get_default_line_style(&line->line_style, &line->dashlength);
461 line->start_arrow = attributes_get_default_start_arrow();
462 line->end_arrow = attributes_get_default_end_arrow();
463 line_update_data(line);
465 *handle1 = obj->handles[0];
466 *handle2 = obj->handles[1];
467 return &line->connection.object;
470 static void
471 line_destroy(Line *line)
473 connection_destroy(&line->connection);
476 static DiaObject *
477 line_copy(Line *line)
479 Line *newline;
480 Connection *conn, *newconn;
481 DiaObject *newobj;
482 int rcc = 0;
484 conn = &line->connection;
486 newline = g_malloc0(sizeof(Line));
487 newconn = &newline->connection;
488 newobj = &newconn->object;
490 connection_copy(conn, newconn);
492 newline->cpl = connpointline_copy(newobj,line->cpl,&rcc);
494 newline->line_color = line->line_color;
495 newline->line_width = line->line_width;
496 newline->line_style = line->line_style;
497 newline->dashlength = line->dashlength;
498 newline->start_arrow = line->start_arrow;
499 newline->end_arrow = line->end_arrow;
500 newline->absolute_start_gap = line->absolute_start_gap;
501 newline->absolute_end_gap = line->absolute_end_gap;
503 line_update_data(line);
505 return &newline->connection.object;
508 static void
509 line_update_data(Line *line)
511 Connection *conn = &line->connection;
512 DiaObject *obj = &conn->object;
513 LineBBExtras *extra = &conn->extra_spacing;
514 Point start, end;
516 extra->start_trans = (line->line_width / 2.0);
517 extra->end_trans = (line->line_width / 2.0);
518 extra->start_long = (line->line_width / 2.0);
519 extra->end_long = (line->line_width / 2.0);
520 if (line->start_arrow.type != ARROW_NONE)
521 extra->start_trans = MAX(extra->start_trans,line->start_arrow.width);
522 if (line->end_arrow.type != ARROW_NONE)
523 extra->end_trans = MAX(extra->end_trans,line->end_arrow.width);
525 if (connpoint_is_autogap(line->connection.endpoint_handles[0].connected_to) ||
526 connpoint_is_autogap(line->connection.endpoint_handles[1].connected_to)) {
527 connection_adjust_for_autogap(line);
529 if (line->absolute_start_gap || line->absolute_end_gap ) {
530 Point gap_endpoints[2];
532 line_adjust_for_absolute_gap(line, gap_endpoints);
533 line_bbox(&gap_endpoints[0],&gap_endpoints[1],
534 &conn->extra_spacing,&conn->object.bounding_box);
535 start = gap_endpoints[0];
536 end = gap_endpoints[1];
537 } else {
538 connection_update_boundingbox(conn);
539 start = conn->endpoints[0];
540 end = conn->endpoints[1];
543 obj->position = conn->endpoints[0];
545 connpointline_update(line->cpl);
546 connpointline_putonaline(line->cpl,&start, &end);
548 connection_update_handles(conn);
552 static void
553 line_save(Line *line, ObjectNode obj_node, const char *filename)
555 dia_object_sanity_check((DiaObject*)line, "Saving line");
557 connection_save(&line->connection, obj_node);
559 connpointline_save(line->cpl,obj_node,"numcp");
561 if (!color_equals(&line->line_color, &color_black))
562 data_add_color(new_attribute(obj_node, "line_color"),
563 &line->line_color);
565 if (line->line_width != 0.1)
566 data_add_real(new_attribute(obj_node, "line_width"),
567 line->line_width);
569 if (line->line_style != LINESTYLE_SOLID)
570 data_add_enum(new_attribute(obj_node, "line_style"),
571 line->line_style);
573 if (line->start_arrow.type != ARROW_NONE) {
574 save_arrow(obj_node, &line->start_arrow,
575 "start_arrow", "start_arrow_length", "startend_arrow_width");
578 if (line->end_arrow.type != ARROW_NONE) {
579 save_arrow(obj_node, &line->end_arrow,
580 "end_arrow", "end_arrow_length", "end_arrow_width");
583 if (line->absolute_start_gap)
584 data_add_real(new_attribute(obj_node, "absolute_start_gap"),
585 line->absolute_start_gap);
586 if (line->absolute_end_gap)
587 data_add_real(new_attribute(obj_node, "absolute_end_gap"),
588 line->absolute_end_gap);
590 if (line->line_style != LINESTYLE_SOLID && line->dashlength != DEFAULT_LINESTYLE_DASHLEN)
591 data_add_real(new_attribute(obj_node, "dashlength"),
592 line->dashlength);
595 static DiaObject *
596 line_load(ObjectNode obj_node, int version, const char *filename)
598 Line *line;
599 Connection *conn;
600 DiaObject *obj;
601 AttributeNode attr;
603 line = g_malloc0(sizeof(Line));
605 conn = &line->connection;
606 obj = &conn->object;
608 obj->type = &line_type;
609 obj->ops = &line_ops;
611 connection_load(conn, obj_node);
613 line->line_color = color_black;
614 attr = object_find_attribute(obj_node, "line_color");
615 if (attr != NULL)
616 data_color(attribute_first_data(attr), &line->line_color);
618 line->line_width = 0.1;
619 attr = object_find_attribute(obj_node, "line_width");
620 if (attr != NULL)
621 line->line_width = data_real(attribute_first_data(attr));
623 line->line_style = LINESTYLE_SOLID;
624 attr = object_find_attribute(obj_node, "line_style");
625 if (attr != NULL)
626 line->line_style = data_enum(attribute_first_data(attr));
628 load_arrow(obj_node, &line->start_arrow,
629 "start_arrow", "start_arrow_length", "start_arrow_width");
631 load_arrow(obj_node, &line->end_arrow,
632 "end_arrow", "end_arrow_length", "end_arrow_width");
634 line->absolute_start_gap = 0.0;
635 attr = object_find_attribute(obj_node, "absolute_start_gap");
636 if (attr != NULL)
637 line->absolute_start_gap = data_real( attribute_first_data(attr) );
638 line->absolute_end_gap = 0.0;
639 attr = object_find_attribute(obj_node, "absolute_end_gap");
640 if (attr != NULL)
641 line->absolute_end_gap = data_real( attribute_first_data(attr) );
643 line->dashlength = DEFAULT_LINESTYLE_DASHLEN;
644 attr = object_find_attribute(obj_node, "dashlength");
645 if (attr != NULL)
646 line->dashlength = data_real(attribute_first_data(attr));
648 connection_init(conn, 2, 0);
650 line->cpl = connpointline_load(obj,obj_node,"numcp",1,NULL);
651 line_update_data(line);
653 return &line->connection.object;