1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 * Copyright (C) 2002 David Hoover
4 *
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.
9 *
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.
14 *
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.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <assert.h>
25 #include <math.h>
26
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"
37
38 #include "tool-icons.h"
39
40 #define DEFAULT_WIDTH 0.25
41
42 typedef struct _LineProperties LineProperties;
43
44 typedef struct _Line {
45 Connection connection;
46
47 ConnPointLine *cpl;
48
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;
56
57 struct _LineProperties {
58 real absolute_start_gap, absolute_end_gap;
59 };
60
61 static LineProperties default_properties;
62
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);
79
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);
83
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);
87
88 void Line_adjust_for_absolute_gap(Line *line, Point *gap_endpoints);
89
90 static ObjectTypeOps line_type_ops =
91 {
92 (CreateFunc) line_create,
93 (LoadFunc) line_load,
94 (SaveFunc) line_save,
95 (GetDefaultsFunc) NULL,
96 (ApplyDefaultsFunc) NULL
97 };
98
99 DiaObjectType line_type =
100 {
101 "Standard - Line", /* name */
102 0, /* version */
103 (char **) line_icon, /* pixmap */
104 &line_type_ops /* ops */
105 };
106
107 DiaObjectType *_line_type = (DiaObjectType *) &line_type;
108
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 (ApplyPropertiesDialogFunc) 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,
123 (TextEditFunc) 0,
124 (ApplyPropertiesListFunc) object_apply_props,
125 };
126
127 static PropNumData gap_range = { -G_MAXFLOAT, G_MAXFLOAT, 0.1};
128
129 static PropDescription line_props[] = {
130 OBJECT_COMMON_PROPERTIES,
131 PROP_STD_LINE_WIDTH,
132 PROP_STD_LINE_COLOUR,
133 PROP_STD_LINE_STYLE,
134 PROP_FRAME_BEGIN("arrows",PROP_FLAG_STANDARD,N_("Arrows")),
135 PROP_STD_START_ARROW,
136 PROP_STD_END_ARROW,
137 PROP_FRAME_END("arrows",PROP_FLAG_STANDARD),
138 { "start_point", PROP_TYPE_POINT, 0,
139 N_("Start point"), NULL },
140 { "end_point", PROP_TYPE_POINT, 0,
141 N_("End point"), NULL },
142
143 PROP_FRAME_BEGIN("gaps",0,N_("Line gaps")),
144 { "absolute_start_gap", PROP_TYPE_REAL, PROP_FLAG_VISIBLE,
145 N_("Absolute start gap"), NULL, &gap_range },
146 { "absolute_end_gap", PROP_TYPE_REAL, PROP_FLAG_VISIBLE,
147 N_("Absolute end gap"), NULL, &gap_range },
148 PROP_FRAME_END("gaps",0),
149
150 PROP_DESC_END
151 };
152
153 static PropDescription *
line_describe_props(Line * line)154 line_describe_props(Line *line)
155 {
156 if (line_props[0].quark == 0)
157 prop_desc_list_calculate_quarks(line_props);
158 return line_props;
159 }
160
161 static PropOffset line_offsets[] = {
162 OBJECT_COMMON_PROPERTIES_OFFSETS,
163 { PROP_STDNAME_LINE_WIDTH, PROP_STDTYPE_LINE_WIDTH, offsetof(Line, line_width) },
164 { "line_colour", PROP_TYPE_COLOUR, offsetof(Line, line_color) },
165 { "line_style", PROP_TYPE_LINESTYLE,
166 offsetof(Line, line_style), offsetof(Line, dashlength) },
167 { "start_arrow", PROP_TYPE_ARROW, offsetof(Line, start_arrow) },
168 { "end_arrow", PROP_TYPE_ARROW, offsetof(Line, end_arrow) },
169 { "start_point", PROP_TYPE_POINT, offsetof(Connection, endpoints[0]) },
170 { "end_point", PROP_TYPE_POINT, offsetof(Connection, endpoints[1]) },
171 PROP_OFFSET_FRAME_BEGIN("gaps"),
172 { "absolute_start_gap", PROP_TYPE_REAL, offsetof(Line, absolute_start_gap) },
173 { "absolute_end_gap", PROP_TYPE_REAL, offsetof(Line, absolute_end_gap) },
174 PROP_OFFSET_FRAME_END("gaps"),
175 { NULL, 0, 0 }
176 };
177
178 static void
line_get_props(Line * line,GPtrArray * props)179 line_get_props(Line *line, GPtrArray *props)
180 {
181 object_get_props_from_offsets(&line->connection.object,
182 line_offsets, props);
183 }
184
185 static void
line_set_props(Line * line,GPtrArray * props)186 line_set_props(Line *line, GPtrArray *props)
187 {
188 object_set_props_from_offsets(&line->connection.object,
189 line_offsets, props);
190 line_update_data(line);
191 }
192
193 static void
line_init_defaults()194 line_init_defaults() {
195 static int defaults_initialized = 0;
196
197 if (!defaults_initialized) {
198 default_properties.absolute_start_gap = 0.0;
199 default_properties.absolute_end_gap = 0.0;
200 defaults_initialized = 1;
201 }
202 }
203
204 static ObjectChange *
line_add_connpoint_callback(DiaObject * obj,Point * clicked,gpointer data)205 line_add_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
206 {
207 ObjectChange *oc;
208 oc = connpointline_add_point(((Line *)obj)->cpl,clicked);
209 line_update_data((Line *)obj);
210 return oc;
211 }
212
213 static ObjectChange *
line_remove_connpoint_callback(DiaObject * obj,Point * clicked,gpointer data)214 line_remove_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
215 {
216 ObjectChange *oc;
217 oc = connpointline_remove_point(((Line *)obj)->cpl,clicked);
218 line_update_data((Line *)obj);
219 return oc;
220 }
221
222 static DiaMenuItem object_menu_items[] = {
223 { N_("Add connection point"), line_add_connpoint_callback, NULL, 1 },
224 { N_("Delete connection point"), line_remove_connpoint_callback,
225 NULL, 1 },
226 };
227
228 static DiaMenu object_menu = {
229 N_("Line"),
230 sizeof(object_menu_items)/sizeof(DiaMenuItem),
231 object_menu_items,
232 NULL
233 };
234
235 static DiaMenu *
line_get_object_menu(Line * line,Point * clickedpoint)236 line_get_object_menu(Line *line, Point *clickedpoint)
237 {
238 ConnPointLine *cpl;
239
240 cpl = line->cpl;
241 /* Set entries sensitive/selected etc here */
242 object_menu_items[0].active =
243 connpointline_can_add_point(cpl, clickedpoint);
244 object_menu_items[1].active =
245 connpointline_can_remove_point(cpl,clickedpoint);
246 return &object_menu;
247 }
248
249
250
251 /** Calculate the absolute gap -- this gap is 'transient', in that
252 * the actual end of the line is not moved, but it is made to look like
253 * it is shorter.
254 */
255 static void
line_adjust_for_absolute_gap(Line * line,Point * gap_endpoints)256 line_adjust_for_absolute_gap(Line *line, Point *gap_endpoints)
257 {
258 Point endpoints[2];
259 real line_length;
260
261 endpoints[0] = line->connection.endpoints[0];
262 endpoints[1] = line->connection.endpoints[1];
263
264 line_length = distance_point_point(&endpoints[0], &endpoints[1]);
265
266 /* puts new 0 to x% of 0->1 */
267 point_convex(&gap_endpoints[0], &endpoints[0], &endpoints[1],
268 1 - line->absolute_start_gap/line_length);
269
270 /* puts new 1 to x% of 1->0 */
271 point_convex(&gap_endpoints[1], &endpoints[1], &endpoints[0],
272 1 - line->absolute_end_gap/line_length);
273 }
274
275 static real
line_distance_from(Line * line,Point * point)276 line_distance_from(Line *line, Point *point)
277 {
278 Point *endpoints;
279
280 endpoints = &line->connection.endpoints[0];
281
282 if (line->absolute_start_gap || line->absolute_end_gap ) {
283 Point gap_endpoints[2]; /* Visible endpoints of line */
284
285 line_adjust_for_absolute_gap(line, gap_endpoints);
286 return distance_line_point( &gap_endpoints[0], &gap_endpoints[1],
287 line->line_width, point);
288 } else {
289 return distance_line_point( &endpoints[0], &endpoints[1],
290 line->line_width, point);
291 }
292 }
293
294 static void
line_select(Line * line,Point * clicked_point,DiaRenderer * interactive_renderer)295 line_select(Line *line, Point *clicked_point,
296 DiaRenderer *interactive_renderer)
297 {
298 connection_update_handles(&line->connection);
299 }
300
301 static ObjectChange*
line_move_handle(Line * line,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)302 line_move_handle(Line *line, Handle *handle,
303 Point *to, ConnectionPoint *cp,
304 HandleMoveReason reason, ModifierKeys modifiers)
305 {
306 assert(line!=NULL);
307 assert(handle!=NULL);
308 assert(to!=NULL);
309
310 connection_move_handle(&line->connection, handle->id, to, cp, reason, modifiers);
311
312 line_update_data(line);
313
314 return NULL;
315 }
316
317 static ObjectChange*
line_move(Line * line,Point * to)318 line_move(Line *line, Point *to)
319 {
320 Point start_to_end;
321 Point *endpoints = &line->connection.endpoints[0];
322
323 start_to_end = endpoints[1];
324 point_sub(&start_to_end, &endpoints[0]);
325
326 endpoints[1] = endpoints[0] = *to;
327 point_add(&endpoints[1], &start_to_end);
328
329 line_update_data(line);
330
331 return NULL;
332 }
333
334 static void
line_draw(Line * line,DiaRenderer * renderer)335 line_draw(Line *line, DiaRenderer *renderer)
336 {
337 Point gap_endpoints[2];
338
339 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
340
341 assert(line != NULL);
342 assert(renderer != NULL);
343
344 renderer_ops->set_linewidth(renderer, line->line_width);
345 renderer_ops->set_linestyle(renderer, line->line_style);
346 renderer_ops->set_dashlength(renderer, line->dashlength);
347 renderer_ops->set_linecaps(renderer, LINECAPS_BUTT);
348
349 if (line->absolute_start_gap || line->absolute_end_gap ) {
350 line_adjust_for_absolute_gap(line, gap_endpoints);
351
352 renderer_ops->draw_line_with_arrows(renderer,
353 &gap_endpoints[0], &gap_endpoints[1],
354 line->line_width,
355 &line->line_color,
356 &line->start_arrow,
357 &line->end_arrow);
358 } else {
359 renderer_ops->draw_line_with_arrows(renderer,
360 &line->connection.endpoints[0],
361 &line->connection.endpoints[1],
362 line->line_width,
363 &line->line_color,
364 &line->start_arrow,
365 &line->end_arrow);
366
367 }
368 }
369
370 static DiaObject *
line_create(Point * startpoint,void * user_data,Handle ** handle1,Handle ** handle2)371 line_create(Point *startpoint,
372 void *user_data,
373 Handle **handle1,
374 Handle **handle2)
375 {
376 Line *line;
377 Connection *conn;
378 DiaObject *obj;
379 Point defaultlen = { 1.0, 1.0 };
380
381 line_init_defaults();
382
383 line = g_malloc0(sizeof(Line));
384
385 line->line_width = attributes_get_default_linewidth();
386 line->line_color = attributes_get_foreground();
387 line->absolute_start_gap = default_properties.absolute_start_gap;
388 line->absolute_end_gap = default_properties.absolute_end_gap;
389
390 conn = &line->connection;
391 conn->endpoints[0] = *startpoint;
392 conn->endpoints[1] = *startpoint;
393 point_add(&conn->endpoints[1], &defaultlen);
394
395 obj = &conn->object;
396
397 obj->type = &line_type;
398 obj->ops = &line_ops;
399
400 connection_init(conn, 2, 0);
401
402 line->cpl = connpointline_create(obj,1);
403
404 attributes_get_default_line_style(&line->line_style, &line->dashlength);
405 line->start_arrow = attributes_get_default_start_arrow();
406 line->end_arrow = attributes_get_default_end_arrow();
407 line_update_data(line);
408
409 *handle1 = obj->handles[0];
410 *handle2 = obj->handles[1];
411 return &line->connection.object;
412 }
413
414 static void
line_destroy(Line * line)415 line_destroy(Line *line)
416 {
417 connpointline_destroy(line->cpl);
418 connection_destroy(&line->connection);
419 }
420
421 static DiaObject *
line_copy(Line * line)422 line_copy(Line *line)
423 {
424 Line *newline;
425 Connection *conn, *newconn;
426 DiaObject *newobj;
427 int rcc = 0;
428
429 conn = &line->connection;
430
431 newline = g_malloc0(sizeof(Line));
432 newconn = &newline->connection;
433 newobj = &newconn->object;
434
435 connection_copy(conn, newconn);
436
437 newline->cpl = connpointline_copy(newobj,line->cpl,&rcc);
438
439 newline->line_color = line->line_color;
440 newline->line_width = line->line_width;
441 newline->line_style = line->line_style;
442 newline->dashlength = line->dashlength;
443 newline->start_arrow = line->start_arrow;
444 newline->end_arrow = line->end_arrow;
445 newline->absolute_start_gap = line->absolute_start_gap;
446 newline->absolute_end_gap = line->absolute_end_gap;
447
448 line_update_data(line);
449
450 return &newline->connection.object;
451 }
452
453 static void
line_update_data(Line * line)454 line_update_data(Line *line)
455 {
456 Connection *conn = &line->connection;
457 DiaObject *obj = &conn->object;
458 LineBBExtras *extra = &conn->extra_spacing;
459 Point start, end;
460
461 extra->start_trans =
462 extra->end_trans =
463 extra->start_long =
464 extra->end_long = (line->line_width / 2.0);
465
466 if (connpoint_is_autogap(line->connection.endpoint_handles[0].connected_to) ||
467 connpoint_is_autogap(line->connection.endpoint_handles[1].connected_to)) {
468 connection_adjust_for_autogap(conn);
469 }
470 if (line->absolute_start_gap || line->absolute_end_gap ) {
471 Point gap_endpoints[2];
472
473 line_adjust_for_absolute_gap(line, gap_endpoints);
474 line_bbox(&gap_endpoints[0],&gap_endpoints[1],
475 &conn->extra_spacing,&conn->object.bounding_box);
476 start = gap_endpoints[0];
477 end = gap_endpoints[1];
478 } else {
479 connection_update_boundingbox(conn);
480 start = conn->endpoints[0];
481 end = conn->endpoints[1];
482 }
483 if (line->start_arrow.type != ARROW_NONE) {
484 Rectangle bbox;
485 Point move_arrow, move_line;
486 Point to = start;
487 Point from = end;
488 calculate_arrow_point(&line->start_arrow, &to, &from,
489 &move_arrow, &move_line, line->line_width);
490 /* move them */
491 point_sub(&to, &move_arrow);
492 point_sub(&from, &move_line);
493
494 arrow_bbox (&line->start_arrow, line->line_width, &to, &from, &bbox);
495 rectangle_union (&obj->bounding_box, &bbox);
496 }
497 if (line->end_arrow.type != ARROW_NONE) {
498 Rectangle bbox;
499 Point move_arrow, move_line;
500 Point to = end;
501 Point from = start;
502 calculate_arrow_point(&line->start_arrow, &to, &from,
503 &move_arrow, &move_line, line->line_width);
504 /* move them */
505 point_sub(&to, &move_arrow);
506 point_sub(&from, &move_line);
507
508 arrow_bbox (&line->end_arrow, line->line_width, &to, &from, &bbox);
509 rectangle_union (&obj->bounding_box, &bbox);
510 }
511
512 obj->position = conn->endpoints[0];
513
514 connpointline_update(line->cpl);
515 connpointline_putonaline(line->cpl, &start, &end);
516
517 connection_update_handles(conn);
518 }
519
520
521 static void
line_save(Line * line,ObjectNode obj_node,const char * filename)522 line_save(Line *line, ObjectNode obj_node, const char *filename)
523 {
524 #ifdef DEBUG
525 dia_object_sanity_check((DiaObject*)line, "Saving line");
526 #endif
527
528 connection_save(&line->connection, obj_node);
529
530 connpointline_save(line->cpl,obj_node,"numcp");
531
532 if (!color_equals(&line->line_color, &color_black))
533 data_add_color(new_attribute(obj_node, "line_color"),
534 &line->line_color);
535
536 if (line->line_width != 0.1)
537 data_add_real(new_attribute(obj_node, PROP_STDNAME_LINE_WIDTH),
538 line->line_width);
539
540 if (line->line_style != LINESTYLE_SOLID)
541 data_add_enum(new_attribute(obj_node, "line_style"),
542 line->line_style);
543
544 if (line->start_arrow.type != ARROW_NONE) {
545 save_arrow(obj_node, &line->start_arrow,
546 "start_arrow", "start_arrow_length", "start_arrow_width");
547 }
548
549 if (line->end_arrow.type != ARROW_NONE) {
550 save_arrow(obj_node, &line->end_arrow,
551 "end_arrow", "end_arrow_length", "end_arrow_width");
552 }
553
554 if (line->absolute_start_gap)
555 data_add_real(new_attribute(obj_node, "absolute_start_gap"),
556 line->absolute_start_gap);
557 if (line->absolute_end_gap)
558 data_add_real(new_attribute(obj_node, "absolute_end_gap"),
559 line->absolute_end_gap);
560
561 if (line->line_style != LINESTYLE_SOLID && line->dashlength != DEFAULT_LINESTYLE_DASHLEN)
562 data_add_real(new_attribute(obj_node, "dashlength"),
563 line->dashlength);
564 }
565
566 static DiaObject *
line_load(ObjectNode obj_node,int version,const char * filename)567 line_load(ObjectNode obj_node, int version, const char *filename)
568 {
569 Line *line;
570 Connection *conn;
571 DiaObject *obj;
572 AttributeNode attr;
573
574 line = g_malloc0(sizeof(Line));
575
576 conn = &line->connection;
577 obj = &conn->object;
578
579 obj->type = &line_type;
580 obj->ops = &line_ops;
581
582 connection_load(conn, obj_node);
583
584 line->line_color = color_black;
585 attr = object_find_attribute(obj_node, "line_color");
586 if (attr != NULL)
587 data_color(attribute_first_data(attr), &line->line_color);
588
589 line->line_width = 0.1;
590 attr = object_find_attribute(obj_node, PROP_STDNAME_LINE_WIDTH);
591 if (attr != NULL)
592 line->line_width = data_real(attribute_first_data(attr));
593
594 line->line_style = LINESTYLE_SOLID;
595 attr = object_find_attribute(obj_node, "line_style");
596 if (attr != NULL)
597 line->line_style = data_enum(attribute_first_data(attr));
598
599 load_arrow(obj_node, &line->start_arrow,
600 "start_arrow", "start_arrow_length", "start_arrow_width");
601
602 load_arrow(obj_node, &line->end_arrow,
603 "end_arrow", "end_arrow_length", "end_arrow_width");
604
605 line->absolute_start_gap = 0.0;
606 attr = object_find_attribute(obj_node, "absolute_start_gap");
607 if (attr != NULL)
608 line->absolute_start_gap = data_real( attribute_first_data(attr) );
609 line->absolute_end_gap = 0.0;
610 attr = object_find_attribute(obj_node, "absolute_end_gap");
611 if (attr != NULL)
612 line->absolute_end_gap = data_real( attribute_first_data(attr) );
613
614 line->dashlength = DEFAULT_LINESTYLE_DASHLEN;
615 attr = object_find_attribute(obj_node, "dashlength");
616 if (attr != NULL)
617 line->dashlength = data_real(attribute_first_data(attr));
618
619 connection_init(conn, 2, 0);
620
621 line->cpl = connpointline_load(obj,obj_node,"numcp",1,NULL);
622 line_update_data(line);
623
624 return &line->connection.object;
625 }
626