/* Dia -- an diagram creation/manipulation program * Copyright (C) 1998 Alexander Larsson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** The Element object type is a rectangular box that has * non-connectable handles on its corners and on the midsts of the * edges. It also has connectionpoints in the same places as the * handles as well as a mainpoint in the middle. */ #include #include #include #include #include /* memcpy() */ #include "element.h" #include "message.h" /** Update the boundingbox information for this element. * @param An object to update bounding box on. */ void element_update_boundingbox(Element *elem) { Rectangle bb; Point *corner; ElementBBExtras *extra = &elem->extra_spacing; assert(elem != NULL); corner = &elem->corner; bb.left = corner->x; bb.right = corner->x + elem->width; bb.top = corner->y; bb.bottom = corner->y + elem->height; rectangle_bbox(&bb,extra,&elem->object.bounding_box); } /** Update the 9 connections of this element to form a rectangle and * point in the center. * The connections go left-to-right, first top row, then middle row, then * bottom row, then center. Do not blindly use this in old objects where * the order is different, as it will mess with the saved files. If an * object uses element_update_handles, it can use this. * @param elem The element to update * @param cps The list of connectionpoints to update, in the order described. * Usually, this will be the same list as elem->connectionpoints. */ void element_update_connections_rectangle(Element *elem, ConnectionPoint* cps) { cps[0].pos = elem->corner; cps[1].pos.x = elem->corner.x + elem->width / 2.0; cps[1].pos.y = elem->corner.y; cps[2].pos.x = elem->corner.x + elem->width; cps[2].pos.y = elem->corner.y; cps[3].pos.x = elem->corner.x; cps[3].pos.y = elem->corner.y + elem->height / 2.0; cps[4].pos.x = elem->corner.x + elem->width; cps[4].pos.y = elem->corner.y + elem->height / 2.0; cps[5].pos.x = elem->corner.x; cps[5].pos.y = elem->corner.y + elem->height; cps[6].pos.x = elem->corner.x + elem->width / 2.0; cps[6].pos.y = elem->corner.y + elem->height; cps[7].pos.x = elem->corner.x + elem->width; cps[7].pos.y = elem->corner.y + elem->height; g_assert(elem->object.num_connections >= 9); cps[8].pos.x = elem->corner.x + elem->width / 2.0; cps[8].pos.y = elem->corner.y + elem->height / 2.0; cps[0].directions = DIR_NORTH|DIR_WEST; cps[1].directions = DIR_NORTH; cps[2].directions = DIR_NORTH|DIR_EAST; cps[3].directions = DIR_WEST; cps[4].directions = DIR_EAST; cps[5].directions = DIR_SOUTH|DIR_WEST; cps[6].directions = DIR_SOUTH; cps[7].directions = DIR_SOUTH|DIR_EAST; cps[8].directions = DIR_ALL; } /** Update the corner and edge handles of an element to reflect its position * and size. * @param elem An element to update. */ void element_update_handles(Element *elem) { Point *corner = &elem->corner; elem->resize_handles[0].id = HANDLE_RESIZE_NW; elem->resize_handles[0].pos.x = corner->x; elem->resize_handles[0].pos.y = corner->y; elem->resize_handles[1].id = HANDLE_RESIZE_N; elem->resize_handles[1].pos.x = corner->x + elem->width/2.0; elem->resize_handles[1].pos.y = corner->y; elem->resize_handles[2].id = HANDLE_RESIZE_NE; elem->resize_handles[2].pos.x = corner->x + elem->width; elem->resize_handles[2].pos.y = corner->y; elem->resize_handles[3].id = HANDLE_RESIZE_W; elem->resize_handles[3].pos.x = corner->x; elem->resize_handles[3].pos.y = corner->y + elem->height/2.0; elem->resize_handles[4].id = HANDLE_RESIZE_E; elem->resize_handles[4].pos.x = corner->x + elem->width; elem->resize_handles[4].pos.y = corner->y + elem->height/2.0; elem->resize_handles[5].id = HANDLE_RESIZE_SW; elem->resize_handles[5].pos.x = corner->x; elem->resize_handles[5].pos.y = corner->y + elem->height; elem->resize_handles[6].id = HANDLE_RESIZE_S; elem->resize_handles[6].pos.x = corner->x + elem->width/2.0; elem->resize_handles[6].pos.y = corner->y + elem->height; elem->resize_handles[7].id = HANDLE_RESIZE_SE; elem->resize_handles[7].pos.x = corner->x + elem->width; elem->resize_handles[7].pos.y = corner->y + elem->height; } /** Handle the moving of one of the elements handles. * This function is suitable for use as the move_handle object operation. * @param elem The element whose handle is being moved. * @param id The id of the handle. * @param to Where it's being moved to. * @param cp Ignored * @param reason What is causing this handle to be moved (creation, movement..) * @param modifiers Any modifier keys (shift, control...) that the user is * pressing. * @return Undo information for this change. */ ObjectChange* element_move_handle(Element *elem, HandleId id, Point *to, ConnectionPoint *cp, HandleMoveReason reason, ModifierKeys modifiers) { Point p; Point *corner; assert(id>=HANDLE_RESIZE_NW); assert(id<=HANDLE_RESIZE_SE); corner = &elem->corner; p = *to; point_sub(&p, &elem->corner); switch(id) { case HANDLE_RESIZE_NW: if ( to->x < (corner->x+elem->width)) { corner->x += p.x; elem->width -= p.x; } if ( to->y < (corner->y+elem->height)) { corner->y += p.y; elem->height -= p.y; } break; case HANDLE_RESIZE_N: if ( to->y < (corner->y+elem->height)) { corner->y += p.y; elem->height -= p.y; } break; case HANDLE_RESIZE_NE: if (p.x>0.0) elem->width = p.x; if ( to->y < (corner->y+elem->height)) { corner->y += p.y; elem->height -= p.y; } break; case HANDLE_RESIZE_W: if ( to->x < (corner->x+elem->width)) { corner->x += p.x; elem->width -= p.x; } break; case HANDLE_RESIZE_E: if (p.x>0.0) elem->width = p.x; break; case HANDLE_RESIZE_SW: if ( to->x < (corner->x+elem->width)) { corner->x += p.x; elem->width -= p.x; } if (p.y>0.0) elem->height = p.y; break; case HANDLE_RESIZE_S: if (p.y>0.0) elem->height = p.y; break; case HANDLE_RESIZE_SE: if (p.x>0.0) elem->width = p.x; if (p.y>0.0) elem->height = p.y; break; default: message_error("Error, called element_move_handle() with wrong handle-id\n"); } return NULL; } /** Move the handle of an element restricted to a certain aspect ration. * @param elem The element to update on * @param id The id of the handle being moved * @param to Where the handle is being moved to * @param aspect_ratio The aspect ratio (width:height) to obey. * The aspect ratio must not be 0. */ void element_move_handle_aspect(Element *elem, HandleId id, Point *to, real aspect_ratio) { Point p; Point *corner; real width, height; real new_width, new_height; real move_x=0; real move_y=0; assert(id>=HANDLE_RESIZE_NW); assert(id<=HANDLE_RESIZE_SE); corner = &elem->corner; p = *to; point_sub(&p, &elem->corner); width = elem->width; height = elem->height; new_width = 0.0; new_height = 0.0; switch(id) { case HANDLE_RESIZE_NW: new_width = width - p.x; new_height = height - p.y; move_x = 1.0; move_y = 1.0; break; case HANDLE_RESIZE_N: new_height = height - p.y; move_y = 1.0; move_x = 0.5; break; case HANDLE_RESIZE_NE: new_width = p.x; new_height = height - p.y; move_x = 0.0; move_y = 1.0; break; case HANDLE_RESIZE_W: new_width = width - p.x; move_x = 1.0; move_y = 0.5; break; case HANDLE_RESIZE_E: new_width = p.x; move_x = 0.0; move_y = 0.5; break; case HANDLE_RESIZE_SW: new_width = width - p.x; new_height = p.y; move_x = 1.0; move_y = 0.0; break; case HANDLE_RESIZE_S: new_height = p.y; move_x = 0.5; move_y = 0.0; break; case HANDLE_RESIZE_SE: new_width = p.x; new_height = p.y; move_x = 0.0; move_y = 0.0; break; default: message_error("Error, called element_move_handle() with wrong handle-id\n"); } /* Which of the two versions to use: */ if (new_width > new_height*aspect_ratio) { new_height = new_width/aspect_ratio; } else { new_width = new_height*aspect_ratio; } if ( (new_width<0.0) || (new_height<0.0)) { new_width = 0.0; new_height = 0.0; } corner->x -= (new_width - width)*move_x; corner->y -= (new_height - height)*move_y; elem->width = new_width; elem->height = new_height; } /** Initialization function for element objects. * An element must have at least 8 handles and 9 connectionpoints. * @param elem The element to initialize. This function will call * object_init() on the element. * @param num_handles The number of handles to set up (>= 8). The handles * will be initialized by this function. * @param num_connections The number of connectionpoints to set up (>= 9). * The connectionpoints will not be * initialized by this function. */ void element_init(Element *elem, int num_handles, int num_connections) { DiaObject *obj; int i; obj = &elem->object; assert(num_handles>=8); object_init(obj, num_handles, num_connections); for (i=0;i<8;i++) { obj->handles[i] = &elem->resize_handles[i]; obj->handles[i]->connect_type = HANDLE_NONCONNECTABLE; obj->handles[i]->connected_to = NULL; obj->handles[i]->type = HANDLE_MAJOR_CONTROL; } } /** Copy an element, initializing the handles. * This function will in turn copy the underlying object. * @paramm from An element to copy from. * @param to An element (already allocated) to copy to. */ void element_copy(Element *from, Element *to) { DiaObject *toobj, *fromobj; int i; fromobj = &from->object; toobj = &to->object; object_copy(fromobj, toobj); to->corner = from->corner; to->width = from->width; to->height = from->height; for (i=0;i<8;i++) { to->resize_handles[i] = from->resize_handles[i]; to->resize_handles[i].connected_to = NULL; toobj->handles[i] = &to->resize_handles[i]; } memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing)); } /** Destroy an elements private information. * This function will in turn call object_destroy. * @param elem The element to destroy. It will not be deallocated * by this call, but will not be valid afterwards. */ void element_destroy(Element *elem) { object_destroy(&elem->object); } /** Save the element-specific parts of this element to XML. * @param elem * @param obj_node */ void element_save(Element *elem, ObjectNode obj_node) { object_save(&elem->object, obj_node); data_add_point(new_attribute(obj_node, "elem_corner"), &elem->corner); data_add_real(new_attribute(obj_node, "elem_width"), elem->width); data_add_real(new_attribute(obj_node, "elem_height"), elem->height); } void element_load(Element *elem, ObjectNode obj_node) { AttributeNode attr; object_load(&elem->object, obj_node); elem->corner.x = 0.0; elem->corner.y = 0.0; attr = object_find_attribute(obj_node, "elem_corner"); if (attr != NULL) data_point( attribute_first_data(attr), &elem->corner ); elem->width = 1.0; attr = object_find_attribute(obj_node, "elem_width"); if (attr != NULL) elem->width = data_real( attribute_first_data(attr)); elem->height = 1.0; attr = object_find_attribute(obj_node, "elem_height"); if (attr != NULL) elem->height = data_real( attribute_first_data(attr)); }