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