/* * GooCanvas. Copyright (C) 2005 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvaspolyline.c - polyline item, with optional arrows. */ /** * SECTION:goocanvaspolyline * @Title: GooCanvasPolyline * @Short_Description: a polyline item (a series of lines with optional arrows). * * GooCanvasPolyline represents a polyline item, which is a series of one or * more lines, with optional arrows at either end. * * It is a subclass of #GooCanvasItemSimple and so inherits all of the style * properties such as "stroke-color", "fill-color" and "line-width". * * It also implements the #GooCanvasItem interface, so you can use the * #GooCanvasItem functions such as goo_canvas_item_raise() and * goo_canvas_item_rotate(). * * To create a #GooCanvasPolyline use goo_canvas_polyline_new(). * * To get or set the properties of an existing #GooCanvasPolyline, use * g_object_get() and g_object_set(). * * To respond to events such as mouse clicks on the polyline you must connect * to the signal handlers of the corresponding #GooCanvasPolylineView objects. * (See goo_canvas_view_get_item_view() and #GooCanvasView::item-view-created.) */ #include #include #include #include #include #include #include "goocanvaspolyline.h" #include "goocanvaspolylineview.h" /* The default line width in cairo, from cairoint.h */ #define GOO_CANVAS_DEFAULT_CAIRO_LINE_WIDTH 2.0 /** * goo_canvas_points_new: * @num_points: the number of points to create space for. * * Creates a new #GooCanvasPoints struct with space for the given number of * points. It should be freed with goo_canvas_points_unref(). * * Returns: a new #GooCanvasPoints struct. **/ GooCanvasPoints* goo_canvas_points_new (int num_points) { GooCanvasPoints *points; points = g_new (GooCanvasPoints, 1); points->num_points = num_points; points->coords = g_new (double, 2 * num_points); points->ref_count = 1; return points; } /** * goo_canvas_points_ref: * @points: a #GooCanvasPoints struct. * * Increments the reference count of the given #GooCanvasPoints struct. * * Returns: the #GooCanvasPoints struct. **/ GooCanvasPoints* goo_canvas_points_ref (GooCanvasPoints *points) { points->ref_count++; return points; } /** * goo_canvas_points_unref: * @points: a #GooCanvasPoints struct. * * Decrements the reference count of the given #GooCanvasPoints struct, * freeing it if the reference count falls to zero. **/ void goo_canvas_points_unref (GooCanvasPoints *points) { if (--points->ref_count == 0) { g_free (points->coords); g_free (points); } } GType goo_canvas_points_get_type (void) { static GType type_canvas_points = 0; if (!type_canvas_points) type_canvas_points = g_boxed_type_register_static ("GooCanvasPoints", (GBoxedCopyFunc) goo_canvas_points_ref, (GBoxedFreeFunc) goo_canvas_points_unref); return type_canvas_points; } enum { PROP_0, PROP_POINTS, PROP_CLOSE_PATH, PROP_START_ARROW, PROP_END_ARROW, PROP_ARROW_LENGTH, PROP_ARROW_WIDTH, PROP_ARROW_TIP_LENGTH }; static void goo_canvas_polyline_finalize (GObject *object); static void item_interface_init (GooCanvasItemIface *iface); static void goo_canvas_polyline_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void goo_canvas_polyline_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); G_DEFINE_TYPE_WITH_CODE (GooCanvasPolyline, goo_canvas_polyline, GOO_TYPE_CANVAS_ITEM_SIMPLE, G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM, item_interface_init)) static void goo_canvas_polyline_class_init (GooCanvasPolylineClass *klass) { GObjectClass *gobject_class = (GObjectClass*) klass; gobject_class->finalize = goo_canvas_polyline_finalize; gobject_class->get_property = goo_canvas_polyline_get_property; gobject_class->set_property = goo_canvas_polyline_set_property; g_object_class_install_property (gobject_class, PROP_POINTS, g_param_spec_boxed ("points", _("Points"), _("The array of points"), GOO_TYPE_CANVAS_POINTS, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_CLOSE_PATH, g_param_spec_boolean ("close-path", _("Close Path"), _("If the last point should be connected to the first"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_START_ARROW, g_param_spec_boolean ("start-arrow", _("Start Arrow"), _("If an arrow should be displayed at the start of the polyline"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_END_ARROW, g_param_spec_boolean ("end-arrow", _("End Arrow"), _("If an arrow should be displayed at the end of the polyline"), FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ARROW_LENGTH, g_param_spec_double ("arrow-length", _("Arrow Length"), _("The length of the arrows, as a multiple of the line width"), 0.0, G_MAXDOUBLE, 5.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ARROW_WIDTH, g_param_spec_double ("arrow-width", _("Arrow Width"), _("The width of the arrows, as a multiple of the line width"), 0.0, G_MAXDOUBLE, 4.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ARROW_TIP_LENGTH, g_param_spec_double ("arrow-tip-length", _("Arrow Tip Length"), _("The length of the arrow tip, as a multiple of the line width"), 0.0, G_MAXDOUBLE, 4.0, G_PARAM_READWRITE)); } static void goo_canvas_polyline_init (GooCanvasPolyline *canvas_polyline) { } static void goo_canvas_polyline_finalize (GObject *object) { GooCanvasPolyline *polyline = (GooCanvasPolyline*) object; g_free (polyline->coords); g_free (polyline->arrow_data); G_OBJECT_CLASS (goo_canvas_polyline_parent_class)->finalize (object); } static void goo_canvas_polyline_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GooCanvasPolyline *polyline = (GooCanvasPolyline*) object; GooCanvasPoints *points; switch (prop_id) { case PROP_POINTS: if (polyline->num_points == 0) { g_value_set_boxed (value, NULL); } else { points = goo_canvas_points_new (polyline->num_points); memcpy (points->coords, polyline->coords, 2 * polyline->num_points * sizeof (double)); g_value_set_boxed (value, points); goo_canvas_points_unref (points); } break; case PROP_CLOSE_PATH: g_value_set_boolean (value, polyline->close_path); break; case PROP_START_ARROW: g_value_set_boolean (value, polyline->start_arrow); break; case PROP_END_ARROW: g_value_set_boolean (value, polyline->end_arrow); break; case PROP_ARROW_LENGTH: g_value_set_double (value, polyline->arrow_data ? polyline->arrow_data->arrow_length : 5.0); break; case PROP_ARROW_WIDTH: g_value_set_double (value, polyline->arrow_data ? polyline->arrow_data->arrow_width : 4.0); break; case PROP_ARROW_TIP_LENGTH: g_value_set_double (value, polyline->arrow_data ? polyline->arrow_data->arrow_tip_length : 4.0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void ensure_arrow_data (GooCanvasPolyline *polyline) { if (!polyline->arrow_data) { polyline->arrow_data = g_new (GooCanvasPolylineArrowData, 1); /* These seem like reasonable defaults for the arrow dimensions. They are specified in multiples of the line width so they scale OK. */ polyline->arrow_data->arrow_width = 4.0; polyline->arrow_data->arrow_length = 5.0; polyline->arrow_data->arrow_tip_length = 4.0; } } #define GOO_CANVAS_EPSILON 1e-10 static void reconfigure_arrow (GooCanvasPolyline *polyline, gint end_point, gint prev_point, gdouble line_width, gdouble *line_coords, gdouble *arrow_coords) { GooCanvasPolylineArrowData *arrow = polyline->arrow_data; double dx, dy, length, sin_theta, cos_theta; double half_arrow_width, arrow_length, arrow_tip_length; double arrow_end_center_x, arrow_end_center_y; double arrow_tip_center_x, arrow_tip_center_y; double x_offset, y_offset, half_line_width, line_trim; dx = polyline->coords[prev_point] - polyline->coords[end_point]; dy = polyline->coords[prev_point + 1] - polyline->coords[end_point + 1]; length = sqrt (dx * dx + dy * dy); if (length < GOO_CANVAS_EPSILON) { /* The length is too short to reliably get the angle so just guess. */ sin_theta = 1.0; cos_theta = 0.0; } else { /* Calculate a unit vector moving from the arrow point along the line. */ sin_theta = dy / length; cos_theta = dx / length; } /* These are all specified in multiples of the line width, so convert. */ half_arrow_width = arrow->arrow_width * line_width / 2; arrow_length = arrow->arrow_length * line_width; arrow_tip_length = arrow->arrow_tip_length * line_width; /* The arrow tip is at the end point of the line. */ arrow_coords[0] = polyline->coords[end_point]; arrow_coords[1] = polyline->coords[end_point + 1]; /* Calculate the end of the arrow, along the center line. */ arrow_end_center_x = arrow_coords[0] + (arrow_length * cos_theta); arrow_end_center_y = arrow_coords[1] + (arrow_length * sin_theta); /* Now calculate the 2 end points of the arrow either side of the line. */ x_offset = half_arrow_width * sin_theta; y_offset = half_arrow_width * cos_theta; arrow_coords[2] = arrow_end_center_x + x_offset; arrow_coords[3] = arrow_end_center_y - y_offset; arrow_coords[8] = arrow_end_center_x - x_offset; arrow_coords[9] = arrow_end_center_y + y_offset; /* Calculate the end of the arrow tip, along the center line. */ arrow_tip_center_x = arrow_coords[0] + (arrow_tip_length * cos_theta); arrow_tip_center_y = arrow_coords[1] + (arrow_tip_length * sin_theta); /* Now calculate the 2 arrow points on either edge of the line. */ half_line_width = line_width / 2.0; x_offset = half_line_width * sin_theta; y_offset = half_line_width * cos_theta; arrow_coords[4] = arrow_tip_center_x + x_offset; arrow_coords[5] = arrow_tip_center_y - y_offset; arrow_coords[6] = arrow_tip_center_x - x_offset; arrow_coords[7] = arrow_tip_center_y + y_offset; /* Calculate the new end of the line. We have to move it back slightly so it doesn't draw over the arrow tip. But we overlap the arrow by a small fraction of the line width to avoid a tiny gap. */ line_trim = arrow_tip_length - (line_width / 10.0); line_coords[0] = arrow_coords[0] + (line_trim * cos_theta); line_coords[1] = arrow_coords[1] + (line_trim * sin_theta); } /* Recalculates the arrow polygons for the line */ void _goo_canvas_polyline_reconfigure_arrows (GooCanvasPolyline *polyline) { GooCanvasItemSimple *item = GOO_CANVAS_ITEM_SIMPLE (polyline); double line_width; if (!polyline->reconfigure_arrows) return; polyline->reconfigure_arrows = FALSE; if (polyline->num_points < 2 || (!polyline->start_arrow && !polyline->end_arrow)) return; if (item->style && (item->style->mask & GOO_CANVAS_STYLE_LINE_WIDTH)) line_width = item->style->line_width; else line_width = GOO_CANVAS_DEFAULT_CAIRO_LINE_WIDTH; ensure_arrow_data (polyline); if (polyline->start_arrow) reconfigure_arrow (polyline, 0, 2, line_width, polyline->arrow_data->line_start, polyline->arrow_data->start_arrow_coords); if (polyline->end_arrow) { int end_point, prev_point; if (polyline->close_path) { end_point = 0; prev_point = polyline->num_points - 1; } else { end_point = polyline->num_points - 1; prev_point = polyline->num_points - 2; } reconfigure_arrow (polyline, end_point * 2, prev_point * 2, line_width, polyline->arrow_data->line_end, polyline->arrow_data->end_arrow_coords); } } static void goo_canvas_polyline_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GooCanvasPolyline *polyline = (GooCanvasPolyline*) object; GooCanvasPoints *points; switch (prop_id) { case PROP_POINTS: points = g_value_get_boxed (value); if (polyline->coords) { g_free (polyline->coords); polyline->coords = NULL; } if (!points) { polyline->num_points = 0; } else { polyline->num_points = points->num_points; polyline->coords = g_new (double, 2 * polyline->num_points); memcpy (polyline->coords, points->coords, 2 * polyline->num_points * sizeof (double)); } polyline->reconfigure_arrows = TRUE; break; case PROP_CLOSE_PATH: polyline->close_path = g_value_get_boolean (value); polyline->reconfigure_arrows = TRUE; break; case PROP_START_ARROW: polyline->start_arrow = g_value_get_boolean (value); polyline->reconfigure_arrows = TRUE; break; case PROP_END_ARROW: polyline->end_arrow = g_value_get_boolean (value); polyline->reconfigure_arrows = TRUE; break; case PROP_ARROW_LENGTH: ensure_arrow_data (polyline); polyline->arrow_data->arrow_length = g_value_get_double (value); polyline->reconfigure_arrows = TRUE; break; case PROP_ARROW_WIDTH: ensure_arrow_data (polyline); polyline->arrow_data->arrow_width = g_value_get_double (value); polyline->reconfigure_arrows = TRUE; break; case PROP_ARROW_TIP_LENGTH: ensure_arrow_data (polyline); polyline->arrow_data->arrow_tip_length = g_value_get_double (value); polyline->reconfigure_arrows = TRUE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } g_signal_emit_by_name (polyline, "changed", TRUE); } static GooCanvasItemView* goo_canvas_polyline_create_view (GooCanvasItem *item, GooCanvasView *canvas_view, GooCanvasItemView *parent_view) { return goo_canvas_polyline_view_new (canvas_view, parent_view, (GooCanvasPolyline*) item); } static void item_interface_init (GooCanvasItemIface *iface) { iface->create_view = goo_canvas_polyline_create_view; } /** * goo_canvas_polyline_new: * @parent: the parent item, or %NULL. If a parent is specified, it will assume * ownership of the item, and the item will automatically be freed when it is * removed from the parent. Otherwise call g_object_unref() to free it. * @close_path: if the last point should be connected to the first. * @num_points: the number of points in the polyline. * @...: the pairs of coordinates for each point in the line, followed by * optional pairs of property names and values, and a terminating %NULL. * * Creates a new polyline item. * * * * Here's an example showing how to create a filled triangle with vertices * at (100,100), (300,100), and (200,300). * * * GooCanvasItem *polyline = goo_canvas_polyline_new (mygroup, TRUE, 3, * 100.0, 100.0, * 300.0, 100.0, * 200.0, 300.0, * "stroke-color", "red", * "line-width", 5.0, * "fill-color", "blue", * NULL); * * * Returns: a new polyline item. **/ GooCanvasItem* goo_canvas_polyline_new (GooCanvasItem *parent, gboolean close_path, gint num_points, ...) { GooCanvasItem *item; GooCanvasPolyline *polyline; va_list var_args; gint i; const char *first_arg_name; item = g_object_new (GOO_TYPE_CANVAS_POLYLINE, NULL); polyline = GOO_CANVAS_POLYLINE (item); polyline->close_path = close_path; polyline->num_points = num_points; polyline->coords = num_points ? g_new (gdouble, num_points * 2) : NULL; va_start (var_args, num_points); for (i = 0; i < num_points * 2; i++) { polyline->coords[i] = va_arg (var_args, gdouble); } first_arg_name = va_arg (var_args, char*); g_object_set_valist (G_OBJECT (item), first_arg_name, var_args); va_end (var_args); if (parent) { goo_canvas_item_add_child (parent, item, -1); g_object_unref (item); } return item; } /** * goo_canvas_polyline_new_line: * @parent: the parent item, or %NULL. * @x1: the x coordinate of the start of the line. * @y1: the y coordinate of the start of the line. * @x2: the x coordinate of the end of the line. * @y2: the y coordinate of the end of the line. * @first_property: the name of the first property to set, or %NULL. * @...: the remaining property names and values to set, terminated with a * %NULL. * * Creates a new polyline item with a single line. * * * * Here's an example showing how to create a line from (100,100) to (300,300). * * * GooCanvasItem *polyline = goo_canvas_polyline_new_line (mygroup, * 100.0, 100.0, * 300.0, 300.0, * "stroke-color", "red", * "line-width", 5.0, * NULL); * * * Returns: a new polyline item. **/ GooCanvasItem* goo_canvas_polyline_new_line (GooCanvasItem *parent, gdouble x1, gdouble y1, gdouble x2, gdouble y2, const gchar *first_property, ...) { GooCanvasItem *item; GooCanvasPolyline *polyline; va_list args; item = g_object_new (GOO_TYPE_CANVAS_POLYLINE, NULL); polyline = GOO_CANVAS_POLYLINE (item); polyline->num_points = 2; polyline->coords = g_new (gdouble, 4); polyline->coords[0] = x1; polyline->coords[1] = y1; polyline->coords[2] = x2; polyline->coords[3] = y2; va_start (args, first_property); g_object_set_valist (G_OBJECT (item), first_property, args); va_end (args); if (parent) { goo_canvas_item_add_child (parent, item, -1); g_object_unref (item); } return item; }