/* * DiaCanvasLine * * This is LGPL'ed code. */ #include "dia-canvas-line.h" #include #include "dia-handle.h" #include "dia-shape.h" #include "diatypebuiltins.h" #include "dia-canvas-i18n.h" #define NUM_ARROW_POINTS 4 enum { PROP_LINE_WIDTH = 1, PROP_COLOR, PROP_CAP, PROP_JOIN, PROP_CYCLIC, PROP_ORTHOGONAL, PROP_HORIZONTAL, PROP_ADD_POINT, PROP_ADD_SEGMENT, PROP_DEL_SEGMENT, PROP_DASH, PROP_HEAD_POS, PROP_TAIL_POS, PROP_HAS_HEAD, PROP_HEAD_A, PROP_HEAD_B, PROP_HEAD_C, PROP_HEAD_D, PROP_HEAD_COLOR, PROP_HEAD_FILL_COLOR, PROP_HAS_TAIL, PROP_TAIL_A, PROP_TAIL_B, PROP_TAIL_C, PROP_TAIL_D, PROP_TAIL_COLOR, PROP_TAIL_FILL_COLOR }; #define get_connected_handles(handle) \ g_object_steal_qdata(G_OBJECT(handle), q_connected_handles) #define set_connected_handles(handle, list) \ g_object_set_qdata_full(G_OBJECT(handle), q_connected_handles, \ list, dia_canvas_line_destroy_connected_handles) static void dia_canvas_line_class_init (DiaCanvasLineClass *klass); static void dia_canvas_line_init (DiaCanvasLine *item); static void dia_canvas_line_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void dia_canvas_line_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void dia_canvas_line_dispose (GObject *object); static void dia_canvas_line_update (DiaCanvasItem *item, gdouble affine[6]); static gboolean dia_canvas_line_get_shape_iter (DiaCanvasItem *item, DiaCanvasIter *iter); static gboolean dia_canvas_line_shape_next (DiaCanvasItem *item, DiaCanvasIter *iter); static DiaShape* dia_canvas_line_shape_value (DiaCanvasItem *item, DiaCanvasIter *iter); static gdouble dia_canvas_line_point (DiaCanvasItem *item, gdouble x, gdouble y); static void dia_canvas_line_handle_motion (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy, DiaEventMask mask); static gdouble dia_canvas_line_glue (DiaCanvasItem *item, DiaHandle *handle, gdouble *x, gdouble *y); static gboolean dia_canvas_line_connect (DiaCanvasItem *item, DiaHandle *handle); static gboolean dia_canvas_line_disconnect (DiaCanvasItem *item, DiaHandle *handle); static void dia_canvas_line_parent_notify (DiaCanvasLine *line); static void dia_canvas_line_destroy_connected_handles (gpointer data); static void add_ortho_constraints (DiaCanvasLine *line); static void remove_ortho_constraints (DiaCanvasLine *line); static DiaCanvasItemClass *parent_class = NULL; /* Quark for othogonal constraints. */ static GQuark q_ortho_constraint = 0; /* A list of handles that are connected to a specific line segment. * The list is of type GSList* and is connected to the last handle of a * segment (this means that handle 0 will never have a list of connected * handles). This list is used in case the user creates or deletes a line * segment. The line is able to create new constraints for the connected * handles (note that the handle->owner::connect() function is not used here. */ static GQuark q_connected_handles = 0; GType dia_canvas_line_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaCanvasLineClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_canvas_line_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaCanvasLine), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_canvas_line_init, }; object_type = g_type_register_static (DIA_TYPE_CANVAS_ITEM, "DiaCanvasLine", &object_info, 0); q_ortho_constraint = g_quark_from_static_string ("DiaCanvasLine::ortho_constraint"); q_connected_handles = g_quark_from_static_string ("DiaCanvasLine::connected_handles"); } return object_type; } static void dia_canvas_line_class_init (DiaCanvasLineClass *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->dispose = dia_canvas_line_dispose; object_class->set_property = dia_canvas_line_set_property; object_class->get_property = dia_canvas_line_get_property; item_class->update = dia_canvas_line_update; item_class->get_shape_iter = dia_canvas_line_get_shape_iter; item_class->shape_next = dia_canvas_line_shape_next; item_class->shape_value = dia_canvas_line_shape_value; item_class->point = dia_canvas_line_point; item_class->handle_motion = dia_canvas_line_handle_motion; item_class->glue = dia_canvas_line_glue; item_class->connect = dia_canvas_line_connect; item_class->disconnect = dia_canvas_line_disconnect; g_object_class_install_property (object_class, PROP_LINE_WIDTH, g_param_spec_double ("line_width", _("Line width"), _("Width of the line"), 0.0, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_COLOR, g_param_spec_ulong ("color", _("Color"), _("Color of the line in RGBA"), 0, G_MAXULONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CAP, g_param_spec_enum ("cap", _("Cap style"), _("Style of the line ends"), DIA_TYPE_CAP_STYLE, DIA_CAP_BUTT, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_JOIN, g_param_spec_enum ("join", _("Join style"), _("Style of the line's corners"), DIA_TYPE_JOIN_STYLE, DIA_JOIN_MITER, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CYCLIC, g_param_spec_boolean ("cyclic", _("Cyclic"), _("Line is cyclic, a polygon"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ORTHOGONAL, g_param_spec_boolean ("orthogonal", _("Orthogonal"), _("Add contains to the line points to make them orthogonal (horizontal and vertical)."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HORIZONTAL, g_param_spec_boolean ("horizontal", _("Horizontal"), _("Make the first segment of the line horizontal. This should be used in conjuncion with the 'orthogonal' option."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ADD_POINT, g_param_spec_boxed ("add_point", _("Add point"), _("Point to add to the line"), DIA_TYPE_POINT, G_PARAM_WRITABLE)); g_object_class_install_property (object_class, PROP_ADD_SEGMENT, g_param_spec_uint ("add_segment", _("Add segment"), _("Add a segment by splitting the given segment into two."), 0, G_MAXUINT, 0, G_PARAM_WRITABLE)); g_object_class_install_property (object_class, PROP_DEL_SEGMENT, g_param_spec_uint ("del_segment", _("Delete segment"), _("Delete a segment, first segment is indexed with 0."), 0, G_MAXUINT, 0, G_PARAM_WRITABLE)); g_object_class_install_property (object_class, PROP_DASH, g_param_spec_boxed ("dash", _("Dash"), _("Dash style, of type DiaDashStyle."), DIA_TYPE_DASH_STYLE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_POS, g_param_spec_boxed ("head_pos", _("Head point position"), _(""), DIA_TYPE_POINT, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_POS, g_param_spec_boxed ("tail_pos", _("Tail point position"), _(""), DIA_TYPE_POINT, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HAS_HEAD, g_param_spec_boolean ("has_head", _("Has Head"), _("The line should have an arrow on the beginning of the line. The arrow is descripbed by 'head_[abcd]'."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HAS_TAIL, g_param_spec_boolean ("has_tail", _("Has Tail"), _("The line should have an arrow on the end of the line. The arrow is descripbed by 'tail_[abcd]'."), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_COLOR, g_param_spec_ulong ("head_color", _("Head color"), _("Color of the head arrow in RGBA"), 0, G_MAXULONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_COLOR, g_param_spec_ulong ("tail_color", _("Tail color"), _("Color of the end arrow in RGBA"), 0, G_MAXULONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_FILL_COLOR, g_param_spec_ulong ("head_fill_color", _("Head fill color"), _("Color of the line in RGBA"), 0, G_MAXULONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_FILL_COLOR, g_param_spec_ulong ("tail_fill_color", _("Tail fill color"), _("Color of the line in RGBA"), 0, G_MAXULONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_A, g_param_spec_double ("head_a", _("Head A"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_B, g_param_spec_double ("head_b", _("Head B"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_C, g_param_spec_double ("head_c", _("Head C"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_HEAD_D, g_param_spec_double ("head_d", _("Head D"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_A, g_param_spec_double ("tail_a", _("Tail A"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_B, g_param_spec_double ("tail_b", _("Tail B"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_C, g_param_spec_double ("tail_c", _("Tail C"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAIL_D, g_param_spec_double ("tail_d", _("Tail D"), _(""), 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); } static void dia_canvas_line_init (DiaCanvasLine *item) { int i; item->color = DIA_COLOR (0, 0, 0); item->line_width = 2.0; item->cap = DIA_CAP_ROUND; item->join = DIA_JOIN_ROUND; item->cyclic = FALSE; item->orthogonal = FALSE; item->horizontal = TRUE; item->n_dash = 0; item->dash = NULL; item->has_head = FALSE; item->has_tail = FALSE; item->head_a = 0.0; item->head_b = 0.0; item->head_c = 0.0; item->head_d = 0.0; item->tail_a = 0.0; item->tail_b = 0.0; item->tail_c = 0.0; item->tail_d = 0.0; item->head_color = DIA_COLOR (0, 0, 0); item->tail_color = DIA_COLOR (0, 0, 0); item->head_fill_color = DIA_COLOR (0, 0, 0); item->tail_fill_color = DIA_COLOR (0, 0, 0); item->line = dia_shape_new (DIA_SHAPE_PATH); item->head_arrow = dia_shape_new (DIA_SHAPE_PATH); item->tail_arrow = dia_shape_new (DIA_SHAPE_PATH); /* A line should have at least two points: */ for (i = 0; i < 2; i++) { g_object_new (DIA_TYPE_HANDLE, "owner", item, "movable", TRUE, "connectable", TRUE, NULL); } /* Set a signal that updates a few variables when the parent changes: */ g_signal_connect (G_OBJECT (item), "notify::parent", G_CALLBACK (dia_canvas_line_parent_notify), NULL); } static void dia_canvas_line_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DiaCanvasItem *item = (DiaCanvasItem*) object; DiaCanvasLine *line = (DiaCanvasLine*) object; switch (property_id) { case PROP_LINE_WIDTH: dia_canvas_item_preserve_property (item, "line_width"); line->line_width = g_value_get_double (value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_COLOR: dia_canvas_item_preserve_property (item, "color"); line->color = (DiaColor) g_value_get_ulong (value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_CAP: dia_canvas_item_preserve_property (item, "cap"); line->cap = (DiaCapStyle) g_value_get_enum (value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_JOIN: dia_canvas_item_preserve_property (item, "join"); line->join = (DiaJoinStyle) g_value_get_enum (value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_CYCLIC: dia_canvas_item_preserve_property (item, "cyclic"); line->cyclic = g_value_get_boolean (value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_ORTHOGONAL: dia_canvas_item_preserve_property (item, "orthogonal"); if (!item->canvas) { line->orthogonal = g_value_get_boolean (value); break; } if (!line->orthogonal) { GList *l; for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } line->orthogonal = g_value_get_boolean (value); if (line->orthogonal) { add_ortho_constraints (line); } else { remove_ortho_constraints (line); } dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_HORIZONTAL: dia_canvas_item_preserve_property (item, "horizontal"); if (!item->canvas) { line->horizontal = g_value_get_boolean (value); break; } if (line->orthogonal) { GList *l; for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } line->horizontal = g_value_get_boolean (value); if (line->orthogonal) { add_ortho_constraints (line); } dia_canvas_item_request_update (item); break; case PROP_ADD_POINT: { DiaPoint *p = g_value_get_boxed (value); DiaHandle *h; /* Add constraints when needed: */ if (item->canvas && line->orthogonal) { GList *l; dia_canvas_item_preserve_property (item, "orthogonal"); for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } if (g_list_length (item->handles) > 1) { h = g_list_last (item->handles)->data; g_object_set (h, "connectable", FALSE, NULL);; if (item->canvas && h->connected_to) dia_canvas_item_disconnect (h->connected_to, h); } h = g_object_new (DIA_TYPE_HANDLE, "owner", item, "pos_i", p, "movable", TRUE, "connectable", TRUE, NULL); if (line->orthogonal) add_ortho_constraints (line); } dia_canvas_item_request_update (item); break; case PROP_ADD_SEGMENT: { guint segment; GSList *handles_conn = NULL, *hc; DiaPoint p1, p2, pnew; DiaHandle *h1, *h2, *hnew; segment = g_value_get_uint(value); if ((segment < 0) && (segment > g_list_length (item->handles) - 2)) { g_warning (_("The new segment should be between 0 and %d inclusive (was %d)"), g_list_length (item->handles) - 2, segment); break; } /* Store original handle positions. */ if (item->canvas && line->orthogonal) { GList *l; dia_canvas_item_preserve_property (item, "orthogonal"); for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } h1 = g_list_nth_data (item->handles, segment); h2 = g_list_nth_data (item->handles, segment + 1); dia_handle_get_pos_i (h1, &p1.x, &p1.y); dia_handle_get_pos_i (h2, &p2.x, &p2.y); /* Create the new handle and put it into place */ pnew.x = (p1.x + p2.x) / 2.0; pnew.y = (p1.y + p2.y) / 2.0; hnew = g_object_new (DIA_TYPE_HANDLE, "owner", item, "pos_i", &pnew, "movable", TRUE, "connectable", FALSE); /* Move the handle between h1 and h2: */ item->handles = g_list_remove (item->handles, hnew); item->handles = g_list_insert (item->handles, hnew, segment + 1); /* Find all connected handles: */ handles_conn = get_connected_handles (h2); /* Reconnect handles connected to h2. We should figure * out if they should be added to h2 or hnew. */ for (hc = handles_conn; hc != NULL; hc = hc->next) { DiaHandle *handle = hc->data; gdouble dist1, dist2; DiaPoint hpos, pos1, pos2; DiaHandle *hbegin, *hend; /* Make the handle move undoable. */ dia_handle_preserve_state (handle); dia_handle_remove_all_constraints (handle); dia_handle_get_pos_w (handle, &hpos.x, &hpos.y); dia_canvas_item_affine_point_w2i (item, &hpos.x, &hpos.y); dist1 = dia_distance_line_point (&p1, &pnew, &hpos, line->line_width, DIA_CAP_ROUND, &pos1); dist2 = dia_distance_line_point (&pnew, &p2, &hpos, line->line_width, DIA_CAP_ROUND, &pos2); if (dist1 < dist2) { hbegin = h1; hend = hnew; } else { hbegin = hnew; hend = h2; } //dia_canvas_preserve_property_last (item->canvas, // G_OBJECT (handle), // "pos_i"); //dia_canvas_preserve_property_last (item->canvas, // G_OBJECT (handle), // "connect"); dia_handle_add_line_constraint (hbegin, hend, handle); /* Store the newly connected handle */ handles_conn = get_connected_handles (hend); handles_conn = g_slist_prepend (handles_conn, g_object_ref (handle)); set_connected_handles (hend, handles_conn); } if (line->orthogonal) add_ortho_constraints (line); } dia_canvas_item_request_update (item); break; case PROP_DEL_SEGMENT: { DiaHandle *h1, *h2, *h3; DiaPoint p1, p2, p3, pnew; GSList *handles_conn = NULL, *hc; guint segment; if (g_list_length (item->handles) < 3) { g_warning (_("Not enough line segments.")); break; } segment = g_value_get_uint(value); /* Store original handle positions. */ if (line->orthogonal) { GList *l; dia_canvas_item_preserve_property (item, "orthogonal"); for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } h1 = g_list_nth_data (item->handles, segment); h2 = g_list_nth_data (item->handles, segment + 1); h3 = g_list_nth_data (item->handles, segment + 2); /* We always try to remove a handle in the middle. */ if (!h3 && segment > 0) { h3 = h2; h2 = h1; h1 = g_list_nth_data (item->handles, segment - 1); } g_assert (h3); if (line->orthogonal) remove_ortho_constraints (line); dia_handle_get_pos_i (h1, &p1.x, &p1.y); dia_handle_get_pos_i (h3, &p3.x, &p3.y); /* Also collect the connected handles from * 'segment + 1'. */ handles_conn = g_slist_concat (get_connected_handles (h2), get_connected_handles (h3)); set_connected_handles (h3, handles_conn); /* Remove the constraints from all handles in the * 'handles_conn' list. We create new constraints * between 'h1 and 'h3'. Be sure this behaves the * same as the _connect() function. * We can not do a simple "connect", cause then the * handles are connected to the closest line segment. * This might not be segment h1-h3... */ for (hc = handles_conn; hc != NULL; hc = hc->next) { DiaHandle *handle = hc->data; dia_handle_get_pos_w (handle, &p2.x, &p2.y); dia_canvas_item_affine_point_w2i (item, &p2.x, &p2.y); /* Make the handle move undoable. */ dia_handle_preserve_state (handle); dia_handle_remove_all_constraints (handle); dia_distance_line_point (&p1, &p3, &p2, line->line_width, DIA_CAP_ROUND, &pnew); dia_canvas_item_affine_point_i2w (item, &pnew.x, &pnew.y); //dia_canvas_preserve_property_last (item->canvas, // G_OBJECT (handle), // "pos_i"); //dia_canvas_preserve_property_last (item->canvas, // G_OBJECT (handle), // "connect"); dia_handle_set_pos_w (handle, pnew.x, pnew.y); dia_handle_add_line_constraint (h1, h3, handle); } g_object_set (G_OBJECT (h2), "owner", NULL, NULL); if (line->orthogonal) add_ortho_constraints (line); } dia_canvas_item_request_update (item); break; case PROP_DASH: { DiaDashStyle *dash; dash = (DiaDashStyle*) g_value_get_boxed(value); g_free (line->dash); line->dash = NULL; line->n_dash = 0; if (dash != NULL) { line->n_dash = dash->n_dash; if (line->n_dash == 0) break; line->dash = g_new (gdouble, line->n_dash); memcpy (line->dash, dash->dash, sizeof (gdouble) * line->n_dash); } } dia_canvas_item_request_update (item); break; case PROP_HEAD_POS: { DiaPoint *pos; DiaHandle *handle; dia_canvas_item_preserve_property (item, "head_pos"); pos = g_value_get_boxed (value); handle = g_list_first (item->handles)->data; dia_handle_set_pos_i (handle, pos->x, pos->y); } break; case PROP_TAIL_POS: { DiaPoint *pos; DiaHandle *handle; dia_canvas_item_preserve_property (item, "tail_pos"); pos = g_value_get_boxed (value); handle = g_list_last (item->handles)->data; dia_handle_set_pos_i (handle, pos->x, pos->y); } break; case PROP_HAS_HEAD: dia_canvas_item_preserve_property (item, "has_head"); line->has_head = g_value_get_boolean(value); dia_canvas_item_request_update (item); break; case PROP_HAS_TAIL: dia_canvas_item_preserve_property (item, "has_tail"); line->has_tail = g_value_get_boolean(value); dia_canvas_item_request_update (item); break; case PROP_HEAD_A: dia_canvas_item_preserve_property (item, "head_a"); line->head_a = g_value_get_double(value); dia_canvas_item_request_update (item); break; case PROP_HEAD_B: dia_canvas_item_preserve_property (item, "head_b"); line->head_b = g_value_get_double(value); dia_canvas_item_request_update (item); break; case PROP_HEAD_C: dia_canvas_item_preserve_property (item, "head_c"); line->head_c = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_HEAD_D: dia_canvas_item_preserve_property (item, "head_d"); line->head_d = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_A: dia_canvas_item_preserve_property (item, "tail_a"); line->tail_a = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_B: dia_canvas_item_preserve_property (item, "tail_b"); line->tail_b = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_C: dia_canvas_item_preserve_property (item, "tail_c"); line->tail_c = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_D: dia_canvas_item_preserve_property (item, "tail_d"); line->tail_d = g_value_get_double(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_HEAD_COLOR: dia_canvas_item_preserve_property (item, "head_color"); line->head_color = g_value_get_ulong(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_COLOR: dia_canvas_item_preserve_property (item, "tail_color"); line->tail_color = g_value_get_ulong(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_HEAD_FILL_COLOR: dia_canvas_item_preserve_property (item, "head_fill_color"); line->head_fill_color = g_value_get_ulong(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; case PROP_TAIL_FILL_COLOR: dia_canvas_item_preserve_property (item, "tail_fill_color"); line->tail_fill_color = g_value_get_ulong(value); dia_canvas_item_request_update (DIA_CANVAS_ITEM (line)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void dia_canvas_line_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_LINE_WIDTH: g_value_set_double(value, DIA_CANVAS_LINE (object)->line_width); break; case PROP_COLOR: g_value_set_ulong(value, DIA_CANVAS_LINE (object)->color); break; case PROP_CAP: g_value_set_enum(value, DIA_CANVAS_LINE (object)->cap); break; case PROP_JOIN: g_value_set_enum(value, DIA_CANVAS_LINE (object)->join); break; case PROP_CYCLIC: g_value_set_boolean(value, DIA_CANVAS_LINE (object)->cyclic); break; case PROP_ORTHOGONAL: g_value_set_boolean(value, DIA_CANVAS_LINE (object)->orthogonal); break; case PROP_HORIZONTAL: g_value_set_boolean(value, DIA_CANVAS_LINE (object)->horizontal); break; case PROP_HEAD_POS: { DiaPoint pos; DiaHandle *handle = g_list_first (DIA_CANVAS_ITEM (object)->handles)->data; dia_handle_get_pos_i (handle, &pos.x, &pos.y); g_value_set_boxed (value, &pos); } break; case PROP_TAIL_POS: { DiaPoint pos; DiaHandle *handle = g_list_last (DIA_CANVAS_ITEM (object)->handles)->data; dia_handle_get_pos_i (handle, &pos.x, &pos.y); g_value_set_boxed (value, &pos); } break; case PROP_HAS_HEAD: g_value_set_boolean(value, DIA_CANVAS_LINE (object)->has_head); break; case PROP_HAS_TAIL: g_value_set_boolean(value, DIA_CANVAS_LINE (object)->has_tail); break; case PROP_HEAD_A: g_value_set_double(value, DIA_CANVAS_LINE (object)->head_a); break; case PROP_HEAD_B: g_value_set_double(value, DIA_CANVAS_LINE (object)->head_b); break; case PROP_HEAD_C: g_value_set_double(value, DIA_CANVAS_LINE (object)->head_c); break; case PROP_HEAD_D: g_value_set_double(value, DIA_CANVAS_LINE (object)->head_d); break; case PROP_TAIL_A: g_value_set_double(value, DIA_CANVAS_LINE (object)->tail_a); break; case PROP_TAIL_B: g_value_set_double(value, DIA_CANVAS_LINE (object)->tail_b); break; case PROP_TAIL_C: g_value_set_double(value, DIA_CANVAS_LINE (object)->tail_c); break; case PROP_TAIL_D: g_value_set_double(value, DIA_CANVAS_LINE (object)->tail_d); break; case PROP_HEAD_COLOR: g_value_set_ulong(value, DIA_CANVAS_LINE (object)->head_color); break; case PROP_TAIL_COLOR: g_value_set_ulong(value, DIA_CANVAS_LINE (object)->tail_color); break; case PROP_HEAD_FILL_COLOR: g_value_set_ulong(value, DIA_CANVAS_LINE (object)->head_fill_color); break; case PROP_TAIL_FILL_COLOR: g_value_set_ulong(value, DIA_CANVAS_LINE (object)->tail_fill_color); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void dia_canvas_line_dispose (GObject *object) { DiaCanvasLine *line = (DiaCanvasLine*) object; /*for (h = DIA_CANVAS_ITEM (object)->handles; h != NULL; h = h->next) { handles_conn = g_object_get_qdata (object, q_connected_handles); g_object_set_qdata (object, q_connected_handles, NULL); g_slist_free (handles_conn); } */ if (line->line) { dia_shape_free (line->line); line->line = NULL; dia_shape_free (line->head_arrow); line->head_arrow = NULL; dia_shape_free (line->tail_arrow); line->tail_arrow = NULL; } remove_ortho_constraints (DIA_CANVAS_LINE (object)); G_OBJECT_CLASS (parent_class)->dispose (object); } /** * create_arrow: * @shape_a: Distance from tip of arrowhead to the center point. * @shape_b: Distance from tip of arrowhead to trailing point, * measured along the shaft. * @shape_c: Distance of trailing point from the shaft (bottom half). * @shape_d: Distance of trailing point from the shaft (top half). * @point: Last bot one point on the line. * @end_point: INOUT Last point on the line. This point will be adjusted * wrt the arrow. * @p: OUT Array of NUM_ARROW_POINTS points. will contain the arrow. * * Create a GnomeCanvasLine like arrow shape for the line. @end_point is * adjusted to attach to the beginpoint of the arrow. * p3|a| * p0/\ d * (point)=============< > p2 * \/ c * | b|p1 **/ static void create_arrow (gdouble shape_a, gdouble shape_b, gdouble shape_c, gdouble shape_d, DiaPoint *point, DiaPoint *end_point, DiaPoint p[NUM_ARROW_POINTS]) { double dx, dy, length; double sin_theta, cos_theta; p[2].x = end_point->x; p[2].y = end_point->y; dx = p[2].x - point->x; dy = p[2].y - point->y; length = sqrt (dx * dx + dy * dy); if (length < 1e-10) { sin_theta = cos_theta = 0.0; } else { sin_theta = dy / length; cos_theta = dx / length; } p[0].x = p[2].x - shape_a * cos_theta; p[0].y = p[2].y - shape_a * sin_theta; p[1].x = p[2].x - shape_b * cos_theta + shape_c * sin_theta; p[1].y = p[2].y - shape_b * sin_theta - shape_c * cos_theta; p[3].x = p[2].x - shape_b * cos_theta - shape_d * sin_theta; p[3].y = p[2].y - shape_b * sin_theta + shape_d * cos_theta; /* Move the first point towards the second so that the corners at * the end of the line are inside the arrowhead. */ *end_point = p[0]; } static void dia_canvas_line_update (DiaCanvasItem *item, gdouble affine[6]) { DiaCanvasLine *line = DIA_CANVAS_LINE (item); DiaPoint *points, *p; gint len, i; gdouble half_lw = line->line_width / 2.0; GList *l; if (DIA_CANVAS_ITEM_CLASS (parent_class)->update) DIA_CANVAS_ITEM_CLASS (parent_class)->update (item, affine); len = g_list_length (item->handles); /* NB. allocated on stack! no free()! */ points = g_newa (DiaPoint, len); p = points; /* First the bounding box. This also fills the array of points * needed for the Path shape. */ if (item->handles) { dia_handle_get_pos_i (item->handles->data, &p->x, &p->y); item->bounds.left = item->bounds.right = p->x; item->bounds.top = item->bounds.bottom = p->y; p++; for (l = item->handles->next; l != NULL; l = l->next, p++) { dia_handle_get_pos_i (l->data, &p->x, &p->y); dia_rectangle_add_point (&item->bounds, p); } } /* Arrows */ if (line->has_head) { DiaPoint arrow[NUM_ARROW_POINTS]; create_arrow(line->head_a, line->head_b, line->head_c, line->head_d, &points[1], &points[0] /* endpoint */, arrow); /* Border */ dia_shape_polygon (line->head_arrow, NUM_ARROW_POINTS, arrow); dia_shape_path_set_line_width (line->head_arrow, line->line_width); dia_shape_path_set_join (line->head_arrow, line->join); dia_shape_path_set_cap (line->head_arrow, line->cap); dia_shape_set_color (line->head_arrow, line->head_color); dia_shape_path_set_fill (line->head_arrow, DIA_FILL_SOLID); dia_shape_path_set_fill_color (line->head_arrow, line->head_fill_color); dia_shape_path_set_cyclic(line->head_arrow, TRUE); /* Extend the bounding box by the arrow head. */ for (i = 0; i < NUM_ARROW_POINTS; i++) dia_rectangle_add_point (&item->bounds, &arrow[i]); } else { dia_shape_set_color (line->head_arrow, DIA_COLOR_A(0,0,0,0)); } if (line->has_tail) { DiaPoint arrow[NUM_ARROW_POINTS]; create_arrow(line->tail_a, line->tail_b, line->tail_c, line->tail_d, &points[len - 2], &points[len - 1], arrow); /* Border */ dia_shape_polygon (line->tail_arrow, NUM_ARROW_POINTS, arrow); dia_shape_path_set_line_width (line->tail_arrow, line->line_width); dia_shape_path_set_join (line->tail_arrow, line->join); dia_shape_path_set_cap (line->tail_arrow, line->cap); dia_shape_set_color (line->tail_arrow, line->tail_color); dia_shape_path_set_fill (line->tail_arrow, DIA_FILL_SOLID); dia_shape_path_set_fill_color (line->tail_arrow, line->tail_fill_color); dia_shape_path_set_cyclic (line->tail_arrow, TRUE); /* Extend the bounding box by the arrow head. */ for (i = 0; i < NUM_ARROW_POINTS; i++) dia_rectangle_add_point (&item->bounds, &arrow[i]); } else { dia_shape_set_color (line->tail_arrow, DIA_COLOR_A(0,0,0,0)); } /* The line. */ dia_shape_polyline (line->line, len, points); dia_shape_path_set_line_width (line->line, line->line_width); dia_shape_path_set_dash (line->line, 0.0, line->n_dash, line->dash); dia_shape_set_color (line->line, line->color); dia_shape_path_set_join (line->line, line->join); dia_shape_path_set_cap (line->line, line->cap); dia_shape_path_set_cyclic (line->line, line->cyclic); dia_shape_request_update (line->line); //g_free (points); /* Update bounds (+/- .5 * linewidth) */ item->bounds.left -= half_lw; item->bounds.top -= half_lw; item->bounds.right += half_lw; item->bounds.bottom += half_lw; } static gboolean dia_canvas_line_get_shape_iter (DiaCanvasItem *item, DiaCanvasIter *iter) { iter->data[0] = DIA_CANVAS_LINE (item)->line; return TRUE; } static gboolean dia_canvas_line_shape_next (DiaCanvasItem *item, DiaCanvasIter *iter) { DiaCanvasLine *line = (DiaCanvasLine*) item; DiaShape *shape = iter->data[0]; if (shape == line->line) { if (line->has_head) iter->data[0] = line->head_arrow; else if (line->has_tail) iter->data[0] = line->tail_arrow; else iter->data[0] = NULL; } else if (shape == line->head_arrow && line->has_tail) iter->data[0] = line->tail_arrow; else iter->data[0] = NULL; return (iter->data[0] != NULL ? TRUE : FALSE); } static DiaShape* dia_canvas_line_shape_value (DiaCanvasItem *item, DiaCanvasIter *iter) { return iter->data[0]; } static gdouble calc_closest_point (DiaCanvasItem *item, gdouble x, gdouble y, gint *segment, DiaPoint *closest) { DiaCanvasLine *line = (DiaCanvasLine*) item; gdouble d, dist = G_MAXDOUBLE; gint s; DiaPoint c = { 0, 0 }; DiaPoint p1, p2; DiaPoint cursor; GList *l; if (! item->handles) return G_MAXDOUBLE; cursor.x = x; cursor.y = y; l = item->handles; dia_handle_get_pos_i ((DiaHandle*) l->data, &p1.x, &p1.y); l = l->next; for (s = 1; l != NULL; l = l->next, s++) { p2 = p1; dia_handle_get_pos_i ((DiaHandle*) l->data, &p1.x, &p1.y); d = dia_distance_line_point (&p1, &p2, &cursor, line->line_width, DIA_CAP_ROUND, &c); if (d < dist) { dist = d; if (closest) *closest = c; if (segment) *segment = s; } } return dist; } static gdouble dia_canvas_line_point (DiaCanvasItem *item, gdouble x, gdouble y) { return calc_closest_point (item, x, y, NULL, NULL); } static void dia_canvas_line_handle_motion (DiaCanvasItem *item, DiaHandle *handle, gdouble *wx, gdouble *wy, DiaEventMask mask) { DiaCanvasLine *line = (DiaCanvasLine*) item; if (!item->canvas) return; /* For orthogonal lines save all handle positions, since the * ortho-constraints might mess up the undo-result. * We also save the connected_to item, so it can be applied to the * right place. */ if (line->orthogonal) { GList *l; for (l = item->handles; l != NULL; l = l->next) dia_canvas_preserve_property (item->canvas, l->data, "pos_i"); } else dia_canvas_preserve_property (item->canvas, G_OBJECT (handle), "pos_i"); } static gdouble dia_canvas_line_glue (DiaCanvasItem *item, DiaHandle *handle, gdouble *x, gdouble *y) { DiaPoint p1, p2; gdouble ix, iy; ix = *x; iy = *y; dia_canvas_item_affine_point_w2i (item, &ix, &iy); calc_closest_point (item, ix, iy, NULL, &p1); dia_canvas_item_affine_point_i2w (item, &p1.x, &p1.y); p2.x = *x; p2.y = *y; *x = p1.x; *y = p1.y; return dia_distance_point_point (&p1, &p2); } static gboolean dia_canvas_line_connect (DiaCanvasItem *item, DiaHandle *handle) { DiaHandle *hbegin, *hend; gdouble x, y; gint segment; DiaPoint closest; GSList *handles_conn; if (!parent_class->connect (item, handle)) return FALSE; //g_message (G_STRLOC":connect"); /* If the handle was previously connected to the same object, find * its 'q_connected_handles' item and remove it from it. */ if (handle->connected_to == item) { GList *hl; for (hl = item->handles; hl != NULL; hl = hl->next) { handles_conn = get_connected_handles (hl->data); if (!g_slist_find (handles_conn, handle)) { handles_conn = g_slist_remove (handles_conn, handle); g_object_unref(handle); set_connected_handles (hl->data, handles_conn); break; } } } dia_handle_get_pos_w (handle, &x, &y); dia_canvas_item_affine_point_w2i (item, &x, &y); calc_closest_point (item, x, y, &segment, &closest); dia_canvas_item_affine_point_i2w (item, &closest.x, &closest.y); dia_handle_set_pos_w (handle, closest.x, closest.y); hbegin = g_list_nth_data (item->handles, segment - 1); hend = g_list_nth_data (item->handles, segment); g_assert (hbegin != NULL); g_assert (hend != NULL); dia_handle_remove_all_constraints (handle); dia_handle_add_line_constraint (hbegin, hend, handle); handles_conn = get_connected_handles (hend); handles_conn = g_slist_prepend (handles_conn, g_object_ref (handle)); set_connected_handles (hend, handles_conn); return TRUE; } static gboolean dia_canvas_line_disconnect (DiaCanvasItem *item, DiaHandle *handle) { gboolean result; result = parent_class->disconnect (item, handle); if (result) { GSList *handles_conn; GList *h; for (h = item->handles; h != NULL; h = h->next) { handles_conn = get_connected_handles (h->data); if (g_slist_find (handles_conn, handle)) { handles_conn = g_slist_remove (handles_conn, handle); g_object_unref (handle); set_connected_handles (h->data, handles_conn); break; } } } return result; } static void dia_canvas_line_parent_notify (DiaCanvasLine *line) { if (DIA_CANVAS_ITEM (line)->canvas) { if (line->orthogonal) add_ortho_constraints (line); } } static void dia_canvas_line_destroy_connected_handles (gpointer data) { GSList *l; for (l = data; l != NULL; l = l->next) g_object_unref (l->data); g_slist_free(data); } /** * add_ortho_constraints: * @line: * * Add constraints to ensure that a line will behave itself as an orthogonal * line (line segments can only be horizontal or vertical). * * We have to take special care of the line ends, since those can be connected * to another element. The order in which the equations are build is important. **/ static void add_ortho_constraints (DiaCanvasLine *line) { GList *l; gboolean horiz = line->horizontal; DiaConstraint *con = NULL; if (!DIA_CANVAS_ITEM (line)->canvas) return; for (l = line->item.handles; l && l->next; l = l->next) { /* Remove the old constraint first... */ con = g_object_steal_qdata (G_OBJECT (l->data), q_ortho_constraint); if (con) { dia_canvas_remove_constraint (DIA_CANVAS_ITEM (line)->canvas, con); g_object_unref (con); } /* Next, add a new constraint... For the first segment, invert * the order in which the constraint is build, this will * ensure that the line will become orthogonal, even if the * line end is connected. */ con = dia_constraint_new (); if (horiz) { if (l == line->item.handles) { dia_constraint_add (con, DIA_HANDLE (l->next->data)->pos_w.y, 1.0); dia_constraint_add (con, DIA_HANDLE (l->data)->pos_w.y, -1.0); } else { dia_constraint_add (con, DIA_HANDLE (l->data)->pos_w.y, -1.0); dia_constraint_add (con, DIA_HANDLE (l->next->data)->pos_w.y, 1.0); } } else { if (l == line->item.handles) { dia_constraint_add (con, DIA_HANDLE (l->next->data)->pos_w.x, 1.0); dia_constraint_add (con, DIA_HANDLE (l->data)->pos_w.x, -1.0); } else { dia_constraint_add (con, DIA_HANDLE (l->data)->pos_w.x, -1.0); dia_constraint_add (con, DIA_HANDLE (l->next->data)->pos_w.x, 1.0); } } dia_canvas_add_constraint (DIA_CANVAS_ITEM (line)->canvas, con); /* the qdata takes over our reference to the constraint. */ g_object_set_qdata_full (G_OBJECT (l->data), q_ortho_constraint, con, g_object_unref); horiz = !horiz; } } static void remove_ortho_constraints (DiaCanvasLine *line) { GList *l; DiaConstraint *con; if (!DIA_CANVAS_ITEM (line)->canvas) return; for (l = line->item.handles; l && l->next; l = l->next) { con = g_object_steal_qdata (G_OBJECT (l->data), q_ortho_constraint); if (con) { dia_canvas_remove_constraint (DIA_CANVAS_ITEM (line)->canvas, con); g_object_unref (con); } } } /** * dia_canvas_line_get_closest_segment: * @line: * @x: * @y: * * Get the segment that is closest to the point described by (@x, @y). * @x and @y should be in item coordinates. * * Return value: **/ gint dia_canvas_line_get_closest_segment (DiaCanvasLine *line, gdouble x, gdouble y) { gint segment = -1; g_return_val_if_fail (DIA_IS_CANVAS_LINE (line), -1); calc_closest_point ((DiaCanvasItem*) line, x, y, &segment, NULL); return segment - 1; }