1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
4  * Objects for drawing task and resources in i* diagrams
5  * Copyright (C) 2002 Christophe Ponsard
6  *
7  * Based on SADT box object
8  * Copyright (C) 2000, 2001 Cyrille Chepelov
9  *
10  * Forked from Flowchart toolbox -- objects for drawing flowcharts.
11  * Copyright (C) 1999 James Henstridge.
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 #include <assert.h>
33 #include <math.h>
34 #include <string.h>
35 #include <glib.h>
36 
37 #include "intl.h"
38 #include "object.h"
39 #include "element.h"
40 #include "connectionpoint.h"
41 #include "diarenderer.h"
42 #include "attributes.h"
43 #include "text.h"
44 #include "widgets.h"
45 #include "message.h"
46 #include "connpoint_line.h"
47 #include "color.h"
48 #include "properties.h"
49 
50 #include "pixmaps/resource.xpm"
51 
52 #define DEFAULT_WIDTH 3.0
53 #define DEFAULT_HEIGHT 1
54 #define DEFAULT_BORDER 0.2
55 #define DEFAULT_PADDING 0.4
56 #define DEFAULT_FONT 0.7
57 #define OTHER_LINE_WIDTH 0.12
58 #define OTHER_FG_COLOR color_black
59 #define OTHER_BG_COLOR color_white
60 
61 typedef enum {
62   ANCHOR_MIDDLE,
63   ANCHOR_START,
64   ANCHOR_END
65 } AnchorShape;
66 
67 typedef enum {
68   RESOURCE,
69   TASK
70 } OtherType;
71 
72 static PropEnumData prop_other_type_data[] = {
73   { N_("Resource"), RESOURCE },
74   { N_("Task"),     TASK },
75   { NULL, 0}
76 };
77 
78 typedef struct _Other {
79   Element element;
80   ConnPointLine *north,*south,*east,*west;
81 
82   Text *text;
83   real padding;
84   OtherType type;
85 
86   TextAttributes attrs;
87   int init;
88 } Other;
89 
90 static real other_distance_from(Other *other, Point *point);
91 static void other_select(Other *other, Point *clicked_point,
92 		       DiaRenderer *interactive_renderer);
93 static ObjectChange* other_move_handle(Other *other, Handle *handle,
94 			    Point *to, ConnectionPoint *cp,
95 			    HandleMoveReason reason, ModifierKeys modifiers);
96 static ObjectChange* other_move(Other *other, Point *to);
97 static void other_draw(Other *other, DiaRenderer *renderer);
98 static void other_update_data(Other *other, AnchorShape horix, AnchorShape vert);
99 static DiaObject *other_create(Point *startpoint,
100 			  void *user_data,
101 			  Handle **handle1,
102 			  Handle **handle2);
103 static void other_destroy(Other *other);
104 static DiaObject *other_load(ObjectNode obj_node, int version,
105                             const char *filename);
106 static DiaMenu *other_get_object_menu(Other *other, Point *clickedpoint);
107 
108 static PropDescription *other_describe_props(Other *other);
109 static void other_get_props(Other *other, GPtrArray *props);
110 static void other_set_props(Other *other, GPtrArray *props);
111 
112 static ObjectTypeOps istar_other_type_ops =
113 {
114   (CreateFunc) other_create,
115   (LoadFunc)   other_load/*using_properties*/,
116   (SaveFunc)   object_save_using_properties,
117   (GetDefaultsFunc)   NULL,
118   (ApplyDefaultsFunc) NULL,
119 };
120 
121 DiaObjectType istar_other_type =
122 {
123   "Istar - other",          /* name */
124   0,                        /* version */
125   (char **) istar_resource_xpm, /* pixmap */
126   &istar_other_type_ops      /* ops */
127 };
128 
129 static ObjectOps other_ops = {
130   (DestroyFunc)         other_destroy,
131   (DrawFunc)            other_draw,
132   (DistanceFunc)        other_distance_from,
133   (SelectFunc)          other_select,
134   (CopyFunc)            object_copy_using_properties,
135   (MoveFunc)            other_move,
136   (MoveHandleFunc)      other_move_handle,
137   (GetPropertiesFunc)   object_create_props_dialog,
138   (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
139   (ObjectMenuFunc)      other_get_object_menu,
140   (DescribePropsFunc)   other_describe_props,
141   (GetPropsFunc)        other_get_props,
142   (SetPropsFunc)        other_set_props,
143   (TextEditFunc) 0,
144   (ApplyPropertiesListFunc) object_apply_props,
145 };
146 
147 static PropDescription other_props[] = {
148   ELEMENT_COMMON_PROPERTIES,
149   { "type", PROP_TYPE_ENUM, PROP_FLAG_VISIBLE|PROP_FLAG_NO_DEFAULTS,
150     N_("Type"),
151     N_("Type"),
152     prop_other_type_data},
153 
154   { "text", PROP_TYPE_TEXT, 0,NULL,NULL},
155   { "cpl_north",PROP_TYPE_CONNPOINT_LINE, 0, NULL, NULL},
156   { "cpl_west",PROP_TYPE_CONNPOINT_LINE, 0, NULL, NULL},
157   { "cpl_south",PROP_TYPE_CONNPOINT_LINE, 0, NULL, NULL},
158   { "cpl_east",PROP_TYPE_CONNPOINT_LINE, 0, NULL, NULL},
159   PROP_DESC_END
160 };
161 
162 static PropDescription *
other_describe_props(Other * other)163 other_describe_props(Other *other)
164 {
165   if (other_props[0].quark == 0) {
166     prop_desc_list_calculate_quarks(other_props);
167   }
168   return other_props;
169 }
170 
171 static PropOffset other_offsets[] = {
172   ELEMENT_COMMON_PROPERTIES_OFFSETS,
173   { "type", PROP_TYPE_ENUM, offsetof(Other,type)},
174   { "text", PROP_TYPE_TEXT, offsetof(Other,text)},
175   { "cpl_north",PROP_TYPE_CONNPOINT_LINE, offsetof(Other,north)},
176   { "cpl_west",PROP_TYPE_CONNPOINT_LINE, offsetof(Other,west)},
177   { "cpl_south",PROP_TYPE_CONNPOINT_LINE, offsetof(Other,south)},
178   { "cpl_east",PROP_TYPE_CONNPOINT_LINE, offsetof(Other,east)},
179   {NULL}
180 };
181 
182 static void
other_get_props(Other * other,GPtrArray * props)183 other_get_props(Other *other, GPtrArray *props)
184 {
185   text_get_attributes(other->text,&other->attrs);
186   object_get_props_from_offsets(&other->element.object,
187                                 other_offsets,props);
188 }
189 
190 static void
other_set_props(Other * other,GPtrArray * props)191 other_set_props(Other *other, GPtrArray *props)
192 {
193   if (other->init==-1) { other->init++; return; }    /* workaround init bug */
194 
195   object_set_props_from_offsets(&other->element.object,
196                                 other_offsets,props);
197   apply_textattr_properties(props,other->text,"text",&other->attrs);
198   other_update_data(other, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
199 }
200 
201 static real
other_distance_from(Other * other,Point * point)202 other_distance_from(Other *other, Point *point)
203 {
204   Element *elem = &other->element;
205   Rectangle rect;
206 
207   rect.left = elem->corner.x - OTHER_LINE_WIDTH/2;
208   rect.right = elem->corner.x + elem->width + OTHER_LINE_WIDTH/2;
209   rect.top = elem->corner.y - OTHER_LINE_WIDTH/2;
210   rect.bottom = elem->corner.y + elem->height + OTHER_LINE_WIDTH/2;
211   return distance_rectangle_point(&rect, point);
212 }
213 
214 static void
other_select(Other * other,Point * clicked_point,DiaRenderer * interactive_renderer)215 other_select(Other *other, Point *clicked_point,
216 	   DiaRenderer *interactive_renderer)
217 {
218   text_set_cursor(other->text, clicked_point, interactive_renderer);
219   text_grab_focus(other->text, &other->element.object);
220   element_update_handles(&other->element);
221 }
222 
223 static ObjectChange*
other_move_handle(Other * other,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)224 other_move_handle(Other *other, Handle *handle,
225 		Point *to, ConnectionPoint *cp,
226 		HandleMoveReason reason, ModifierKeys modifiers)
227 {
228   AnchorShape horiz = ANCHOR_MIDDLE, vert = ANCHOR_MIDDLE;
229 
230   assert(other!=NULL);
231   assert(handle!=NULL);
232   assert(to!=NULL);
233 
234   element_move_handle(&other->element, handle->id, to, cp, reason, modifiers);
235 
236   switch (handle->id) {
237   case HANDLE_RESIZE_NW:
238     horiz = ANCHOR_END; vert = ANCHOR_END; break;
239   case HANDLE_RESIZE_N:
240     vert = ANCHOR_END; break;
241   case HANDLE_RESIZE_NE:
242     horiz = ANCHOR_START; vert = ANCHOR_END; break;
243   case HANDLE_RESIZE_E:
244     horiz = ANCHOR_START; break;
245   case HANDLE_RESIZE_SE:
246     horiz = ANCHOR_START; vert = ANCHOR_START; break;
247   case HANDLE_RESIZE_S:
248     vert = ANCHOR_START; break;
249   case HANDLE_RESIZE_SW:
250     horiz = ANCHOR_END; vert = ANCHOR_START; break;
251   case HANDLE_RESIZE_W:
252     horiz = ANCHOR_END; break;
253   default:
254     break;
255   }
256   other_update_data(other, horiz, vert);
257   return NULL;
258 }
259 
260 static ObjectChange*
other_move(Other * other,Point * to)261 other_move(Other *other, Point *to)
262 {
263   other->element.corner = *to;
264 
265   other_update_data(other, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
266   return NULL;
267 }
268 
compute_task(Other * other,Point * pl)269 static void compute_task(Other *other, Point *pl) {
270      double rx,ry,w,h,h2;
271      Element *elem;
272 
273      elem=&other->element;
274      w=elem->width;
275      h=elem->height;
276      rx=elem->corner.x;
277      ry=elem->corner.y;
278      h2=h/2;
279 
280      pl[0].x=rx;
281      pl[0].y=ry+h2;
282      pl[1].x=rx+h2;
283      pl[1].y=ry;
284      pl[2].x=rx+w-h2;
285      pl[2].y=ry;
286      pl[3].x=rx+w;
287      pl[3].y=ry+h2;
288      pl[4].x=rx+w-h2;
289      pl[4].y=ry+h;
290      pl[5].x=rx+h2;
291      pl[5].y=ry+h;
292 }
293 
294 /* drawing stuff */
295 static void
other_draw(Other * other,DiaRenderer * renderer)296 other_draw(Other *other, DiaRenderer *renderer)
297 {
298   DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
299   Point pl[6],p1,p2;
300   Element *elem;
301 
302   /* some asserts */
303   assert(other != NULL);
304   assert(renderer != NULL);
305 
306   elem = &other->element;
307 
308   renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
309   renderer_ops->set_linejoin(renderer, LINEJOIN_MITER);
310 
311   switch (other->type) {
312     case RESOURCE:
313       p1.x=elem->corner.x;
314       p1.y= elem->corner.y;
315       p2.x=p1.x+elem->width;
316       p2.y=p1.y+elem->height;
317       renderer_ops->fill_rect(renderer,&p1,&p2, &OTHER_BG_COLOR);
318       renderer_ops->set_linewidth(renderer, OTHER_LINE_WIDTH);
319       renderer_ops->draw_rect(renderer,&p1,&p2, &OTHER_FG_COLOR);
320       break;
321     case TASK:
322       compute_task(other,pl);
323       renderer_ops->set_fillstyle(renderer, FILLSTYLE_SOLID);
324       renderer_ops->fill_polygon(renderer, pl, 6, &OTHER_BG_COLOR);
325       renderer_ops->set_linewidth(renderer, OTHER_LINE_WIDTH);
326       renderer_ops->draw_polygon(renderer, pl, 6, &OTHER_FG_COLOR);
327       break;
328   }
329 
330   /* drawing text */
331   text_draw(other->text, renderer);
332 }
333 
334 static void
other_update_data(Other * other,AnchorShape horiz,AnchorShape vert)335 other_update_data(Other *other, AnchorShape horiz, AnchorShape vert)
336 {
337   Element *elem = &other->element;
338   ElementBBExtras *extra = &elem->extra_spacing;
339   DiaObject *obj = &elem->object;
340   Point center, bottom_right;
341   Point p;
342   real width, height;
343   Point nw,ne,se,sw;
344 
345   /* save starting points */
346   center = bottom_right = elem->corner;
347   center.x += elem->width/2;
348   bottom_right.x += elem->width;
349   center.y += elem->height/2;
350   bottom_right.y += elem->height;
351 
352   text_calc_boundingbox(other->text, NULL);
353   width = other->text->max_width + other->padding*2;
354   height = other->text->height * other->text->numlines + other->padding*2;
355 
356   /* autoscale here */
357   if (width > elem->width) elem->width = width;
358   if (height > elem->height) elem->height = height;
359   if (elem->width<elem->height*1.5) elem->width=elem->height*1.5;
360 
361   /* move shape if necessary ... */
362   switch (horiz) {
363   case ANCHOR_MIDDLE:
364     elem->corner.x = center.x - elem->width/2; break;
365   case ANCHOR_END:
366     elem->corner.x = bottom_right.x - elem->width; break;
367   default:
368     break;
369   }
370   switch (vert) {
371   case ANCHOR_MIDDLE:
372     elem->corner.y = center.y - elem->height/2; break;
373   case ANCHOR_END:
374     elem->corner.y = bottom_right.y - elem->height; break;
375   default:
376     break;
377   }
378 
379   p = elem->corner;
380   p.x += elem->width / 2.0;
381 
382   p.y += elem->height / 2.0 - other->text->height * other->text->numlines / 2 +
383     other->text->ascent;
384   text_set_position(other->text, &p);
385 
386   extra->border_trans = OTHER_LINE_WIDTH / 2.0;
387   element_update_boundingbox(elem);
388 
389   obj->position = elem->corner;
390 
391   element_update_handles(elem);
392 
393   /* Update connections: */
394   nw = elem->corner;
395   se.x = nw.x + elem->width;
396   se.y = nw.y + elem->height;
397   ne.x = se.x;
398   ne.y = nw.y;
399   sw.y = se.y;
400   sw.x = nw.x;
401 
402   connpointline_update(other->north);
403   connpointline_putonaline(other->north,&ne,&nw);
404   connpointline_update(other->west);
405   connpointline_putonaline(other->west,&nw,&sw);
406   connpointline_update(other->south);
407   connpointline_putonaline(other->south,&sw,&se);
408   connpointline_update(other->east);
409   connpointline_putonaline(other->east,&se,&ne);
410 }
411 
412 static ConnPointLine *
other_get_clicked_border(Other * other,Point * clicked)413 other_get_clicked_border(Other *other, Point *clicked)
414 {
415   ConnPointLine *cpl;
416   real dist,dist2;
417 
418   cpl = other->north;
419   dist = distance_line_point(&other->north->start,&other->north->end,0,clicked);
420 
421   dist2 = distance_line_point(&other->west->start,&other->west->end,0,clicked);
422   if (dist2 < dist) {
423     cpl = other->west;
424     dist = dist2;
425   }
426   dist2 = distance_line_point(&other->south->start,&other->south->end,0,clicked);
427   if (dist2 < dist) {
428     cpl = other->south;
429     dist = dist2;
430   }
431   dist2 = distance_line_point(&other->east->start,&other->east->end,0,clicked);
432   if (dist2 < dist) {
433     cpl = other->east;
434     /*dist = dist2;*/
435   }
436   return cpl;
437 }
438 
439 inline static ObjectChange *
other_create_change(Other * other,ObjectChange * inner,ConnPointLine * cpl)440 other_create_change(Other *other, ObjectChange *inner, ConnPointLine *cpl) {
441   return (ObjectChange *)inner;
442 }
443 
444 static ObjectChange *
other_add_connpoint_callback(DiaObject * obj,Point * clicked,gpointer data)445 other_add_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
446 {
447   ObjectChange *change;
448   ConnPointLine *cpl;
449   Other *other = (Other *)obj;
450 
451   cpl = other_get_clicked_border(other,clicked);
452   change = connpointline_add_point(cpl, clicked);
453   other_update_data((Other *)obj,ANCHOR_MIDDLE, ANCHOR_MIDDLE);
454   return other_create_change(other,change,cpl);
455 }
456 
457 static ObjectChange *
other_remove_connpoint_callback(DiaObject * obj,Point * clicked,gpointer data)458 other_remove_connpoint_callback(DiaObject *obj, Point *clicked, gpointer data)
459 {
460   ObjectChange *change;
461   ConnPointLine *cpl;
462   Other *other = (Other *)obj;
463 
464   cpl = other_get_clicked_border(other,clicked);
465   change = connpointline_remove_point(cpl, clicked);
466   other_update_data((Other *)obj,ANCHOR_MIDDLE, ANCHOR_MIDDLE);
467   return other_create_change(other,change,cpl);
468 }
469 
470 static DiaMenuItem object_menu_items[] = {
471   { N_("Add connection point"), other_add_connpoint_callback, NULL, 1 },
472   { N_("Delete connection point"), other_remove_connpoint_callback,
473     NULL, 1 },
474 };
475 
476 static DiaMenu object_menu = {
477   N_("i* other"),
478   sizeof(object_menu_items)/sizeof(DiaMenuItem),
479   object_menu_items,
480   NULL
481 };
482 
483 static DiaMenu *
other_get_object_menu(Other * other,Point * clickedpoint)484 other_get_object_menu(Other *other, Point *clickedpoint)
485 {
486   ConnPointLine *cpl;
487 
488   cpl = other_get_clicked_border(other,clickedpoint);
489   /* Set entries sensitive/selected etc here */
490   object_menu_items[0].active = connpointline_can_add_point(cpl, clickedpoint);
491   object_menu_items[1].active = connpointline_can_remove_point(cpl, clickedpoint);
492   return &object_menu;
493 }
494 
495 /* creating stuff */
496 static DiaObject *
other_create(Point * startpoint,void * user_data,Handle ** handle1,Handle ** handle2)497 other_create(Point *startpoint,
498 	   void *user_data,
499 	   Handle **handle1,
500 	   Handle **handle2)
501 {
502   Other *other;
503   Element *elem;
504   DiaObject *obj;
505   Point p;
506   DiaFont* font;
507 
508   other = g_malloc0(sizeof(Other));
509   elem = &other->element;
510   obj = &elem->object;
511 
512   obj->type = &istar_other_type;
513 
514   obj->ops = &other_ops;
515 
516   elem->corner = *startpoint;
517   elem->width = DEFAULT_WIDTH;
518   elem->height = DEFAULT_HEIGHT;
519 
520   other->padding = DEFAULT_PADDING;
521 
522   p = *startpoint;
523   p.x += elem->width / 2.0;
524   p.y += elem->height / 2.0 + DEFAULT_FONT / 2;
525 
526   font = dia_font_new_from_style( DIA_FONT_SANS, DEFAULT_FONT);
527 
528   other->text = new_text("", font,
529                        DEFAULT_FONT, &p,
530                        &color_black,
531                        ALIGN_CENTER);
532   dia_font_unref(font);
533   text_get_attributes(other->text,&other->attrs);
534 
535   element_init(elem, 8, 0);
536 
537   other->north = connpointline_create(obj,3);
538   other->west = connpointline_create(obj,1);
539   other->south = connpointline_create(obj,3);
540   other->east = connpointline_create(obj,1);
541 
542   other->element.extra_spacing.border_trans = OTHER_LINE_WIDTH/2.0;
543   other_update_data(other, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
544 
545   *handle1 = NULL;
546   *handle2 = obj->handles[7];
547 
548   /* handling type info */
549   switch (GPOINTER_TO_INT(user_data)) {
550     case 1:  other->type=RESOURCE; break;
551     case 2:  other->type=TASK;     break;
552     default: other->type=RESOURCE; break;
553   }
554 
555   if (GPOINTER_TO_INT(user_data)!=0) other->init=-1; else other->init=0;
556 
557   return &other->element.object;
558 }
559 
560 static void
other_destroy(Other * other)561 other_destroy(Other *other)
562 {
563   text_destroy(other->text);
564 
565   connpointline_destroy(other->east);
566   connpointline_destroy(other->south);
567   connpointline_destroy(other->west);
568   connpointline_destroy(other->north);
569 
570   element_destroy(&other->element);
571 }
572 
573 
574 static DiaObject *
other_load(ObjectNode obj_node,int version,const char * filename)575 other_load(ObjectNode obj_node, int version, const char *filename)
576 {
577   return object_load_using_properties(&istar_other_type,
578                                       obj_node,version,filename);
579 }
580 
581 
582