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