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