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