2006-12-05 David Lodge <dave@cirt.net>
[dia.git] / objects / standard / box.c
blobf895eddfbe06465ba471c4c31a7d9f9e1eba7854
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
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.
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.
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.
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <assert.h>
24 #include <math.h>
26 #include "intl.h"
27 #include "object.h"
28 #include "element.h"
29 #include "connectionpoint.h"
30 #include "diarenderer.h"
31 #include "attributes.h"
32 #include "widgets.h"
33 #include "message.h"
34 #include "properties.h"
36 #include "tool-icons.h"
38 #define DEFAULT_WIDTH 2.0
39 #define DEFAULT_HEIGHT 1.0
40 #define DEFAULT_BORDER 0.25
42 #define NUM_CONNECTIONS 9
44 typedef enum {
45 FREE_ASPECT,
46 FIXED_ASPECT,
47 SQUARE_ASPECT
48 } AspectType;
50 typedef struct _Box Box;
52 struct _Box {
53 Element element;
55 ConnectionPoint connections[NUM_CONNECTIONS];
57 real border_width;
58 Color border_color;
59 Color inner_color;
60 gboolean show_background;
61 LineStyle line_style;
62 real dashlength;
63 real corner_radius;
64 AspectType aspect;
67 static struct _BoxProperties {
68 gboolean show_background;
69 real corner_radius;
70 AspectType aspect;
71 } default_properties = { TRUE, 0.0 };
73 static real box_distance_from(Box *box, Point *point);
74 static void box_select(Box *box, Point *clicked_point,
75 DiaRenderer *interactive_renderer);
76 static ObjectChange* box_move_handle(Box *box, Handle *handle,
77 Point *to, ConnectionPoint *cp,
78 HandleMoveReason reason,
79 ModifierKeys modifiers);
80 static ObjectChange* box_move(Box *box, Point *to);
81 static void box_draw(Box *box, DiaRenderer *renderer);
82 static void box_update_data(Box *box);
83 static DiaObject *box_create(Point *startpoint,
84 void *user_data,
85 Handle **handle1,
86 Handle **handle2);
87 static void box_destroy(Box *box);
88 static DiaObject *box_copy(Box *box);
90 static PropDescription *box_describe_props(Box *box);
91 static void box_get_props(Box *box, GPtrArray *props);
92 static void box_set_props(Box *box, GPtrArray *props);
94 static void box_save(Box *box, ObjectNode obj_node, const char *filename);
95 static DiaObject *box_load(ObjectNode obj_node, int version, const char *filename);
96 static DiaMenu *box_get_object_menu(Box *box, Point *clickedpoint);
98 static ObjectTypeOps box_type_ops =
100 (CreateFunc) box_create,
101 (LoadFunc) box_load,
102 (SaveFunc) box_save,
103 (GetDefaultsFunc) NULL,
104 (ApplyDefaultsFunc) NULL
107 DiaObjectType box_type =
109 "Standard - Box", /* name */
110 0, /* version */
111 (char **) box_icon, /* pixmap */
113 &box_type_ops /* ops */
116 DiaObjectType *_box_type = (DiaObjectType *) &box_type;
118 static ObjectOps box_ops = {
119 (DestroyFunc) box_destroy,
120 (DrawFunc) box_draw,
121 (DistanceFunc) box_distance_from,
122 (SelectFunc) box_select,
123 (CopyFunc) box_copy,
124 (MoveFunc) box_move,
125 (MoveHandleFunc) box_move_handle,
126 (GetPropertiesFunc) object_create_props_dialog,
127 (ApplyPropertiesFunc) object_apply_props_from_dialog,
128 (ObjectMenuFunc) box_get_object_menu,
129 (DescribePropsFunc) box_describe_props,
130 (GetPropsFunc) box_get_props,
131 (SetPropsFunc) box_set_props,
134 static PropNumData corner_radius_data = { 0.0, 10.0, 0.1 };
136 static PropEnumData prop_aspect_data[] = {
137 { N_("Free"), FREE_ASPECT },
138 { N_("Fixed"), FIXED_ASPECT },
139 { N_("Square"), SQUARE_ASPECT },
140 { NULL, 0 }
142 static PropDescription box_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 { "corner_radius", PROP_TYPE_LENGTH, PROP_FLAG_VISIBLE,
150 N_("Corner radius"), NULL, &corner_radius_data },
151 { "aspect", PROP_TYPE_ENUM, PROP_FLAG_VISIBLE,
152 N_("Aspect ratio"), NULL, prop_aspect_data },
153 PROP_DESC_END
156 static PropDescription *
157 box_describe_props(Box *box)
159 if (box_props[0].quark == 0)
160 prop_desc_list_calculate_quarks(box_props);
161 return box_props;
164 static PropOffset box_offsets[] = {
165 ELEMENT_COMMON_PROPERTIES_OFFSETS,
166 { "line_width", PROP_TYPE_REAL, offsetof(Box, border_width) },
167 { "line_colour", PROP_TYPE_COLOUR, offsetof(Box, border_color) },
168 { "fill_colour", PROP_TYPE_COLOUR, offsetof(Box, inner_color) },
169 { "show_background", PROP_TYPE_BOOL, offsetof(Box, show_background) },
170 { "aspect", PROP_TYPE_ENUM, offsetof(Box, aspect) },
171 { "line_style", PROP_TYPE_LINESTYLE,
172 offsetof(Box, line_style), offsetof(Box, dashlength) },
173 { "corner_radius", PROP_TYPE_LENGTH, offsetof(Box, corner_radius) },
174 { NULL, 0, 0 }
177 static void
178 box_get_props(Box *box, GPtrArray *props)
180 object_get_props_from_offsets(&box->element.object,
181 box_offsets, props);
184 static void
185 box_set_props(Box *box, GPtrArray *props)
187 object_set_props_from_offsets(&box->element.object,
188 box_offsets, props);
189 box_update_data(box);
192 static real
193 box_distance_from(Box *box, Point *point)
195 Element *elem = &box->element;
196 Rectangle rect;
198 rect.left = elem->corner.x - box->border_width/2;
199 rect.right = elem->corner.x + elem->width + box->border_width/2;
200 rect.top = elem->corner.y - box->border_width/2;
201 rect.bottom = elem->corner.y + elem->height + box->border_width/2;
202 return distance_rectangle_point(&rect, point);
205 static void
206 box_select(Box *box, Point *clicked_point,
207 DiaRenderer *interactive_renderer)
209 real radius;
211 element_update_handles(&box->element);
213 if (box->corner_radius > 0) {
214 Element *elem = (Element *)box;
215 radius = box->corner_radius;
216 radius = MIN(radius, elem->width/2);
217 radius = MIN(radius, elem->height/2);
218 radius *= (1-M_SQRT1_2);
220 elem->resize_handles[0].pos.x += radius;
221 elem->resize_handles[0].pos.y += radius;
222 elem->resize_handles[2].pos.x -= radius;
223 elem->resize_handles[2].pos.y += radius;
224 elem->resize_handles[5].pos.x += radius;
225 elem->resize_handles[5].pos.y -= radius;
226 elem->resize_handles[7].pos.x -= radius;
227 elem->resize_handles[7].pos.y -= radius;
231 static ObjectChange*
232 box_move_handle(Box *box, Handle *handle,
233 Point *to, ConnectionPoint *cp,
234 HandleMoveReason reason, ModifierKeys modifiers)
236 assert(box!=NULL);
237 assert(handle!=NULL);
238 assert(to!=NULL);
240 if (box->aspect != FREE_ASPECT){
241 double width, height;
242 double new_width, new_height;
243 double to_width, aspect_width;
244 Point corner = box->element.corner;
245 Point se_to;
247 width = box->element.width;
248 height = box->element.height;
249 switch (handle->id) {
250 case HANDLE_RESIZE_N:
251 case HANDLE_RESIZE_S:
252 new_height = fabs(to->y - corner.y);
253 new_width = new_height / height * width;
254 break;
255 case HANDLE_RESIZE_W:
256 case HANDLE_RESIZE_E:
257 new_width = fabs(to->x - corner.x);
258 new_height = new_width / width * height;
259 break;
260 case HANDLE_RESIZE_NW:
261 case HANDLE_RESIZE_NE:
262 case HANDLE_RESIZE_SW:
263 case HANDLE_RESIZE_SE:
264 to_width = fabs(to->x - corner.x);
265 aspect_width = fabs(to->y - corner.y) / height * width;
266 new_width = to_width > aspect_width ? to_width : aspect_width;
267 new_height = new_width / width * height;
268 break;
269 default:
270 new_width = width;
271 new_height = height;
272 break;
275 se_to.x = corner.x + new_width;
276 se_to.y = corner.y + new_height;
278 element_move_handle(&box->element, HANDLE_RESIZE_SE, &se_to, cp, reason, modifiers);
279 } else {
280 element_move_handle(&box->element, handle->id, to, cp, reason, modifiers);
283 box_update_data(box);
285 return NULL;
288 static ObjectChange*
289 box_move(Box *box, Point *to)
291 box->element.corner = *to;
293 box_update_data(box);
295 return NULL;
298 static void
299 box_draw(Box *box, DiaRenderer *renderer)
301 Point lr_corner;
302 Element *elem;
303 DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
306 assert(box != NULL);
307 assert(renderer != NULL);
309 elem = &box->element;
311 lr_corner.x = elem->corner.x + elem->width;
312 lr_corner.y = elem->corner.y + elem->height;
314 renderer_ops->set_linewidth(renderer, box->border_width);
315 renderer_ops->set_linestyle(renderer, box->line_style);
316 renderer_ops->set_dashlength(renderer, box->dashlength);
317 renderer_ops->set_linejoin(renderer, LINEJOIN_MITER);
319 if (box->show_background) {
320 renderer_ops->set_fillstyle(renderer, FILLSTYLE_SOLID);
322 /* Problem: How do we make the fill with rounded corners? */
323 if (box->corner_radius > 0) {
324 renderer_ops->fill_rounded_rect(renderer,
325 &elem->corner,
326 &lr_corner,
327 &box->inner_color,
328 box->corner_radius);
329 } else {
330 renderer_ops->fill_rect(renderer,
331 &elem->corner,
332 &lr_corner,
333 &box->inner_color);
337 if (box->corner_radius > 0) {
338 renderer_ops->draw_rounded_rect(renderer,
339 &elem->corner,
340 &lr_corner,
341 &box->border_color,
342 box->corner_radius);
343 } else {
344 renderer_ops->draw_rect(renderer,
345 &elem->corner,
346 &lr_corner,
347 &box->border_color);
352 static void
353 box_update_data(Box *box)
355 Element *elem = &box->element;
356 ElementBBExtras *extra = &elem->extra_spacing;
357 DiaObject *obj = &elem->object;
358 real radius;
360 if (box->aspect == SQUARE_ASPECT){
361 float size = elem->height < elem->width ? elem->height : elem->width;
362 elem->height = elem->width = size;
365 radius = box->corner_radius;
366 radius = MIN(radius, elem->width/2);
367 radius = MIN(radius, elem->height/2);
368 radius *= (1-M_SQRT1_2);
370 /* Update connections: */
371 box->connections[0].pos.x = elem->corner.x + radius;
372 box->connections[0].pos.y = elem->corner.y + radius;
373 box->connections[1].pos.x = elem->corner.x + elem->width / 2.0;
374 box->connections[1].pos.y = elem->corner.y;
375 box->connections[2].pos.x = elem->corner.x + elem->width - radius;
376 box->connections[2].pos.y = elem->corner.y + radius;
377 box->connections[3].pos.x = elem->corner.x;
378 box->connections[3].pos.y = elem->corner.y + elem->height / 2.0;
379 box->connections[4].pos.x = elem->corner.x + elem->width;
380 box->connections[4].pos.y = elem->corner.y + elem->height / 2.0;
381 box->connections[5].pos.x = elem->corner.x + radius;
382 box->connections[5].pos.y = elem->corner.y + elem->height - radius;
383 box->connections[6].pos.x = elem->corner.x + elem->width / 2.0;
384 box->connections[6].pos.y = elem->corner.y + elem->height;
385 box->connections[7].pos.x = elem->corner.x + elem->width - radius;
386 box->connections[7].pos.y = elem->corner.y + elem->height - radius;
387 box->connections[8].pos.x = elem->corner.x + elem->width / 2.0;
388 box->connections[8].pos.y = elem->corner.y + elem->height / 2.0;
390 box->connections[0].directions = DIR_NORTH|DIR_WEST;
391 box->connections[1].directions = DIR_NORTH;
392 box->connections[2].directions = DIR_NORTH|DIR_EAST;
393 box->connections[3].directions = DIR_WEST;
394 box->connections[4].directions = DIR_EAST;
395 box->connections[5].directions = DIR_SOUTH|DIR_WEST;
396 box->connections[6].directions = DIR_SOUTH;
397 box->connections[7].directions = DIR_SOUTH|DIR_EAST;
398 box->connections[8].directions = DIR_ALL;
400 extra->border_trans = box->border_width / 2.0;
401 element_update_boundingbox(elem);
403 obj->position = elem->corner;
405 element_update_handles(elem);
407 if (radius > 0.0) {
408 /* Fix the handles, too */
409 elem->resize_handles[0].pos.x += radius;
410 elem->resize_handles[0].pos.y += radius;
411 elem->resize_handles[2].pos.x -= radius;
412 elem->resize_handles[2].pos.y += radius;
413 elem->resize_handles[5].pos.x += radius;
414 elem->resize_handles[5].pos.y -= radius;
415 elem->resize_handles[7].pos.x -= radius;
416 elem->resize_handles[7].pos.y -= radius;
420 static DiaObject *
421 box_create(Point *startpoint,
422 void *user_data,
423 Handle **handle1,
424 Handle **handle2)
426 Box *box;
427 Element *elem;
428 DiaObject *obj;
429 int i;
431 box = g_malloc0(sizeof(Box));
432 elem = &box->element;
433 obj = &elem->object;
435 obj->type = &box_type;
437 obj->ops = &box_ops;
439 elem->corner = *startpoint;
440 elem->width = DEFAULT_WIDTH;
441 elem->height = DEFAULT_HEIGHT;
443 box->border_width = attributes_get_default_linewidth();
444 box->border_color = attributes_get_foreground();
445 box->inner_color = attributes_get_background();
446 attributes_get_default_line_style(&box->line_style, &box->dashlength);
447 /* For non-default objects, this is overridden by the default */
448 box->show_background = default_properties.show_background;
449 box->corner_radius = default_properties.corner_radius;
450 box->aspect = default_properties.aspect;
452 element_init(elem, 8, NUM_CONNECTIONS);
454 for (i=0;i<NUM_CONNECTIONS;i++) {
455 obj->connections[i] = &box->connections[i];
456 box->connections[i].object = obj;
457 box->connections[i].connected = NULL;
459 box->connections[8].flags = CP_FLAGS_MAIN;
461 box_update_data(box);
463 *handle1 = NULL;
464 *handle2 = obj->handles[7];
465 return &box->element.object;
468 static void
469 box_destroy(Box *box)
471 element_destroy(&box->element);
474 static DiaObject *
475 box_copy(Box *box)
477 int i;
478 Box *newbox;
479 Element *elem, *newelem;
480 DiaObject *newobj;
482 elem = &box->element;
484 newbox = g_malloc0(sizeof(Box));
485 newelem = &newbox->element;
486 newobj = &newelem->object;
488 element_copy(elem, newelem);
490 newbox->border_width = box->border_width;
491 newbox->border_color = box->border_color;
492 newbox->inner_color = box->inner_color;
493 newbox->show_background = box->show_background;
494 newbox->line_style = box->line_style;
495 newbox->dashlength = box->dashlength;
496 newbox->corner_radius = box->corner_radius;
497 newbox->aspect = box->aspect;
499 for (i=0;i<NUM_CONNECTIONS;i++) {
500 newobj->connections[i] = &newbox->connections[i];
501 newbox->connections[i].object = newobj;
502 newbox->connections[i].connected = NULL;
503 newbox->connections[i].pos = box->connections[i].pos;
504 newbox->connections[i].last_pos = box->connections[i].last_pos;
505 newbox->connections[i].flags = box->connections[i].flags;
508 return &newbox->element.object;
511 static void
512 box_save(Box *box, ObjectNode obj_node, const char *filename)
514 element_save(&box->element, obj_node);
516 if (box->border_width != 0.1)
517 data_add_real(new_attribute(obj_node, "border_width"),
518 box->border_width);
520 if (!color_equals(&box->border_color, &color_black))
521 data_add_color(new_attribute(obj_node, "border_color"),
522 &box->border_color);
524 if (!color_equals(&box->inner_color, &color_white))
525 data_add_color(new_attribute(obj_node, "inner_color"),
526 &box->inner_color);
528 data_add_boolean(new_attribute(obj_node, "show_background"), box->show_background);
530 if (box->line_style != LINESTYLE_SOLID)
531 data_add_enum(new_attribute(obj_node, "line_style"),
532 box->line_style);
534 if (box->line_style != LINESTYLE_SOLID &&
535 box->dashlength != DEFAULT_LINESTYLE_DASHLEN)
536 data_add_real(new_attribute(obj_node, "dashlength"),
537 box->dashlength);
539 if (box->corner_radius > 0.0)
540 data_add_real(new_attribute(obj_node, "corner_radius"),
541 box->corner_radius);
543 if (box->aspect != FREE_ASPECT)
544 data_add_enum(new_attribute(obj_node, "aspect"),
545 box->aspect);
548 static DiaObject *
549 box_load(ObjectNode obj_node, int version, const char *filename)
551 Box *box;
552 Element *elem;
553 DiaObject *obj;
554 int i;
555 AttributeNode attr;
557 box = g_malloc0(sizeof(Box));
558 elem = &box->element;
559 obj = &elem->object;
561 obj->type = &box_type;
562 obj->ops = &box_ops;
564 element_load(elem, obj_node);
566 box->border_width = 0.1;
567 attr = object_find_attribute(obj_node, "border_width");
568 if (attr != NULL)
569 box->border_width = data_real( attribute_first_data(attr) );
571 box->border_color = color_black;
572 attr = object_find_attribute(obj_node, "border_color");
573 if (attr != NULL)
574 data_color(attribute_first_data(attr), &box->border_color);
576 box->inner_color = color_white;
577 attr = object_find_attribute(obj_node, "inner_color");
578 if (attr != NULL)
579 data_color(attribute_first_data(attr), &box->inner_color);
581 box->show_background = TRUE;
582 attr = object_find_attribute(obj_node, "show_background");
583 if (attr != NULL)
584 box->show_background = data_boolean( attribute_first_data(attr) );
586 box->line_style = LINESTYLE_SOLID;
587 attr = object_find_attribute(obj_node, "line_style");
588 if (attr != NULL)
589 box->line_style = data_enum( attribute_first_data(attr) );
591 box->dashlength = DEFAULT_LINESTYLE_DASHLEN;
592 attr = object_find_attribute(obj_node, "dashlength");
593 if (attr != NULL)
594 box->dashlength = data_real(attribute_first_data(attr));
596 box->corner_radius = 0.0;
597 attr = object_find_attribute(obj_node, "corner_radius");
598 if (attr != NULL)
599 box->corner_radius = data_real( attribute_first_data(attr) );
601 box->aspect = FREE_ASPECT;
602 attr = object_find_attribute(obj_node, "aspect");
603 if (attr != NULL)
604 box->aspect = data_enum(attribute_first_data(attr));
606 element_init(elem, 8, NUM_CONNECTIONS);
608 for (i=0;i<NUM_CONNECTIONS;i++) {
609 obj->connections[i] = &box->connections[i];
610 box->connections[i].object = obj;
611 box->connections[i].connected = NULL;
613 box->connections[8].flags = CP_FLAGS_MAIN;
615 box_update_data(box);
617 return &box->element.object;
621 struct AspectChange {
622 ObjectChange obj_change;
623 AspectType old_type, new_type;
624 /* The points before this got applied. Afterwards, all points can be
625 * calculated.
627 Point topleft;
628 real width, height;
631 static void
632 aspect_change_free(struct AspectChange *change)
636 static void
637 aspect_change_apply(struct AspectChange *change, DiaObject *obj)
639 Box *box = (Box*)obj;
641 box->aspect = change->new_type;
642 box_update_data(box);
645 static void
646 aspect_change_revert(struct AspectChange *change, DiaObject *obj)
648 Box *box = (Box*)obj;
650 box->aspect = change->old_type;
651 box->element.corner = change->topleft;
652 box->element.width = change->width;
653 box->element.height = change->height;
654 box_update_data(box);
657 static ObjectChange *
658 aspect_create_change(Box *box, AspectType aspect)
660 struct AspectChange *change;
662 change = g_new0(struct AspectChange, 1);
664 change->obj_change.apply = (ObjectChangeApplyFunc) aspect_change_apply;
665 change->obj_change.revert = (ObjectChangeRevertFunc) aspect_change_revert;
666 change->obj_change.free = (ObjectChangeFreeFunc) aspect_change_free;
668 change->old_type = box->aspect;
669 change->new_type = aspect;
670 change->topleft = box->element.corner;
671 change->width = box->element.width;
672 change->height = box->element.height;
674 return (ObjectChange *)change;
678 static ObjectChange *
679 box_set_aspect_callback (DiaObject* obj, Point* clicked, gpointer data)
681 ObjectChange *change;
683 change = aspect_create_change((Box*)obj, (AspectType)data);
684 change->apply(change, obj);
686 return change;
689 static DiaMenuItem box_menu_items[] = {
690 { N_("Free aspect"), box_set_aspect_callback, (void*)FREE_ASPECT,
691 DIAMENU_ACTIVE|DIAMENU_TOGGLE },
692 { N_("Fixed aspect"), box_set_aspect_callback, (void*)FIXED_ASPECT,
693 DIAMENU_ACTIVE|DIAMENU_TOGGLE },
694 { N_("Square"), box_set_aspect_callback, (void*)SQUARE_ASPECT,
695 DIAMENU_ACTIVE|DIAMENU_TOGGLE},
698 static DiaMenu box_menu = {
699 "Box",
700 sizeof(box_menu_items)/sizeof(DiaMenuItem),
701 box_menu_items,
702 NULL
705 static DiaMenu *
706 box_get_object_menu(Box *box, Point *clickedpoint)
708 /* Set entries sensitive/selected etc here */
709 box_menu_items[0].active = DIAMENU_ACTIVE|DIAMENU_TOGGLE;
710 box_menu_items[1].active = DIAMENU_ACTIVE|DIAMENU_TOGGLE;
711 box_menu_items[2].active = DIAMENU_ACTIVE|DIAMENU_TOGGLE;
713 box_menu_items[box->aspect].active =
714 DIAMENU_ACTIVE|DIAMENU_TOGGLE|DIAMENU_TOGGLE_ON;
716 return &box_menu;