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.
29 #include "connectionpoint.h"
30 #include "diarenderer.h"
31 #include "attributes.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
50 typedef struct _Box Box
;
55 ConnectionPoint connections
[NUM_CONNECTIONS
];
60 gboolean show_background
;
67 static struct _BoxProperties
{
68 gboolean show_background
;
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
,
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
,
103 (GetDefaultsFunc
) NULL
,
104 (ApplyDefaultsFunc
) NULL
107 DiaObjectType box_type
=
109 "Standard - Box", /* name */
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
,
121 (DistanceFunc
) box_distance_from
,
122 (SelectFunc
) box_select
,
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
},
142 static PropDescription box_props
[] = {
143 ELEMENT_COMMON_PROPERTIES
,
145 PROP_STD_LINE_COLOUR
,
146 PROP_STD_FILL_COLOUR
,
147 PROP_STD_SHOW_BACKGROUND
,
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
},
156 static PropDescription
*
157 box_describe_props(Box
*box
)
159 if (box_props
[0].quark
== 0)
160 prop_desc_list_calculate_quarks(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
) },
178 box_get_props(Box
*box
, GPtrArray
*props
)
180 object_get_props_from_offsets(&box
->element
.object
,
185 box_set_props(Box
*box
, GPtrArray
*props
)
187 object_set_props_from_offsets(&box
->element
.object
,
189 box_update_data(box
);
193 box_distance_from(Box
*box
, Point
*point
)
195 Element
*elem
= &box
->element
;
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
);
206 box_select(Box
*box
, Point
*clicked_point
,
207 DiaRenderer
*interactive_renderer
)
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
;
232 box_move_handle(Box
*box
, Handle
*handle
,
233 Point
*to
, ConnectionPoint
*cp
,
234 HandleMoveReason reason
, ModifierKeys modifiers
)
237 assert(handle
!=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
;
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
;
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
;
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
;
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
);
280 element_move_handle(&box
->element
, handle
->id
, to
, cp
, reason
, modifiers
);
283 box_update_data(box
);
289 box_move(Box
*box
, Point
*to
)
291 box
->element
.corner
= *to
;
293 box_update_data(box
);
299 box_draw(Box
*box
, DiaRenderer
*renderer
)
303 DiaRendererClass
*renderer_ops
= DIA_RENDERER_GET_CLASS (renderer
);
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
,
330 renderer_ops
->fill_rect(renderer
,
337 if (box
->corner_radius
> 0) {
338 renderer_ops
->draw_rounded_rect(renderer
,
344 renderer_ops
->draw_rect(renderer
,
353 box_update_data(Box
*box
)
355 Element
*elem
= &box
->element
;
356 ElementBBExtras
*extra
= &elem
->extra_spacing
;
357 DiaObject
*obj
= &elem
->object
;
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
);
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
;
421 box_create(Point
*startpoint
,
431 box
= g_malloc0(sizeof(Box
));
432 elem
= &box
->element
;
435 obj
->type
= &box_type
;
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
);
464 *handle2
= obj
->handles
[7];
465 return &box
->element
.object
;
469 box_destroy(Box
*box
)
471 element_destroy(&box
->element
);
479 Element
*elem
, *newelem
;
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
;
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"),
520 if (!color_equals(&box
->border_color
, &color_black
))
521 data_add_color(new_attribute(obj_node
, "border_color"),
524 if (!color_equals(&box
->inner_color
, &color_white
))
525 data_add_color(new_attribute(obj_node
, "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"),
534 if (box
->line_style
!= LINESTYLE_SOLID
&&
535 box
->dashlength
!= DEFAULT_LINESTYLE_DASHLEN
)
536 data_add_real(new_attribute(obj_node
, "dashlength"),
539 if (box
->corner_radius
> 0.0)
540 data_add_real(new_attribute(obj_node
, "corner_radius"),
543 if (box
->aspect
!= FREE_ASPECT
)
544 data_add_enum(new_attribute(obj_node
, "aspect"),
549 box_load(ObjectNode obj_node
, int version
, const char *filename
)
557 box
= g_malloc0(sizeof(Box
));
558 elem
= &box
->element
;
561 obj
->type
= &box_type
;
564 element_load(elem
, obj_node
);
566 box
->border_width
= 0.1;
567 attr
= object_find_attribute(obj_node
, "border_width");
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");
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");
579 data_color(attribute_first_data(attr
), &box
->inner_color
);
581 box
->show_background
= TRUE
;
582 attr
= object_find_attribute(obj_node
, "show_background");
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");
589 box
->line_style
= data_enum( attribute_first_data(attr
) );
591 box
->dashlength
= DEFAULT_LINESTYLE_DASHLEN
;
592 attr
= object_find_attribute(obj_node
, "dashlength");
594 box
->dashlength
= data_real(attribute_first_data(attr
));
596 box
->corner_radius
= 0.0;
597 attr
= object_find_attribute(obj_node
, "corner_radius");
599 box
->corner_radius
= data_real( attribute_first_data(attr
) );
601 box
->aspect
= FREE_ASPECT
;
602 attr
= object_find_attribute(obj_node
, "aspect");
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
632 aspect_change_free(struct AspectChange
*change
)
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
);
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
);
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
= {
700 sizeof(box_menu_items
)/sizeof(DiaMenuItem
),
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
;