/*
* 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;
}