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