/* * DiaCanvasElement * * This is LGPL'ed code. */ #include "dia-canvas-element.h" #include "dia-handle.h" #include "dia-shape.h" #include #include #include "dia-canvas-i18n.h" enum { PROP_WIDTH = 1, PROP_HEIGHT, PROP_MIN_WIDTH, PROP_MIN_HEIGHT }; static void dia_canvas_element_class_init (DiaCanvasElementClass *klass); static void dia_canvas_element_init (DiaCanvasElement *item); static void dia_canvas_element_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void dia_canvas_element_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void dia_canvas_element_update (DiaCanvasItem *item, gdouble affine[6]); static gdouble dia_canvas_element_point (DiaCanvasItem *item, gdouble x, gdouble y); static void dia_canvas_element_handle_motion (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy, DiaEventMask mask); static gdouble dia_canvas_element_glue (DiaCanvasItem *item, DiaHandle *handle, gdouble *x, gdouble *y); static gboolean dia_canvas_element_connect (DiaCanvasItem *item, DiaHandle *handle); static gboolean dia_canvas_element_disconnect (DiaCanvasItem *item, DiaHandle *handle); #define align_handles(item) \ dia_canvas_element_align_handles ((DiaCanvasElement*) item) #define GET_HANDLE(item, dir) (DIA_HANDLE (g_list_nth_data (((DiaCanvasItem*) (item))->handles, dir))) #define GET_HANDLE_DIR(item, handle) (g_list_index (((DiaCanvasItem*) (item))->handles, handle)) static DiaCanvasItemClass *parent_class = NULL; GType dia_canvas_element_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaCanvasElementClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_canvas_element_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaCanvasElement), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_canvas_element_init, }; object_type = g_type_register_static (DIA_TYPE_CANVAS_ITEM, "DiaCanvasElement", &object_info, G_TYPE_FLAG_ABSTRACT); } return object_type; } static void dia_canvas_element_class_init (DiaCanvasElementClass *klass) { GObjectClass *object_class; DiaCanvasItemClass *item_class; object_class = (GObjectClass*) klass; item_class = DIA_CANVAS_ITEM_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->get_property = dia_canvas_element_get_property; object_class->set_property = dia_canvas_element_set_property; g_object_class_install_property (object_class, PROP_WIDTH, g_param_spec_double ("width", _("Width"), _("Width of the element"), 0.0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEIGHT, g_param_spec_double ("height", _("Height"), _("Height of the element"), 0.0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MIN_WIDTH, g_param_spec_double ("min_width", _("Minimal width"), _("Minimal width of the element"), 0.0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MIN_HEIGHT, g_param_spec_double ("min_height", _("Minimal height"), _("Minimal height of the element"), 0.0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); item_class->update = dia_canvas_element_update; item_class->point = dia_canvas_element_point; item_class->handle_motion = dia_canvas_element_handle_motion; item_class->glue = dia_canvas_element_glue; item_class->connect = dia_canvas_element_connect; item_class->disconnect = dia_canvas_element_disconnect; } static void dia_canvas_element_init (DiaCanvasElement *item) { int i; item->width = 100.0; item->height = 100.0; item->min_width = 0.0; item->min_height = 0.0; for (i = 0; i < 8; i++) { g_object_new (DIA_TYPE_HANDLE, "owner", item, "strength", DIA_STRENGTH_STRONG, "movable", TRUE, NULL); } //DIA_SET_FLAGS ((DiaCanvasItem*)item, DIA_NEED_ALIGN_HANDLES); align_handles (item); } static void dia_canvas_element_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DiaCanvasElement *element = (DiaCanvasElement*) (object); switch (property_id) { case PROP_WIDTH: dia_canvas_item_preserve_property (&element->item, "width"); element->width = g_value_get_double (value); align_handles (DIA_CANVAS_ITEM (element)); dia_canvas_item_request_update (DIA_CANVAS_ITEM (element)); break; case PROP_HEIGHT: dia_canvas_item_preserve_property (&element->item, "height"); element->height = g_value_get_double (value); align_handles (DIA_CANVAS_ITEM (element)); dia_canvas_item_request_update (DIA_CANVAS_ITEM (element)); break; case PROP_MIN_WIDTH: dia_canvas_item_preserve_property (&element->item, "min_width"); element->min_width = g_value_get_double (value); if (element->width < element->min_width) { dia_canvas_item_preserve_property (&element->item, "width"); element->width = element->min_width; align_handles (DIA_CANVAS_ITEM (element)); dia_canvas_item_request_update (DIA_CANVAS_ITEM (element)); } dia_canvas_item_request_update (DIA_CANVAS_ITEM (element)); break; case PROP_MIN_HEIGHT: dia_canvas_item_preserve_property (&element->item, "min_height"); element->min_height = g_value_get_double (value); if (element->height < element->min_height) { dia_canvas_item_preserve_property (&element->item, "height"); element->height = element->min_height; align_handles (DIA_CANVAS_ITEM (element)); dia_canvas_item_request_update (DIA_CANVAS_ITEM (element)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void dia_canvas_element_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_WIDTH: g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->width); break; case PROP_HEIGHT: g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->height); break; case PROP_MIN_WIDTH: g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->min_width); break; case PROP_MIN_HEIGHT: g_value_set_double (value, DIA_CANVAS_ELEMENT (object)->min_height); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /** * dia_canvas_element_get_opposite_handle: * @item: * @handle: * * Return the opposite handle of @handle. * * Return value: opposite handle. **/ DiaHandle* dia_canvas_element_get_opposite_handle (DiaCanvasItem *item, DiaHandle *handle) { int opp = -1; switch(GET_HANDLE_DIR(item,handle)){ case DIA_HANDLE_N: opp = DIA_HANDLE_S; break; case DIA_HANDLE_S: opp = DIA_HANDLE_N; break; case DIA_HANDLE_E: opp = DIA_HANDLE_W; break; case DIA_HANDLE_W: opp = DIA_HANDLE_E; break; case DIA_HANDLE_NW: opp = DIA_HANDLE_SE; break; case DIA_HANDLE_SW: opp = DIA_HANDLE_NE; break; case DIA_HANDLE_NE: opp = DIA_HANDLE_SW; break; case DIA_HANDLE_SE: opp = DIA_HANDLE_NW; break; default: g_assert_not_reached (); } return GET_HANDLE (item, opp); } void dia_canvas_element_align_handles (DiaCanvasElement *element) { DiaCanvasItem *item = DIA_CANVAS_ITEM (element); //g_message (G_STRLOC": w %f h %f", el->width, el->height); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_NW), 0.0, 0.0); /* Alter the affine transformation matrix so that the NW handle will * be at (0,0). */ dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_N), element->width/2, 0.0); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_NE), element->width, 0.0); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_E), element->width, element->height/2); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_SE), element->width, element->height); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_S), element->width/2, element->height); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_SW), 0.0, element->height); dia_handle_set_pos_i (GET_HANDLE (item, DIA_HANDLE_W), 0.0, element->height/2); } static void dia_canvas_element_update (DiaCanvasItem *item, gdouble affine[6]) { if (DIA_CANVAS_ITEM_CLASS (parent_class)->update) DIA_CANVAS_ITEM_CLASS (parent_class)->update (item, affine); /* if (item->flags & DIA_NEED_ALIGN_HANDLES) { align_handles (item); DIA_UNSET_FLAGS (item, DIA_NEED_ALIGN_HANDLES); } */ item->bounds.left = 0.0; item->bounds.top = 0.0; item->bounds.right = DIA_CANVAS_ELEMENT (item)->width; item->bounds.bottom = DIA_CANVAS_ELEMENT (item)->height; } static gdouble dia_canvas_element_point (DiaCanvasItem *item, gdouble x, gdouble y) { DiaPoint p; p.x = x; p.y = y; return dia_distance_rectangle_point (&item->bounds, &p); } /** * dia_canvas_element_handle_motion_move: * @item: * @handle: * @wx: * @wy: * * Included in dia_canvas_element_handle_motion(). Used to move a handle. **/ static void dia_canvas_element_handle_motion_move (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy) { DiaCanvasElement *el = (DiaCanvasElement*) item; gdouble ix, iy; gdouble nw_dx = 0.0, nw_dy = 0.0; gdouble x, y; gdouble dx, dy; /* The movement relative to the handle */ gint dir; /* Do not align handles if the handle is not part of the first 8 * handles. */ if (GET_HANDLE_DIR (item, handle) > DIA_HANDLE_SE) return; g_object_freeze_notify (G_OBJECT (item)); dia_canvas_preserve_property (item->canvas, (GObject*) item, "width"); dia_canvas_preserve_property (item->canvas, (GObject*) item, "height"); dia_handle_get_pos_i (handle, &x, &y); dir = GET_HANDLE_DIR (item, handle); ix = *wx; iy = *wy; dia_canvas_item_affine_point_w2i (item, &ix, &iy); dx = ix - x; dy = iy - y; /* If the north or west side is moved, the object should be moved to * keep the origin in the upper right corner. */ if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_N) || (dir == DIA_HANDLE_NE)) { register gdouble se_y = dia_variable_get_value (GET_HANDLE (item, DIA_HANDLE_SE)->pos_i.y); if (iy > se_y) dy = se_y - y; nw_dy = dy; dy = -dy; } if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_W) || (dir == DIA_HANDLE_SW)) { register gdouble se_x = dia_variable_get_value (GET_HANDLE (item, DIA_HANDLE_SE)->pos_i.x); if (ix > se_x) dx = se_x - x; nw_dx = dx; dx = -dx; } if ((dir != DIA_HANDLE_N) && (dir != DIA_HANDLE_S)) { el->width += dx; g_object_notify ((GObject*) item, "width"); } if ((dir != DIA_HANDLE_W) && (dir != DIA_HANDLE_E)) { el->height += dy; g_object_notify ((GObject*) item, "height"); } if (el->width <= el->min_width) { if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_W) || (dir == DIA_HANDLE_SW)) nw_dx += el->width - el->min_width; el->width = el->min_width; } if (el->height <= el->min_height) { if ((dir == DIA_HANDLE_NW) || (dir == DIA_HANDLE_N) || (dir == DIA_HANDLE_NE)) nw_dy += el->height - el->min_height; el->height = el->min_height; } /* Move the entire object if the north or west handle is moved. */ if ((nw_dx != 0.0) || (nw_dy != 0.0)) { gdouble parent_affine[6], a[6]; dia_canvas_preserve_property (item->canvas, (GObject*) item, "affine"); dia_canvas_item_affine_w2i (DIA_CANVAS_ITEM (item->parent), parent_affine); art_affine_translate (a, nw_dx, nw_dy); art_affine_multiply (a, a, parent_affine); art_affine_multiply (item->affine, a, item->affine); g_object_notify ((GObject*) item, "affine"); } align_handles (item); dia_handle_get_pos_w (handle, wx, wy); g_object_thaw_notify ((GObject*) item); } /** * dia_canvas_element_handle_motion_rotate: * @item: * @handle: * @wx: * @wy: * * This function is included in dia_canvas_element_handle_motion(), to * provide rotation for the corner-handles. **/ static inline void dia_canvas_element_handle_motion_rotate (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy, gboolean pinned) { gdouble x, y; gdouble ix,iy; gdouble a, b; gdouble alpha, angle; dia_handle_get_pos_i (handle, &x, &y); ix = *wx; iy = *wy; dia_canvas_item_affine_point_w2i (item, &ix, &iy); /* (a, b) is the center of the object. */ a = item->bounds.left + (item->bounds.right - item->bounds.left) / 2; b = item->bounds.top + (item->bounds.bottom - item->bounds.top) / 2; /* if (pinned) { gdouble ta = a, tb = b; a = item->affine[0] * ta + item->affine[2] * tb; b = item->affine[1] * ta + item->affine[3] * tb; } */ alpha = atan2 ((a - ix), (b - iy)); if (pinned) { alpha = (((int) (alpha / M_PI * 180.0)) / 5) * 5; //g_message (G_STRLOC": alpha = %f", alpha); alpha = alpha * M_PI / 180.0; } angle = (atan2 ((a - x), (b - y)) - alpha) / M_PI * 180.0; dia_canvas_item_rotate (item, angle); align_handles (item); dia_handle_get_pos_w (handle, wx, wy); } /** * dia_canvas_element_handle_motion_shear: * @item: * @handle: * @wx: * @wy: * * Included in dia_canvas_element_handle_motion(). Used to shear the North, * South, east and West handles (when CTRL is pressed). **/ static inline void dia_canvas_element_handle_motion_shear (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy) { gdouble x, y; gdouble ix, iy; //gdouble tx, ty; gdouble affine[6]; dia_handle_get_pos_i (handle, &x, &y); //dia_handle_get_pos_w (handle, &ix, &iy); dia_canvas_item_affine_w2i (item, affine); ix = *wx; iy = *wy; dia_canvas_item_affine_point_w2i (item, &ix, &iy); /* We need to ignore the x or y variable of the cursor position * (*wx, *wy). Give it the handle's position. */ switch (GET_HANDLE_DIR (item, handle)) { case DIA_HANDLE_N: dia_canvas_item_shear_x (item, x - ix, y - iy); break; case DIA_HANDLE_S: dia_canvas_item_shear_x (item, ix - x, iy - y); break; case DIA_HANDLE_E: dia_canvas_item_shear_y (item, ix - x, iy - y); break; case DIA_HANDLE_W: dia_canvas_item_shear_y (item, x - ix, y - iy); break; default: g_assert_not_reached (); } align_handles (item); /* Force an i->w update */ dia_handle_get_pos_w (handle, wx, wy); } /** * dia_canvas_element_handle_motion: * @item: * @handle: * @wx: * @wy: * @mask: * * Move a handle, inlines dia_canvas_element_handle_motion_move(), * dia_canvas_element_handle_motion_rotate() and * dia_canvas_element_handle_motion_shear(). **/ static void dia_canvas_element_handle_motion (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy, DiaEventMask mask) { if (mask & DIA_EVENT_MASK_CTRL) { guint dir = GET_HANDLE_DIR (item, handle); switch (dir) { case DIA_HANDLE_NW: case DIA_HANDLE_NE: case DIA_HANDLE_SE: case DIA_HANDLE_SW: dia_canvas_element_handle_motion_rotate (item, handle, wx, wy, mask & DIA_EVENT_MASK_SHIFT); break; case DIA_HANDLE_N: case DIA_HANDLE_S: case DIA_HANDLE_E: case DIA_HANDLE_W: dia_canvas_element_handle_motion_shear (item, handle, wx, wy); break; default: g_assert_not_reached (); } } else if (mask & DIA_EVENT_MASK_SHIFT) { gdouble hx, hy, ox, oy; DiaHandle *opphandle = dia_canvas_element_get_opposite_handle (item, handle); dia_handle_get_pos_w (handle, &hx, &hy); dia_handle_get_pos_w (opphandle, &ox, &oy); ox -= *wx - hx; oy -= *wy - hy; g_object_freeze_notify ((GObject*) item); dia_canvas_element_handle_motion_move (item, handle, wx, wy); dia_canvas_element_handle_motion_move (item, opphandle, &ox, &oy); g_object_thaw_notify ((GObject*) item); } else { dia_canvas_element_handle_motion_move (item, handle, wx, wy); } dia_canvas_item_request_update (item); } /** * calc_glue_point: * @item: * @handle: * @p: in item coordinates! * @segment: First segment, clockwise. * * * * Return value: **/ static gdouble calc_glue_point (DiaCanvasItem *item, DiaHandle *handle, DiaPoint *p, gint *segment) { DiaPoint ul, ur, ll, lr; DiaPoint pol1, pol2, pol3, pol4; gdouble min, d1, d2, d3, d4; dia_handle_get_pos_i (GET_HANDLE (item, DIA_HANDLE_NW), &ul.x, &ul.y); dia_handle_get_pos_i (GET_HANDLE (item, DIA_HANDLE_SE), &lr.x, &lr.y); ur.x = lr.x; ur.y = ul.y; ll.x = ul.x; ll.y = lr.y; d1 = dia_distance_line_point (&ul, &ur, p, 0.0, DIA_CAP_BUTT, &pol1); d2 = dia_distance_line_point (&ur, &lr, p, 0.0, DIA_CAP_BUTT, &pol2); d3 = dia_distance_line_point (&ll, &lr, p, 0.0, DIA_CAP_BUTT, &pol3); d4 = dia_distance_line_point (&ul, &ll, p, 0.0, DIA_CAP_BUTT, &pol4); min = MIN (MIN(d1, d2), MIN(d3, d4)); //art_affine_invert (inv, a); if (min == d1) { if (p) *p = pol1; if (segment) *segment = DIA_HANDLE_NW; return d1; } else if (min == d2) { if (p) *p = pol2; if (segment) *segment = DIA_HANDLE_NE; return d2; } else if (min == d3) { if (p) *p = pol3; if (segment) *segment = DIA_HANDLE_SE; return d3; } else { if (p) *p = pol4; if (segment) *segment = DIA_HANDLE_SW; return d4; } } static gdouble dia_canvas_element_glue (DiaCanvasItem *item, DiaHandle *handle, gdouble *x, gdouble *y) { DiaPoint p; gdouble a[6], inv[6], dist; dia_canvas_item_affine_w2i (item, a); p.x = *x * a[0] + *y * a[2] + a[4]; p.y = *x * a[1] + *y * a[3] + a[5]; dist = calc_glue_point (item, handle, &p, NULL); art_affine_invert (inv, a); *x = p.x * inv[0] + p.y * inv[2] + inv[4]; *y = p.x * inv[1] + p.y * inv[3] + inv[5]; return dist; } static gboolean dia_canvas_element_connect (DiaCanvasItem *item, DiaHandle *handle) { //DiaCanvasElement *el = (DiaCanvasElement*) item; DiaHandle *hbegin, *hend = NULL; gint segment; DiaPoint p; if (!parent_class->connect (item, handle)) return FALSE; dia_handle_get_pos_w (handle, &p.x, &p.y); dia_canvas_item_affine_point_w2i (item, &p.x, &p.y); calc_glue_point (item, handle, &p, &segment); dia_canvas_item_affine_point_i2w (item, &p.x, &p.y); dia_handle_set_pos_w (handle, p.x, p.y); hbegin = GET_HANDLE(item, segment); switch (segment) { case DIA_HANDLE_NW: hend = GET_HANDLE (item, DIA_HANDLE_NE); break; case DIA_HANDLE_NE: hend = GET_HANDLE (item, DIA_HANDLE_SE); break; case DIA_HANDLE_SE: hend = GET_HANDLE (item, DIA_HANDLE_SW); break; case DIA_HANDLE_SW: hend = GET_HANDLE (item, DIA_HANDLE_NW); break; default: g_assert_not_reached (); } g_assert (hbegin != NULL); g_assert (hend != NULL); dia_handle_remove_all_constraints (handle); dia_handle_add_line_constraint (hbegin, hend, handle); return TRUE; } static gboolean dia_canvas_element_disconnect (DiaCanvasItem *item, DiaHandle *handle) { return parent_class->disconnect (item, handle); }