/* * GooCanvas. Copyright (C) 2005-6 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvaspath.c - a path item, very similar to the SVG path element. */ /** * SECTION:goocanvaspath * @Title: GooCanvasPath * @Short_Description: a path item (a series of lines and curves). * * GooCanvasPath represents a path item, which is a series of one or more * lines, bezier curves, or elliptical arcs. * * 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(). * * #GooCanvasPath uses the same path specification strings as the Scalable * Vector Graphics (SVG) path element. For details see the * SVG specification. * * To create a #GooCanvasPath use goo_canvas_path_new(). * * To get or set the properties of an existing #GooCanvasPath, use * g_object_get() and g_object_set(). * * To respond to events such as mouse clicks on the path you must connect * to the signal handlers of the corresponding #GooCanvasPathView objects. * (See goo_canvas_view_get_item_view() and #GooCanvasView::item-view-created.) */ #include #include #include #include "goocanvaspath.h" #include "goocanvaspathview.h" enum { PROP_0, PROP_DATA, }; static void goo_canvas_path_finalize (GObject *object); static void item_interface_init (GooCanvasItemIface *iface); static void goo_canvas_path_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void goo_canvas_path_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); G_DEFINE_TYPE_WITH_CODE (GooCanvasPath, goo_canvas_path, GOO_TYPE_CANVAS_ITEM_SIMPLE, G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM, item_interface_init)) static void goo_canvas_path_class_init (GooCanvasPathClass *klass) { GObjectClass *gobject_class = (GObjectClass*) klass; gobject_class->finalize = goo_canvas_path_finalize; gobject_class->get_property = goo_canvas_path_get_property; gobject_class->set_property = goo_canvas_path_set_property; /** * GooCanvasPath:data * * The sequence of path commands, specified as a string using the same syntax * as in the Scalable Vector * Graphics (SVG) path element. */ g_object_class_install_property (gobject_class, PROP_DATA, g_param_spec_string ("data", _("Path Data"), _("The sequence of path commands"), NULL, G_PARAM_WRITABLE)); } static void goo_canvas_path_init (GooCanvasPath *path) { } static void goo_canvas_path_finalize (GObject *object) { GooCanvasPath *path = (GooCanvasPath*) object; if (path->commands) g_array_free (path->commands, TRUE); G_OBJECT_CLASS (goo_canvas_path_parent_class)->finalize (object); } static void goo_canvas_path_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { /*GooCanvasPath *path = (GooCanvasPath*) object;*/ switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gdouble parse_double (gchar **pos, gboolean *error) { gdouble result; gchar *p; /* If an error has already occurred, just return. */ if (*error) return 0; /* Skip whitespace and commas. */ p = *pos; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++; /* Parse the double, and set pos to the first char after it. */ result = g_ascii_strtod (p, pos); /* If no characters were parsed, set the error flag. */ if (p == *pos) *error = TRUE; return result; } static gint parse_flag (gchar **pos, gboolean *error) { gint result = 0; gchar *p; /* If an error has already occurred, just return. */ if (*error) return 0; /* Skip whitespace and commas. */ p = *pos; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n' || *p == ',') p++; /* The flag must be a '0' or a '1'. */ if (*p == '0') result = 0; else if (*p == '1') result = 1; else { *error = TRUE; return 0; } *pos = p + 1; return result; } static void goo_canvas_path_parse_data (GooCanvasPath *path, const gchar *path_data) { GooCanvasPathCommand cmd; gchar *pos, command = 0, next_command; gboolean error = FALSE; /* Free the current path data. */ if (path->commands) g_array_free (path->commands, TRUE); path->commands = g_array_new (0, 0, sizeof (GooCanvasPathCommand)); pos = (gchar*) path_data; for (;;) { while (*pos == ' ' || *pos == '\t' || *pos == '\r' || *pos == '\n') pos++; if (!*pos) break; next_command = *pos; /* If there is no command letter, we use the same command as the last one, except for the first command, and 'moveto' (which becomes 'lineto'). */ if ((next_command < 'a' || next_command > 'z') && (next_command < 'A' || next_command > 'Z')) { /* If this is the first command, then set the error flag and assume a simple close-path command. */ if (!command) { error = TRUE; command = 'Z'; } /* moveto commands change to lineto. */ else if (command == 'm') command = 'l'; else if (command == 'M') command = 'L'; } else { command = next_command; pos++; } cmd.simple.relative = 0; switch (command) { /* Simple commands like moveto and lineto: MmZzLlHhVv. */ case 'm': cmd.simple.relative = 1; case 'M': cmd.simple.type = GOO_CANVAS_PATH_MOVE_TO; cmd.simple.x = parse_double (&pos, &error); cmd.simple.y = parse_double (&pos, &error); break; case 'Z': case 'z': cmd.simple.type = GOO_CANVAS_PATH_CLOSE_PATH; break; case 'l': cmd.simple.relative = 1; case 'L': cmd.simple.type = GOO_CANVAS_PATH_LINE_TO; cmd.simple.x = parse_double (&pos, &error); cmd.simple.y = parse_double (&pos, &error); break; case 'h': cmd.simple.relative = 1; case 'H': cmd.simple.type = GOO_CANVAS_PATH_HORIZONTAL_LINE_TO; cmd.simple.x = parse_double (&pos, &error); break; case 'v': cmd.simple.relative = 1; case 'V': cmd.simple.type = GOO_CANVAS_PATH_VERTICAL_LINE_TO; cmd.simple.y = parse_double (&pos, &error); break; /* Bezier curve commands: CcSsQqTt. */ case 'c': cmd.curve.relative = 1; case 'C': cmd.curve.type = GOO_CANVAS_PATH_CURVE_TO; cmd.curve.x1 = parse_double (&pos, &error); cmd.curve.y1 = parse_double (&pos, &error); cmd.curve.x2 = parse_double (&pos, &error); cmd.curve.y2 = parse_double (&pos, &error); cmd.curve.x = parse_double (&pos, &error); cmd.curve.y = parse_double (&pos, &error); break; case 's': cmd.curve.relative = 1; case 'S': cmd.curve.type = GOO_CANVAS_PATH_SMOOTH_CURVE_TO; cmd.curve.x2 = parse_double (&pos, &error); cmd.curve.y2 = parse_double (&pos, &error); cmd.curve.x = parse_double (&pos, &error); cmd.curve.y = parse_double (&pos, &error); break; case 'q': cmd.curve.relative = 1; case 'Q': cmd.curve.type = GOO_CANVAS_PATH_QUADRATIC_CURVE_TO; cmd.curve.x1 = parse_double (&pos, &error); cmd.curve.y1 = parse_double (&pos, &error); cmd.curve.x = parse_double (&pos, &error); cmd.curve.y = parse_double (&pos, &error); break; case 't': cmd.curve.relative = 1; case 'T': cmd.curve.type = GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO; cmd.curve.x = parse_double (&pos, &error); cmd.curve.y = parse_double (&pos, &error); break; /* The elliptical arc commands: Aa. */ case 'a': cmd.arc.relative = 1; case 'A': cmd.arc.type = GOO_CANVAS_PATH_ELLIPTICAL_ARC; cmd.arc.rx = parse_double (&pos, &error); cmd.arc.ry = parse_double (&pos, &error); cmd.arc.x_axis_rotation = parse_double (&pos, &error); cmd.arc.large_arc_flag = parse_flag (&pos, &error); cmd.arc.sweep_flag = parse_flag (&pos, &error); cmd.arc.x = parse_double (&pos, &error); cmd.arc.y = parse_double (&pos, &error); break; default: error = TRUE; break; } /* If an error has occurred, return without adding the new command. Thus we include everything in the path up to the error, like SVG. */ if (error) return; g_array_append_val (path->commands, cmd); } } static void goo_canvas_path_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GooCanvasPath *path = (GooCanvasPath*) object; switch (prop_id) { case PROP_DATA: goo_canvas_path_parse_data (path, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } g_signal_emit_by_name (path, "changed", TRUE); } static GooCanvasItemView* goo_canvas_path_create_view (GooCanvasItem *item, GooCanvasView *canvas_view, GooCanvasItemView *parent_view) { return goo_canvas_path_view_new (canvas_view, parent_view, (GooCanvasPath*) item); } static void item_interface_init (GooCanvasItemIface *iface) { iface->create_view = goo_canvas_path_create_view; } /** * goo_canvas_path_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. * @path_data: the sequence of path commands, specified as a string using the * same syntax as in the Scalable * Vector Graphics (SVG) path element. * @...: optional pairs of property names and values, and a terminating %NULL. * * Creates a new path item. * * * * Here's an example showing how to create a red line from (20,20) to (40,40): * * * GooCanvasItem *path = goo_canvas_path_new (mygroup, * "M 20 20 L 40 40", * "stroke-color", "red", * NULL); * * * This example creates a cubic bezier curve from (20,100) to (100,100) with * the control points at (20,50) and (100,50): * * * GooCanvasItem *path = goo_canvas_path_new (mygroup, * "M20,100 C20,50 100,50 100,100", * "stroke-color", "blue", * NULL); * * * This example uses an elliptical arc to create a filled circle with one * quarter missing: * * * GooCanvasItem *path = goo_canvas_path_new (mygroup, * "M200,500 h-150 a150,150 0 1,0 150,-150 z", * "fill-color", "red", * "stroke-color", "blue", * "line-width", 5.0, * NULL); * * * Returns: a new path item. **/ GooCanvasItem* goo_canvas_path_new (GooCanvasItem *parent, gchar *path_data, ...) { GooCanvasItem *item; GooCanvasPath *path; va_list var_args; const char *first_arg_name; item = g_object_new (GOO_TYPE_CANVAS_PATH, NULL); path = GOO_CANVAS_PATH (item); goo_canvas_path_parse_data (path, path_data); va_start (var_args, path_data); 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; }