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