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 "element.h"
30 #include "diarenderer.h"
31 #include "attributes.h"
32 #include "text.h"
33 #include "properties.h"
34 #include "message.h"
35
36 #include "pixmaps/state.xpm"
37
38 typedef struct _State State;
39 typedef struct _StateState StateState;
40
41
42 enum {
43 STATE_NORMAL,
44 STATE_BEGIN,
45 STATE_END
46 };
47
48 typedef enum {
49 ENTRY_ACTION,
50 DO_ACTION,
51 EXIT_ACTION
52 } StateAction;
53
54 #define NUM_CONNECTIONS 9
55
56 struct _State {
57 Element element;
58
59 ConnectionPoint connections[NUM_CONNECTIONS];
60
61 Text *text;
62 int state_type;
63
64 TextAttributes attrs;
65
66 Color line_color;
67 Color fill_color;
68
69 gchar* entry_action;
70 gchar* do_action;
71 gchar* exit_action;
72 };
73
74
75 #define STATE_WIDTH 4
76 #define STATE_HEIGHT 3
77 #define STATE_RATIO 1
78 #define STATE_ENDRATIO 1.5
79 #define STATE_LINEWIDTH 0.1
80 #define STATE_MARGIN_X 0.5
81 #define STATE_MARGIN_Y 0.5
82
83 static real state_distance_from(State *state, Point *point);
84 static void state_select(State *state, Point *clicked_point,
85 DiaRenderer *interactive_renderer);
86 static ObjectChange* state_move_handle(State *state, Handle *handle,
87 Point *to, ConnectionPoint *cp,
88 HandleMoveReason reason, ModifierKeys modifiers);
89 static ObjectChange* state_move(State *state, Point *to);
90 static void state_draw(State *state, DiaRenderer *renderer);
91 static DiaObject *state_create(Point *startpoint,
92 void *user_data,
93 Handle **handle1,
94 Handle **handle2);
95 static void state_destroy(State *state);
96 static DiaObject *state_load(ObjectNode obj_node, int version,
97 const char *filename);
98 static PropDescription *state_describe_props(State *state);
99 static void state_get_props(State *state, GPtrArray *props);
100 static void state_set_props(State *state, GPtrArray *props);
101 static void state_update_data(State *state);
102 static gchar* state_get_action_text(State* state, StateAction action);
103 static void state_calc_action_text_pos(State* state, StateAction action, Point* pos);
104
105 static ObjectTypeOps state_type_ops =
106 {
107 (CreateFunc) state_create,
108 (LoadFunc) state_load,/*using_properties*/ /* load */
109 (SaveFunc) object_save_using_properties, /* save */
110 (GetDefaultsFunc) NULL,
111 (ApplyDefaultsFunc) NULL
112 };
113
114 DiaObjectType state_type =
115 {
116 "UML - State", /* name */
117 0, /* version */
118 (char **) state_xpm, /* pixmap */
119
120 &state_type_ops /* ops */
121 };
122
123 static ObjectOps state_ops = {
124 (DestroyFunc) state_destroy,
125 (DrawFunc) state_draw,
126 (DistanceFunc) state_distance_from,
127 (SelectFunc) state_select,
128 (CopyFunc) object_copy_using_properties,
129 (MoveFunc) state_move,
130 (MoveHandleFunc) state_move_handle,
131 (GetPropertiesFunc) object_create_props_dialog,
132 (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
133 (ObjectMenuFunc) NULL,
134 (DescribePropsFunc) state_describe_props,
135 (GetPropsFunc) state_get_props,
136 (SetPropsFunc) state_set_props,
137 (TextEditFunc) 0,
138 (ApplyPropertiesListFunc) object_apply_props,
139 };
140
141 static PropDescription state_props[] = {
142 ELEMENT_COMMON_PROPERTIES,
143 /* see below for the next field.
144
145 Warning: break this and you'll get angry UML users after you. */
146 { "type", PROP_TYPE_INT, PROP_FLAG_NO_DEFAULTS|PROP_FLAG_LOAD_ONLY|PROP_FLAG_OPTIONAL,
147 "hack", NULL, NULL },
148
149 PROP_STD_LINE_COLOUR_OPTIONAL,
150 PROP_STD_FILL_COLOUR_OPTIONAL,
151 PROP_STD_TEXT_FONT,
152 PROP_STD_TEXT_HEIGHT,
153 PROP_STD_TEXT_COLOUR_OPTIONAL,
154 { "text", PROP_TYPE_TEXT, 0, N_("Text"), NULL, NULL },
155 { "entry_action", PROP_TYPE_STRING, PROP_FLAG_OPTIONAL | PROP_FLAG_VISIBLE, N_("Entry action"), NULL, NULL },
156 { "do_action", PROP_TYPE_STRING, PROP_FLAG_OPTIONAL | PROP_FLAG_VISIBLE, N_("Do action"), NULL, NULL },
157 { "exit_action", PROP_TYPE_STRING, PROP_FLAG_OPTIONAL | PROP_FLAG_VISIBLE, N_("Exit action"), NULL, NULL },
158 PROP_DESC_END
159 };
160
161 static PropDescription *
state_describe_props(State * state)162 state_describe_props(State *state)
163 {
164 if (state_props[0].quark == 0) {
165 prop_desc_list_calculate_quarks(state_props);
166 }
167 return state_props;
168 }
169
170 static PropOffset state_offsets[] = {
171 ELEMENT_COMMON_PROPERTIES_OFFSETS,
172 {"line_colour",PROP_TYPE_COLOUR,offsetof(State,line_color)},
173 {"fill_colour",PROP_TYPE_COLOUR,offsetof(State,fill_color)},
174 {"text",PROP_TYPE_TEXT,offsetof(State,text)},
175 {"text_font",PROP_TYPE_FONT,offsetof(State,attrs.font)},
176 {PROP_STDNAME_TEXT_HEIGHT,PROP_STDTYPE_TEXT_HEIGHT,offsetof(State,attrs.height)},
177 {"text_colour",PROP_TYPE_COLOUR,offsetof(State,attrs.color)},
178 {"entry_action",PROP_TYPE_STRING,offsetof(State,entry_action)},
179 {"do_action",PROP_TYPE_STRING,offsetof(State,do_action)},
180 {"exit_action",PROP_TYPE_STRING,offsetof(State,exit_action)},
181 /* HACK: this is to recover files from 0.88.1 and older.
182 if sizeof(enum) != sizeof(int), we're toast. -- CC
183 */
184 { "type", PROP_TYPE_INT, offsetof(State, state_type) },
185
186 { NULL, 0, 0 },
187 };
188
189 static void
state_get_props(State * state,GPtrArray * props)190 state_get_props(State * state, GPtrArray *props)
191 {
192 text_get_attributes(state->text,&state->attrs);
193 object_get_props_from_offsets(&state->element.object,
194 state_offsets,props);
195 }
196
197 static void
state_set_props(State * state,GPtrArray * props)198 state_set_props(State *state, GPtrArray *props)
199 {
200 object_set_props_from_offsets(&state->element.object,
201 state_offsets,props);
202 apply_textattr_properties(props,state->text,"text",&state->attrs);
203 state_update_data(state);
204 }
205
206 static real
state_distance_from(State * state,Point * point)207 state_distance_from(State *state, Point *point)
208 {
209 DiaObject *obj = &state->element.object;
210 return distance_rectangle_point(&obj->bounding_box, point);
211 }
212
213 static void
state_select(State * state,Point * clicked_point,DiaRenderer * interactive_renderer)214 state_select(State *state, Point *clicked_point,
215 DiaRenderer *interactive_renderer)
216 {
217 text_set_cursor(state->text, clicked_point, interactive_renderer);
218 text_grab_focus(state->text, &state->element.object);
219 element_update_handles(&state->element);
220 }
221
222 static ObjectChange*
state_move_handle(State * state,Handle * handle,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)223 state_move_handle(State *state, Handle *handle,
224 Point *to, ConnectionPoint *cp,
225 HandleMoveReason reason, ModifierKeys modifiers)
226 {
227 assert(state!=NULL);
228 assert(handle!=NULL);
229 assert(to!=NULL);
230
231 assert(handle->id < 8);
232
233 return NULL;
234 }
235
236 static ObjectChange*
state_move(State * state,Point * to)237 state_move(State *state, Point *to)
238 {
239 state->element.corner = *to;
240 state_update_data(state);
241
242 return NULL;
243 }
244
245 static void
state_draw_action_string(State * state,DiaRenderer * renderer,StateAction action)246 state_draw_action_string(State *state, DiaRenderer *renderer, StateAction action)
247 {
248 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
249 Point pos;
250 gchar* action_text = state_get_action_text(state, action);
251 state_calc_action_text_pos(state, action, &pos);
252 renderer_ops->set_font(renderer, state->text->font, state->text->height);
253 renderer_ops->draw_string(renderer,
254 action_text,
255 &pos,
256 ALIGN_LEFT,
257 &state->attrs.color);
258 g_free(action_text);
259 }
260
261 static void
state_draw(State * state,DiaRenderer * renderer)262 state_draw(State *state, DiaRenderer *renderer)
263 {
264 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
265 Element *elem;
266 real x, y, w, h, r;
267 Point p1, p2, split_line_left, split_line_right;
268 gboolean has_actions;
269
270 assert(state != NULL);
271 assert(renderer != NULL);
272
273 elem = &state->element;
274
275 x = elem->corner.x;
276 y = elem->corner.y;
277 w = elem->width;
278 h = elem->height;
279
280 renderer_ops->set_fillstyle(renderer, FILLSTYLE_SOLID);
281 renderer_ops->set_linewidth(renderer, STATE_LINEWIDTH);
282 renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
283
284 if (state->state_type!=STATE_NORMAL) {
285 p1.x = x + w/2;
286 p1.y = y + h/2;
287 if (state->state_type==STATE_END) {
288 r = STATE_ENDRATIO;
289 renderer_ops->fill_ellipse(renderer,
290 &p1,
291 r, r,
292 &state->fill_color);
293
294 renderer_ops->draw_ellipse(renderer,
295 &p1,
296 r, r,
297 &state->line_color);
298 }
299 r = STATE_RATIO;
300 renderer_ops->fill_ellipse(renderer,
301 &p1,
302 r, r,
303 &state->line_color);
304 } else {
305 p1.x = x;
306 p1.y = y;
307 p2.x = x + w;
308 p2.y = y + h;
309 renderer_ops->fill_rounded_rect(renderer, &p1, &p2, &state->fill_color, 0.5);
310 renderer_ops->draw_rounded_rect(renderer, &p1, &p2, &state->line_color, 0.5);
311
312 text_draw(state->text, renderer);
313 has_actions = FALSE;
314 if (state->entry_action && strlen(state->entry_action) != 0) {
315 state_draw_action_string(state, renderer, ENTRY_ACTION);
316 has_actions = TRUE;
317 }
318 if (state->do_action && strlen(state->do_action) != 0) {
319 state_draw_action_string(state, renderer, DO_ACTION);
320 has_actions = TRUE;
321 }
322 if (state->exit_action && strlen(state->exit_action) != 0) {
323 state_draw_action_string(state, renderer, EXIT_ACTION);
324 has_actions = TRUE;
325 }
326
327 if (has_actions) {
328 split_line_left.x = x;
329 split_line_right.x = x+w;
330 split_line_left.y = split_line_right.y
331 = state->element.corner.y + STATE_MARGIN_Y +
332 state->text->numlines*state->text->height;
333 renderer_ops->draw_line(renderer, &split_line_left, &split_line_right,
334 &state->line_color);
335 }
336 }
337 }
338
339
340 static void
state_update_width_and_height_with_action_text(State * state,StateAction action,real * width,real * height)341 state_update_width_and_height_with_action_text(State* state,
342 StateAction action,
343 real* width,
344 real* height)
345 {
346 gchar* action_text = state_get_action_text(state, action);
347 *width = MAX(*width, dia_font_string_width(action_text, state->text->font,
348 state->text->height) + 2*STATE_MARGIN_X);
349 g_free(action_text);
350 *height += state->text->height;
351 }
352
353 static void
state_update_data(State * state)354 state_update_data(State *state)
355 {
356 real w, h;
357
358 Element *elem = &state->element;
359 ElementBBExtras *extra = &elem->extra_spacing;
360 DiaObject *obj = &elem->object;
361 Point p;
362
363 text_calc_boundingbox(state->text, NULL);
364 if (state->state_type==STATE_NORMAL) {
365 /* First consider the state description text */
366 w = state->text->max_width + 2*STATE_MARGIN_X;
367 h = state->text->height*state->text->numlines +2*STATE_MARGIN_Y;
368 if (w < STATE_WIDTH)
369 w = STATE_WIDTH;
370 /* Then consider the actions texts */
371 if (state->entry_action && strlen(state->entry_action) != 0) {
372 state_update_width_and_height_with_action_text(state, ENTRY_ACTION, &w, &h);
373 }
374 if (state->do_action && strlen(state->do_action) != 0) {
375 state_update_width_and_height_with_action_text(state, DO_ACTION, &w, &h);
376 }
377 if (state->exit_action && strlen(state->exit_action) != 0) {
378 state_update_width_and_height_with_action_text(state, EXIT_ACTION, &w, &h);
379 }
380
381 p.x = elem->corner.x + w/2.0;
382 p.y = elem->corner.y + STATE_MARGIN_Y + state->text->ascent;
383 text_set_position(state->text, &p);
384 } else {
385 w = h = (state->state_type==STATE_END) ? STATE_ENDRATIO: STATE_RATIO;
386 }
387
388 elem->width = w;
389 elem->height = h;
390 extra->border_trans = STATE_LINEWIDTH / 2.0;
391
392 /* Update connections: */
393 element_update_connections_rectangle(elem, state->connections);
394
395 element_update_boundingbox(elem);
396
397 obj->position = elem->corner;
398
399 element_update_handles(elem);
400 }
401
402 static DiaObject *
state_create(Point * startpoint,void * user_data,Handle ** handle1,Handle ** handle2)403 state_create(Point *startpoint,
404 void *user_data,
405 Handle **handle1,
406 Handle **handle2)
407 {
408 State *state;
409 Element *elem;
410 DiaObject *obj;
411 Point p;
412 DiaFont *font;
413 int i;
414
415 state = g_malloc0(sizeof(State));
416 elem = &state->element;
417 obj = &elem->object;
418
419 obj->type = &state_type;
420 obj->ops = &state_ops;
421 elem->corner = *startpoint;
422 elem->width = STATE_WIDTH;
423 elem->height = STATE_HEIGHT;
424
425 state->line_color = attributes_get_foreground();
426 state->fill_color = attributes_get_background();
427
428 font = dia_font_new_from_style(DIA_FONT_SANS, 0.8);
429 p = *startpoint;
430 p.x += STATE_WIDTH/2.0;
431 p.y += STATE_HEIGHT/2.0;
432
433 state->text = new_text("", font, 0.8, &p, &color_black, ALIGN_CENTER);
434 text_get_attributes(state->text,&state->attrs);
435
436 dia_font_unref(font);
437
438 state->state_type = STATE_NORMAL;
439 element_init(elem, 8, NUM_CONNECTIONS);
440
441 for (i=0;i<NUM_CONNECTIONS;i++) {
442 obj->connections[i] = &state->connections[i];
443 state->connections[i].object = obj;
444 state->connections[i].connected = NULL;
445 }
446 state->connections[8].flags = CP_FLAGS_MAIN;
447 elem->extra_spacing.border_trans = 0.0;
448 state_update_data(state);
449
450 for (i=0;i<8;i++) {
451 obj->handles[i]->type = HANDLE_NON_MOVABLE;
452 }
453
454 *handle1 = NULL;
455 *handle2 = NULL;
456 return &state->element.object;
457 }
458
459 static void
state_destroy(State * state)460 state_destroy(State *state)
461 {
462 g_free (state->entry_action);
463 g_free (state->do_action);
464 g_free (state->exit_action);
465
466 text_destroy(state->text);
467
468 element_destroy(&state->element);
469 }
470
471 static DiaObject *
state_load(ObjectNode obj_node,int version,const char * filename)472 state_load(ObjectNode obj_node, int version, const char *filename)
473 {
474 State *obj = (State*)object_load_using_properties(&state_type,
475 obj_node,version,filename);
476 if (obj->state_type != STATE_NORMAL) {
477 /* Would like to create a state_term instead, but making the connections
478 * is a pain */
479 message_warning(_("This diagram uses the State object for initial/final states.\nThat option will go away in future versions.\nPlease use the Initial/Final State object instead\n"));
480 }
481 return (DiaObject *)obj;
482 }
483
484 static void
state_calc_action_text_pos(State * state,StateAction action,Point * pos)485 state_calc_action_text_pos(State* state, StateAction action, Point* pos)
486 {
487 int entry_action_valid = state->entry_action && strlen(state->entry_action) != 0;
488 int do_action_valid = state->do_action && strlen(state->do_action) != 0;
489
490 real first_action_y = state->text->numlines*state->text->height +
491 state->text->position.y;
492
493 pos->x = state->element.corner.x + STATE_MARGIN_X;
494
495 switch (action)
496 {
497 case ENTRY_ACTION:
498 pos->y = first_action_y;
499 break;
500
501 case DO_ACTION:
502 pos->y = first_action_y;
503 if (entry_action_valid) pos->y += state->text->height;
504 break;
505
506 case EXIT_ACTION:
507 pos->y = first_action_y;
508 if (entry_action_valid) pos->y += state->text->height;
509 if (do_action_valid) pos->y += state->text->height;
510 break;
511 }
512 }
513
514
515 static gchar*
state_get_action_text(State * state,StateAction action)516 state_get_action_text(State* state, StateAction action)
517 {
518 switch (action)
519 {
520 case ENTRY_ACTION:
521 return g_strdup_printf("entry/ %s", state->entry_action);
522 break;
523
524 case DO_ACTION:
525 return g_strdup_printf("do/ %s", state->do_action);
526 break;
527
528 case EXIT_ACTION:
529 return g_strdup_printf("exit/ %s", state->exit_action);
530 break;
531 }
532 return NULL;
533 }
534
535
536