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 /** The Element object type is a rectangular box that has
20 * non-connectable handles on its corners and on the midsts of the
21 * edges. It also has connectionpoints in the same places as the
22 * handles as well as a mainpoint in the middle.
23 */
24
25 #include <config.h>
26
27 #include <stdio.h>
28 #include <assert.h>
29 #include <math.h>
30 #include <string.h> /* memcpy() */
31
32 #include "element.h"
33 #include "message.h"
34
35 /** Update the boundingbox information for this element.
36 * @param An object to update bounding box on.
37 */
38 void
element_update_boundingbox(Element * elem)39 element_update_boundingbox(Element *elem) {
40 Rectangle bb;
41 Point *corner;
42 ElementBBExtras *extra = &elem->extra_spacing;
43
44 assert(elem != NULL);
45
46 corner = &elem->corner;
47 bb.left = corner->x;
48 bb.right = corner->x + elem->width;
49 bb.top = corner->y;
50 bb.bottom = corner->y + elem->height;
51
52 rectangle_bbox(&bb,extra,&elem->object.bounding_box);
53 }
54
55 /** Update the 9 connections of this element to form a rectangle and
56 * point in the center.
57 * The connections go left-to-right, first top row, then middle row, then
58 * bottom row, then center. Do not blindly use this in old objects where
59 * the order is different, as it will mess with the saved files. If an
60 * object uses element_update_handles, it can use this.
61 * @param elem The element to update
62 * @param cps The list of connectionpoints to update, in the order described.
63 * Usually, this will be the same list as elem->connectionpoints.
64 */
65 void
element_update_connections_rectangle(Element * elem,ConnectionPoint * cps)66 element_update_connections_rectangle(Element *elem,
67 ConnectionPoint* cps)
68 {
69 cps[0].pos = elem->corner;
70 cps[1].pos.x = elem->corner.x + elem->width / 2.0;
71 cps[1].pos.y = elem->corner.y;
72 cps[2].pos.x = elem->corner.x + elem->width;
73 cps[2].pos.y = elem->corner.y;
74 cps[3].pos.x = elem->corner.x;
75 cps[3].pos.y = elem->corner.y + elem->height / 2.0;
76 cps[4].pos.x = elem->corner.x + elem->width;
77 cps[4].pos.y = elem->corner.y + elem->height / 2.0;
78 cps[5].pos.x = elem->corner.x;
79 cps[5].pos.y = elem->corner.y + elem->height;
80 cps[6].pos.x = elem->corner.x + elem->width / 2.0;
81 cps[6].pos.y = elem->corner.y + elem->height;
82 cps[7].pos.x = elem->corner.x + elem->width;
83 cps[7].pos.y = elem->corner.y + elem->height;
84 g_assert(elem->object.num_connections >= 9);
85 cps[8].pos.x = elem->corner.x + elem->width / 2.0;
86 cps[8].pos.y = elem->corner.y + elem->height / 2.0;
87
88 cps[0].directions = DIR_NORTH|DIR_WEST;
89 cps[1].directions = DIR_NORTH;
90 cps[2].directions = DIR_NORTH|DIR_EAST;
91 cps[3].directions = DIR_WEST;
92 cps[4].directions = DIR_EAST;
93 cps[5].directions = DIR_SOUTH|DIR_WEST;
94 cps[6].directions = DIR_SOUTH;
95 cps[7].directions = DIR_SOUTH|DIR_EAST;
96 cps[8].directions = DIR_ALL;
97 }
98
99 /** Update the corner and edge handles of an element to reflect its position
100 * and size.
101 * @param elem An element to update.
102 */
103 void
element_update_handles(Element * elem)104 element_update_handles(Element *elem)
105 {
106 Point *corner = &elem->corner;
107
108 elem->resize_handles[0].id = HANDLE_RESIZE_NW;
109 elem->resize_handles[0].pos.x = corner->x;
110 elem->resize_handles[0].pos.y = corner->y;
111
112 elem->resize_handles[1].id = HANDLE_RESIZE_N;
113 elem->resize_handles[1].pos.x = corner->x + elem->width/2.0;
114 elem->resize_handles[1].pos.y = corner->y;
115
116 elem->resize_handles[2].id = HANDLE_RESIZE_NE;
117 elem->resize_handles[2].pos.x = corner->x + elem->width;
118 elem->resize_handles[2].pos.y = corner->y;
119
120 elem->resize_handles[3].id = HANDLE_RESIZE_W;
121 elem->resize_handles[3].pos.x = corner->x;
122 elem->resize_handles[3].pos.y = corner->y + elem->height/2.0;
123
124 elem->resize_handles[4].id = HANDLE_RESIZE_E;
125 elem->resize_handles[4].pos.x = corner->x + elem->width;
126 elem->resize_handles[4].pos.y = corner->y + elem->height/2.0;
127
128 elem->resize_handles[5].id = HANDLE_RESIZE_SW;
129 elem->resize_handles[5].pos.x = corner->x;
130 elem->resize_handles[5].pos.y = corner->y + elem->height;
131
132 elem->resize_handles[6].id = HANDLE_RESIZE_S;
133 elem->resize_handles[6].pos.x = corner->x + elem->width/2.0;
134 elem->resize_handles[6].pos.y = corner->y + elem->height;
135
136 elem->resize_handles[7].id = HANDLE_RESIZE_SE;
137 elem->resize_handles[7].pos.x = corner->x + elem->width;
138 elem->resize_handles[7].pos.y = corner->y + elem->height;
139 }
140
141 /** Handle the moving of one of the elements handles.
142 * This function is suitable for use as the move_handle object operation.
143 * @param elem The element whose handle is being moved.
144 * @param id The id of the handle.
145 * @param to Where it's being moved to.
146 * @param cp Ignored
147 * @param reason What is causing this handle to be moved (creation, movement..)
148 * @param modifiers Any modifier keys (shift, control...) that the user is
149 * pressing.
150 * @return Undo information for this change.
151 */
152 ObjectChange*
element_move_handle(Element * elem,HandleId id,Point * to,ConnectionPoint * cp,HandleMoveReason reason,ModifierKeys modifiers)153 element_move_handle(Element *elem, HandleId id,
154 Point *to, ConnectionPoint *cp,
155 HandleMoveReason reason, ModifierKeys modifiers)
156 {
157 Point p;
158 Point *corner;
159
160 assert(id>=HANDLE_RESIZE_NW);
161 assert(id<=HANDLE_RESIZE_SE);
162
163 corner = &elem->corner;
164
165 p = *to;
166 point_sub(&p, &elem->corner);
167
168 switch(id) {
169 case HANDLE_RESIZE_NW:
170 if ( to->x < (corner->x+elem->width)) {
171 corner->x += p.x;
172 elem->width -= p.x;
173 }
174 if ( to->y < (corner->y+elem->height)) {
175 corner->y += p.y;
176 elem->height -= p.y;
177 }
178 break;
179 case HANDLE_RESIZE_N:
180 if ( to->y < (corner->y+elem->height)) {
181 corner->y += p.y;
182 elem->height -= p.y;
183 }
184 break;
185 case HANDLE_RESIZE_NE:
186 if (p.x>0.0)
187 elem->width = p.x;
188 if ( to->y < (corner->y+elem->height)) {
189 corner->y += p.y;
190 elem->height -= p.y;
191 }
192 break;
193 case HANDLE_RESIZE_W:
194 if ( to->x < (corner->x+elem->width)) {
195 corner->x += p.x;
196 elem->width -= p.x;
197 }
198 break;
199 case HANDLE_RESIZE_E:
200 if (p.x>0.0)
201 elem->width = p.x;
202 break;
203 case HANDLE_RESIZE_SW:
204 if ( to->x < (corner->x+elem->width)) {
205 corner->x += p.x;
206 elem->width -= p.x;
207 }
208 if (p.y>0.0)
209 elem->height = p.y;
210 break;
211 case HANDLE_RESIZE_S:
212 if (p.y>0.0)
213 elem->height = p.y;
214 break;
215 case HANDLE_RESIZE_SE:
216 if (p.x>0.0)
217 elem->width = p.x;
218 if (p.y>0.0)
219 elem->height = p.y;
220 break;
221 default:
222 message_error("Error, called element_move_handle() with wrong handle-id\n");
223 }
224 return NULL;
225 }
226
227 /** Move the handle of an element restricted to a certain aspect ration.
228 * @param elem The element to update on
229 * @param id The id of the handle being moved
230 * @param to Where the handle is being moved to
231 * @param aspect_ratio The aspect ratio (width:height) to obey.
232 * The aspect ratio must not be 0.
233 */
234 void
element_move_handle_aspect(Element * elem,HandleId id,Point * to,real aspect_ratio)235 element_move_handle_aspect(Element *elem, HandleId id,
236 Point *to, real aspect_ratio)
237 {
238 Point p;
239 Point *corner;
240 real width, height;
241 real new_width, new_height;
242 real move_x=0;
243 real move_y=0;
244
245 assert(id>=HANDLE_RESIZE_NW);
246 assert(id<=HANDLE_RESIZE_SE);
247
248 corner = &elem->corner;
249
250 p = *to;
251 point_sub(&p, &elem->corner);
252
253 width = elem->width;
254 height = elem->height;
255
256 new_width = 0.0;
257 new_height = 0.0;
258
259
260 switch(id) {
261 case HANDLE_RESIZE_NW:
262 new_width = width - p.x;
263 new_height = height - p.y;
264 move_x = 1.0;
265 move_y = 1.0;
266 break;
267 case HANDLE_RESIZE_N:
268 new_height = height - p.y;
269 move_y = 1.0;
270 move_x = 0.5;
271 break;
272 case HANDLE_RESIZE_NE:
273 new_width = p.x;
274 new_height = height - p.y;
275 move_x = 0.0;
276 move_y = 1.0;
277 break;
278 case HANDLE_RESIZE_W:
279 new_width = width - p.x;
280 move_x = 1.0;
281 move_y = 0.5;
282 break;
283 case HANDLE_RESIZE_E:
284 new_width = p.x;
285 move_x = 0.0;
286 move_y = 0.5;
287 break;
288 case HANDLE_RESIZE_SW:
289 new_width = width - p.x;
290 new_height = p.y;
291 move_x = 1.0;
292 move_y = 0.0;
293 break;
294 case HANDLE_RESIZE_S:
295 new_height = p.y;
296 move_x = 0.5;
297 move_y = 0.0;
298 break;
299 case HANDLE_RESIZE_SE:
300 new_width = p.x;
301 new_height = p.y;
302 move_x = 0.0;
303 move_y = 0.0;
304 break;
305 default:
306 message_error("Error, called element_move_handle() with wrong handle-id\n");
307 }
308
309 /* Which of the two versions to use: */
310 if (new_width > new_height*aspect_ratio) {
311 new_height = new_width/aspect_ratio;
312 } else {
313 new_width = new_height*aspect_ratio;
314 }
315
316 if ( (new_width<0.0) || (new_height<0.0)) {
317 new_width = 0.0;
318 new_height = 0.0;
319 }
320
321 corner->x -= (new_width - width)*move_x;
322 corner->y -= (new_height - height)*move_y;
323
324 elem->width = new_width;
325 elem->height = new_height;
326 }
327
328 /** Initialization function for element objects.
329 * An element must have at least 8 handles and 9 connectionpoints.
330 * @param elem The element to initialize. This function will call
331 * object_init() on the element.
332 * @param num_handles The number of handles to set up (>= 8). The handles
333 * will be initialized by this function.
334 * @param num_connections The number of connectionpoints to set up (>= 9).
335 * The connectionpoints will <em>not</em> be
336 * initialized by this function.
337 */
338 void
element_init(Element * elem,int num_handles,int num_connections)339 element_init(Element *elem, int num_handles, int num_connections)
340 {
341 DiaObject *obj;
342 int i;
343
344 obj = &elem->object;
345
346 assert(num_handles>=8);
347
348 object_init(obj, num_handles, num_connections);
349
350 for (i=0;i<8;i++) {
351 obj->handles[i] = &elem->resize_handles[i];
352 obj->handles[i]->connect_type = HANDLE_NONCONNECTABLE;
353 obj->handles[i]->connected_to = NULL;
354 obj->handles[i]->type = HANDLE_MAJOR_CONTROL;
355 }
356 }
357
358 /** Copy an element, initializing the handles.
359 * This function will in turn copy the underlying object.
360 * @paramm from An element to copy from.
361 * @param to An element (already allocated) to copy to.
362 */
363 void
element_copy(Element * from,Element * to)364 element_copy(Element *from, Element *to)
365 {
366 DiaObject *toobj, *fromobj;
367 int i;
368
369 fromobj = &from->object;
370 toobj = &to->object;
371
372 object_copy(fromobj, toobj);
373
374 to->corner = from->corner;
375 to->width = from->width;
376 to->height = from->height;
377
378 for (i=0;i<8;i++) {
379 to->resize_handles[i] = from->resize_handles[i];
380 to->resize_handles[i].connected_to = NULL;
381 toobj->handles[i] = &to->resize_handles[i];
382 }
383 memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing));
384 }
385
386 /** Destroy an elements private information.
387 * This function will in turn call object_destroy.
388 * @param elem The element to destroy. It will <em>not</em> be deallocated
389 * by this call, but will not be valid afterwards.
390 */
391 void
element_destroy(Element * elem)392 element_destroy(Element *elem)
393 {
394 object_destroy(&elem->object);
395 }
396
397 /** Save the element-specific parts of this element to XML.
398 * @param elem
399 * @param obj_node
400 */
401 void
element_save(Element * elem,ObjectNode obj_node)402 element_save(Element *elem, ObjectNode obj_node)
403 {
404 object_save(&elem->object, obj_node);
405
406 data_add_point(new_attribute(obj_node, "elem_corner"),
407 &elem->corner);
408 data_add_real(new_attribute(obj_node, "elem_width"),
409 elem->width);
410 data_add_real(new_attribute(obj_node, "elem_height"),
411 elem->height);
412 }
413
element_load(Element * elem,ObjectNode obj_node)414 void element_load(Element *elem, ObjectNode obj_node)
415 {
416 AttributeNode attr;
417
418 object_load(&elem->object, obj_node);
419
420 elem->corner.x = 0.0;
421 elem->corner.y = 0.0;
422 attr = object_find_attribute(obj_node, "elem_corner");
423 if (attr != NULL)
424 data_point( attribute_first_data(attr), &elem->corner );
425
426 elem->width = 1.0;
427 attr = object_find_attribute(obj_node, "elem_width");
428 if (attr != NULL)
429 elem->width = data_real( attribute_first_data(attr));
430
431 elem->height = 1.0;
432 attr = object_find_attribute(obj_node, "elem_height");
433 if (attr != NULL)
434 elem->height = data_real( attribute_first_data(attr));
435
436 }
437