1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <assert.h>
24 #include <math.h>
25 #include <string.h>
26 
27 #include "intl.h"
28 #include "object.h"
29 #include "orth_conn.h"
30 #include "diarenderer.h"
31 #include "attributes.h"
32 #include "arrows.h"
33 
34 #include "properties.h"
35 
36 #include "stereotype.h"
37 #include "uml.h"
38 
39 #include "pixmaps/dependency.xpm"
40 
41 typedef struct _Dependency Dependency;
42 
43 struct _Dependency {
44   OrthConn orth;
45 
46   Point text_pos;
47   Alignment text_align;
48   real text_width;
49 
50   Color text_color;
51   Color line_color;
52 
53   int draw_arrow;
54   char *name;
55   char *stereotype; /* excluding << and >> */
56   char *st_stereotype; /* including << and >> */
57 };
58 
59 
60 #define DEPENDENCY_WIDTH 0.1
61 #define DEPENDENCY_ARROWLEN 0.8
62 #define DEPENDENCY_ARROWWIDTH 0.5
63 #define DEPENDENCY_DASHLEN 0.4
64 #define DEPENDENCY_FONTHEIGHT 0.8
65 
66 static DiaFont *dep_font = NULL;
67 
68 static real dependency_distance_from(Dependency *dep, Point *point);
69 static void dependency_select(Dependency *dep, Point *clicked_point,
70 			      DiaRenderer *interactive_renderer);
71 static ObjectChange* dependency_move_handle(Dependency *dep, Handle *handle,
72 					    Point *to, ConnectionPoint *cp,
73 					    HandleMoveReason reason, ModifierKeys modifiers);
74 static ObjectChange* dependency_move(Dependency *dep, Point *to);
75 static void dependency_draw(Dependency *dep, DiaRenderer *renderer);
76 static DiaObject *dependency_create(Point *startpoint,
77 				 void *user_data,
78 				 Handle **handle1,
79 				 Handle **handle2);
80 static void dependency_destroy(Dependency *dep);
81 static DiaMenu *dependency_get_object_menu(Dependency *dep,
82 					   Point *clickedpoint);
83 
84 static DiaObject *dependency_load(ObjectNode obj_node, int version,
85 			       const char *filename);
86 static PropDescription *dependency_describe_props(Dependency *dependency);
87 static void dependency_get_props(Dependency * dependency, GPtrArray *props);
88 static void dependency_set_props(Dependency * dependency, GPtrArray *props);
89 
90 static void dependency_update_data(Dependency *dep);
91 
92 static ObjectTypeOps dependency_type_ops =
93 {
94   (CreateFunc) dependency_create,
95   (LoadFunc)   dependency_load,/*using_properties*/     /* load */
96   (SaveFunc)   object_save_using_properties,      /* save */
97   (GetDefaultsFunc)   NULL,
98   (ApplyDefaultsFunc) NULL
99 };
100 
101 DiaObjectType dependency_type =
102 {
103   "UML - Dependency",   /* name */
104   /* Version 0 had no autorouting and so shouldn't have it set by default. */
105   1,                      /* version */
106   (char **) dependency_xpm,  /* pixmap */
107 
108   &dependency_type_ops,      /* ops */
109   NULL,                 /* pixmap_file */
110   0                     /* default_user_data */
111 };
112 
113 static ObjectOps dependency_ops = {
114   (DestroyFunc)         dependency_destroy,
115   (DrawFunc)            dependency_draw,
116   (DistanceFunc)        dependency_distance_from,
117   (SelectFunc)          dependency_select,
118   (CopyFunc)            object_copy_using_properties,
119   (MoveFunc)            dependency_move,
120   (MoveHandleFunc)      dependency_move_handle,
121   (GetPropertiesFunc)   object_create_props_dialog,
122   (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
123   (ObjectMenuFunc)      dependency_get_object_menu,
124   (DescribePropsFunc)   dependency_describe_props,
125   (GetPropsFunc)        dependency_get_props,
126   (SetPropsFunc)        dependency_set_props,
127   (TextEditFunc) 0,
128   (ApplyPropertiesListFunc) object_apply_props,
129 };
130 
131 static PropDescription dependency_props[] = {
132   ORTHCONN_COMMON_PROPERTIES,
133   /* can't use PROP_STD_TEXT_COLOUR_OPTIONAL cause it has PROP_FLAG_DONT_SAVE. It is designed to fill the Text object - not some subset */
134   PROP_STD_TEXT_COLOUR_OPTIONS(PROP_FLAG_VISIBLE|PROP_FLAG_STANDARD|PROP_FLAG_OPTIONAL),
135   PROP_STD_LINE_COLOUR_OPTIONAL,
136   { "name", PROP_TYPE_STRING, PROP_FLAG_VISIBLE,
137     N_("Name:"), NULL, NULL },
138   { "stereotype", PROP_TYPE_STRING, PROP_FLAG_VISIBLE,
139     N_("Stereotype:"), NULL, NULL },
140   { "draw_arrow", PROP_TYPE_BOOL, PROP_FLAG_VISIBLE,
141     N_("Show arrow:"), NULL, NULL },
142   PROP_DESC_END
143 };
144 
145 static PropDescription *
dependency_describe_props(Dependency * dependency)146 dependency_describe_props(Dependency *dependency)
147 {
148   if (dependency_props[0].quark == 0) {
149     prop_desc_list_calculate_quarks(dependency_props);
150   }
151   return dependency_props;
152 }
153 
154 static PropOffset dependency_offsets[] = {
155   ORTHCONN_COMMON_PROPERTIES_OFFSETS,
156   { "text_colour", PROP_TYPE_COLOUR, offsetof(Dependency, text_color) },
157   { "line_colour", PROP_TYPE_COLOUR, offsetof(Dependency, line_color) },
158   { "name", PROP_TYPE_STRING, offsetof(Dependency, name) },
159   { "stereotype", PROP_TYPE_STRING, offsetof(Dependency, stereotype) },
160   { "draw_arrow", PROP_TYPE_BOOL, offsetof(Dependency, draw_arrow) },
161   { NULL, 0, 0 }
162 };
163 
164 static void
dependency_get_props(Dependency * dependency,GPtrArray * props)165 dependency_get_props(Dependency * dependency, GPtrArray *props)
166 {
167   object_get_props_from_offsets(&dependency->orth.object,
168                                 dependency_offsets,props);
169 }
170 
171 static void
dependency_set_props(Dependency * dependency,GPtrArray * props)172 dependency_set_props(Dependency *dependency, GPtrArray *props)
173 {
174   object_set_props_from_offsets(&dependency->orth.object,
175                                 dependency_offsets, props);
176   g_free(dependency->st_stereotype);
177   dependency->st_stereotype = NULL;
178   dependency_update_data(dependency);
179 }
180 
181 static real
dependency_distance_from(Dependency * dep,Point * point)182 dependency_distance_from(Dependency *dep, Point *point)
183 {
184   OrthConn *orth = &dep->orth;
185   return orthconn_distance_from(orth, point, DEPENDENCY_WIDTH);
186 }
187 
188 static void
dependency_select(Dependency * dep,Point * clicked_point,DiaRenderer * interactive_renderer)189 dependency_select(Dependency *dep, Point *clicked_point,
190 		  DiaRenderer *interactive_renderer)
191 {
192   orthconn_update_data(&dep->orth);
193 }
194 
195 static ObjectChange*
dependency_move_handle(Dependency * dep,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)196 dependency_move_handle(Dependency *dep, Handle *handle,
197 		       Point *to, ConnectionPoint *cp,
198 		       HandleMoveReason reason, ModifierKeys modifiers)
199 {
200   ObjectChange *change;
201   assert(dep!=NULL);
202   assert(handle!=NULL);
203   assert(to!=NULL);
204 
205   change = orthconn_move_handle(&dep->orth, handle, to, cp, reason, modifiers);
206   dependency_update_data(dep);
207 
208   return change;
209 }
210 
211 static ObjectChange*
dependency_move(Dependency * dep,Point * to)212 dependency_move(Dependency *dep, Point *to)
213 {
214   ObjectChange *change;
215 
216   change = orthconn_move(&dep->orth, to);
217   dependency_update_data(dep);
218 
219   return change;
220 }
221 
222 static void
dependency_draw(Dependency * dep,DiaRenderer * renderer)223 dependency_draw(Dependency *dep, DiaRenderer *renderer)
224 {
225   DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
226   OrthConn *orth = &dep->orth;
227   Point *points;
228   int n;
229   Point pos;
230   Arrow arrow;
231 
232   points = &orth->points[0];
233   n = orth->numpoints;
234 
235   renderer_ops->set_linewidth(renderer, DEPENDENCY_WIDTH);
236   renderer_ops->set_linestyle(renderer, LINESTYLE_DASHED);
237   renderer_ops->set_dashlength(renderer, DEPENDENCY_DASHLEN);
238   renderer_ops->set_linejoin(renderer, LINEJOIN_MITER);
239   renderer_ops->set_linecaps(renderer, LINECAPS_BUTT);
240 
241   arrow.type = ARROW_LINES;
242   arrow.length = DEPENDENCY_ARROWLEN;
243   arrow.width = DEPENDENCY_ARROWWIDTH;
244 
245   renderer_ops->draw_polyline_with_arrows(renderer,
246 					   points, n,
247 					   DEPENDENCY_WIDTH,
248 					   &dep->line_color,
249 					   NULL, &arrow);
250 
251   renderer_ops->set_font(renderer, dep_font, DEPENDENCY_FONTHEIGHT);
252   pos = dep->text_pos;
253 
254   if (dep->st_stereotype != NULL && dep->st_stereotype[0] != '\0') {
255     renderer_ops->draw_string(renderer,
256 			       dep->st_stereotype,
257 			       &pos, dep->text_align,
258 			       &dep->text_color);
259 
260     pos.y += DEPENDENCY_FONTHEIGHT;
261   }
262 
263   if (dep->name != NULL && dep->name[0] != '\0') {
264     renderer_ops->draw_string(renderer,
265 			       dep->name,
266 			       &pos, dep->text_align,
267 			       &dep->text_color);
268   }
269 
270 }
271 
272 static void
dependency_update_data(Dependency * dep)273 dependency_update_data(Dependency *dep)
274 {
275   OrthConn *orth = &dep->orth;
276   DiaObject *obj = &orth->object;
277   PolyBBExtras *extra = &orth->extra_spacing;
278   int num_segm, i;
279   Point *points;
280   Rectangle rect;
281 
282   orthconn_update_data(orth);
283 
284   dep->stereotype = remove_stereotype_from_string(dep->stereotype);
285   if (!dep->st_stereotype) {
286     dep->st_stereotype =  string_to_stereotype(dep->stereotype);
287   }
288 
289   dep->text_width = 0.0;
290   if (dep->name)
291     dep->text_width = dia_font_string_width(dep->name, dep_font,
292 					DEPENDENCY_FONTHEIGHT);
293   if (dep->stereotype)
294     dep->text_width = MAX(dep->text_width,
295 			  dia_font_string_width(dep->stereotype, dep_font,
296 					    DEPENDENCY_FONTHEIGHT));
297 
298   extra->start_trans =
299     extra->start_long =
300     extra->middle_trans = DEPENDENCY_WIDTH/2.0;
301 
302   extra->end_trans =
303     extra->end_long = (dep->draw_arrow?
304                        (DEPENDENCY_WIDTH + DEPENDENCY_ARROWLEN)/2.0:
305                        DEPENDENCY_WIDTH/2.0);
306 
307   orthconn_update_boundingbox(orth);
308 
309   /* Calc text pos: */
310   num_segm = dep->orth.numpoints - 1;
311   points = dep->orth.points;
312   i = num_segm / 2;
313 
314   if ((num_segm % 2) == 0) { /* If no middle segment, use horizontal */
315     if (dep->orth.orientation[i]==VERTICAL)
316       i--;
317   }
318 
319   switch (dep->orth.orientation[i]) {
320   case HORIZONTAL:
321     dep->text_align = ALIGN_CENTER;
322     dep->text_pos.x = 0.5*(points[i].x+points[i+1].x);
323     dep->text_pos.y = points[i].y;
324     if (dep->name)
325       dep->text_pos.y -= dia_font_descent(dep->name,
326 					  dep_font,
327 					  DEPENDENCY_FONTHEIGHT);
328     break;
329   case VERTICAL:
330     dep->text_align = ALIGN_LEFT;
331     dep->text_pos.x = points[i].x + 0.1;
332     dep->text_pos.y =
333       0.5*(points[i].y+points[i+1].y);
334     if (dep->name)
335       dep->text_pos.y -= dia_font_descent(dep->name,
336 					  dep_font,
337 					  DEPENDENCY_FONTHEIGHT);
338     break;
339   }
340 
341   /* Add the text recangle to the bounding box: */
342   rect.left = dep->text_pos.x;
343   if (dep->text_align == ALIGN_CENTER)
344     rect.left -= dep->text_width/2.0;
345   rect.right = rect.left + dep->text_width;
346   rect.top = dep->text_pos.y;
347   if (dep->name)
348     rect.top -= dia_font_ascent(dep->name,
349 				dep_font,
350 				DEPENDENCY_FONTHEIGHT);
351   rect.bottom = rect.top + 2*DEPENDENCY_FONTHEIGHT;
352 
353   rectangle_union(&obj->bounding_box, &rect);
354 }
355 
356 static ObjectChange *
dependency_add_segment_callback(DiaObject * obj,Point * clicked,gpointer data)357 dependency_add_segment_callback(DiaObject *obj, Point *clicked, gpointer data)
358 {
359   ObjectChange *change;
360   change = orthconn_add_segment((OrthConn *)obj, clicked);
361   dependency_update_data((Dependency *)obj);
362   return change;
363 }
364 
365 static ObjectChange *
dependency_delete_segment_callback(DiaObject * obj,Point * clicked,gpointer data)366 dependency_delete_segment_callback(DiaObject *obj, Point *clicked, gpointer data)
367 {
368   ObjectChange *change;
369   change = orthconn_delete_segment((OrthConn *)obj, clicked);
370   dependency_update_data((Dependency *)obj);
371   return change;
372 }
373 
374 
375 static DiaMenuItem object_menu_items[] = {
376   { N_("Add segment"), dependency_add_segment_callback, NULL, 1 },
377   { N_("Delete segment"), dependency_delete_segment_callback, NULL, 1 },
378   ORTHCONN_COMMON_MENUS,
379 };
380 
381 static DiaMenu object_menu = {
382   "Dependency",
383   sizeof(object_menu_items)/sizeof(DiaMenuItem),
384   object_menu_items,
385   NULL
386 };
387 
388 static DiaMenu *
dependency_get_object_menu(Dependency * dep,Point * clickedpoint)389 dependency_get_object_menu(Dependency *dep, Point *clickedpoint)
390 {
391   OrthConn *orth;
392 
393   orth = &dep->orth;
394   /* Set entries sensitive/selected etc here */
395   object_menu_items[0].active = orthconn_can_add_segment(orth, clickedpoint);
396   object_menu_items[1].active = orthconn_can_delete_segment(orth, clickedpoint);
397   orthconn_update_object_menu(orth, clickedpoint, &object_menu_items[2]);
398 
399   return &object_menu;
400 }
401 
402 static DiaObject *
dependency_create(Point * startpoint,void * user_data,Handle ** handle1,Handle ** handle2)403 dependency_create(Point *startpoint,
404 	       void *user_data,
405   	       Handle **handle1,
406 	       Handle **handle2)
407 {
408   Dependency *dep;
409   OrthConn *orth;
410   DiaObject *obj;
411 
412   if (dep_font == NULL) {
413       dep_font = dia_font_new_from_style(DIA_FONT_MONOSPACE, DEPENDENCY_FONTHEIGHT);
414   }
415 
416   dep = g_new0(Dependency, 1);
417   orth = &dep->orth;
418   obj = (DiaObject *)dep;
419 
420   obj->type = &dependency_type;
421 
422   obj->ops = &dependency_ops;
423 
424   orthconn_init(orth, startpoint);
425 
426   dep->text_color = color_black;
427   dep->line_color = attributes_get_foreground();
428   dep->draw_arrow = TRUE;
429   dep->name = NULL;
430   dep->stereotype = NULL;
431   dep->st_stereotype = NULL;
432   dep->text_width = 0;
433 
434   dependency_update_data(dep);
435 
436   *handle1 = orth->handles[0];
437   *handle2 = orth->handles[orth->numpoints-2];
438 
439   return (DiaObject *)dep;
440 }
441 
442 static void
dependency_destroy(Dependency * dep)443 dependency_destroy(Dependency *dep)
444 {
445   g_free(dep->name);
446   g_free(dep->stereotype);
447   g_free(dep->st_stereotype);
448 
449   orthconn_destroy(&dep->orth);
450 }
451 
452 static DiaObject *
dependency_load(ObjectNode obj_node,int version,const char * filename)453 dependency_load(ObjectNode obj_node, int version, const char *filename)
454 {
455   DiaObject *obj = object_load_using_properties(&dependency_type,
456                                                 obj_node,version,filename);
457   if (version == 0) {
458     AttributeNode attr;
459     /* In old objects with no autorouting, set it to false. */
460     attr = object_find_attribute(obj_node, "autorouting");
461     if (attr == NULL)
462       ((OrthConn*)obj)->autorouting = FALSE;
463   }
464   return obj;
465 }
466 
467 
468