2006-12-03 Dimitris Glezos <dimitris@glezos.com>
[dia.git] / objects / flowchart / diamond.c
blobf713c9e1456f714b62f3bc3f1da743d632a4ed83
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * Flowchart toolbox -- objects for drawing flowcharts.
5 * Copyright (C) 1999 James Henstridge.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 /* DO NOT USE THIS OBJECT AS A BASIS FOR A NEW OBJECT. */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include <assert.h>
29 #include <math.h>
31 #include "intl.h"
32 #include "object.h"
33 #include "element.h"
34 #include "connectionpoint.h"
35 #include "diarenderer.h"
36 #include "attributes.h"
37 #include "text.h"
38 #include "widgets.h"
39 #include "message.h"
40 #include "properties.h"
42 #include "pixmaps/diamond.xpm"
44 #define DEFAULT_WIDTH 2.0
45 #define DEFAULT_HEIGHT 1.0
46 #define DEFAULT_BORDER 0.25
48 #define NUM_CONNECTIONS 17
50 /* used when resizing to decide which side of the shape to expand/shrink */
51 typedef enum {
52 ANCHOR_MIDDLE,
53 ANCHOR_START,
54 ANCHOR_END
55 } AnchorShape;
57 typedef struct _Diamond Diamond;
59 struct _Diamond {
60 Element element;
62 ConnectionPoint connections[NUM_CONNECTIONS];
63 real border_width;
64 Color border_color;
65 Color inner_color;
66 gboolean show_background;
67 LineStyle line_style;
68 real dashlength;
70 Text *text;
71 TextAttributes attrs;
72 real padding;
75 typedef struct _DiamondProperties {
76 gboolean show_background;
78 real padding;
79 } DiamondProperties;
81 static DiamondProperties default_properties;
83 static real diamond_distance_from(Diamond *diamond, Point *point);
84 static void diamond_select(Diamond *diamond, Point *clicked_point,
85 DiaRenderer *interactive_renderer);
86 static ObjectChange* diamond_move_handle(Diamond *diamond, Handle *handle,
87 Point *to, ConnectionPoint *cp,
88 HandleMoveReason reason,
89 ModifierKeys modifiers);
90 static ObjectChange* diamond_move(Diamond *diamond, Point *to);
91 static void diamond_draw(Diamond *diamond, DiaRenderer *renderer);
92 static void diamond_update_data(Diamond *diamond, AnchorShape h,AnchorShape v);
93 static DiaObject *diamond_create(Point *startpoint,
94 void *user_data,
95 Handle **handle1,
96 Handle **handle2);
97 static void diamond_destroy(Diamond *diamond);
99 static PropDescription *diamond_describe_props(Diamond *diamond);
100 static void diamond_get_props(Diamond *diamond, GPtrArray *props);
101 static void diamond_set_props(Diamond *diamond, GPtrArray *props);
103 static void diamond_save(Diamond *diamond, ObjectNode obj_node, const char *filename);
104 static DiaObject *diamond_load(ObjectNode obj_node, int version, const char *filename);
106 static ObjectTypeOps diamond_type_ops =
108 (CreateFunc) diamond_create,
109 (LoadFunc) diamond_load,
110 (SaveFunc) diamond_save,
111 (GetDefaultsFunc) NULL,
112 (ApplyDefaultsFunc) NULL
115 DiaObjectType diamond_type =
117 "Flowchart - Diamond", /* name */
118 0, /* version */
119 (char **) diamond_xpm, /* pixmap */
121 &diamond_type_ops /* ops */
124 static ObjectOps diamond_ops = {
125 (DestroyFunc) diamond_destroy,
126 (DrawFunc) diamond_draw,
127 (DistanceFunc) diamond_distance_from,
128 (SelectFunc) diamond_select,
129 (CopyFunc) object_copy_using_properties,
130 (MoveFunc) diamond_move,
131 (MoveHandleFunc) diamond_move_handle,
132 (GetPropertiesFunc) object_create_props_dialog,
133 (ApplyPropertiesFunc) object_apply_props_from_dialog,
134 (ObjectMenuFunc) NULL,
135 (DescribePropsFunc) diamond_describe_props,
136 (GetPropsFunc) diamond_get_props,
137 (SetPropsFunc) diamond_set_props,
140 static PropNumData text_padding_data = { 0.0, 10.0, 0.1 };
142 static PropDescription diamond_props[] = {
143 ELEMENT_COMMON_PROPERTIES,
144 PROP_STD_LINE_WIDTH,
145 PROP_STD_LINE_COLOUR,
146 PROP_STD_FILL_COLOUR,
147 PROP_STD_SHOW_BACKGROUND,
148 PROP_STD_LINE_STYLE,
149 { "padding", PROP_TYPE_REAL, PROP_FLAG_VISIBLE,
150 N_("Text padding"), NULL, &text_padding_data },
151 PROP_STD_TEXT_FONT,
152 PROP_STD_TEXT_HEIGHT,
153 PROP_STD_TEXT_COLOUR,
154 PROP_STD_TEXT_ALIGNMENT,
155 PROP_STD_SAVED_TEXT,
157 { NULL, 0, 0, NULL, NULL, NULL, 0}
160 static PropDescription *
161 diamond_describe_props(Diamond *diamond)
163 if (diamond_props[0].quark == 0)
164 prop_desc_list_calculate_quarks(diamond_props);
165 return diamond_props;
168 static PropOffset diamond_offsets[] = {
169 ELEMENT_COMMON_PROPERTIES_OFFSETS,
170 { "line_width", PROP_TYPE_REAL, offsetof(Diamond, border_width) },
171 { "line_colour", PROP_TYPE_COLOUR, offsetof(Diamond, border_color) },
172 { "fill_colour", PROP_TYPE_COLOUR, offsetof(Diamond, inner_color) },
173 { "show_background", PROP_TYPE_BOOL, offsetof(Diamond, show_background) },
174 { "line_style", PROP_TYPE_LINESTYLE,
175 offsetof(Diamond, line_style), offsetof(Diamond, dashlength) },
176 { "padding", PROP_TYPE_REAL, offsetof(Diamond, padding) },
177 {"text",PROP_TYPE_TEXT,offsetof(Diamond,text)},
178 {"text_font",PROP_TYPE_FONT,offsetof(Diamond,attrs.font)},
179 {"text_height",PROP_TYPE_REAL,offsetof(Diamond,attrs.height)},
180 {"text_colour",PROP_TYPE_COLOUR,offsetof(Diamond,attrs.color)},
181 {"text_alignment",PROP_TYPE_ENUM,offsetof(Diamond,attrs.alignment)},
182 { NULL, 0, 0 },
185 static void
186 diamond_get_props(Diamond *diamond, GPtrArray *props)
188 text_get_attributes(diamond->text,&diamond->attrs);
189 object_get_props_from_offsets(&diamond->element.object,
190 diamond_offsets,props);
193 static void
194 diamond_set_props(Diamond *diamond, GPtrArray *props)
196 object_set_props_from_offsets(&diamond->element.object,
197 diamond_offsets,props);
198 apply_textattr_properties(props,diamond->text,"text",&diamond->attrs);
199 diamond_update_data(diamond,ANCHOR_MIDDLE,ANCHOR_MIDDLE);
202 static void
203 init_default_values() {
204 static int defaults_initialized = 0;
206 if (!defaults_initialized) {
207 default_properties.show_background = 1;
208 default_properties.padding = 0.5 * M_SQRT1_2;
209 defaults_initialized = 1;
213 static real
214 diamond_distance_from(Diamond *diamond, Point *point)
216 Element *elem = &diamond->element;
217 Rectangle rect;
219 rect.left = elem->corner.x - diamond->border_width/2;
220 rect.right = elem->corner.x + elem->width + diamond->border_width/2;
221 rect.top = elem->corner.y - diamond->border_width/2;
222 rect.bottom = elem->corner.y + elem->height + diamond->border_width/2;
224 if (rect.top > point->y)
225 return rect.top - point->y +
226 fabs(point->x - elem->corner.x + elem->width / 2.0);
227 else if (point->y > rect.bottom)
228 return point->y - rect.bottom +
229 fabs(point->x - elem->corner.x + elem->width / 2.0);
230 else if (rect.left > point->x)
231 return rect.left - point->x +
232 fabs(point->y - elem->corner.y + elem->height / 2.0);
233 else if (point->x > rect.right)
234 return point->x - rect.right +
235 fabs(point->y - elem->corner.y + elem->height / 2.0);
236 else {
237 /* inside the bounding box of diamond ... this is where it gets harder */
238 real x = point->x, y = point->y;
239 real dx, dy;
241 /* reflect point into upper left quadrant of diamond */
242 if (x > elem->corner.x + elem->width / 2.0)
243 x = 2 * (elem->corner.x + elem->width / 2.0) - x;
244 if (y > elem->corner.y + elem->height / 2.0)
245 y = 2 * (elem->corner.y + elem->height / 2.0) - y;
247 dx = -x + elem->corner.x + elem->width / 2.0 -
248 elem->width/elem->height * (y-elem->corner.y) - diamond->border_width/2;
249 dy = -y + elem->corner.y + elem->height / 2.0 -
250 elem->height/elem->width * (x-elem->corner.x) - diamond->border_width/2;
251 if (dx <= 0 || dy <= 0)
252 return 0;
253 return MIN(dx, dy);
257 static void
258 diamond_select(Diamond *diamond, Point *clicked_point,
259 DiaRenderer *interactive_renderer)
261 text_set_cursor(diamond->text, clicked_point, interactive_renderer);
262 text_grab_focus(diamond->text, &diamond->element.object);
264 element_update_handles(&diamond->element);
267 static ObjectChange*
268 diamond_move_handle(Diamond *diamond, Handle *handle,
269 Point *to, ConnectionPoint *cp,
270 HandleMoveReason reason, ModifierKeys modifiers)
272 AnchorShape horiz = ANCHOR_MIDDLE, vert = ANCHOR_MIDDLE;
274 assert(diamond!=NULL);
275 assert(handle!=NULL);
276 assert(to!=NULL);
278 element_move_handle(&diamond->element, handle->id, to, cp,
279 reason, modifiers);
281 switch (handle->id) {
282 case HANDLE_RESIZE_NW:
283 horiz = ANCHOR_END; vert = ANCHOR_END; break;
284 case HANDLE_RESIZE_N:
285 vert = ANCHOR_END; break;
286 case HANDLE_RESIZE_NE:
287 horiz = ANCHOR_START; vert = ANCHOR_END; break;
288 case HANDLE_RESIZE_E:
289 horiz = ANCHOR_START; break;
290 case HANDLE_RESIZE_SE:
291 horiz = ANCHOR_START; vert = ANCHOR_START; break;
292 case HANDLE_RESIZE_S:
293 vert = ANCHOR_START; break;
294 case HANDLE_RESIZE_SW:
295 horiz = ANCHOR_END; vert = ANCHOR_START; break;
296 case HANDLE_RESIZE_W:
297 horiz = ANCHOR_END; break;
298 default:
299 break;
301 diamond_update_data(diamond, horiz, vert);
303 return NULL;
306 static ObjectChange*
307 diamond_move(Diamond *diamond, Point *to)
309 diamond->element.corner = *to;
311 diamond_update_data(diamond, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
313 return NULL;
316 static void
317 diamond_draw(Diamond *diamond, DiaRenderer *renderer)
319 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
320 Point pts[4];
321 Element *elem;
323 assert(diamond != NULL);
324 assert(renderer != NULL);
326 elem = &diamond->element;
328 pts[0] = pts[1] = pts[2] = pts[3] = elem->corner;
329 pts[0].x += elem->width / 2.0;
330 pts[1].x += elem->width;
331 pts[1].y += elem->height / 2.0;
332 pts[2].x += elem->width / 2.0;
333 pts[2].y += elem->height;
334 pts[3].y += elem->height / 2.0;
336 if (diamond->show_background) {
337 renderer_ops->set_fillstyle(renderer, FILLSTYLE_SOLID);
339 renderer_ops->fill_polygon(renderer,
340 pts, 4,
341 &diamond->inner_color);
344 renderer_ops->set_linewidth(renderer, diamond->border_width);
345 renderer_ops->set_linestyle(renderer, diamond->line_style);
346 renderer_ops->set_dashlength(renderer, diamond->dashlength);
347 renderer_ops->set_linejoin(renderer, LINEJOIN_MITER);
349 renderer_ops->draw_polygon(renderer,
350 pts, 4,
351 &diamond->border_color);
353 text_draw(diamond->text, renderer);
357 static void
358 diamond_update_data(Diamond *diamond, AnchorShape horiz, AnchorShape vert)
360 Element *elem = &diamond->element;
361 DiaObject *obj = &elem->object;
362 ElementBBExtras *extra = &elem->extra_spacing;
363 Point center, bottom_right;
364 Point p;
365 real dw, dh;
366 real width, height;
368 /* save starting points */
369 center = bottom_right = elem->corner;
370 center.x += elem->width/2;
371 bottom_right.x += elem->width;
372 center.y += elem->height/2;
373 bottom_right.y += elem->height;
375 text_calc_boundingbox(diamond->text, NULL);
376 width = diamond->text->max_width + 2*diamond->padding+diamond->border_width;
377 height = diamond->text->height * diamond->text->numlines +
378 2 * diamond->padding + diamond->border_width;
380 if (height > (elem->width - width) * elem->height / elem->width) {
381 /* increase size of the diamond while keeping its aspect ratio */
382 real grad = elem->width/elem->height;
383 if (grad < 1.0/4) grad = 1.0/4;
384 if (grad > 4) grad = 4;
385 elem->width = width + height * grad;
386 elem->height = height + width / grad;
387 } else {
388 /* Need this for text alignment soon */
389 real grad = elem->width/elem->height;
390 if (grad < 1.0/4) grad = 1.0/4;
391 if (grad > 4) grad = 4;
392 width = elem->width - height*grad;
395 /* move shape if necessary ... */
396 switch (horiz) {
397 case ANCHOR_MIDDLE:
398 elem->corner.x = center.x - elem->width/2; break;
399 case ANCHOR_END:
400 elem->corner.x = bottom_right.x - elem->width; break;
401 default:
402 break;
404 switch (vert) {
405 case ANCHOR_MIDDLE:
406 elem->corner.y = center.y - elem->height/2; break;
407 case ANCHOR_END:
408 elem->corner.y = bottom_right.y - elem->height; break;
409 default:
410 break;
413 p = elem->corner;
414 p.x += elem->width / 2.0;
415 p.y += elem->height / 2.0 - diamond->text->height*diamond->text->numlines/2 +
416 diamond->text->ascent;
417 switch (diamond->text->alignment) {
418 case ALIGN_LEFT:
419 p.x -= width/2;
420 break;
421 case ALIGN_RIGHT:
422 p.x += width/2;
423 break;
424 case ALIGN_CENTER:
425 break;
427 text_set_position(diamond->text, &p);
429 dw = elem->width / 8.0;
430 dh = elem->height / 8.0;
431 /* Update connections: */
432 diamond->connections[0].pos.x = elem->corner.x + 4*dw;
433 diamond->connections[0].pos.y = elem->corner.y;
434 diamond->connections[1].pos.x = elem->corner.x + 5*dw;
435 diamond->connections[1].pos.y = elem->corner.y + dh;
436 diamond->connections[2].pos.x = elem->corner.x + 6*dw;
437 diamond->connections[2].pos.y = elem->corner.y + 2*dh;
438 diamond->connections[3].pos.x = elem->corner.x + 7*dw;
439 diamond->connections[3].pos.y = elem->corner.y + 3*dh;
440 diamond->connections[4].pos.x = elem->corner.x + elem->width;
441 diamond->connections[4].pos.y = elem->corner.y + 4*dh;
442 diamond->connections[5].pos.x = elem->corner.x + 7*dw;
443 diamond->connections[5].pos.y = elem->corner.y + 5*dh;
444 diamond->connections[6].pos.x = elem->corner.x + 6*dw;
445 diamond->connections[6].pos.y = elem->corner.y + 6*dh;
446 diamond->connections[7].pos.x = elem->corner.x + 5*dw;
447 diamond->connections[7].pos.y = elem->corner.y + 7*dh;
448 diamond->connections[8].pos.x = elem->corner.x + 4*dw;
449 diamond->connections[8].pos.y = elem->corner.y + elem->height;
450 diamond->connections[9].pos.x = elem->corner.x + 3*dw;
451 diamond->connections[9].pos.y = elem->corner.y + 7*dh;
452 diamond->connections[10].pos.x = elem->corner.x + 2*dw;
453 diamond->connections[10].pos.y = elem->corner.y + 6*dh;
454 diamond->connections[11].pos.x = elem->corner.x + dw;
455 diamond->connections[11].pos.y = elem->corner.y + 5*dh;
456 diamond->connections[12].pos.x = elem->corner.x;
457 diamond->connections[12].pos.y = elem->corner.y + 4*dh;
458 diamond->connections[13].pos.x = elem->corner.x + dw;
459 diamond->connections[13].pos.y = elem->corner.y + 3*dh;
460 diamond->connections[14].pos.x = elem->corner.x + 2*dw;
461 diamond->connections[14].pos.y = elem->corner.y + 2*dh;
462 diamond->connections[15].pos.x = elem->corner.x + 3*dw;
463 diamond->connections[15].pos.y = elem->corner.y + dh;
464 diamond->connections[16].pos.x = elem->corner.x + 4*dw;
465 diamond->connections[16].pos.y = elem->corner.y + 4*dh;
467 extra->border_trans = diamond->border_width / 2.0;
468 element_update_boundingbox(elem);
470 obj->position = elem->corner;
472 element_update_handles(elem);
475 static DiaObject *
476 diamond_create(Point *startpoint,
477 void *user_data,
478 Handle **handle1,
479 Handle **handle2)
481 Diamond *diamond;
482 Element *elem;
483 DiaObject *obj;
484 Point p;
485 int i;
486 DiaFont *font = NULL;
487 real font_height;
489 init_default_values();
491 diamond = g_malloc0(sizeof(Diamond));
492 elem = &diamond->element;
493 obj = &elem->object;
495 obj->type = &diamond_type;
497 obj->ops = &diamond_ops;
499 elem->corner = *startpoint;
500 elem->width = DEFAULT_WIDTH;
501 elem->height = DEFAULT_HEIGHT;
503 diamond->border_width = attributes_get_default_linewidth();
504 diamond->border_color = attributes_get_foreground();
505 diamond->inner_color = attributes_get_background();
506 diamond->show_background = default_properties.show_background;
507 attributes_get_default_line_style(&diamond->line_style,&diamond->dashlength);
509 diamond->padding = default_properties.padding;
511 attributes_get_default_font(&font, &font_height);
512 p = *startpoint;
513 p.x += elem->width / 2.0;
514 p.y += elem->height / 2.0 + font_height / 2;
515 diamond->text = new_text("", font, font_height, &p, &diamond->border_color,
516 ALIGN_CENTER);
517 text_get_attributes(diamond->text,&diamond->attrs);
518 dia_font_unref(font);
520 element_init(elem, 8, NUM_CONNECTIONS);
522 for (i=0;i<NUM_CONNECTIONS;i++) {
523 obj->connections[i] = &diamond->connections[i];
524 diamond->connections[i].object = obj;
525 diamond->connections[i].connected = NULL;
526 diamond->connections[i].flags = 0;
528 diamond->connections[16].flags = CP_FLAGS_MAIN;
530 diamond_update_data(diamond, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
532 *handle1 = NULL;
533 *handle2 = obj->handles[7];
534 return &diamond->element.object;
537 static void
538 diamond_destroy(Diamond *diamond)
540 text_destroy(diamond->text);
542 element_destroy(&diamond->element);
546 static void
547 diamond_save(Diamond *diamond, ObjectNode obj_node, const char *filename)
549 element_save(&diamond->element, obj_node);
551 if (diamond->border_width != 0.1)
552 data_add_real(new_attribute(obj_node, "border_width"),
553 diamond->border_width);
555 if (!color_equals(&diamond->border_color, &color_black))
556 data_add_color(new_attribute(obj_node, "border_color"),
557 &diamond->border_color);
559 if (!color_equals(&diamond->inner_color, &color_white))
560 data_add_color(new_attribute(obj_node, "inner_color"),
561 &diamond->inner_color);
563 data_add_boolean(new_attribute(obj_node, "show_background"), diamond->show_background);
565 if (diamond->line_style != LINESTYLE_SOLID)
566 data_add_enum(new_attribute(obj_node, "line_style"),
567 diamond->line_style);
569 if (diamond->line_style != LINESTYLE_SOLID &&
570 diamond->dashlength != DEFAULT_LINESTYLE_DASHLEN)
571 data_add_real(new_attribute(obj_node, "dashlength"),
572 diamond->dashlength);
574 data_add_real(new_attribute(obj_node, "padding"), diamond->padding);
576 data_add_text(new_attribute(obj_node, "text"), diamond->text);
579 static DiaObject *
580 diamond_load(ObjectNode obj_node, int version, const char *filename)
582 Diamond *diamond;
583 Element *elem;
584 DiaObject *obj;
585 int i;
586 AttributeNode attr;
588 diamond = g_malloc0(sizeof(Diamond));
589 elem = &diamond->element;
590 obj = &elem->object;
592 obj->type = &diamond_type;
593 obj->ops = &diamond_ops;
595 element_load(elem, obj_node);
597 diamond->border_width = 0.1;
598 attr = object_find_attribute(obj_node, "border_width");
599 if (attr != NULL)
600 diamond->border_width = data_real( attribute_first_data(attr) );
602 diamond->border_color = color_black;
603 attr = object_find_attribute(obj_node, "border_color");
604 if (attr != NULL)
605 data_color(attribute_first_data(attr), &diamond->border_color);
607 diamond->inner_color = color_white;
608 attr = object_find_attribute(obj_node, "inner_color");
609 if (attr != NULL)
610 data_color(attribute_first_data(attr), &diamond->inner_color);
612 diamond->show_background = TRUE;
613 attr = object_find_attribute(obj_node, "show_background");
614 if (attr != NULL)
615 diamond->show_background = data_boolean( attribute_first_data(attr) );
617 diamond->line_style = LINESTYLE_SOLID;
618 attr = object_find_attribute(obj_node, "line_style");
619 if (attr != NULL)
620 diamond->line_style = data_enum( attribute_first_data(attr) );
622 diamond->dashlength = DEFAULT_LINESTYLE_DASHLEN;
623 attr = object_find_attribute(obj_node, "dashlength");
624 if (attr != NULL)
625 diamond->dashlength = data_real(attribute_first_data(attr));
627 diamond->padding = default_properties.padding;
628 attr = object_find_attribute(obj_node, "padding");
629 if (attr != NULL)
630 diamond->padding = data_real( attribute_first_data(attr) );
632 diamond->text = NULL;
633 attr = object_find_attribute(obj_node, "text");
634 if (attr != NULL)
635 diamond->text = data_text(attribute_first_data(attr));
637 element_init(elem, 8, NUM_CONNECTIONS);
639 for (i=0;i<NUM_CONNECTIONS;i++) {
640 obj->connections[i] = &diamond->connections[i];
641 diamond->connections[i].object = obj;
642 diamond->connections[i].connected = NULL;
643 diamond->connections[i].flags = 0;
645 diamond->connections[16].flags = CP_FLAGS_MAIN;
647 diamond_update_data(diamond, ANCHOR_MIDDLE, ANCHOR_MIDDLE);
649 return &diamond->element.object;