[anjuta/class-inheritance-yap: 1/3] libgoocanvas: Introduced goocanvas library to replace foocanvas for canvas graphics



commit 39f4e41197fc1c8d792cc9754f6b4d1b4fd51d23
Author: Naba Kumar <naba gnome org>
Date:   Sun Jun 12 20:38:52 2011 +0300

    libgoocanvas: Introduced goocanvas library to replace foocanvas for canvas graphics

 libgoocanvas/Makefile.am           |  134 ++
 libgoocanvas/goocanvas.c           | 4442 ++++++++++++++++++++++++++++++++++++
 libgoocanvas/goocanvas.h           |  334 +++
 libgoocanvas/goocanvasatk.c        |  776 +++++++
 libgoocanvas/goocanvasatk.h        |   22 +
 libgoocanvas/goocanvasellipse.c    |  614 +++++
 libgoocanvas/goocanvasellipse.h    |  120 +
 libgoocanvas/goocanvasgrid.c       | 1170 ++++++++++
 libgoocanvas/goocanvasgrid.h       |  153 ++
 libgoocanvas/goocanvasgroup.c      | 1082 +++++++++
 libgoocanvas/goocanvasgroup.h      |  109 +
 libgoocanvas/goocanvasimage.c      |  738 ++++++
 libgoocanvas/goocanvasimage.h      |  121 +
 libgoocanvas/goocanvasitem.c       | 2344 +++++++++++++++++++
 libgoocanvas/goocanvasitem.h       |  479 ++++
 libgoocanvas/goocanvasitemmodel.c  | 1187 ++++++++++
 libgoocanvas/goocanvasitemmodel.h  |  275 +++
 libgoocanvas/goocanvasitemsimple.c | 2106 +++++++++++++++++
 libgoocanvas/goocanvasitemsimple.h |  247 ++
 libgoocanvas/goocanvasmarshal.list |    8 +
 libgoocanvas/goocanvaspath.c       |  806 +++++++
 libgoocanvas/goocanvaspath.h       |  116 +
 libgoocanvas/goocanvaspolyline.c   | 1350 +++++++++++
 libgoocanvas/goocanvaspolyline.h   |  176 ++
 libgoocanvas/goocanvasprivate.h    |   59 +
 libgoocanvas/goocanvasrect.c       |  603 +++++
 libgoocanvas/goocanvasrect.h       |  121 +
 libgoocanvas/goocanvasstyle.c      |  547 +++++
 libgoocanvas/goocanvasstyle.h      |  110 +
 libgoocanvas/goocanvastable.c      | 3023 ++++++++++++++++++++++++
 libgoocanvas/goocanvastable.h      |  139 ++
 libgoocanvas/goocanvastext.c       | 1090 +++++++++
 libgoocanvas/goocanvastext.h       |  133 ++
 libgoocanvas/goocanvasutils.c      | 1294 +++++++++++
 libgoocanvas/goocanvasutils.h      |  378 +++
 libgoocanvas/goocanvaswidget.c     |  597 +++++
 libgoocanvas/goocanvaswidget.h     |   66 +
 37 files changed, 27069 insertions(+), 0 deletions(-)
---
diff --git a/libgoocanvas/Makefile.am b/libgoocanvas/Makefile.am
new file mode 100644
index 0000000..5cb65d4
--- /dev/null
+++ b/libgoocanvas/Makefile.am
@@ -0,0 +1,134 @@
+## Process this file with automake to produce Makefile.in
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+
+INCLUDES = \
+	-DPACKAGE_DATA_DIR=\""$(datadir)"\" \
+	-DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
+	-DG_LOG_DOMAIN=\"GooCanvas\" \
+        $(WARN_CFLAGS) \
+        $(ANJUTA_CFLAGS)
+
+#	-DG_DISABLE_DEPRECATED -DPANGO_DISABLE_DEPRECATED \
+#	-DGDK_DISABLE_DEPRECATED -DGDK_PIXBUF_DISABLE_DEPRECATED \
+#	-DGTK_DISABLE_DEPRECATED
+
+lib_LTLIBRARIES = libanjuta-goocanvas-2.0.la
+
+libanjuta_goocanvas_2_0_la_LIBADD = $(ANJUTA_LIBS) $(INTLLIBS)
+
+libanjuta_goocanvas_2_0_la_LDFLAGS = $(libtool_opts) 
+libgoocanvas_public_headers = \
+	goocanvasellipse.h		\
+	goocanvasgrid.h			\
+	goocanvasgroup.h		\
+	goocanvasimage.h		\
+	goocanvasitem.h			\
+	goocanvasitemmodel.h		\
+	goocanvasitemsimple.h		\
+	goocanvaspolyline.h		\
+	goocanvaspath.h			\
+	goocanvasrect.h			\
+	goocanvasstyle.h		\
+	goocanvastable.h		\
+	goocanvastext.h			\
+	goocanvasutils.h		\
+	goocanvaswidget.h		\
+	goocanvas.h
+
+libanjuta_goocanvas_2_0_la_SOURCES =		\
+	$(libgoocanvas_public_headers   \
+	$(libgoocanvas_built_headers)   \
+	goocanvasatk.h			\
+	goocanvasatk.c			\
+	goocanvasellipse.c		\
+	goocanvasenumtypes.c		\
+	goocanvasgrid.c			\
+	goocanvasgroup.c		\
+	goocanvasimage.c		\
+	goocanvasitem.c			\
+	goocanvasitemmodel.c		\
+	goocanvasitemsimple.c		\
+	goocanvasmarshal.c		\
+	goocanvaspolyline.c		\
+	goocanvaspath.c			\
+	goocanvasprivate.h		\
+	goocanvasrect.c			\
+	goocanvasstyle.c		\
+	goocanvastable.c		\
+	goocanvastext.c			\
+	goocanvasutils.c		\
+	goocanvaswidget.c		\
+	goocanvas.c 
+
+libgoocanvas_extra_sources =		\
+	goocanvasmarshal.list
+
+libgoocanvas_built_headers =		\
+	goocanvasenumtypes.h		\
+	goocanvasmarshal.h
+
+libgoocanvas_built_sources =		\
+	goocanvasenumtypes.c		\
+	goocanvasmarshal.c
+
+stamp_files =				\
+	stamp-goocanvasmarshal.h	\
+	stamp-goocanvasenumtypes.h
+
+
+#
+# Most of the stuff below has been pinched from the GTK+ Makefile.am, as
+# it is tricky to get right and we know it works for GTK+.
+#
+
+# all autogenerated files need to be generated in the srcdir,
+# so old versions get remade and are not confused with newer
+# versions in the build dir. thus a development setup requires
+# srcdir to be writable, passing --disable-rebuilds to
+# ../configure will supress all autogeneration rules.
+goocanvasmarshal.h: stamp-goocanvasmarshal.h
+	@true
+stamp-goocanvasmarshal.h: goocanvasmarshal.list
+	$(GLIB_GENMARSHAL) --prefix=goo_canvas_marshal $(srcdir)/goocanvasmarshal.list --header >> xgen-gmlh \
+	&& (cmp -s xgen-gmlh goocanvasmarshal.h || cp xgen-gmlh goocanvasmarshal.h) \
+	&& rm -f xgen-gmlh \
+	&& echo timestamp > $(@F)	
+goocanvasmarshal.c: goocanvasmarshal.list
+	(echo "#include \"goocanvasmarshal.h\""; \
+	 $(GLIB_GENMARSHAL) --prefix=goo_canvas_marshal $(srcdir)/goocanvasmarshal.list --body) >> xgen-gmlc \
+	&& cp xgen-gmlc goocanvasmarshal.c \
+	&& rm -f xgen-gmlc
+
+
+goocanvasenumtypes.h: stamp-goocanvasenumtypes.h	
+	@true
+stamp-goocanvasenumtypes.h: $(goo_canvas_public_headers) Makefile
+	(cd $(srcdir) && $(GLIB_MKENUMS) \
+		--fhead "#ifndef __GOO_CANVAS_ENUM_TYPES_H__\n#define __GOO_CANVAS_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+		--fprod "/* Enumerations from \"@filename \" */\n" \
+		--vhead "GType @enum_name _get_type (void);\n#define GOO_TYPE_ ENUMSHORT@ (@enum_name _get_type())\n\n\n" \
+		--ftail "G_END_DECLS\n\n#endif /* __GOO_CANVAS_ENUM_TYPES_H__ */" \
+		$(libgoocanvas_public_headers)) >> xgen-gtbh \
+	&& (cmp -s xgen-gtbh goocanvasenumtypes.h || cp xgen-gtbh goocanvasenumtypes.h ) \
+	&& rm -f xgen-gtbh \
+	&& echo timestamp > $(@F)
+
+goocanvasenumtypes.c: $(goo_canvas_public_headers) Makefile
+	(cd $(srcdir) && $(GLIB_MKENUMS) \
+		--fhead "#include <glib-object.h>\n" \
+		--fhead "#include \"goocanvas.h\"\n" \
+		--fprod "\n/* Enumerations from \"@filename \" */" \
+		--vhead "GType\n enum_name@_get_type (void)\n{\n  static GType etype = 0;\n if( etype == 0 ) \n  {\n    static const G Type@Value values[] = {" \
+		--vprod "      { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+		--vtail "      { 0, NULL, NULL }\n    };\n    etype = g_ type@_register_static (\"@EnumName \", values );\n  }\n  return etype;\n}\n" \
+		$(libgoocanvas_public_headers)) > xgen-gtbc \
+	&& cp xgen-gtbc goocanvasenumtypes.c  \
+	&& rm -f xgen-gtbc
+
+
+gen_sources = xgen-gdef xgen-gtbh xgen-gtic xgen-gmh xgen-gmc xgen-gmlh xgen-gmlc
+CLEANFILES = $(gen_sources)
+BUILT_SOURCES = $(libgoocanvas_built_headers) $(libgoocanvas_built_sources)
+MAINTAINERCLEANFILES = $(BUILT_SOURCES) $(stamp_files)
+EXTRA_DIST = $(BUILT_SOURCES) $(libgoocanvas_extra_sources)
diff --git a/libgoocanvas/goocanvas.c b/libgoocanvas/goocanvas.c
new file mode 100644
index 0000000..aba3b6f
--- /dev/null
+++ b/libgoocanvas/goocanvas.c
@@ -0,0 +1,4442 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvas.c - the main canvas widget.
+ */
+
+/**
+ * SECTION: goocanvas
+ * @Title: GooCanvas
+ * @Short_Description: the main canvas widget.
+ *
+ * #GooCanvas is the main widget containing a number of canvas items.
+ *
+ * Here is a simple example:
+ *
+ * <informalexample><programlisting>
+ *  &num;include &lt;goocanvas.h&gt;
+ *
+ *  static gboolean on_rect_button_press (GooCanvasItem  *view,
+ *                                        GooCanvasItem  *target,
+ *                                        GdkEventButton *event,
+ *                                        gpointer        data);
+ *
+ *  int
+ *  main (int argc, char *argv[])
+ *  {
+ *    GtkWidget *window, *scrolled_win, *canvas;
+ *    GooCanvasItem *root, *rect_item, *text_item;
+ *
+ *    /&ast; Initialize GTK+. &ast;/
+ *    gtk_init (&amp;argc, &amp;argv);
+ *
+ *    /&ast; Create the window and widgets. &ast;/
+ *    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ *    gtk_window_set_default_size (GTK_WINDOW (window), 640, 600);
+ *    gtk_widget_show (window);
+ *    g_signal_connect (window, "delete_event", (GtkSignalFunc) on_delete_event,
+ *                      NULL);
+ *
+ *    scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ *    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
+ *                                         GTK_SHADOW_IN);
+ *    gtk_widget_show (scrolled_win);
+ *    gtk_container_add (GTK_CONTAINER (window), scrolled_win);
+ *
+ *    canvas = goo_canvas_new&nbsp;();
+ *    gtk_widget_set_size_request (canvas, 600, 450);
+ *    goo_canvas_set_bounds (GOO_CANVAS (canvas), 0, 0, 1000, 1000);
+ *    gtk_widget_show (canvas);
+ *    gtk_container_add (GTK_CONTAINER (scrolled_win), canvas);
+ *
+ *    root = goo_canvas_get_root_item (GOO_CANVAS (canvas));
+ *
+ *    /&ast; Add a few simple items. &ast;/
+ *    rect_item = goo_canvas_rect_new (root, 100, 100, 400, 400,
+ *                                     "line-width", 10.0,
+ *                                     "radius-x", 20.0,
+ *                                     "radius-y", 10.0,
+ *                                     "stroke-color", "yellow",
+ *                                     "fill-color", "red",
+ *                                     NULL);
+ *
+ *    text_item = goo_canvas_text_new (root, "Hello World", 300, 300, -1,
+ *                                     GOO_CANVAS_ANCHOR_CENTER,
+ *                                     "font", "Sans 24",
+ *                                     NULL);
+ *    goo_canvas_item_rotate (text_item, 45, 300, 300);
+ *
+ *    /&ast; Connect a signal handler for the rectangle item. &ast;/
+ *    g_signal_connect (rect_item, "button_press_event",
+ *                      (GtkSignalFunc) on_rect_button_press, NULL);
+ *
+ *    /&ast; Pass control to the GTK+ main event loop. &ast;/
+ *    gtk_main&nbsp;();
+ *
+ *    return 0;
+ *  }
+ *
+ *
+ *  /&ast; This handles button presses in item views. We simply output a message to
+ *     the console. &ast;/
+ *  static gboolean
+ *  on_rect_button_press (GooCanvasItem  *item,
+ *                        GooCanvasItem  *target,
+ *                        GdkEventButton *event,
+ *                        gpointer        data)
+ *  {
+ *    g_print ("rect item received button press event\n");
+ *    return TRUE;
+ *  }
+ *
+ * </programlisting></informalexample>
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasatk.h"
+#include "goocanvas.h"
+#include "goocanvasitemmodel.h"
+#include "goocanvasitem.h"
+#include "goocanvasgroup.h"
+#include "goocanvasmarshal.h"
+
+
+#define GOO_CANVAS_GET_PRIVATE(canvas)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((canvas), GOO_TYPE_CANVAS, GooCanvasPrivate))
+
+typedef struct _GooCanvasPrivate GooCanvasPrivate;
+struct _GooCanvasPrivate {
+  GooCanvasItem *static_root_item;
+  GooCanvasItemModel *static_root_item_model;
+  gint window_x, window_y;
+  gint static_window_x, static_window_y;
+};
+
+
+enum {
+  PROP_0,
+
+  PROP_SCALE,
+  PROP_SCALE_X,
+  PROP_SCALE_Y,
+  PROP_ANCHOR,
+  PROP_X1,
+  PROP_Y1,
+  PROP_X2,
+  PROP_Y2,
+  PROP_AUTOMATIC_BOUNDS,
+  PROP_BOUNDS_FROM_ORIGIN,
+  PROP_BOUNDS_PADDING,
+  PROP_UNITS,
+  PROP_RESOLUTION_X,
+  PROP_RESOLUTION_Y,
+  PROP_BACKGROUND_COLOR,
+  PROP_BACKGROUND_COLOR_RGB,
+  PROP_INTEGER_LAYOUT,
+  PROP_CLEAR_BACKGROUND,
+  PROP_REDRAW_WHEN_SCROLLED,
+  PROP_HADJUSTMENT,
+  PROP_VADJUSTMENT,
+  PROP_HSCROLL_POLICY,
+  PROP_VSCROLL_POLICY
+};
+
+enum {
+  ITEM_CREATED,
+
+  LAST_SIGNAL
+};
+
+
+static guint canvas_signals[LAST_SIGNAL] = { 0 };
+
+static void     goo_canvas_dispose	   (GObject          *object);
+static void     goo_canvas_finalize	   (GObject          *object);
+static void     goo_canvas_realize         (GtkWidget        *widget);
+static void     goo_canvas_unrealize	   (GtkWidget	     *widget);
+static void     goo_canvas_map		   (GtkWidget	     *widget);
+static void     goo_canvas_style_set	   (GtkWidget	     *widget,
+					    GtkStyle	     *old_style);
+static void     goo_canvas_get_preferred_width (GtkWidget     *widget,
+						gint          *minimum,
+						gint          *natural);
+static void     goo_canvas_get_preferred_height (GtkWidget     *widget,
+						 gint          *minimum,
+						 gint          *natural);
+static void     goo_canvas_size_allocate   (GtkWidget        *widget,
+					    GtkAllocation    *allocation);
+static void     goo_canvas_set_hadjustment (GooCanvas        *canvas,
+					    GtkAdjustment    *adjustment);
+static void     goo_canvas_set_vadjustment (GooCanvas        *canvas,
+					    GtkAdjustment    *adjustment);
+static gboolean goo_canvas_draw	   (GtkWidget        *widget,
+					    cairo_t          *cr);
+static gboolean goo_canvas_button_press    (GtkWidget        *widget,
+					    GdkEventButton   *event);
+static gboolean goo_canvas_button_release  (GtkWidget        *widget,
+					    GdkEventButton   *event);
+static gboolean goo_canvas_motion          (GtkWidget        *widget,
+					    GdkEventMotion   *event);
+static gboolean goo_canvas_scroll          (GtkWidget        *widget,
+					    GdkEventScroll   *event);
+static gboolean goo_canvas_focus	   (GtkWidget        *widget,
+					    GtkDirectionType  direction);
+static gboolean goo_canvas_key_press       (GtkWidget        *widget,
+					    GdkEventKey      *event);
+static gboolean goo_canvas_key_release     (GtkWidget        *widget,
+					    GdkEventKey      *event);
+static gboolean goo_canvas_crossing        (GtkWidget        *widget,
+					    GdkEventCrossing *event);
+static gboolean goo_canvas_focus_in        (GtkWidget        *widget,
+					    GdkEventFocus    *event);
+static gboolean goo_canvas_focus_out       (GtkWidget        *widget,
+					    GdkEventFocus    *event);
+static gboolean goo_canvas_grab_broken     (GtkWidget        *widget,
+					    GdkEventGrabBroken *event);
+static void     goo_canvas_get_property    (GObject          *object,
+					    guint             prop_id,
+					    GValue           *value,
+					    GParamSpec       *pspec);
+static void     goo_canvas_set_property    (GObject          *object,
+					    guint             prop_id,
+					    const GValue     *value,
+					    GParamSpec       *pspec);
+static void     goo_canvas_remove          (GtkContainer     *container,
+					    GtkWidget        *widget);
+static void     goo_canvas_forall          (GtkContainer     *container,
+					    gboolean          include_internals,
+					    GtkCallback       callback,
+					    gpointer          callback_data);
+static gboolean goo_canvas_query_tooltip   (GtkWidget	     *widget,
+					    gint              x,
+					    gint              y,
+					    gboolean          keyboard_tip,
+					    GtkTooltip       *tooltip);
+
+static void	goo_canvas_set_scale_internal (GooCanvas     *canvas,
+					       gdouble        scale_x,
+					       gdouble        scale_y);
+
+static void     set_item_pointer           (GooCanvasItem   **item,
+					    GooCanvasItem    *new_item);
+static void     update_pointer_item        (GooCanvas        *canvas,
+					    GdkEvent         *event);
+static void     reconfigure_canvas	   (GooCanvas        *canvas,
+					    gboolean          redraw_if_needed);
+static void	goo_canvas_update_automatic_bounds (GooCanvas       *canvas);
+
+static void	goo_canvas_convert_from_window_pixels (GooCanvas     *canvas,
+						       gdouble       *x,
+						       gdouble       *y);
+static void     goo_canvas_convert_to_static_item_space (GooCanvas     *canvas,
+							 gdouble       *x,
+							 gdouble       *y);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvas, goo_canvas, GTK_TYPE_CONTAINER,
+  G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+/* This evaluates to TRUE if an item is still in the canvas. */
+#define ITEM_IS_VALID(item) (goo_canvas_item_get_canvas (item))
+
+#define GOO_CANVAS_DEFAULT_WIDTH	1000.0
+#define GOO_CANVAS_DEFAULT_HEIGHT	1000.0
+
+static void
+goo_canvas_class_init (GooCanvasClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
+  GtkContainerClass *container_class = (GtkContainerClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasPrivate));
+
+  gobject_class->dispose	     = goo_canvas_dispose;
+  gobject_class->finalize	     = goo_canvas_finalize;
+  gobject_class->get_property	     = goo_canvas_get_property;
+  gobject_class->set_property	     = goo_canvas_set_property;
+
+  widget_class->realize              = goo_canvas_realize;
+  widget_class->unrealize            = goo_canvas_unrealize;
+  widget_class->map                  = goo_canvas_map;
+  widget_class->get_preferred_width  = goo_canvas_get_preferred_width;
+  widget_class->get_preferred_height = goo_canvas_get_preferred_height;
+  widget_class->size_allocate        = goo_canvas_size_allocate;
+  widget_class->style_set            = goo_canvas_style_set;
+  widget_class->draw                 = goo_canvas_draw;
+  widget_class->button_press_event   = goo_canvas_button_press;
+  widget_class->button_release_event = goo_canvas_button_release;
+  widget_class->motion_notify_event  = goo_canvas_motion;
+  widget_class->scroll_event         = goo_canvas_scroll;
+  widget_class->focus                = goo_canvas_focus;
+  widget_class->key_press_event      = goo_canvas_key_press;
+  widget_class->key_release_event    = goo_canvas_key_release;
+  widget_class->enter_notify_event   = goo_canvas_crossing;
+  widget_class->leave_notify_event   = goo_canvas_crossing;
+  widget_class->focus_in_event       = goo_canvas_focus_in;
+  widget_class->focus_out_event      = goo_canvas_focus_out;
+  widget_class->grab_broken_event    = goo_canvas_grab_broken;
+  widget_class->query_tooltip	     = goo_canvas_query_tooltip;
+
+  container_class->remove	     = goo_canvas_remove;
+  container_class->forall            = goo_canvas_forall;
+
+  /* Register our accessible factory, but only if accessibility is enabled. */
+  if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
+    {
+      atk_registry_set_factory_type (atk_get_default_registry (),
+				     GOO_TYPE_CANVAS,
+				     goo_canvas_accessible_factory_get_type ());
+    }
+
+  g_object_class_install_property (gobject_class, PROP_SCALE,
+				   g_param_spec_double ("scale",
+							_("Scale"),
+							_("The magnification factor of the canvas"),
+							0.0, G_MAXDOUBLE, 1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_SCALE_X,
+				   g_param_spec_double ("scale-x",
+							_("Scale X"),
+							_("The horizontal magnification factor of the canvas"),
+							0.0, G_MAXDOUBLE, 1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_SCALE_Y,
+				   g_param_spec_double ("scale-y",
+							_("Scale Y"),
+							_("The vertical magnification factor of the canvas"),
+							0.0, G_MAXDOUBLE, 1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_ANCHOR,
+				   g_param_spec_enum ("anchor",
+						      _("Anchor"),
+						      _("Where to place the canvas when it is smaller than the widget's allocated area"),
+						      GOO_TYPE_CANVAS_ANCHOR_TYPE,
+						      GOO_CANVAS_ANCHOR_NW,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X1,
+				   g_param_spec_double ("x1",
+							_("X1"),
+							_("The x coordinate of the left edge of the canvas bounds, in canvas units"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y1,
+				   g_param_spec_double ("y1",
+							_("Y1"),
+							_("The y coordinate of the top edge of the canvas bounds, in canvas units"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X2,
+				   g_param_spec_double ("x2",
+							_("X2"),
+							_("The x coordinate of the right edge of the canvas bounds, in canvas units"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE,
+							GOO_CANVAS_DEFAULT_WIDTH,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y2,
+				   g_param_spec_double ("y2",
+							_("Y2"),
+							_("The y coordinate of the bottom edge of the canvas bounds, in canvas units"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE,
+							GOO_CANVAS_DEFAULT_HEIGHT,
+							G_PARAM_READWRITE));
+
+
+  g_object_class_install_property (gobject_class, PROP_AUTOMATIC_BOUNDS,
+                                   g_param_spec_boolean ("automatic-bounds",
+							 _("Automatic Bounds"),
+							 _("If the bounds are automatically calculated based on the bounds of all the items in the canvas"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_BOUNDS_FROM_ORIGIN,
+                                   g_param_spec_boolean ("bounds-from-origin",
+							 _("Bounds From Origin"),
+							 _("If the automatic bounds are calculated from the origin"),
+							 TRUE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_BOUNDS_PADDING,
+				   g_param_spec_double ("bounds-padding",
+							_("Bounds Padding"),
+							_("The padding added to the automatic bounds"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_UNITS,
+				   g_param_spec_enum ("units",
+						      _("Units"),
+						      _("The units to use for the canvas"),
+						      GTK_TYPE_UNIT,
+						      GTK_UNIT_PIXEL,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RESOLUTION_X,
+				   g_param_spec_double ("resolution-x",
+							_("Resolution X"),
+							_("The horizontal resolution of the display, in dots per inch"),
+							0.0, G_MAXDOUBLE,
+							96.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RESOLUTION_Y,
+				   g_param_spec_double ("resolution-y",
+							_("Resolution Y"),
+							_("The vertical resolution of the display, in dots per inch"),
+							0.0, G_MAXDOUBLE,
+							96.0,
+							G_PARAM_READWRITE));
+
+  /* Convenience properties - writable only. */
+  g_object_class_install_property (gobject_class, PROP_BACKGROUND_COLOR,
+				   g_param_spec_string ("background-color",
+							_("Background Color"),
+							_("The color to use for the canvas background"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_BACKGROUND_COLOR_RGB,
+				   g_param_spec_uint ("background-color-rgb",
+						      _("Background Color RGB"),
+						      _("The color to use for the canvas background, specified as a 24-bit integer value, 0xRRGGBB"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_INTEGER_LAYOUT,
+                                   g_param_spec_boolean ("integer-layout",
+							 _("Integer Layout"),
+							 _("If all item layout is done to the nearest integer"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_CLEAR_BACKGROUND,
+                                   g_param_spec_boolean ("clear-background",
+							 _("Clear Background"),
+							 _("If the background is cleared before the canvas is painted"),
+							 TRUE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_REDRAW_WHEN_SCROLLED,
+                                   g_param_spec_boolean ("redraw-when-scrolled",
+							 _("Redraw When Scrolled"),
+							 _("If the canvas is completely redrawn when scrolled, to reduce the flicker of static items"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  /* GtkScrollable interface */
+  g_object_class_override_property (gobject_class, PROP_HADJUSTMENT, "hadjustment");
+  g_object_class_override_property (gobject_class, PROP_VADJUSTMENT, "vadjustment");
+  g_object_class_override_property (gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+  g_object_class_override_property (gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+  /* Signals. */
+
+  /**
+   * GooCanvas::item-created
+   * @canvas: the canvas.
+   * @item: the new item.
+   * @model: the item's model.
+   *
+   * This is emitted when a new canvas item is created, in model/view mode.
+   *
+   * Applications can set up signal handlers for the new items here.
+   */
+  canvas_signals[ITEM_CREATED] =
+    g_signal_new ("item-created",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GooCanvasClass, item_created),
+		  NULL, NULL,
+		  goo_canvas_marshal_VOID__OBJECT_OBJECT,
+		  G_TYPE_NONE, 2,
+		  GOO_TYPE_CANVAS_ITEM,
+		  GOO_TYPE_CANVAS_ITEM_MODEL);
+}
+
+
+static void
+goo_canvas_init (GooCanvas *canvas)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+
+  /* We set GTK_CAN_FOCUS by default, so it works as people expect.
+     Though developers can turn this off if not needed for efficiency. */
+  gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE);
+
+  canvas->scale_x = 1.0;
+  canvas->scale_y = 1.0;
+  canvas->scale = 1.0;
+  canvas->need_update = TRUE;
+  canvas->need_entire_subtree_update = TRUE;
+  canvas->crossing_event.type = GDK_LEAVE_NOTIFY;
+  canvas->anchor = GOO_CANVAS_ANCHOR_NORTH_WEST;
+  canvas->clear_background = TRUE;
+  canvas->redraw_when_scrolled = FALSE;
+  canvas->before_initial_draw = TRUE;
+
+  /* Set the default bounds to a reasonable size. */
+  canvas->bounds.x1 = 0.0;
+  canvas->bounds.y1 = 0.0;
+  canvas->bounds.x2 = GOO_CANVAS_DEFAULT_WIDTH;
+  canvas->bounds.y2 = GOO_CANVAS_DEFAULT_HEIGHT;
+  canvas->automatic_bounds = FALSE;
+  canvas->bounds_from_origin = TRUE;
+  canvas->bounds_padding = 0.0;
+
+  canvas->units = GTK_UNIT_PIXEL;
+  canvas->resolution_x = 96.0;
+  canvas->resolution_y = 96.0;
+
+  /* Create our own adjustments, in case we aren't inserted into a scrolled
+     window. The accessibility code needs these. */
+  canvas->hadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
+							    0.0, 0.0, 0.0));
+  canvas->vadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
+							    0.0, 0.0, 0.0));
+
+  g_object_ref_sink (canvas->hadjustment);
+  g_object_ref_sink (canvas->vadjustment);
+
+  canvas->model_to_item = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  /* Use a simple group as the default root item, which is fine 99% of the
+     time. Apps can set their own root item if required. */
+  canvas->root_item = goo_canvas_group_new (NULL, NULL);
+  goo_canvas_item_set_canvas (canvas->root_item, canvas);
+
+  priv->static_root_item = goo_canvas_group_new (NULL, NULL);
+  goo_canvas_item_set_canvas (priv->static_root_item, canvas);
+  goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
+  priv->static_root_item_model = NULL;
+
+  priv->window_x = priv->static_window_x = 0;
+  priv->window_y = priv->static_window_y = 0;
+}
+
+
+/**
+ * goo_canvas_new:
+ *
+ * Creates a new #GooCanvas widget.
+ *
+ * A #GooCanvasGroup is created automatically as the root item of the canvas,
+ * though this can be overriden with goo_canvas_set_root_item() or
+ * goo_canvas_set_root_item_model().
+ *
+ * Returns: a new #GooCanvas widget.
+ **/
+GtkWidget*
+goo_canvas_new (void)
+{
+  return GTK_WIDGET (g_object_new (GOO_TYPE_CANVAS, NULL));
+}
+
+static void
+goo_canvas_dispose (GObject *object)
+{
+  GooCanvas *canvas = (GooCanvas*) object;
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+
+  if (canvas->model_to_item)
+    {
+      g_hash_table_destroy (canvas->model_to_item);
+      canvas->model_to_item = NULL;
+    }
+
+  if (canvas->root_item)
+    {
+      g_object_unref (canvas->root_item);
+      canvas->root_item = NULL;
+    }
+
+  if (canvas->root_item_model)
+    {
+      g_object_unref (canvas->root_item_model);
+      canvas->root_item_model = NULL;
+    }
+
+  if (priv->static_root_item)
+    {
+      g_object_unref (priv->static_root_item);
+      priv->static_root_item = NULL;
+    }
+
+  if (priv->static_root_item_model)
+    {
+      g_object_unref (priv->static_root_item_model);
+      priv->static_root_item_model = NULL;
+    }
+
+  if (canvas->idle_id)
+    {
+      g_source_remove (canvas->idle_id);
+      canvas->idle_id = 0;
+    }
+
+  /* Release any references we hold to items. */
+  set_item_pointer (&canvas->pointer_item, NULL);
+  set_item_pointer (&canvas->pointer_grab_item, NULL);
+  set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
+  set_item_pointer (&canvas->focused_item, NULL);
+  set_item_pointer (&canvas->keyboard_grab_item, NULL);
+
+  if (canvas->hadjustment)
+    {
+      g_object_unref (canvas->hadjustment);
+      canvas->hadjustment = NULL;
+    }
+
+  if (canvas->vadjustment)
+    {
+      g_object_unref (canvas->vadjustment);
+      canvas->vadjustment = NULL;
+    }
+
+  G_OBJECT_CLASS (goo_canvas_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_finalize (GObject *object)
+{
+  /*GooCanvas *canvas = (GooCanvas*) object;*/
+
+  G_OBJECT_CLASS (goo_canvas_parent_class)->finalize (object);
+}
+
+
+/**
+ * goo_canvas_get_default_line_width:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the default line width, which depends on the current units setting.
+ *
+ * Returns: the default line width of the canvas.
+ **/
+gdouble
+goo_canvas_get_default_line_width (GooCanvas *canvas)
+{
+  gdouble line_width = 2.0;
+
+  if (!canvas)
+    return 2.0;
+
+  /* We use the same default as cairo when using pixels, i.e. 2 pixels.
+     For other units we use 2 points, or thereabouts. */
+  switch (canvas->units)
+    {
+    case GTK_UNIT_PIXEL:
+      line_width = 2.0;
+      break;
+    case GTK_UNIT_POINTS:
+      line_width = 2.0;
+      break;
+    case GTK_UNIT_INCH:
+      line_width = 2.0 / 72.0;
+      break;
+    case GTK_UNIT_MM:
+      line_width = 0.7;
+      break;
+    }
+
+  return line_width;
+}
+
+
+static void
+goo_canvas_setup_cairo_context (GooCanvas *canvas,
+				cairo_t   *cr)
+{
+  /* We use CAIRO_ANTIALIAS_GRAY as the default antialiasing mode, as that is
+     what is recommended when using unhinted text. */
+  cairo_set_antialias (cr, CAIRO_ANTIALIAS_GRAY);
+
+  /* Set the default line width based on the current units setting. */
+  cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas));
+}
+
+/**
+ * goo_canvas_create_cairo_context:
+ * @canvas: a #GooCanvas.
+ *
+ * Creates a cairo context, initialized with the default canvas settings.
+ *
+ * Returns: a new cairo context. It should be freed with cairo_destroy().
+ **/
+cairo_t*
+goo_canvas_create_cairo_context (GooCanvas *canvas)
+{
+  cairo_t *cr;
+  cairo_surface_t *surface;
+
+  /* If the canvas is realized we can use the GDK function to create a cairo
+     context for the canvas window. Otherwise we create a small temporary
+     image surface. */
+  if (canvas && canvas->canvas_window)
+    {
+      cr = gdk_cairo_create (canvas->canvas_window);
+    }
+  else
+    {
+      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+      cr = cairo_create (surface);
+      /* The cairo context will keep a reference to the surface so we can
+	 drop our reference. */
+      cairo_surface_destroy (surface);
+    }
+
+  goo_canvas_setup_cairo_context (canvas, cr);
+
+  return cr;
+}
+
+
+static void
+goo_canvas_get_property    (GObject            *object,
+			    guint               prop_id,
+			    GValue             *value,
+			    GParamSpec         *pspec)
+{
+  GooCanvas *canvas = (GooCanvas*) object;
+
+  switch (prop_id)
+    {
+    case PROP_SCALE:
+      g_value_set_double (value, canvas->scale);
+      break;
+    case PROP_SCALE_X:
+      g_value_set_double (value, canvas->scale_x);
+      break;
+    case PROP_SCALE_Y:
+      g_value_set_double (value, canvas->scale_y);
+      break;
+    case PROP_ANCHOR:
+      g_value_set_enum (value, canvas->anchor);
+      break;
+    case PROP_X1:
+      g_value_set_double (value, canvas->bounds.x1);
+      break;
+    case PROP_Y1:
+      g_value_set_double (value, canvas->bounds.y1);
+      break;
+    case PROP_X2:
+      g_value_set_double (value, canvas->bounds.x2);
+      break;
+    case PROP_Y2:
+      g_value_set_double (value, canvas->bounds.y2);
+      break;
+    case PROP_AUTOMATIC_BOUNDS:
+      g_value_set_boolean (value, canvas->automatic_bounds);
+      break;
+    case PROP_BOUNDS_FROM_ORIGIN:
+      g_value_set_boolean (value, canvas->bounds_from_origin);
+      break;
+    case PROP_BOUNDS_PADDING:
+      g_value_set_double (value, canvas->bounds_padding);
+      break;
+    case PROP_UNITS:
+      g_value_set_enum (value, canvas->units);
+      break;
+    case PROP_RESOLUTION_X:
+      g_value_set_double (value, canvas->resolution_x);
+      break;
+    case PROP_RESOLUTION_Y:
+      g_value_set_double (value, canvas->resolution_y);
+      break;
+    case PROP_INTEGER_LAYOUT:
+      g_value_set_boolean (value, canvas->integer_layout);
+      break;
+    case PROP_CLEAR_BACKGROUND:
+      g_value_set_boolean (value, canvas->clear_background);
+      break;
+    case PROP_REDRAW_WHEN_SCROLLED:
+      g_value_set_boolean (value, canvas->redraw_when_scrolled);
+      break;
+    case PROP_HADJUSTMENT:
+      g_value_set_object (value, canvas->hadjustment);
+      break;
+    case PROP_VADJUSTMENT:
+      g_value_set_object (value, canvas->vadjustment);
+      break;
+    case PROP_HSCROLL_POLICY:
+      g_value_set_enum (value, canvas->hscroll_policy);
+      break;
+    case PROP_VSCROLL_POLICY:
+      g_value_set_enum (value, canvas->vscroll_policy);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_set_property    (GObject            *object,
+			    guint               prop_id,
+			    const GValue       *value,
+			    GParamSpec         *pspec)
+{
+  GooCanvas *canvas = (GooCanvas*) object;
+  GdkColor color = { 0, 0, 0, 0, };
+  gboolean need_reconfigure = FALSE;
+  gboolean need_update_automatic_bounds = FALSE;
+  guint rgb;
+
+  switch (prop_id)
+    {
+    case PROP_SCALE:
+      goo_canvas_set_scale (canvas, g_value_get_double (value));
+      break;
+    case PROP_SCALE_X:
+      goo_canvas_set_scale_internal (canvas, g_value_get_double (value),
+				     canvas->scale_y);
+      break;
+    case PROP_SCALE_Y:
+      goo_canvas_set_scale_internal (canvas, canvas->scale_x,
+				     g_value_get_double (value));
+      break;
+    case PROP_ANCHOR:
+      canvas->anchor = g_value_get_enum (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_X1:
+      canvas->bounds.x1 = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_Y1:
+      canvas->bounds.y1 = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_X2:
+      canvas->bounds.x2 = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_Y2:
+      canvas->bounds.y2 = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_AUTOMATIC_BOUNDS:
+      canvas->automatic_bounds = g_value_get_boolean (value);
+      if (canvas->automatic_bounds)
+	need_update_automatic_bounds = TRUE;
+      break;
+    case PROP_BOUNDS_FROM_ORIGIN:
+      canvas->bounds_from_origin = g_value_get_boolean (value);
+      if (canvas->automatic_bounds)
+	need_update_automatic_bounds = TRUE;
+      break;
+    case PROP_BOUNDS_PADDING:
+      canvas->bounds_padding = g_value_get_double (value);
+      if (canvas->automatic_bounds)
+	need_update_automatic_bounds = TRUE;
+      break;
+    case PROP_UNITS:
+      canvas->units = g_value_get_enum (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_RESOLUTION_X:
+      canvas->resolution_x = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_RESOLUTION_Y:
+      canvas->resolution_y = g_value_get_double (value);
+      need_reconfigure = TRUE;
+      break;
+    case PROP_BACKGROUND_COLOR:
+      if (!g_value_get_string (value))
+	gtk_widget_modify_bg ((GtkWidget*) canvas, GTK_STATE_NORMAL, NULL);
+      else if (gdk_color_parse (g_value_get_string (value), &color))
+	gtk_widget_modify_bg ((GtkWidget*) canvas, GTK_STATE_NORMAL, &color);
+      else
+	g_warning ("Unknown color: %s", g_value_get_string (value));
+      break;
+    case PROP_BACKGROUND_COLOR_RGB:
+      rgb = g_value_get_uint (value);
+      color.red   = ((rgb >> 16) & 0xFF) * 257;
+      color.green = ((rgb >> 8)  & 0xFF) * 257;
+      color.blue  = ((rgb)       & 0xFF) * 257;
+      gtk_widget_modify_bg  ((GtkWidget*) canvas, GTK_STATE_NORMAL, &color);
+      break;
+    case PROP_INTEGER_LAYOUT:
+      canvas->integer_layout = g_value_get_boolean (value);
+      canvas->need_entire_subtree_update = TRUE;
+      goo_canvas_request_update (canvas);
+      break;
+    case PROP_CLEAR_BACKGROUND:
+      canvas->clear_background = g_value_get_boolean (value);
+      break;
+    case PROP_REDRAW_WHEN_SCROLLED:
+      canvas->redraw_when_scrolled = g_value_get_boolean (value);
+      break;
+    case PROP_HADJUSTMENT:
+      goo_canvas_set_hadjustment (canvas, g_value_get_object (value));
+      break;
+    case PROP_VADJUSTMENT:
+      goo_canvas_set_vadjustment (canvas, g_value_get_object (value));
+      break;
+    case PROP_HSCROLL_POLICY:
+      canvas->hscroll_policy = g_value_get_enum (value);
+      gtk_widget_queue_resize (GTK_WIDGET (canvas));
+      break;
+    case PROP_VSCROLL_POLICY:
+      canvas->vscroll_policy = g_value_get_enum (value);
+      gtk_widget_queue_resize (GTK_WIDGET (canvas));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  if (need_update_automatic_bounds)
+    {
+      goo_canvas_update_automatic_bounds (canvas);
+    }
+
+  if (need_reconfigure)
+    {
+      reconfigure_canvas (canvas, FALSE);
+      gtk_widget_queue_draw (GTK_WIDGET (canvas));
+    }
+}
+
+
+/**
+ * goo_canvas_get_root_item_model:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the root item model of the canvas.
+ *
+ * Returns: the root item model, or %NULL if there is no root item model.
+ **/
+GooCanvasItemModel*
+goo_canvas_get_root_item_model (GooCanvas	*canvas)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  return canvas->root_item_model;
+}
+
+
+/**
+ * goo_canvas_set_root_item_model:
+ * @canvas: a #GooCanvas.
+ * @model: a #GooCanvasItemModel.
+ *
+ * Sets the root item model of the canvas.
+ *
+ * A hierarchy of canvas items will be created, corresponding to the hierarchy
+ * of items in the model. Any current canvas items will be removed.
+ **/
+void
+goo_canvas_set_root_item_model (GooCanvas          *canvas,
+				GooCanvasItemModel *model)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+
+  if (canvas->root_item_model == model)
+    return;
+
+  if (canvas->root_item_model)
+    {
+      g_object_unref (canvas->root_item_model);
+      canvas->root_item_model = NULL;
+    }
+
+  if (canvas->root_item)
+    {
+      g_object_unref (canvas->root_item);
+      canvas->root_item = NULL;
+    }
+
+  if (model)
+    {
+      canvas->root_item_model = g_object_ref (model);
+
+      /* Create a hierarchy of canvas items for all the items in the model. */
+      canvas->root_item = goo_canvas_create_item (canvas, model);
+    }
+  else
+    {
+      /* The model has been reset so we go back to a default root group. */
+      canvas->root_item = goo_canvas_group_new (NULL, NULL);
+    }
+
+  goo_canvas_item_set_canvas (canvas->root_item, canvas);
+  canvas->need_update = TRUE;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    goo_canvas_update (canvas);
+
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_get_root_item:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the root item of the canvas, usually a #GooCanvasGroup.
+ *
+ * Returns: the root item, or %NULL if there is no root item.
+ **/
+GooCanvasItem*
+goo_canvas_get_root_item (GooCanvas     *canvas)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  return canvas->root_item;
+}
+
+
+/**
+ * goo_canvas_set_root_item:
+ * @canvas: a #GooCanvas.
+ * @item: the root canvas item.
+ *
+ * Sets the root item of the canvas. Any existing canvas items are removed.
+ **/
+void
+goo_canvas_set_root_item    (GooCanvas		*canvas,
+			     GooCanvasItem      *item)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+
+  if (canvas->root_item == item)
+    return;
+
+  /* Remove any current model. */
+  if (canvas->root_item_model)
+    {
+      g_object_unref (canvas->root_item_model);
+      canvas->root_item_model = NULL;
+    }
+
+  if (canvas->root_item)
+    g_object_unref (canvas->root_item);
+
+  canvas->root_item = g_object_ref (item);
+  goo_canvas_item_set_canvas (canvas->root_item, canvas);
+
+  canvas->need_update = TRUE;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    goo_canvas_update (canvas);
+
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_get_static_root_item:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the static root item of the canvas.
+ *
+ * Static items are exactly the same as ordinary canvas items, except that
+ * they do not move or change size when the canvas is scrolled or the scale
+ * changes.
+ *
+ * Static items are added to the static root item in exactly the same way that
+ * ordinary items are added to the root item.
+ *
+ * Returns: the static root item, or %NULL.
+ **/
+GooCanvasItem*
+goo_canvas_get_static_root_item    (GooCanvas		*canvas)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  return GOO_CANVAS_GET_PRIVATE (canvas)->static_root_item;
+}
+
+
+/**
+ * goo_canvas_set_static_root_item:
+ * @canvas: a #GooCanvas.
+ * @item: the static root item.
+ *
+ * Sets the static root item. Any existing static items are removed.
+ *
+ * Static items are exactly the same as ordinary canvas items, except that
+ * they do not move or change size when the canvas is scrolled or the scale
+ * changes.
+ *
+ * Static items are added to the static root item in exactly the same way that
+ * ordinary items are added to the root item.
+ **/
+void
+goo_canvas_set_static_root_item    (GooCanvas		*canvas,
+				    GooCanvasItem       *item)
+{
+  GooCanvasPrivate *priv;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+
+  priv = GOO_CANVAS_GET_PRIVATE (canvas);
+
+  if (priv->static_root_item == item)
+    return;
+
+  /* Remove any current model. */
+  if (priv->static_root_item_model)
+    {
+      g_object_unref (priv->static_root_item_model);
+      priv->static_root_item_model = NULL;
+    }
+
+  if (priv->static_root_item)
+    g_object_unref (priv->static_root_item);
+
+  priv->static_root_item = g_object_ref (item);
+  goo_canvas_item_set_canvas (priv->static_root_item, canvas);
+  goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
+
+  canvas->need_update = TRUE;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    goo_canvas_update (canvas);
+
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_get_static_root_item_model:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the static root item model of the canvas.
+ *
+ * Static item models are exactly the same as ordinary item models, except that
+ * the corresponding items do not move or change size when the canvas is
+ * scrolled or the scale changes.
+ *
+ * Static items models are added to the static root item model in exactly the
+ * same way that ordinary item models are added to the root item model.
+ *
+ * Returns: the static root item model, or %NULL.
+ **/
+GooCanvasItemModel*
+goo_canvas_get_static_root_item_model (GooCanvas	  *canvas)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  return GOO_CANVAS_GET_PRIVATE (canvas)->static_root_item_model;
+}
+
+
+/**
+ * goo_canvas_set_static_root_item_model:
+ * @canvas: a #GooCanvas.
+ * @model: the static root item model.
+ *
+ * Sets the static root item model. Any existing static item models are
+ * removed.
+ *
+ * Static item models are exactly the same as ordinary item models, except that
+ * the corresponding items do not move or change size when the canvas is
+ * scrolled or the scale changes.
+ *
+ * Static items models are added to the static root item model in exactly the
+ * same way that ordinary item models are added to the root item model.
+ **/
+void
+goo_canvas_set_static_root_item_model (GooCanvas	  *canvas,
+				       GooCanvasItemModel *model)
+{
+  GooCanvasPrivate *priv;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+
+  priv = GOO_CANVAS_GET_PRIVATE (canvas);
+
+  if (priv->static_root_item_model == model)
+    return;
+
+  if (priv->static_root_item_model)
+    {
+      g_object_unref (priv->static_root_item_model);
+      priv->static_root_item_model = NULL;
+    }
+
+  if (priv->static_root_item)
+    {
+      g_object_unref (priv->static_root_item);
+      priv->static_root_item = NULL;
+    }
+
+  if (model)
+    {
+      priv->static_root_item_model = g_object_ref (model);
+
+      /* Create a hierarchy of canvas items for all the items in the model. */
+      priv->static_root_item = goo_canvas_create_item (canvas, model);
+    }
+  else
+    {
+      /* The model has been reset so we go back to a default root group. */
+      priv->static_root_item = goo_canvas_group_new (NULL, NULL);
+    }
+
+  goo_canvas_item_set_canvas (priv->static_root_item, canvas);
+  goo_canvas_item_set_is_static (priv->static_root_item, TRUE);
+  canvas->need_update = TRUE;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    goo_canvas_update (canvas);
+
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_get_item:
+ * @canvas: a #GooCanvas.
+ * @model: a #GooCanvasItemModel.
+ *
+ * Gets the canvas item associated with the given #GooCanvasItemModel.
+ * This is only useful when goo_canvas_set_root_item_model() has been used to
+ * set a model for the canvas.
+ *
+ * For simple applications you can use goo_canvas_get_item() to set up
+ * signal handlers for your items, e.g.
+ *
+ * <informalexample><programlisting>
+ *    item = goo_canvas_get_item (GOO_CANVAS (canvas), my_item);
+ *    g_signal_connect (item, "button_press_event",
+ *                      (GtkSignalFunc) on_my_item_button_press, NULL);
+ * </programlisting></informalexample>
+ *
+ * More complex applications may want to use the #GooCanvas::item-created
+ * signal to hook up their signal handlers.
+ *
+ * Returns: the canvas item corresponding to the given #GooCanvasItemModel,
+ *  or %NULL if no canvas item has been created for it yet.
+ **/
+GooCanvasItem*
+goo_canvas_get_item (GooCanvas          *canvas,
+		     GooCanvasItemModel *model)
+{
+  GooCanvasItem *item = NULL;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model), NULL);
+
+  if (canvas->model_to_item)
+    item = g_hash_table_lookup (canvas->model_to_item, model);
+
+  /* If the item model has a canvas item check it is valid. */
+  g_return_val_if_fail (!item || GOO_IS_CANVAS_ITEM (item), NULL);
+
+  return item;
+}
+
+
+/**
+ * goo_canvas_get_item_at:
+ * @canvas: a #GooCanvas.
+ * @x: the x coordinate of the point.
+ * @y: the y coordinate of the point
+ * @is_pointer_event: %TRUE if the "pointer-events" property of
+ *  items should be used to determine which parts of the item are tested.
+ *
+ * Gets the item at the given point.
+ *
+ * Returns: the item found at the given point, or %NULL if no item was found.
+ **/
+GooCanvasItem*
+goo_canvas_get_item_at (GooCanvas     *canvas,
+			gdouble        x,
+			gdouble        y,
+			gboolean       is_pointer_event)
+{
+  GooCanvasPrivate *priv;
+  cairo_t *cr;
+  GooCanvasItem *result = NULL;
+  GList *list = NULL;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  cr = goo_canvas_create_cairo_context (canvas);
+
+  if (canvas->root_item)
+    list = goo_canvas_item_get_items_at (canvas->root_item, x, y, cr,
+					 is_pointer_event, TRUE, NULL);
+
+  if (!list && priv->static_root_item)
+    {
+      gdouble static_x = x, static_y = y;
+
+      goo_canvas_convert_to_static_item_space (canvas, &static_x, &static_y);
+      list = goo_canvas_item_get_items_at (priv->static_root_item,
+					   static_x, static_y, cr,
+					   is_pointer_event, TRUE, NULL);
+    }
+
+  cairo_destroy (cr);
+
+  /* We just return the top item in the list. */
+  if (list)
+    result = list->data;
+
+  g_list_free (list);
+
+  return result;
+}
+
+
+/**
+ * goo_canvas_get_items_at:
+ * @canvas: a #GooCanvas.
+ * @x: the x coordinate of the point.
+ * @y: the y coordinate of the point
+ * @is_pointer_event: %TRUE if the "pointer-events" property of
+ *  items should be used to determine which parts of the item are tested.
+ *
+ * Gets all items at the given point.
+ *
+ * Returns: a list of items found at the given point, with the top item at
+ *  the start of the list, or %NULL if no items were found. The list must be
+ *  freed with g_list_free().
+ **/
+GList*
+goo_canvas_get_items_at (GooCanvas     *canvas,
+			 gdouble        x,
+			 gdouble        y,
+			 gboolean       is_pointer_event)
+{
+  GooCanvasPrivate *priv;
+  cairo_t *cr;
+  GList *result = NULL;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  cr = goo_canvas_create_cairo_context (canvas);
+
+  if (canvas->root_item)
+    result = goo_canvas_item_get_items_at (canvas->root_item, x, y, cr,
+					   is_pointer_event, TRUE, NULL);
+
+  if (priv->static_root_item)
+    {
+      gdouble static_x = x, static_y = y;
+
+      goo_canvas_convert_to_static_item_space (canvas, &static_x, &static_y);
+      result = goo_canvas_item_get_items_at (priv->static_root_item,
+					     static_x, static_y, cr,
+					     is_pointer_event, TRUE, result);
+    }
+
+  cairo_destroy (cr);
+
+  return result;
+}
+
+
+static GList*
+goo_canvas_get_items_in_area_recurse (GooCanvas		    *canvas,
+				      GooCanvasItem         *item,
+				      const GooCanvasBounds *area,
+				      gboolean		     inside_area,
+				      gboolean               allow_overlaps,
+				      gboolean               include_containers,
+				      GList                 *found_items)
+{
+  GooCanvasBounds bounds;
+  gboolean completely_inside = FALSE, completely_outside = FALSE;
+  gboolean is_container, add_item = FALSE;
+  gint n_children, i;
+
+  /* First check the item/container itself. */
+  goo_canvas_item_get_bounds (item, &bounds);
+
+  is_container = goo_canvas_item_is_container (item);
+
+  if (bounds.x1 >= area->x1 && bounds.x2 <= area->x2
+      && bounds.y1 >= area->y1 && bounds.y2 <= area->y2)
+    completely_inside = TRUE;
+
+  if (bounds.x1 > area->x2 || bounds.x2 < area->x1
+      || bounds.y1 > area->y2 || bounds.y2 < area->y1)
+    completely_outside = TRUE;
+
+  if (inside_area)
+    {
+      if (completely_inside
+	  || (allow_overlaps && !completely_outside))
+	add_item = TRUE;
+    }
+  else
+    {
+      if (completely_outside
+	  || (allow_overlaps && !completely_inside))
+	add_item = TRUE;
+    }
+
+  if (add_item && (!is_container || include_containers))
+    found_items = g_list_prepend (found_items, item);
+
+  /* Now check any children, if appropriate. */
+  if ((inside_area && !completely_outside)
+      || (!inside_area && !completely_inside))
+    {
+      n_children = goo_canvas_item_get_n_children (item);
+      for (i = 0; i < n_children; i++)
+	{
+	  GooCanvasItem *child = goo_canvas_item_get_child (item, i);
+	  found_items = goo_canvas_get_items_in_area_recurse (canvas, child,
+							      area,
+							      inside_area,
+							      allow_overlaps,
+							      include_containers,
+							      found_items);
+	}
+    }
+
+  return found_items;
+}
+
+
+/**
+ * goo_canvas_get_items_in_area:
+ * @canvas: a #GooCanvas.
+ * @area: the area to compare with each item's bounds.
+ * @inside_area: %TRUE if items inside @area should be returned, or %FALSE if
+ *  items outside @area should be returned.
+ * @allow_overlaps: %TRUE if items which are partly inside and partly outside
+ *  should be returned.
+ * @include_containers: %TRUE if containers should be checked as well as
+ *  normal items.
+ *
+ * Gets a list of items inside or outside a given area.
+ *
+ * Returns: a list of items in the given area, or %NULL if no items are found.
+ *  The list should be freed with g_list_free().
+ **/
+GList*
+goo_canvas_get_items_in_area (GooCanvas		    *canvas,
+			      const GooCanvasBounds *area,
+			      gboolean		     inside_area,
+			      gboolean               allow_overlaps,
+			      gboolean               include_containers)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), NULL);
+
+  /* If no root item is set, just return NULL. */
+  if (!canvas->root_item)
+    return NULL;
+
+  return goo_canvas_get_items_in_area_recurse (canvas, canvas->root_item,
+					       area, inside_area,
+					       allow_overlaps,
+					       include_containers, NULL);
+}
+
+
+static void
+goo_canvas_realize (GtkWidget *widget)
+{
+  GooCanvas *canvas;
+  GooCanvasPrivate *priv;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  gint width_pixels, height_pixels;
+  GList *tmp_list;
+  GtkAllocation allocation;
+  GdkWindow* window;
+
+  g_return_if_fail (GOO_IS_CANVAS (widget));
+
+  canvas = GOO_CANVAS (widget);
+  priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  gtk_widget_set_realized (GTK_WIDGET (canvas), TRUE);
+
+  gtk_widget_get_allocation (widget, &allocation);
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+  window = gdk_window_new (gtk_widget_get_parent_window (widget),
+				   &attributes, attributes_mask);
+  gtk_widget_set_window (widget, window);
+  gdk_window_set_user_data (window, widget);
+
+  /* We want to round the sizes up to the next pixel. */
+  width_pixels = ((canvas->bounds.x2 - canvas->bounds.x1) * canvas->device_to_pixels_x) + 1;
+  height_pixels = ((canvas->bounds.y2 - canvas->bounds.y1) * canvas->device_to_pixels_y) + 1;
+
+  attributes.x = canvas->hadjustment ? - gtk_adjustment_get_value (canvas->hadjustment) : 0,
+  attributes.y = canvas->vadjustment ? - gtk_adjustment_get_value (canvas->vadjustment) : 0;
+  attributes.width = MAX (width_pixels, allocation.width);
+  attributes.height = MAX (height_pixels, allocation.height);
+  attributes.event_mask = GDK_EXPOSURE_MASK
+			 | GDK_SCROLL_MASK
+			 | GDK_BUTTON_PRESS_MASK
+			 | GDK_BUTTON_RELEASE_MASK
+			 | GDK_POINTER_MOTION_MASK
+			 | GDK_POINTER_MOTION_HINT_MASK
+			 | GDK_KEY_PRESS_MASK
+			 | GDK_KEY_RELEASE_MASK
+			 | GDK_ENTER_NOTIFY_MASK
+			 | GDK_LEAVE_NOTIFY_MASK
+			 | GDK_FOCUS_CHANGE_MASK
+                         | gtk_widget_get_events (widget);
+
+  priv->window_x = priv->static_window_x = attributes.x;
+  priv->window_y = priv->static_window_y = attributes.y;
+
+  canvas->canvas_window = gdk_window_new (window,
+					  &attributes, attributes_mask);
+  gdk_window_set_user_data (canvas->canvas_window, widget);
+
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.event_mask = 0;
+
+  canvas->tmp_window = gdk_window_new (gtk_widget_get_parent_window (widget),
+				       &attributes, attributes_mask);
+  gdk_window_set_user_data (canvas->tmp_window, widget);
+
+  gtk_widget_set_style(widget, gtk_style_attach (gtk_widget_get_style (widget), window));
+
+  /* Make sure the window backgrounds aren't set, to avoid flicker when
+     scrolling (due to the delay between X clearing the background and
+     GooCanvas painting it). */
+  /* TODO: Do this with GTK+ 3 too? */
+#if 0
+  gdk_window_set_background_pattern (window, NULL);
+  gdk_window_set_background_pattern (canvas->canvas_window, NULL);
+  gdk_window_set_background_pattern (canvas->tmp_window, NULL);
+#endif
+
+  /* Set the parent window of all the child widget items. */
+  tmp_list = canvas->widget_items;
+  while (tmp_list)
+    {
+      GooCanvasWidget *witem = tmp_list->data;
+      tmp_list = tmp_list->next;
+
+      if (witem->widget)
+	gtk_widget_set_parent_window (witem->widget, canvas->canvas_window);
+    }
+
+  goo_canvas_update (GOO_CANVAS (widget));
+}
+
+
+static void
+goo_canvas_unrealize (GtkWidget *widget)
+{
+  GooCanvas *canvas;
+
+  g_return_if_fail (GOO_IS_CANVAS (widget));
+
+  canvas = GOO_CANVAS (widget);
+
+  gdk_window_set_user_data (canvas->canvas_window, NULL);
+  gdk_window_destroy (canvas->canvas_window);
+  canvas->canvas_window = NULL;
+
+  gdk_window_set_user_data (canvas->tmp_window, NULL);
+  gdk_window_destroy (canvas->tmp_window);
+  canvas->tmp_window = NULL;
+
+  if (GTK_WIDGET_CLASS (goo_canvas_parent_class)->unrealize)
+    GTK_WIDGET_CLASS (goo_canvas_parent_class)->unrealize (widget);
+}
+
+
+static void
+goo_canvas_map (GtkWidget *widget)
+{
+  GooCanvas *canvas;
+  GList *tmp_list;
+
+  g_return_if_fail (GOO_IS_CANVAS (widget));
+
+  canvas = GOO_CANVAS (widget);
+
+  gtk_widget_set_mapped (widget, TRUE);
+
+  tmp_list = canvas->widget_items;
+  while (tmp_list)
+    {
+      GooCanvasWidget *witem = tmp_list->data;
+      tmp_list = tmp_list->next;
+
+      if (witem->widget && gtk_widget_get_visible (witem->widget))
+ 	{
+	  if (!gtk_widget_get_mapped (witem->widget))
+	    gtk_widget_map (witem->widget);
+	}
+    }
+
+  gdk_window_show (canvas->canvas_window);
+  gdk_window_show (gtk_widget_get_window (widget));
+}
+
+
+static void
+goo_canvas_style_set (GtkWidget *widget,
+		      GtkStyle  *old_style)
+{
+  if (GTK_WIDGET_CLASS (goo_canvas_parent_class)->style_set)
+    GTK_WIDGET_CLASS (goo_canvas_parent_class)->style_set (widget, old_style);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      /* Make sure the window backgrounds aren't set, to avoid flicker when
+	 scrolling (due to the delay between X clearing the background and
+	 GooCanvas painting it). */
+      /* TODO: Do this with GTK+ 3 too? */
+#if 0
+      gdk_window_set_background_pattern (gtk_widget_get_window (widget), NULL);
+      gdk_window_set_background_pattern (GOO_CANVAS (widget)->canvas_window, NULL);
+#endif
+    }
+}
+
+
+static void
+goo_canvas_configure_hadjustment (GooCanvas *canvas,
+				  gint       window_width)
+{
+  GtkWidget *widget = GTK_WIDGET (canvas);
+  GtkAdjustment *adj = canvas->hadjustment;
+  gboolean changed = FALSE;
+  gboolean value_changed = FALSE;
+  gdouble max_value;
+  gdouble page_size;
+  GtkAllocation allocation;
+
+  canvas->freeze_count++;
+
+  if (gtk_adjustment_get_upper (adj) != window_width)
+    {
+      gtk_adjustment_set_upper (adj, window_width);
+      changed = TRUE;
+    }
+
+  gtk_widget_get_allocation (widget, &allocation);
+  page_size = gtk_adjustment_get_page_size (adj);
+  if (page_size != allocation.width)
+    {
+      page_size = allocation.width;
+      gtk_adjustment_set_page_size (adj, page_size);
+      gtk_adjustment_set_page_increment (adj, page_size * 0.9);
+      gtk_adjustment_set_step_increment (adj, page_size * 0.1);
+      changed = TRUE;
+    }
+
+  max_value = MAX (0.0, gtk_adjustment_get_upper (adj) - page_size);
+  if (gtk_adjustment_get_value (adj) > max_value)
+    {
+      gtk_adjustment_set_value (adj, max_value);
+      value_changed = TRUE;
+    }
+
+  canvas->freeze_count--;
+
+  if (changed)
+    gtk_adjustment_changed (adj);
+
+  if (value_changed)
+    gtk_adjustment_value_changed (adj);
+}
+
+
+static void
+goo_canvas_configure_vadjustment (GooCanvas *canvas,
+				  gint       window_height)
+{
+  GtkWidget *widget = GTK_WIDGET (canvas);
+  GtkAdjustment *adj = canvas->vadjustment;
+  gboolean changed = FALSE;
+  gboolean value_changed = FALSE;
+  gdouble max_value;
+  GtkAllocation allocation;
+  gdouble page_size;
+
+  canvas->freeze_count++;
+
+  if (gtk_adjustment_get_upper (adj) != window_height)
+    {
+      gtk_adjustment_set_upper (adj, window_height);
+      changed = TRUE;
+    }
+
+  gtk_widget_get_allocation (widget, &allocation);
+  page_size = gtk_adjustment_get_page_size (adj);
+  if (page_size != allocation.height)
+    {
+      page_size = allocation.height;
+      gtk_adjustment_set_page_size (adj, page_size);
+      gtk_adjustment_set_page_increment (adj, page_size * 0.9);
+      gtk_adjustment_set_step_increment (adj, page_size * 0.1);
+      changed = TRUE;
+    }
+
+  max_value = MAX (0.0, gtk_adjustment_get_upper (adj) - page_size);
+  if (gtk_adjustment_get_value (adj) > max_value)
+    {
+      gtk_adjustment_set_value (adj, max_value);
+      value_changed = TRUE;
+    }
+
+  canvas->freeze_count--;
+
+  if (changed)
+    gtk_adjustment_changed (adj);
+
+  if (value_changed)
+    gtk_adjustment_value_changed (adj);
+}
+
+
+static void
+recalculate_scales (GooCanvas *canvas)
+{
+  switch (canvas->units)
+    {
+    case GTK_UNIT_PIXEL:
+      canvas->device_to_pixels_x = canvas->scale_x;
+      canvas->device_to_pixels_y = canvas->scale_y;
+      break;
+    case GTK_UNIT_POINTS:
+      canvas->device_to_pixels_x = canvas->scale_x * (canvas->resolution_x / 72.0);
+      canvas->device_to_pixels_y = canvas->scale_y * (canvas->resolution_y / 72.0);
+      break;
+    case GTK_UNIT_INCH:
+      canvas->device_to_pixels_x = canvas->scale_x * canvas->resolution_x;
+      canvas->device_to_pixels_y = canvas->scale_y * canvas->resolution_y;
+      break;
+    case GTK_UNIT_MM:
+      /* There are 25.4 mm to an inch. */
+      canvas->device_to_pixels_x = canvas->scale_x * (canvas->resolution_x / 25.4);
+      canvas->device_to_pixels_y = canvas->scale_y * (canvas->resolution_y / 25.4);
+      break;
+    }
+}
+
+
+static void
+request_static_redraw (GooCanvas             *canvas,
+		       const GooCanvasBounds *bounds)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  GdkRectangle rect;
+
+  if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) || (bounds->x1 == bounds->x2))
+    return;
+
+  /* We subtract one from the left & top edges, in case anti-aliasing makes
+     the drawing use an extra pixel. */
+  rect.x = (double) bounds->x1 - priv->window_x - 1;
+  rect.y = (double) bounds->y1 - priv->window_y - 1;
+
+  /* We add an extra one here for the same reason. (The other extra one is to
+     round up to the next pixel.) And one for luck! */
+  rect.width = (double) bounds->x2 - priv->window_x - rect.x + 2 + 1;
+  rect.height = (double) bounds->y2 - priv->window_y - rect.y + 2 + 1;
+
+#if 0
+  g_print ("Invalidating rect: %i,%i %ix%i\n",
+	   rect.x, rect.y, rect.width, rect.height);
+#endif
+  gdk_window_invalidate_rect (canvas->canvas_window, &rect, FALSE);
+}
+
+
+/* This requests a redraw of all the toplevel static items at their current
+   position, but redraws them at their given new position.
+   We redraw one item at a time to avoid GTK+ merging the rectangles into
+   one big one. */
+static void
+redraw_static_items_at_position (GooCanvas *canvas,
+				 gint       x,
+				 gint       y)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  GooCanvasBounds bounds;
+  GooCanvasItem *item;
+  gint n_children, i, window_x_copy, window_y_copy;
+
+  if (!priv->static_root_item)
+    return;
+
+  window_x_copy = priv->static_window_x;
+  window_y_copy = priv->static_window_y;
+
+  n_children = goo_canvas_item_get_n_children (priv->static_root_item);
+  for (i = 0; i < n_children; i++)
+    {
+      item = goo_canvas_item_get_child (priv->static_root_item, i);
+
+      /* Get the bounds of all the static items, relative to the window. */
+      goo_canvas_item_get_bounds (item, &bounds);
+
+      /* Request a redraw of the old position. */
+      request_static_redraw (canvas, &bounds);
+
+      /* Redraw the item in its new position. */
+      priv->static_window_x = x;
+      priv->static_window_y = y;
+
+      /* FIXME: This causes flicker. Do we need it? */
+      /*gdk_window_process_updates (canvas->canvas_window, TRUE);*/
+
+      /* Now reset the window position. */
+      priv->static_window_x = window_x_copy;
+      priv->static_window_y = window_y_copy;
+    }
+}
+
+
+/* This makes sure the canvas is all set up correctly, i.e. the scrollbar
+   adjustments are set, the canvas x & y offsets are calculated, and the
+   canvas window is sized. */
+static void
+reconfigure_canvas (GooCanvas *canvas,
+		    gboolean   redraw_if_needed)
+{
+  gint width_pixels, height_pixels;
+  gint window_x = 0, window_y = 0, window_width, window_height;
+  gint new_x_offset = 0, new_y_offset = 0;
+  GtkWidget *widget;
+  GtkAllocation allocation;
+
+  widget = GTK_WIDGET (canvas);
+
+  /* Make sure the bounds are sane. */
+  if (canvas->bounds.x2 < canvas->bounds.x1)
+    canvas->bounds.x2 = canvas->bounds.x1;
+  if (canvas->bounds.y2 < canvas->bounds.y1)
+    canvas->bounds.y2 = canvas->bounds.y1;
+
+  /* Recalculate device_to_pixels_x & device_to_pixels_y. */
+  recalculate_scales (canvas);
+
+  /* This is the natural size of the canvas window in pixels, rounded up to
+     the next pixel. */
+  width_pixels = ((canvas->bounds.x2 - canvas->bounds.x1) * canvas->device_to_pixels_x) + 1;
+  height_pixels = ((canvas->bounds.y2 - canvas->bounds.y1) * canvas->device_to_pixels_y) + 1;
+
+  /* The actual window size is always at least as big as the widget's window.*/
+  gtk_widget_get_allocation (widget, &allocation);
+  window_width = MAX (width_pixels, allocation.width);
+  window_height = MAX (height_pixels, allocation.height);
+
+  /* If the width or height is smaller than the window, we need to calculate
+     the canvas x & y offsets according to the anchor. */
+  if (width_pixels < allocation.width)
+    {
+      switch (canvas->anchor)
+	{
+	case GOO_CANVAS_ANCHOR_NORTH_WEST:
+	case GOO_CANVAS_ANCHOR_WEST:
+	case GOO_CANVAS_ANCHOR_SOUTH_WEST:
+	  new_x_offset = 0;
+	  break;
+	case GOO_CANVAS_ANCHOR_NORTH:
+	case GOO_CANVAS_ANCHOR_CENTER:
+	case GOO_CANVAS_ANCHOR_SOUTH:
+	  new_x_offset = (allocation.width - width_pixels) / 2;
+	  break;
+	case GOO_CANVAS_ANCHOR_NORTH_EAST:
+	case GOO_CANVAS_ANCHOR_EAST:
+	case GOO_CANVAS_ANCHOR_SOUTH_EAST:
+	  new_x_offset = allocation.width - width_pixels;
+	  break;
+	}
+    }
+
+  if (height_pixels < allocation.height)
+    {
+      switch (canvas->anchor)
+	{
+	case GOO_CANVAS_ANCHOR_NORTH_WEST:
+	case GOO_CANVAS_ANCHOR_NORTH:
+	case GOO_CANVAS_ANCHOR_NORTH_EAST:
+	  new_y_offset = 0;
+	  break;
+	case GOO_CANVAS_ANCHOR_WEST:
+	case GOO_CANVAS_ANCHOR_CENTER:
+	case GOO_CANVAS_ANCHOR_EAST:
+	  new_y_offset = (allocation.height - height_pixels) / 2;
+	  break;
+	case GOO_CANVAS_ANCHOR_SOUTH_WEST:
+	case GOO_CANVAS_ANCHOR_SOUTH:
+	case GOO_CANVAS_ANCHOR_SOUTH_EAST:
+	  new_y_offset = allocation.height - height_pixels;
+	  break;
+	}
+    }
+
+  canvas->freeze_count++;
+
+  if (canvas->hadjustment)
+    {
+      goo_canvas_configure_hadjustment (canvas, window_width);
+      window_x = - gtk_adjustment_get_value (canvas->hadjustment);
+    }
+
+  if (canvas->vadjustment)
+    {
+      goo_canvas_configure_vadjustment (canvas, window_height);
+      window_y = - gtk_adjustment_get_value (canvas->vadjustment);
+    }
+
+  canvas->freeze_count--;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    {
+      gdk_window_move_resize (canvas->canvas_window, window_x, window_y,
+			      window_width, window_height);
+    }
+
+  /* If one of the offsets has changed we have to redraw the widget. */
+  if (canvas->canvas_x_offset != new_x_offset
+      || canvas->canvas_y_offset != new_y_offset)
+    {
+      canvas->canvas_x_offset = new_x_offset;
+      canvas->canvas_y_offset = new_y_offset;
+
+      if (redraw_if_needed)
+	gtk_widget_queue_draw (GTK_WIDGET (canvas));
+    }
+}
+
+
+static void
+goo_canvas_get_preferred_width (GtkWidget *widget,
+				gint      *minimal_width,
+				gint      *natural_width)
+{
+  *minimal_width = *natural_width = 0;
+}
+
+
+static void
+goo_canvas_get_preferred_height (GtkWidget *widget,
+				 gint      *minimal_height,
+				 gint      *natural_height)
+{
+  *minimal_height = *natural_height = 0;
+}
+
+
+static void
+goo_canvas_allocate_child_widget (GooCanvas       *canvas,
+				  GooCanvasWidget *witem)
+{
+  GooCanvasBounds bounds;
+  GtkAllocation allocation;
+
+  goo_canvas_item_get_bounds ((GooCanvasItem*) witem, &bounds);
+
+  goo_canvas_convert_to_pixels (canvas, &bounds.x1, &bounds.y1);
+  goo_canvas_convert_to_pixels (canvas, &bounds.x2, &bounds.y2);
+
+  /* Note that we only really support integers for the bounds, and we don't
+     support scaling of a canvas with widget items in it. */
+  allocation.x = bounds.x1;
+  allocation.y = bounds.y1;
+  allocation.width = bounds.x2 - allocation.x;
+  allocation.height = bounds.y2 - allocation.y;
+
+  gtk_widget_size_allocate (witem->widget, &allocation);
+}
+
+
+static void
+goo_canvas_size_allocate (GtkWidget     *widget,
+			  GtkAllocation *allocation)
+{
+  GooCanvas *canvas;
+  GList *tmp_list;
+
+  g_return_if_fail (GOO_IS_CANVAS (widget));
+
+  canvas = GOO_CANVAS (widget);
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      /* We can only allocate our children when we are realized, since we
+	 need a window to create a cairo_t which we use for layout. */
+      tmp_list = canvas->widget_items;
+      while (tmp_list)
+	{
+	  GooCanvasWidget *witem = tmp_list->data;
+	  tmp_list = tmp_list->next;
+
+	  if (witem->widget)
+	    goo_canvas_allocate_child_widget (canvas, witem);
+	}
+
+      gdk_window_move_resize (gtk_widget_get_window (widget),
+			      allocation->x, allocation->y,
+			      allocation->width, allocation->height);
+      gdk_window_move_resize (canvas->tmp_window,
+			      allocation->x, allocation->y,
+			      allocation->width, allocation->height);
+    }
+
+  reconfigure_canvas (canvas, TRUE);
+}
+
+
+static void
+goo_canvas_adjustment_value_changed (GtkAdjustment *adjustment,
+				     GooCanvas     *canvas)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  AtkObject *accessible;
+  int new_window_x, new_window_y;
+
+  if (!canvas->freeze_count && gtk_widget_get_realized (GTK_WIDGET(canvas)))
+    {
+      /* These get truncated to ints. */
+      new_window_x = -gtk_adjustment_get_value (canvas->hadjustment);
+      new_window_y = -gtk_adjustment_get_value (canvas->vadjustment);
+
+      if (canvas->redraw_when_scrolled)
+	{
+	  /* Map the temporary window to stop the canvas window being scrolled.
+	     When it is unmapped the entire canvas will be redrawn. */
+	  if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
+	    gdk_window_show (canvas->tmp_window);
+	}
+      else
+	{
+	  /* Redraw the area currently occupied by the static items. But
+	     draw the static items in their new position. This stops them
+	     from being "dragged" when the window is scrolled. */
+	  redraw_static_items_at_position (canvas, new_window_x, new_window_y);
+	}
+
+      /* Move the window to the new position. */
+      priv->window_x = priv->static_window_x = new_window_x;
+      priv->window_y = priv->static_window_y = new_window_y;
+
+      gdk_window_move (canvas->canvas_window, new_window_x, new_window_y);
+
+      if (canvas->redraw_when_scrolled)
+	{
+	  /* Unmap the temporary window, causing the entire canvas to be
+	     redrawn. */
+	  if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
+	    gdk_window_hide (canvas->tmp_window);
+	}
+      else
+	{
+	  /* Process updates here for smoother scrolling. */
+	  /* FIXME: This causes flicker. Do we need it? */
+	  /*gdk_window_process_updates (canvas->canvas_window, TRUE);*/
+
+	  /* Now ensure the static items are redrawn in their new position. */
+	  redraw_static_items_at_position (canvas, priv->window_x,
+					   priv->window_y);
+	}
+
+      /* Notify any accessibility modules that the view has changed. */
+      accessible = gtk_widget_get_accessible (GTK_WIDGET (canvas));
+      g_signal_emit_by_name (accessible, "visible_data_changed");
+    }
+}
+
+static void
+goo_canvas_set_hadjustment (GooCanvas *canvas,
+			    GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  if (adjustment && canvas->hadjustment == adjustment)
+    return;
+
+  if (canvas->hadjustment)
+    {
+      g_signal_handlers_disconnect_by_func (canvas->hadjustment,
+                                            goo_canvas_adjustment_value_changed,
+                                            canvas);
+      g_object_unref (canvas->hadjustment);
+    }
+
+  if (adjustment == NULL)
+    adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+  g_signal_connect (adjustment, "value-changed",
+                    G_CALLBACK (goo_canvas_adjustment_value_changed), canvas);
+  canvas->hadjustment = g_object_ref_sink (adjustment);
+  reconfigure_canvas (canvas, TRUE);
+
+  g_object_notify (G_OBJECT (canvas), "hadjustment");
+}
+
+static void
+goo_canvas_set_vadjustment (GooCanvas   *canvas,
+			    GtkAdjustment *adjustment)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  if (adjustment && canvas->vadjustment == adjustment)
+    return;
+
+  if (canvas->vadjustment)
+    {
+      g_signal_handlers_disconnect_by_func (canvas->vadjustment,
+                                            goo_canvas_adjustment_value_changed,
+                                            canvas);
+      g_object_unref (canvas->vadjustment);
+    }
+
+  if (adjustment == NULL)
+    adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+  g_signal_connect (adjustment, "value-changed",
+                    G_CALLBACK (goo_canvas_adjustment_value_changed), canvas);
+  canvas->vadjustment = g_object_ref_sink (adjustment);
+  reconfigure_canvas (canvas, TRUE);
+
+  g_object_notify (G_OBJECT (canvas), "vadjustment");
+}
+
+/* Sets one of our pointers to an item, adding a reference to it and
+   releasing any reference to the current item. */
+static void
+set_item_pointer (GooCanvasItem **item,
+		  GooCanvasItem  *new)
+{
+  /* If the item hasn't changed, just return. */
+  if (*item == new)
+    return;
+
+  /* Unref the current item, if it isn't NULL. */
+  if (*item)
+    g_object_unref (*item);
+
+  /* Set the new item. */
+  *item = new;
+
+  /* Add a reference to it, if it isn't NULL. */
+  if (*item)
+    g_object_ref (*item);
+}
+
+
+/**
+ * goo_canvas_get_bounds:
+ * @canvas: a #GooCanvas.
+ * @left: a pointer to a #gdouble to return the left edge, or %NULL.
+ * @top: a pointer to a #gdouble to return the top edge, or %NULL.
+ * @right: a pointer to a #gdouble to return the right edge, or %NULL.
+ * @bottom: a pointer to a #gdouble to return the bottom edge, or %NULL.
+ *
+ * Gets the bounds of the canvas, in canvas units.
+ *
+ * By default, canvas units are pixels, though the #GooCanvas:units property
+ * can be used to change the units to points, inches or millimeters.
+ **/
+void
+goo_canvas_get_bounds	(GooCanvas *canvas,
+			 gdouble   *left,
+			 gdouble   *top,
+			 gdouble   *right,
+			 gdouble   *bottom)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  if (left)
+    *left = canvas->bounds.x1;
+  if (top)
+    *top = canvas->bounds.y1;
+  if (right)
+    *right = canvas->bounds.x2;
+  if (bottom)
+    *bottom = canvas->bounds.y2;
+}
+
+
+/**
+ * goo_canvas_set_bounds:
+ * @canvas: a #GooCanvas.
+ * @left: the left edge.
+ * @top: the top edge.
+ * @right: the right edge.
+ * @bottom: the bottom edge.
+ *
+ * Sets the bounds of the #GooCanvas, in canvas units.
+ *
+ * By default, canvas units are pixels, though the #GooCanvas:units property
+ * can be used to change the units to points, inches or millimeters.
+ **/
+void
+goo_canvas_set_bounds	(GooCanvas *canvas,
+			 gdouble    left,
+			 gdouble    top,
+			 gdouble    right,
+			 gdouble    bottom)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  canvas->bounds.x1 = left;
+  canvas->bounds.y1 = top;
+  canvas->bounds.x2 = right;
+  canvas->bounds.y2 = bottom;
+
+  reconfigure_canvas (canvas, FALSE);
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_scroll_to:
+ * @canvas: a #GooCanvas.
+ * @left: the x coordinate to scroll to.
+ * @top: the y coordinate to scroll to.
+ *
+ * Scrolls the canvas, placing the given point as close to the top-left of
+ * the view as possible.
+ **/
+void
+goo_canvas_scroll_to	     (GooCanvas     *canvas,
+			      gdouble        left,
+			      gdouble        top)
+{
+  gdouble x = left, y = top;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  /* The scrollbar adjustments use pixel values, so convert to pixels. */
+  goo_canvas_convert_to_pixels (canvas, &x, &y);
+
+  /* Make sure we stay within the bounds. */
+  x = CLAMP (x, gtk_adjustment_get_lower (canvas->hadjustment),
+	     gtk_adjustment_get_upper (canvas->hadjustment) - gtk_adjustment_get_page_size (canvas->hadjustment));
+  y = CLAMP (y, gtk_adjustment_get_lower (canvas->vadjustment),
+	     gtk_adjustment_get_upper (canvas->vadjustment) - gtk_adjustment_get_page_size (canvas->vadjustment));
+
+  canvas->freeze_count++;
+
+  gtk_adjustment_set_value (canvas->hadjustment, x);
+  gtk_adjustment_set_value (canvas->vadjustment, y);
+
+  canvas->freeze_count--;
+  goo_canvas_adjustment_value_changed (NULL, canvas);
+}
+
+
+/* This makes sure the given item is displayed, scrolling if necessary. */
+static void
+goo_canvas_scroll_to_item (GooCanvas     *canvas,
+			   GooCanvasItem *item)
+{
+  GooCanvasBounds bounds;
+  gdouble hvalue, vvalue;
+
+  /* We can't scroll to static items. */
+  if (goo_canvas_item_get_is_static (item))
+    return;
+
+  goo_canvas_item_get_bounds (item, &bounds);
+
+  goo_canvas_convert_to_pixels (canvas, &bounds.x1, &bounds.y1);
+  goo_canvas_convert_to_pixels (canvas, &bounds.x2, &bounds.y2);
+
+  canvas->freeze_count++;
+
+  /* Remember the current adjustment values. */
+  hvalue = gtk_adjustment_get_value (canvas->hadjustment);
+  vvalue = gtk_adjustment_get_value (canvas->vadjustment);
+
+  /* Update the adjustments so the item is displayed. */
+  gtk_adjustment_clamp_page (canvas->hadjustment, bounds.x1, bounds.x2);
+  gtk_adjustment_clamp_page (canvas->vadjustment, bounds.y1, bounds.y2);
+
+  canvas->freeze_count--;
+
+  /* If the adjustments have changed we need to scroll. */
+  if (hvalue != gtk_adjustment_get_value (canvas->hadjustment)
+      || vvalue != gtk_adjustment_get_value (canvas->vadjustment))
+    goo_canvas_adjustment_value_changed (NULL, canvas);
+}
+
+
+/**
+ * goo_canvas_get_scale:
+ * @canvas: a #GooCanvas.
+ *
+ * Gets the current scale of the canvas.
+ *
+ * The scale specifies the magnification factor of the canvas, e.g. if an item
+ * has a width of 2 pixels and the scale is set to 3, it will be displayed with
+ * a width of 2 x 3 = 6 pixels.
+ *
+ * Returns: the current scale setting.
+ **/
+gdouble
+goo_canvas_get_scale	(GooCanvas *canvas)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), 1.0);
+
+  return canvas->scale;
+}
+
+
+static void
+goo_canvas_set_scale_internal	(GooCanvas *canvas,
+				 gdouble    scale_x,
+				 gdouble    scale_y)
+{
+  gdouble x, y;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  /* Calculate the coords of the current center point in pixels. */
+  x = gtk_adjustment_get_value (canvas->hadjustment) + gtk_adjustment_get_page_size (canvas->hadjustment)/ 2;
+  y = gtk_adjustment_get_value (canvas->vadjustment) + gtk_adjustment_get_page_size (canvas->vadjustment)/ 2;
+
+  /* Convert from pixel units to device units. */
+  goo_canvas_convert_from_pixels (canvas, &x, &y);
+
+  /* Show our temporary window above the canvas window, so that the windowing
+     system doesn't try to scroll the contents when we change the adjustments.
+     Since we are changing the scale we need to redraw everything so the
+     scrolling is unnecessary and really ugly.
+     FIXME: There is a possible issue with keyboard focus/input methods here,
+     since hidden windows can't have the keyboard focus. */
+#if 0
+  if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
+    gdk_window_show (canvas->tmp_window);
+#endif
+
+  canvas->freeze_count++;
+
+  canvas->scale_x = scale_x;
+  canvas->scale_y = scale_y;
+  canvas->scale = MIN (scale_x, scale_y);
+  reconfigure_canvas (canvas, FALSE);
+
+  /* Convert from the center point to the new desired top-left posision. */
+  x -= gtk_adjustment_get_page_size (canvas->hadjustment)/ canvas->device_to_pixels_x / 2;
+  y -= gtk_adjustment_get_page_size (canvas->vadjustment)/ canvas->device_to_pixels_y / 2;
+
+  /* Now try to scroll to it. */
+  goo_canvas_scroll_to (canvas, x, y);
+
+  canvas->freeze_count--;
+  goo_canvas_adjustment_value_changed (NULL, canvas);
+
+  /* Now hide the temporary window, so the canvas window will get a draw
+     signal. */
+#if 0
+  if (gtk_widget_get_mapped (GTK_WIDGET (canvas)))
+    gdk_window_hide (canvas->tmp_window);
+#endif
+  gtk_widget_queue_draw (GTK_WIDGET (canvas));
+}
+
+
+/**
+ * goo_canvas_set_scale:
+ * @canvas: a #GooCanvas.
+ * @scale: the new scale setting.
+ *
+ * Sets the scale of the canvas.
+ *
+ * The scale specifies the magnification factor of the canvas, e.g. if an item
+ * has a width of 2 pixels and the scale is set to 3, it will be displayed with
+ * a width of 2 x 3 = 6 pixels.
+ **/
+void
+goo_canvas_set_scale	(GooCanvas *canvas,
+			 gdouble    scale)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+
+  goo_canvas_set_scale_internal (canvas, scale, scale);
+}
+
+
+/**
+ * goo_canvas_unregister_item:
+ * @canvas: a #GooCanvas.
+ * @model: the item model whose canvas item is being finalized.
+ *
+ * This function is only intended to be used when implementing new canvas
+ * items.
+ *
+ * It should be called in the finalize method of #GooCanvasItem
+ * objects, to remove the canvas item from the #GooCanvas's hash table.
+ **/
+void
+goo_canvas_unregister_item (GooCanvas          *canvas,
+			    GooCanvasItemModel *model)
+{
+  if (canvas->model_to_item)
+    g_hash_table_remove (canvas->model_to_item, model);
+}
+
+
+/**
+ * goo_canvas_create_item:
+ * @canvas: a #GooCanvas.
+ * @model: the item model to create a canvas item for.
+ *
+ * This function is only intended to be used when implementing new canvas
+ * items, typically container items such as #GooCanvasGroup.
+ *
+ * It creates a new canvas item for the given item model, and recursively
+ * creates items for any children.
+ *
+ * It uses the create_item() virtual method if it has been set.
+ * Subclasses of #GooCanvas can define this method if they want to use
+ * custom views for items.
+ *
+ * It emits the #GooCanvas::item-created signal after creating the view, so
+ * application code can connect signal handlers to the new view if desired.
+ *
+ * Returns: a new canvas item.
+ **/
+GooCanvasItem*
+goo_canvas_create_item  (GooCanvas          *canvas,
+			 GooCanvasItemModel *model)
+{
+  GooCanvasItem *item = NULL;
+
+  /* Use the virtual method if it has been set. */
+  if (GOO_CANVAS_GET_CLASS (canvas)->create_item)
+    item = GOO_CANVAS_GET_CLASS (canvas)->create_item (canvas, model);
+
+  /* The virtual method can return NULL to use the default view for an item. */
+  if (!item)
+    item = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model)->create_item (model,
+								 canvas);
+
+  if (canvas->model_to_item)
+    g_hash_table_insert (canvas->model_to_item, model, item);
+
+  /* Emit a signal so apps can hook up signal handlers if they want. */
+  g_signal_emit (canvas, canvas_signals[ITEM_CREATED], 0, item, model);
+
+  return item;
+}
+
+
+static void
+goo_canvas_update_automatic_bounds (GooCanvas       *canvas)
+{
+  GooCanvasBounds bounds = { 0.0, 0.0, GOO_CANVAS_DEFAULT_WIDTH,
+			     GOO_CANVAS_DEFAULT_HEIGHT };
+
+  if (canvas->root_item)
+    goo_canvas_item_get_bounds (canvas->root_item, &bounds);
+
+  /* Calculate the new automatic bounds, which is the bounds of all the items
+     in the canvas plus any specified padding. If bounds_from_origin is set
+     x1 and y1 are set to 0.0. */
+  if (canvas->bounds_from_origin)
+    {
+      bounds.x1 = 0.0;
+      bounds.y1 = 0.0;
+      bounds.x2 += canvas->bounds_padding;
+      bounds.y2 += canvas->bounds_padding;
+    }
+  else
+    {
+      bounds.x1 -= canvas->bounds_padding;
+      bounds.y1 -= canvas->bounds_padding;
+      bounds.x2 += canvas->bounds_padding;
+      bounds.y2 += canvas->bounds_padding;
+    }
+
+  /* Make sure the bounds are sane. */
+  if (bounds.x2 < bounds.x1)
+    bounds.x2 = bounds.x1;
+  if (bounds.y2 < bounds.y1)
+    bounds.y2 = bounds.y1;
+
+  /* If the bounds have changed, reconfigure the canvas and redraw. */
+  if (bounds.x1 != canvas->bounds.x1
+      || bounds.y1 != canvas->bounds.y1
+      || bounds.x2 != canvas->bounds.x2
+      || bounds.y2 != canvas->bounds.y2)
+    {
+      canvas->bounds = bounds;
+      reconfigure_canvas (canvas, FALSE);
+      gtk_widget_queue_draw (GTK_WIDGET (canvas));
+    }
+}
+
+
+static void
+goo_canvas_update_internal (GooCanvas *canvas,
+			    cairo_t   *cr)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  GooCanvasBounds bounds, static_bounds;
+
+  /* It is possible that processing the first set of updates causes other
+     updates to be scheduled, so we loop round until all are done. Items
+     should ensure that they don't cause this to loop forever. */
+  while (canvas->need_update)
+    {
+      gboolean entire_tree = canvas->need_entire_subtree_update;
+
+      canvas->need_update = FALSE;
+      canvas->need_entire_subtree_update = FALSE;
+      if (canvas->root_item)
+	goo_canvas_item_update (canvas->root_item, entire_tree, cr, &bounds);
+
+      if (priv->static_root_item)
+	goo_canvas_item_update (priv->static_root_item, entire_tree, cr,
+				&static_bounds);
+    }
+
+  /* If the bounds are automatically-calculated, update them now. */
+  if (canvas->root_item && canvas->automatic_bounds)
+    goo_canvas_update_automatic_bounds (canvas);
+
+  /* Check which item is under the pointer. */
+  update_pointer_item (canvas, NULL);
+}
+
+
+/**
+ * goo_canvas_update:
+ * @canvas: a #GooCanvas.
+ *
+ * This function is only intended to be used by subclasses of #GooCanvas or
+ * #GooCanvasItem implementations.
+ *
+ * It updates any items that need updating.
+ *
+ * If the bounds of items change, they will request a redraw of the old and
+ * new bounds so the display is updated correctly.
+ **/
+void
+goo_canvas_update (GooCanvas *canvas)
+{
+  cairo_t *cr = goo_canvas_create_cairo_context (canvas);
+  goo_canvas_update_internal (canvas, cr);
+  cairo_destroy (cr);
+}
+
+
+static gint
+goo_canvas_idle_handler (GooCanvas *canvas)
+{
+  GDK_THREADS_ENTER ();
+
+  goo_canvas_update (canvas);
+
+  /* Reset idle id. Note that we do this after goo_canvas_update(), to
+     make sure we don't schedule another idle handler while that is running. */
+  canvas->idle_id = 0;
+
+  GDK_THREADS_LEAVE ();
+
+  /* Return FALSE to remove the idle handler. */
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_request_update:
+ * @canvas: a #GooCanvas.
+ *
+ * This function is only intended to be used by subclasses of #GooCanvas or
+ * #GooCanvasItem implementations.
+ *
+ * It schedules an update of the #GooCanvas. This will be performed in
+ * the idle loop, after all pending events have been handled, but before
+ * the canvas has been repainted.
+ **/
+void
+goo_canvas_request_update (GooCanvas   *canvas)
+{
+  canvas->need_update = TRUE;
+
+  /* We have to wait until we are realized. We'll do a full update then. */
+  if (!gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    return;
+
+  /* We use a higher priority than the normal GTK+ resize/redraw idle handlers
+   * so the canvas state will be updated before allocating sizes & redrawing.
+   */
+  if (!canvas->idle_id)
+    canvas->idle_id = g_idle_add_full (GTK_PRIORITY_RESIZE - 5, (GSourceFunc) goo_canvas_idle_handler, canvas, NULL);
+}
+
+
+/**
+ * goo_canvas_request_redraw:
+ * @canvas: a #GooCanvas.
+ * @bounds: the bounds to redraw, in device space.
+ *
+ * This function is only intended to be used by subclasses of #GooCanvas or
+ * #GooCanvasItem implementations.
+ *
+ * Requests that the given bounds be redrawn. The bounds must be in the canvas
+ * coordinate space.
+ **/
+void
+goo_canvas_request_redraw (GooCanvas             *canvas,
+			   const GooCanvasBounds *bounds)
+{
+  GdkRectangle rect;
+
+  if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) || (bounds->x1 == bounds->x2))
+    return;
+
+  /* We subtract one from the left & top edges, in case anti-aliasing makes
+     the drawing use an extra pixel. */
+  rect.x = (double) (bounds->x1 - canvas->bounds.x1) * canvas->device_to_pixels_x - 1;
+  rect.y = (double) (bounds->y1 - canvas->bounds.y1) * canvas->device_to_pixels_y - 1;
+
+  /* We add an extra one here for the same reason. (The other extra one is to
+     round up to the next pixel.) And one for luck! */
+  rect.width = (double) (bounds->x2 - canvas->bounds.x1) * canvas->device_to_pixels_x
+    - rect.x + 2 + 1;
+  rect.height = (double) (bounds->y2 - canvas->bounds.y1) * canvas->device_to_pixels_y
+    - rect.y + 2 + 1;
+
+  rect.x += canvas->canvas_x_offset;
+  rect.y += canvas->canvas_y_offset;
+
+  gdk_window_invalidate_rect (canvas->canvas_window, &rect, FALSE);
+}
+
+
+/**
+ * goo_canvas_request_item_redraw:
+ * @canvas: a #GooCanvas.
+ * @bounds: the bounds of the item to redraw.
+ * @is_static: if the item is static.
+ *
+ * This function is only intended to be used by subclasses of #GooCanvas or
+ * #GooCanvasItem implementations.
+ *
+ * Requests that the given bounds be redrawn. If @is_static is %TRUE the bounds
+ * are assumed to be in the static item coordinate space, otherwise they are
+ * assumed to be in the canvas coordinate space.
+ *
+ * If @is_static is %FALSE this function behaves the same as
+ * goo_canvas_request_redraw().
+ **/
+void
+goo_canvas_request_item_redraw (GooCanvas             *canvas,
+				const GooCanvasBounds *bounds,
+				gboolean               is_static)
+{
+  /* If the canvas hasn't been painted yet, we can just return as it all needs
+     a redraw. This can save a lot of time if there are lots of items. */
+  if (canvas->before_initial_draw)
+    return;
+
+  if (is_static)
+    request_static_redraw (canvas, bounds);
+  else
+    goo_canvas_request_redraw (canvas, bounds);
+}
+
+
+static void
+paint_static_items (GooCanvas       *canvas,
+		    cairo_t         *cr,
+		    GooCanvasBounds *clip_bounds)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+
+  cairo_save (cr);
+  cairo_identity_matrix (cr);
+  cairo_translate (cr, -priv->static_window_x, -priv->static_window_y);
+  /* FIXME: Uses pixels at present - use canvas units instead? */
+  goo_canvas_item_paint (priv->static_root_item, cr, clip_bounds, 1.0);
+  cairo_restore (cr);
+}
+
+
+static gboolean
+goo_canvas_draw (GtkWidget      *widget,
+		 cairo_t        *cr)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+  GooCanvasBounds clip_bounds, bounds, root_item_bounds;
+  double x1, y1, x2, y2;
+
+  if (!gtk_cairo_should_draw_window (cr, canvas->canvas_window))
+    return FALSE;
+
+  if (!canvas->root_item)
+    {
+      canvas->before_initial_draw = FALSE;
+      return FALSE;
+    }
+
+  /* TODO: Need to worry about child widgets? */
+
+  /* The clip extents tell us which parts of the window need to be drawn,
+     in pixels, where (0,0) is the top-left of the widget window (not the
+     entire canvas window as was the case with the expose_event signal). */
+  cairo_clip_extents (cr, &clip_bounds.x1, &clip_bounds.y1,
+		      &clip_bounds.x2, &clip_bounds.y2);
+
+  cairo_save (cr);
+
+  /* Get rid of the translation passed in by GTK+. We use our own. */
+  cairo_identity_matrix (cr);
+
+  /* Set our default drawing settins - antialias, line width. */
+  goo_canvas_setup_cairo_context (canvas, cr);
+
+  /* Clear the background. */
+  if (canvas->clear_background)
+    {
+      const GtkStyle* style = gtk_widget_get_style (widget);
+      const GtkStateType state = gtk_widget_get_state (widget);
+      gdk_cairo_set_source_color (cr, &(style->base[state]));
+      cairo_paint (cr);
+      cairo_set_source_rgb (cr, 0, 0, 0);
+   }
+
+  if (canvas->need_update)
+    goo_canvas_update_internal (canvas, cr);
+
+  bounds = clip_bounds;
+  goo_canvas_convert_from_window_pixels (canvas, &bounds.x1, &bounds.y1);
+  goo_canvas_convert_from_window_pixels (canvas, &bounds.x2, &bounds.y2);
+
+  /* Get rid of the current clip, as it uses the wrong coordinate space.
+     FIXME: Maybe we should always set a clip with the new GTK+ drawing code. */
+  cairo_reset_clip (cr);
+
+  /* Translate it to use the canvas pixel offsets (used when the canvas is
+     smaller than the window and the anchor isn't set to NORTH_WEST). */
+  cairo_translate (cr, canvas->canvas_x_offset, canvas->canvas_y_offset);
+
+  /* Scale it so we can use canvas coordinates. */
+  cairo_scale (cr, canvas->device_to_pixels_x, canvas->device_to_pixels_y);
+
+  /* Translate it so the top-left of the canvas becomes (0,0). */
+  cairo_translate (cr, -canvas->bounds.x1, -canvas->bounds.y1);
+
+  /* TODO: check this works OK with the clipping from GTK+ 3. */
+  /* Clip to the canvas bounds, if necessary. We only need to clip if the
+     items in the canvas extend outside the canvas bounds and the canvas
+     bounds is less than the area being painted. */
+  goo_canvas_item_get_bounds (canvas->root_item, &root_item_bounds);
+  if ((root_item_bounds.x1 < canvas->bounds.x1
+       && canvas->bounds.x1 > bounds.x1)
+      || (root_item_bounds.x2 > canvas->bounds.x2
+	  && canvas->bounds.x2 < bounds.x2)
+      || (root_item_bounds.y1 < canvas->bounds.y1
+	  && canvas->bounds.y1 > bounds.y1)
+      || (root_item_bounds.y2 > canvas->bounds.y2
+	  && canvas->bounds.y2 < bounds.y2))
+    {
+      /* Clip to the intersection of the canvas bounds and the expose
+	 bounds, to avoid cairo's 16-bit limits. */
+      x1 = MAX (canvas->bounds.x1, bounds.x1);
+      y1 = MAX (canvas->bounds.y1, bounds.y1);
+      x2 = MIN (canvas->bounds.x2, bounds.x2);
+      y2 = MIN (canvas->bounds.y2, bounds.y2);
+
+      cairo_new_path (cr);
+      cairo_move_to (cr, x1, y1);
+      cairo_line_to (cr, x2, y1);
+      cairo_line_to (cr, x2, y2);
+      cairo_line_to (cr, x1, y2);
+      cairo_close_path (cr);
+      cairo_clip (cr);
+    }
+
+#if 0
+  g_print ("Painting bounds: %g, %g - %g, %g\n", bounds.x1, bounds.y1,
+	   bounds.x2, bounds.y2);
+#endif
+  goo_canvas_item_paint (canvas->root_item, cr, &bounds, canvas->scale);
+
+  cairo_restore (cr);
+
+  paint_static_items (canvas, cr, &clip_bounds);
+
+  GTK_WIDGET_CLASS (goo_canvas_parent_class)->draw (widget, cr);
+
+  canvas->before_initial_draw = FALSE;
+
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_render:
+ * @canvas: a #GooCanvas.
+ * @cr: a cairo context.
+ * @bounds: the area to render, or %NULL to render the entire canvas.
+ * @scale: the scale to compare with each item's visibility
+ * threshold to see if they should be rendered. This only affects items that
+ * have their visibility set to %GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD.
+ *
+ * Renders all or part of a canvas to the given cairo context.
+ **/
+void
+goo_canvas_render (GooCanvas             *canvas,
+		   cairo_t               *cr,
+		   const GooCanvasBounds *bounds,
+		   gdouble                scale)
+{
+  if (canvas->need_update)
+    goo_canvas_update (canvas);
+
+  /* Set the default line width based on the current units setting. */
+  cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas));
+
+  if (bounds)
+    {
+      /* Clip to the given bounds. */
+      cairo_new_path (cr);
+      cairo_move_to (cr, bounds->x1, bounds->y1);
+      cairo_line_to (cr, bounds->x2, bounds->y1);
+      cairo_line_to (cr, bounds->x2, bounds->y2);
+      cairo_line_to (cr, bounds->x1, bounds->y2);
+      cairo_close_path (cr);
+      cairo_clip (cr);
+
+      goo_canvas_item_paint (canvas->root_item, cr, bounds, scale);
+    }
+  else
+    {
+      goo_canvas_item_paint (canvas->root_item, cr, &canvas->bounds, scale);
+    }
+}
+
+
+/*
+ * Keyboard/Mouse Event & Grab Handling.
+ */
+
+/* Initializes a synthesized crossing event from a given button press/release,
+   motion, or crossing event. */
+static void
+initialize_crossing_event (GooCanvas *canvas,
+			   GdkEvent  *event)
+{
+  GdkEventCrossing *crossing_event = &canvas->crossing_event;
+
+  /* Initialize the crossing event. */
+  crossing_event->type       = event->any.type;
+  crossing_event->window     = event->any.window;
+  crossing_event->send_event = event->any.send_event;
+  crossing_event->subwindow  = NULL;
+  crossing_event->detail     = GDK_NOTIFY_ANCESTOR;
+  crossing_event->focus      = FALSE;
+  crossing_event->mode       = GDK_CROSSING_NORMAL;
+
+  switch (event->type)
+    {
+    case GDK_MOTION_NOTIFY:
+      crossing_event->time   = event->motion.time;
+      crossing_event->x      = event->motion.x;
+      crossing_event->y      = event->motion.y;
+      crossing_event->x_root = event->motion.x_root;
+      crossing_event->y_root = event->motion.y_root;
+      crossing_event->state  = event->motion.state;
+      break;
+
+    case GDK_ENTER_NOTIFY:
+    case GDK_LEAVE_NOTIFY:
+      crossing_event->time   = event->crossing.time;
+      crossing_event->x      = event->crossing.x;
+      crossing_event->y      = event->crossing.y;
+      crossing_event->x_root = event->crossing.x_root;
+      crossing_event->y_root = event->crossing.y_root;
+      crossing_event->state  = event->crossing.state;
+      break;
+
+    case GDK_SCROLL:
+      crossing_event->time   = event->scroll.time;
+      crossing_event->x      = event->scroll.x;
+      crossing_event->y      = event->scroll.y;
+      crossing_event->x_root = event->scroll.x_root;
+      crossing_event->y_root = event->scroll.y_root;
+      crossing_event->state  = event->scroll.state;
+      break;
+
+    default:
+      /* It must be a button press/release event. */
+      crossing_event->time   = event->button.time;
+      crossing_event->x      = event->button.x;
+      crossing_event->y      = event->button.y;
+      crossing_event->x_root = event->button.x_root;
+      crossing_event->y_root = event->button.y_root;
+      crossing_event->state  = event->button.state;
+      break;
+    }
+}
+
+
+/* Emits a signal for an item and all its parents, until one of them
+   returns TRUE. */
+static gboolean
+propagate_event (GooCanvas     *canvas,
+		 GooCanvasItem *item,
+		 gchar         *signal_name,
+		 GdkEvent      *event)
+{
+  GooCanvasItem *ancestor;
+  gboolean stop_emission = FALSE, valid;
+
+  /* Don't emit any events if the canvas is not realized. */
+  if (!gtk_widget_get_realized (GTK_WIDGET (canvas)))
+    return FALSE;
+
+  if (item)
+    {
+      /* Check if the item is still in the canvas. */
+      if (!ITEM_IS_VALID (item))
+	return FALSE;
+      ancestor = item;
+    }
+  else
+    {
+      /* If there is no target item, we send the event to the root item,
+	 with target set to NULL. */
+      ancestor = canvas->root_item;
+    }
+
+  /* Make sure the item pointer remains valid throughout the emission. */
+  if (item)
+    g_object_ref (item);
+
+  while (ancestor)
+    {
+      g_object_ref (ancestor);
+
+      g_signal_emit_by_name (ancestor, signal_name, item, event,
+			     &stop_emission);
+
+      /* Check if the ancestor is still in the canvas. */
+      valid = ITEM_IS_VALID (ancestor) ? TRUE : FALSE;
+
+      g_object_unref (ancestor);
+
+      if (stop_emission || !valid)
+	break;
+
+      ancestor = goo_canvas_item_get_parent (ancestor);
+    }
+
+  if (item)
+    g_object_unref (item);
+
+  return stop_emission;
+}
+
+
+/* This is called to emit pointer events - enter/leave notify, motion notify,
+   and button press/release. */
+static gboolean
+emit_pointer_event (GooCanvas *canvas,
+		    gchar     *signal_name,
+		    GdkEvent  *original_event)
+{
+  GdkEvent event = *original_event;
+  GooCanvasItem *target_item = canvas->pointer_item;
+  double *x, *y, *x_root, *y_root;
+
+  /* Check if an item has grabbed the pointer. */
+  if (canvas->pointer_grab_item)
+    {
+      /* When the pointer is grabbed, it receives all the pointer motion,
+	 button press/release events and its own enter/leave notify events.
+	 Enter/leave notify events for other items are discarded. */
+      if ((event.type == GDK_ENTER_NOTIFY || event.type == GDK_LEAVE_NOTIFY)
+	  && canvas->pointer_item != canvas->pointer_grab_item)
+	return FALSE;
+
+      target_item = canvas->pointer_grab_item;
+    }
+
+  /* Check if the target item is still in the canvas. */
+  if (target_item && !ITEM_IS_VALID (target_item))
+    return FALSE;
+
+  /* Translate the x & y coordinates to the item's space. */
+  switch (event.type)
+    {
+    case GDK_MOTION_NOTIFY:
+      x = &event.motion.x;
+      y = &event.motion.y;
+      x_root = &event.motion.x_root;
+      y_root = &event.motion.y_root;
+      break;
+    case GDK_ENTER_NOTIFY:
+    case GDK_LEAVE_NOTIFY:
+      x = &event.crossing.x;
+      y = &event.crossing.y;
+      x_root = &event.crossing.x_root;
+      y_root = &event.crossing.y_root;
+      break;
+    case GDK_SCROLL:
+      x = &event.scroll.x;
+      y = &event.scroll.y;
+      x_root = &event.scroll.x_root;
+      y_root = &event.scroll.y_root;
+      break;
+    default:
+      /* It must be a button press/release event. */
+      x = &event.button.x;
+      y = &event.button.y;
+      x_root = &event.button.x_root;
+      y_root = &event.button.y_root;
+      break;
+    }
+
+  /* Add 0.5 to the pixel coordinates so we use the center of the pixel. */
+  *x += 0.5;
+  *y += 0.5;
+
+  /* Convert to the canvas coordinate space. */
+  goo_canvas_convert_from_pixels (canvas, x, y);
+
+  /* Convert to static item space, if necessary. */
+  if (target_item && goo_canvas_item_get_is_static (target_item))
+    goo_canvas_convert_to_static_item_space (canvas, x, y);
+
+  /* Copy to the x_root & y_root fields. */
+  *x_root = *x;
+  *y_root = *y;
+
+  /* Convert to the item's coordinate space. */
+  goo_canvas_convert_to_item_space (canvas, target_item, x, y);
+
+  return propagate_event (canvas, target_item, signal_name, &event);
+}
+
+
+/* Finds the item that the mouse is over, using the given event's
+ * coordinates. It emits enter/leave events for items as appropriate.
+ */
+static void
+update_pointer_item (GooCanvas *canvas,
+		     GdkEvent  *event)
+{
+  GooCanvasItem *new_item = NULL;
+
+  if (event)
+    initialize_crossing_event (canvas, event);
+
+  /* If the event type is GDK_LEAVE_NOTIFY, the mouse has left the canvas,
+     so we leave new_item as NULL, otherwise we find which item is
+     underneath the mouse. Note that we initialize the type to GDK_LEAVE_NOTIFY
+     in goo_canvas_init() to indicate the mouse isn't in the canvas. */
+  if (canvas->crossing_event.type != GDK_LEAVE_NOTIFY && canvas->root_item)
+    {
+      double x = canvas->crossing_event.x;
+      double y = canvas->crossing_event.y;
+
+      goo_canvas_convert_from_pixels (canvas, &x, &y);
+      new_item = goo_canvas_get_item_at (canvas, x, y, TRUE);
+    }
+
+  /* If the current item hasn't changed, just return. */
+  if (new_item == canvas->pointer_item)
+    return;
+
+  /* Ref the new item, in case it is removed below. */
+  if (new_item)
+    g_object_ref (new_item);
+
+  /* Emit a leave-notify event for the current item. */
+  if (canvas->pointer_item)
+    {
+      canvas->crossing_event.type = GDK_LEAVE_NOTIFY;
+      emit_pointer_event (canvas, "leave_notify_event",
+			  (GdkEvent*) &canvas->crossing_event);
+    }
+
+  /* If there is no new item we are done. */
+  if (!new_item)
+    {
+      set_item_pointer (&canvas->pointer_item, NULL);
+      return;
+    }
+
+  /* If the new item isn't in the canvas any more, don't use it. */
+  if (!ITEM_IS_VALID (new_item))
+    {
+      set_item_pointer (&canvas->pointer_item, NULL);
+      g_object_unref (new_item);
+      return;
+    }
+
+  /* Emit an enter-notify for the new current item. */
+  set_item_pointer (&canvas->pointer_item, new_item);
+  canvas->crossing_event.type = GDK_ENTER_NOTIFY;
+  emit_pointer_event (canvas, "enter_notify_event",
+		      (GdkEvent*) &canvas->crossing_event);
+
+  g_object_unref (new_item);
+}
+
+
+static gboolean
+goo_canvas_crossing        (GtkWidget        *widget,
+			    GdkEventCrossing *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+
+  if (event->window != canvas->canvas_window)
+    return FALSE;
+
+  /* This will result in synthesizing focus_in/out events as appropriate. */
+  update_pointer_item (canvas, (GdkEvent*) event);
+
+  return FALSE;
+}
+
+
+static gboolean
+goo_canvas_motion          (GtkWidget      *widget,
+			    GdkEventMotion *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+
+  if (event->window != canvas->canvas_window)
+    return FALSE;
+
+  /* For motion notify hint events we need to call gdk_window_get_pointer()
+     to let X know we're ready for another pointer event. */
+  if (event->is_hint)
+    gdk_window_get_pointer (event->window, NULL, NULL, NULL);
+
+  update_pointer_item (canvas, (GdkEvent*) event);
+
+  return emit_pointer_event (canvas, "motion_notify_event", (GdkEvent*) event);
+}
+
+
+static gboolean
+goo_canvas_button_press (GtkWidget      *widget,
+			 GdkEventButton *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+  GdkDisplay *display;
+
+  if (event->window != canvas->canvas_window)
+    return FALSE;
+
+  update_pointer_item (canvas, (GdkEvent*) event);
+
+  /* Check if this is the start of an implicit pointer grab, i.e. if we
+     don't already have a grab and the app has no active grab. */
+  display = gtk_widget_get_display (widget);
+  if (!canvas->pointer_grab_item
+      && !gdk_display_pointer_is_grabbed (display))
+    {
+      set_item_pointer (&canvas->pointer_grab_initial_item,
+			canvas->pointer_item);
+      set_item_pointer (&canvas->pointer_grab_item,
+			canvas->pointer_item);
+      canvas->pointer_grab_button = event->button;
+    }
+
+  return emit_pointer_event (canvas, "button_press_event", (GdkEvent*) event);
+}
+
+
+static gboolean
+goo_canvas_button_release (GtkWidget      *widget,
+			   GdkEventButton *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+  GdkDisplay *display;
+  gboolean retval;
+
+  if (event->window != canvas->canvas_window)
+    return FALSE;
+
+  update_pointer_item (canvas, (GdkEvent*) event);
+
+  retval = emit_pointer_event (canvas, "button_release_event",
+			       (GdkEvent*) event);
+
+  /* Check if an implicit (passive) grab has ended. */
+  display = gtk_widget_get_display (widget);
+  if (canvas->pointer_grab_item
+      && event->button == canvas->pointer_grab_button
+      && !gdk_display_pointer_is_grabbed (display))
+    {
+      /* We set the pointer item back to the item it was in before the
+	 grab, so we'll synthesize enter/leave notify events as appropriate. */
+      if (canvas->pointer_grab_initial_item
+	  && ITEM_IS_VALID (canvas->pointer_grab_initial_item))
+	set_item_pointer (&canvas->pointer_item,
+			  canvas->pointer_grab_initial_item);
+      else
+	set_item_pointer (&canvas->pointer_item, NULL);
+
+      set_item_pointer (&canvas->pointer_grab_item, NULL);
+      set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
+
+      update_pointer_item (canvas, (GdkEvent*) event);
+    }
+
+  return retval;
+}
+
+
+static gint
+goo_canvas_scroll	(GtkWidget      *widget,
+			 GdkEventScroll *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+  GtkAdjustment *adj;
+  gdouble delta, new_value;
+
+  if (event->window == canvas->canvas_window)
+    {
+      /* See if the current item wants the scroll event. */
+      update_pointer_item (canvas, (GdkEvent*) event);
+      if (emit_pointer_event (canvas, "scroll_event", (GdkEvent*) event))
+        return TRUE;
+    }
+
+  if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN)
+    adj = canvas->vadjustment;
+  else
+    adj = canvas->hadjustment;
+
+  delta = pow (gtk_adjustment_get_page_size (adj), 2.0 / 3.0);
+
+  if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT)
+    delta = - delta;
+
+  new_value = CLAMP (gtk_adjustment_get_value (adj) + delta, gtk_adjustment_get_lower (adj),
+		     gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
+
+  gtk_adjustment_set_value (adj, new_value);
+
+  return TRUE;
+}
+
+
+static gboolean
+goo_canvas_focus_in        (GtkWidget      *widget,
+			    GdkEventFocus  *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+
+  if (canvas->focused_item)
+    return propagate_event (canvas, canvas->focused_item,
+			    "focus_in_event", (GdkEvent*) event);
+  else
+    return FALSE;
+}
+
+
+static gboolean
+goo_canvas_focus_out       (GtkWidget      *widget,
+			    GdkEventFocus  *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+
+  if (canvas->focused_item)
+    return propagate_event (canvas, canvas->focused_item,
+			    "focus_out_event", (GdkEvent*) event);
+  else
+    return FALSE;
+}
+
+
+static gboolean
+goo_canvas_key_press       (GtkWidget      *widget,
+			    GdkEventKey    *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+  if (gtk_widget_has_focus (GTK_WIDGET (canvas)) && canvas->focused_item)
+    if (propagate_event (canvas, canvas->focused_item, "key_press_event",
+			 (GdkEvent*) event))
+    return TRUE;
+
+  return GTK_WIDGET_CLASS (goo_canvas_parent_class)->key_press_event (widget, event);
+}
+
+
+static gboolean
+goo_canvas_key_release     (GtkWidget      *widget,
+			    GdkEventKey    *event)
+{
+  GooCanvas *canvas = GOO_CANVAS (widget);
+
+  if (gtk_widget_has_focus (GTK_WIDGET (canvas)) && canvas->focused_item)
+    if (propagate_event (canvas, canvas->focused_item, "key_release_event",
+			 (GdkEvent*) event))
+    return TRUE;
+
+  return GTK_WIDGET_CLASS (goo_canvas_parent_class)->key_release_event (widget, event);
+}
+
+
+static void
+generate_grab_broken (GooCanvas     *canvas,
+		      GooCanvasItem *item,
+		      gboolean       keyboard,
+		      gboolean       implicit)
+
+{
+  GdkEventGrabBroken event;
+
+  if (!ITEM_IS_VALID (item))
+    return;
+
+  event.type = GDK_GRAB_BROKEN;
+  event.window = canvas->canvas_window;
+  event.send_event = 0;
+  event.keyboard = keyboard;
+  event.implicit = implicit;
+  event.grab_window = event.window;
+
+  propagate_event (canvas, item, "grab_broken_event",
+		   (GdkEvent*) &event);
+}
+
+
+static gboolean
+goo_canvas_grab_broken     (GtkWidget          *widget,
+			    GdkEventGrabBroken *event)
+{
+  GooCanvas *canvas;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (widget), FALSE);
+
+  canvas = GOO_CANVAS (widget);
+
+  if (event->keyboard)
+    {
+      if (canvas->keyboard_grab_item)
+	{
+	  generate_grab_broken (canvas, canvas->keyboard_grab_item,
+				event->keyboard, event->implicit);
+	  set_item_pointer (&canvas->keyboard_grab_item, NULL);
+	}
+    }
+  else
+    {
+      if (canvas->pointer_grab_item)
+	{
+	  generate_grab_broken (canvas, canvas->pointer_grab_item,
+				event->keyboard, event->implicit);
+	  set_item_pointer (&canvas->pointer_grab_item, NULL);
+	}
+    }
+
+  return TRUE;
+}
+
+
+/**
+ * goo_canvas_grab_focus:
+ * @canvas: a #GooCanvas.
+ * @item: the item to grab the focus.
+ *
+ * Grabs the keyboard focus for the given item.
+ **/
+void
+goo_canvas_grab_focus (GooCanvas     *canvas,
+		       GooCanvasItem *item)
+{
+  GdkEventFocus event;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+  g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (canvas)));
+
+  if (canvas->focused_item) {
+    event.type = GDK_FOCUS_CHANGE;
+    event.window = canvas->canvas_window;
+    event.send_event = FALSE;
+    event.in = FALSE;
+
+    propagate_event (canvas, canvas->focused_item,
+		     "focus_out_event", (GdkEvent*) &event);
+  }
+
+  set_item_pointer (&canvas->focused_item, item);
+
+  gtk_widget_grab_focus (GTK_WIDGET (canvas));
+
+  if (canvas->focused_item) {
+    event.type = GDK_FOCUS_CHANGE;
+    event.window = canvas->canvas_window;
+    event.send_event = FALSE;
+    event.in = TRUE;
+
+    propagate_event (canvas, canvas->focused_item,
+		     "focus_in_event", (GdkEvent*) &event);
+  }
+}
+
+
+/*
+ * Pointer/keyboard grabbing & ungrabbing.
+ */
+
+/**
+ * goo_canvas_pointer_grab:
+ * @canvas: a #GooCanvas.
+ * @item: the item to grab the pointer for.
+ * @event_mask: the events to receive during the grab.
+ * @cursor: the cursor to display during the grab, or NULL.
+ * @time: the time of the event that lead to the pointer grab. This should
+ *  come from the relevant #GdkEvent.
+ *
+ * Attempts to grab the pointer for the given item.
+ *
+ * Returns: %GDK_GRAB_SUCCESS if the grab succeeded.
+ **/
+GdkGrabStatus
+goo_canvas_pointer_grab (GooCanvas     *canvas,
+			 GooCanvasItem *item,
+			 GdkEventMask   event_mask,
+			 GdkCursor     *cursor,
+			 guint32        time)
+{
+  GdkGrabStatus status = GDK_GRAB_SUCCESS;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), GDK_GRAB_NOT_VIEWABLE);
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
+
+  /* If another item already has the pointer grab, we need to synthesize a
+     grab-broken event for the current grab item. */
+  if (canvas->pointer_grab_item
+      && canvas->pointer_grab_item != item)
+    {
+      generate_grab_broken (canvas, canvas->pointer_grab_item,
+			    FALSE, FALSE);
+      set_item_pointer (&canvas->pointer_grab_item, NULL);
+    }
+
+  /* This overrides any existing grab. */
+  status = gdk_pointer_grab (canvas->canvas_window, FALSE,
+			     event_mask, NULL, cursor, time);
+
+  if (status == GDK_GRAB_SUCCESS)
+    {
+      set_item_pointer (&canvas->pointer_grab_initial_item,
+			     canvas->pointer_item);
+      set_item_pointer (&canvas->pointer_grab_item,
+			     item);
+    }
+
+  return status;
+}
+
+
+/**
+ * goo_canvas_pointer_ungrab:
+ * @canvas: a #GooCanvas.
+ * @item: the item that has the grab.
+ * @time: the time of the event that lead to the pointer ungrab. This should
+ *  come from the relevant #GdkEvent.
+ *
+ * Ungrabs the pointer, if the given item has the pointer grab.
+ **/
+void
+goo_canvas_pointer_ungrab (GooCanvas     *canvas,
+			   GooCanvasItem *item,
+			   guint32        time)
+{
+  GdkDisplay *display;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+
+  /* If the item doesn't actually have the pointer grab, just return. */
+  if (canvas->pointer_grab_item != item)
+    return;
+
+  /* If it is an active pointer grab, ungrab it explicitly. */
+  display = gtk_widget_get_display (GTK_WIDGET (canvas));
+  if (gdk_display_pointer_is_grabbed (display))
+    gdk_display_pointer_ungrab (display, time);
+
+  /* We set the pointer item back to the item it was in before the
+     grab, so we'll synthesize enter/leave notify events as appropriate. */
+  if (canvas->pointer_grab_initial_item
+      && ITEM_IS_VALID (canvas->pointer_grab_initial_item))
+    set_item_pointer (&canvas->pointer_item,
+		      canvas->pointer_grab_initial_item);
+  else
+    set_item_pointer (&canvas->pointer_item, NULL);
+
+  set_item_pointer (&canvas->pointer_grab_item, NULL);
+  set_item_pointer (&canvas->pointer_grab_initial_item, NULL);
+
+  update_pointer_item (canvas, NULL);
+ }
+
+
+/**
+ * goo_canvas_keyboard_grab:
+ * @canvas: a #GooCanvas.
+ * @item: the item to grab the keyboard for.
+ * @owner_events: %TRUE if keyboard events for this application will be
+ *  reported normally, or %FALSE if all keyboard events will be reported with
+ *  respect to the grab item.
+ * @time: the time of the event that lead to the keyboard grab. This should
+ *  come from the relevant #GdkEvent.
+ *
+ * Attempts to grab the keyboard for the given item.
+ *
+ * Returns: %GDK_GRAB_SUCCESS if the grab succeeded.
+ **/
+GdkGrabStatus
+goo_canvas_keyboard_grab (GooCanvas     *canvas,
+			  GooCanvasItem *item,
+			  gboolean       owner_events,
+			  guint32        time)
+{
+  GdkGrabStatus status = GDK_GRAB_SUCCESS;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (canvas), GDK_GRAB_NOT_VIEWABLE);
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE);
+
+  /* Check if the item already has the keyboard grab. */
+  if (canvas->keyboard_grab_item == item)
+    return GDK_GRAB_ALREADY_GRABBED;
+
+  /* If another item already has the keyboard grab, we need to synthesize a
+     grab-broken event for the current grab item. */
+  if (canvas->keyboard_grab_item)
+    {
+      generate_grab_broken (canvas, canvas->keyboard_grab_item, TRUE, FALSE);
+      set_item_pointer (&canvas->keyboard_grab_item, NULL);
+    }
+
+  /* This overrides any existing grab. */
+  status = gdk_keyboard_grab (canvas->canvas_window,
+			      owner_events, time);
+
+  if (status == GDK_GRAB_SUCCESS)
+    set_item_pointer (&canvas->keyboard_grab_item, item);
+
+  return status;
+}
+
+
+/**
+ * goo_canvas_keyboard_ungrab:
+ * @canvas: a #GooCanvas.
+ * @item: the item that has the keyboard grab.
+ * @time: the time of the event that lead to the keyboard ungrab. This should
+ *  come from the relevant #GdkEvent.
+ *
+ * Ungrabs the keyboard, if the given item has the keyboard grab.
+ **/
+void
+goo_canvas_keyboard_ungrab (GooCanvas     *canvas,
+			    GooCanvasItem *item,
+			    guint32        time)
+{
+  GdkDisplay *display;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+
+  /* If the item doesn't actually have the keyboard grab, just return. */
+  if (canvas->keyboard_grab_item != item)
+    return;
+
+  set_item_pointer (&canvas->keyboard_grab_item, NULL);
+
+  display = gtk_widget_get_display (GTK_WIDGET (canvas));
+  gdk_display_keyboard_ungrab (display, time);
+}
+
+
+/*
+ * Coordinate conversion.
+ */
+
+/**
+ * goo_canvas_convert_to_pixels:
+ * @canvas: a #GooCanvas.
+ * @x: a pointer to the x coordinate to convert.
+ * @y: a pointer to the y coordinate to convert.
+ *
+ * Converts a coordinate from the canvas coordinate space to pixels.
+ *
+ * The canvas coordinate space is specified in the call to
+ * goo_canvas_set_bounds().
+ *
+ * The pixel coordinate space specifies pixels from the top-left of the entire
+ * canvas window, according to the current scale setting.
+ * See goo_canvas_set_scale().
+ **/
+void
+goo_canvas_convert_to_pixels (GooCanvas     *canvas,
+			      gdouble       *x,
+			      gdouble       *y)
+{
+  *x = ((*x - canvas->bounds.x1) * canvas->device_to_pixels_x) + canvas->canvas_x_offset;
+  *y = ((*y - canvas->bounds.y1) * canvas->device_to_pixels_y) + canvas->canvas_y_offset;
+}
+
+
+/**
+ * goo_canvas_convert_from_pixels:
+ * @canvas: a #GooCanvas.
+ * @x: a pointer to the x coordinate to convert.
+ * @y: a pointer to the y coordinate to convert.
+ *
+ * Converts a coordinate from pixels to the canvas coordinate space.
+ *
+ * The pixel coordinate space specifies pixels from the top-left of the entire
+ * canvas window, according to the current scale setting.
+ * See goo_canvas_set_scale().
+ *
+ * The canvas coordinate space is specified in the call to
+ * goo_canvas_set_bounds().
+ *
+ **/
+void
+goo_canvas_convert_from_pixels (GooCanvas     *canvas,
+				gdouble       *x,
+				gdouble       *y)
+{
+  *x = ((*x - canvas->canvas_x_offset) / canvas->device_to_pixels_x) + canvas->bounds.x1;
+  *y = ((*y - canvas->canvas_y_offset) / canvas->device_to_pixels_y) + canvas->bounds.y1;
+}
+
+
+static void
+goo_canvas_convert_from_window_pixels (GooCanvas     *canvas,
+				       gdouble       *x,
+				       gdouble       *y)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  *x -= priv->window_x;
+  *y -= priv->window_y;
+  goo_canvas_convert_from_pixels (canvas, x, y);
+}
+
+
+/* Converts from the canvas coordinate space to the static item coordinate
+   space, i.e. in pixels from the top-left of the viewport window. */
+static void
+goo_canvas_convert_to_static_item_space (GooCanvas     *canvas,
+					 gdouble       *x,
+					 gdouble       *y)
+{
+  GooCanvasPrivate *priv = GOO_CANVAS_GET_PRIVATE (canvas);
+  *x = ((*x - canvas->bounds.x1) * canvas->device_to_pixels_x)
+    + canvas->canvas_x_offset + priv->window_x;
+  *y = ((*y - canvas->bounds.y1) * canvas->device_to_pixels_y)
+    + canvas->canvas_y_offset + priv->window_y;
+}
+
+
+static void
+get_transform_to_item_space (GooCanvasItem  *item,
+			     cairo_matrix_t *transform)
+{
+  GooCanvasItem *tmp = item, *parent, *child;
+  GList *list = NULL, *l;
+  cairo_matrix_t item_transform, inverse = { 1, 0, 0, 1, 0, 0 };
+  gboolean has_transform;
+
+  /* Step up from the item to the top, pushing items onto the list. */
+  while (tmp)
+    {
+      list = g_list_prepend (list, tmp);
+      tmp = goo_canvas_item_get_parent (tmp);
+    }
+
+  /* Now step down applying the inverse of each item's transformation. */
+  for (l = list; l; l = l->next)
+    {
+      parent = (GooCanvasItem*) l->data;
+      child = l->next ? (GooCanvasItem*) l->next->data : NULL;
+      has_transform = goo_canvas_item_get_transform_for_child (parent, child,
+							       &item_transform);
+      if (has_transform)
+	{
+	  cairo_matrix_invert (&item_transform);
+	  cairo_matrix_multiply (&inverse, &inverse, &item_transform);
+	}
+    }
+  g_list_free (list);
+
+  *transform = inverse;
+}
+
+
+/**
+ * goo_canvas_convert_to_item_space:
+ * @canvas: a #GooCanvas.
+ * @item: a #GooCanvasItem.
+ * @x: a pointer to the x coordinate to convert.
+ * @y: a pointer to the y coordinate to convert.
+ *
+ * Converts a coordinate from the canvas coordinate space to the given
+ * item's coordinate space, applying all transformation matrices including the
+ * item's own transformation matrix, if it has one.
+ **/
+void
+goo_canvas_convert_to_item_space (GooCanvas     *canvas,
+				  GooCanvasItem *item,
+				  gdouble       *x,
+				  gdouble       *y)
+{
+  cairo_matrix_t transform;
+
+  get_transform_to_item_space (item, &transform);
+  cairo_matrix_transform_point (&transform, x, y);
+}
+
+
+/**
+ * goo_canvas_convert_from_item_space:
+ * @canvas: a #GooCanvas.
+ * @item: a #GooCanvasItem.
+ * @x: a pointer to the x coordinate to convert.
+ * @y: a pointer to the y coordinate to convert.
+ *
+ * Converts a coordinate from the given item's coordinate space to the canvas
+ * coordinate space, applying all transformation matrices including the
+ * item's own transformation matrix, if it has one.
+ **/
+void
+goo_canvas_convert_from_item_space (GooCanvas     *canvas,
+				    GooCanvasItem *item,
+				    gdouble       *x,
+				    gdouble       *y)
+{
+  GooCanvasItem *tmp = item, *parent, *child;
+  GList *list = NULL, *l;
+  cairo_matrix_t item_transform, transform = { 1, 0, 0, 1, 0, 0 };
+  gboolean has_transform;
+
+  /* Step up from the item to the top, pushing items onto the list. */
+  while (tmp)
+    {
+      list = g_list_prepend (list, tmp);
+      tmp = goo_canvas_item_get_parent (tmp);
+    }
+
+  /* Now step down applying each item's transformation. */
+  for (l = list; l; l = l->next)
+    {
+      parent = (GooCanvasItem*) l->data;
+      child = l->next ? (GooCanvasItem*) l->next->data : NULL;
+      has_transform = goo_canvas_item_get_transform_for_child (parent, child,
+							       &item_transform);
+      if (has_transform)
+	{
+	  cairo_matrix_multiply (&transform, &item_transform, &transform);
+	}
+    }
+  g_list_free (list);
+
+  /* Now convert the coordinates. */
+  cairo_matrix_transform_point (&transform, x, y);
+}
+
+
+/**
+ * goo_canvas_convert_bounds_to_item_space:
+ * @canvas: a #GooCanvas.
+ * @item: a #GooCanvasItem.
+ * @bounds: the bounds in canvas coordinate space, to be converted.
+ *
+ * Converts the given bounds in the canvas coordinate space to a bounding box
+ * in item space. This is useful in the item paint() methods to convert the
+ * bounds to be painted to the item's coordinate space.
+ **/
+void
+goo_canvas_convert_bounds_to_item_space (GooCanvas           *canvas,
+					 GooCanvasItem       *item,
+					 GooCanvasBounds     *bounds)
+{
+  GooCanvasBounds tmp_bounds = *bounds, tmp_bounds2 = *bounds;
+  cairo_matrix_t transform;
+
+  get_transform_to_item_space (item, &transform);
+
+  /* Convert the top-left and bottom-right corners to device coords. */
+  cairo_matrix_transform_point (&transform, &tmp_bounds.x1, &tmp_bounds.y1);
+  cairo_matrix_transform_point (&transform, &tmp_bounds.x2, &tmp_bounds.y2);
+
+  /* Now convert the top-right and bottom-left corners. */
+  cairo_matrix_transform_point (&transform, &tmp_bounds2.x1, &tmp_bounds2.y2);
+  cairo_matrix_transform_point (&transform, &tmp_bounds2.x2, &tmp_bounds2.y1);
+
+  /* Calculate the minimum x coordinate seen and put in x1. */
+  bounds->x1 = MIN (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x1);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x2);
+
+  /* Calculate the maximum x coordinate seen and put in x2. */
+  bounds->x2 = MAX (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x1);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x2);
+
+  /* Calculate the minimum y coordinate seen and put in y1. */
+  bounds->y1 = MIN (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y1);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y2);
+
+  /* Calculate the maximum y coordinate seen and put in y2. */
+  bounds->y2 = MAX (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y1);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y2);
+}
+
+
+/*
+ * Keyboard focus navigation.
+ */
+
+typedef struct _GooCanvasFocusData GooCanvasFocusData;
+struct _GooCanvasFocusData
+{
+  /* The item to start from, usually the currently focused item, or NULL. */
+  GooCanvasItem *start_item;
+
+  /* The bounds of the start item. We try to find the next closest one in the
+     desired direction. */
+  GooCanvasBounds start_bounds;
+  gdouble start_center_x, start_center_y;
+
+  /* The direction to move the focus in. */
+  GtkDirectionType  direction;
+
+  /* The text direction of the widget. */
+  GtkTextDirection text_direction;
+
+  /* The best item found so far, and the offsets for it. */
+  GooCanvasItem *best_item;
+  gdouble best_x_offset, best_y_offset, best_score;
+
+  /* The offsets for the item being tested. */
+  GooCanvasBounds current_bounds;
+  gdouble current_x_offset, current_y_offset, current_score;
+};
+
+
+/* This tries to figure out the bounds of the start item or widget. */
+static void
+goo_canvas_get_start_bounds (GooCanvas          *canvas,
+			     GooCanvasFocusData *data)
+{
+  GooCanvasBounds *bounds;
+  GtkWidget *toplevel, *focus_widget;
+  GtkAllocation allocation;
+  GtkAllocation focus_widget_allocation;
+  gint focus_widget_x, focus_widget_y;
+
+  /* If an item is currently focused, we just need its bounds. */
+  if (data->start_item)
+    {
+      goo_canvas_item_get_bounds (data->start_item, &data->start_bounds);
+      return;
+    }
+
+  /* Otherwise try to get the currently focused widget in the window. */
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
+  bounds = &data->start_bounds;
+  if (toplevel && GTK_IS_WINDOW (toplevel)
+      && gtk_window_get_focus (GTK_WINDOW (toplevel)))
+    {
+      focus_widget = gtk_window_get_focus (GTK_WINDOW (toplevel));
+
+      /* Translate the allocation to be relative to the GooCanvas.
+	 Skip ancestor widgets as the coords won't help. */
+      if (!gtk_widget_is_ancestor (GTK_WIDGET (canvas), focus_widget)
+	  && gtk_widget_translate_coordinates (focus_widget,
+					       GTK_WIDGET (canvas),
+					       0, 0,
+					       &focus_widget_x,
+					       &focus_widget_y))
+	{
+	  /* Translate into device units. */
+    gtk_widget_get_allocation (focus_widget, &focus_widget_allocation);
+	  bounds->x1 = focus_widget_x;
+	  bounds->y1 = focus_widget_y;
+	  bounds->x2 = focus_widget_x + focus_widget_allocation.width;
+	  bounds->y2 = focus_widget_y + focus_widget_allocation.height;
+
+	  goo_canvas_convert_from_window_pixels (canvas, &bounds->x1,
+						 &bounds->y1);
+	  goo_canvas_convert_from_window_pixels (canvas, &bounds->x2,
+						 &bounds->y2);
+	  return;
+	}
+    }
+
+  /* As a last resort, we guess a starting position based on the direction. */
+  gtk_widget_get_allocation (GTK_WIDGET (canvas), &allocation);
+  switch (data->direction)
+    {
+    case GTK_DIR_DOWN:
+    case GTK_DIR_RIGHT:
+      /* Start from top-left. */
+      bounds->x1 = 0.0;
+      bounds->y1 = 0.0;
+      break;
+
+    case GTK_DIR_UP:
+      /* Start from bottom-left. */
+      bounds->x1 = 0.0;
+      bounds->y1 = allocation.height;
+      break;
+
+    case GTK_DIR_LEFT:
+      /* Start from top-right. */
+      bounds->x1 = allocation.width;
+      bounds->y1 = 0.0;
+      break;
+
+    case GTK_DIR_TAB_FORWARD:
+      bounds->y1 = 0.0;
+      if (data->text_direction == GTK_TEXT_DIR_RTL)
+	/* Start from top-right. */
+	bounds->x1 = allocation.width;
+      else
+	/* Start from top-left. */
+	bounds->x1 = 0.0;
+      break;
+
+    case GTK_DIR_TAB_BACKWARD:
+      bounds->y1 = allocation.height;
+      if (data->text_direction == GTK_TEXT_DIR_RTL)
+	/* Start from bottom-left. */
+	bounds->x1 = 0.0;
+      else
+	/* Start from bottom-right. */
+	bounds->x1 = allocation.width;
+      break;
+    }
+
+  goo_canvas_convert_from_window_pixels (canvas, &bounds->x1, &bounds->y1);
+  bounds->x2 = bounds->x1;
+  bounds->y2 = bounds->y1;
+}
+
+
+/* Check if the given item is a better candidate for the focus than
+   the current best one in the data struct. */
+static gboolean
+goo_canvas_focus_check_is_best (GooCanvas          *canvas,
+				GooCanvasItem      *item,
+				GooCanvasFocusData *data)
+{
+  gdouble center_x, center_y;
+  gdouble abs_x_offset = 0.0, abs_y_offset = 0.0;
+
+  data->current_score = 0.0;
+
+  goo_canvas_item_get_bounds (item, &data->current_bounds);
+  center_x = (data->current_bounds.x1 + data->current_bounds.x2) / 2.0;
+  center_y = (data->current_bounds.y1 + data->current_bounds.y2) / 2.0;
+
+  /* Calculate the offsets of the center of this item from the center
+     of the current focus item or widget. */
+  data->current_x_offset = center_x - data->start_center_x;
+  data->current_y_offset = center_y - data->start_center_y;
+
+  /* If the item overlaps the current one at all we use 0 as the offset. */
+  if (data->current_bounds.x1 > data->start_bounds.x2
+      || data->current_bounds.x2 < data->start_bounds.x2)
+      abs_x_offset = fabs (data->current_x_offset);
+
+  if (data->current_bounds.y1 > data->start_bounds.y2
+      || data->current_bounds.y2 < data->start_bounds.y2)
+      abs_y_offset = fabs (data->current_y_offset);
+
+  /* FIXME: I'm still not sure about the score calculations here. There
+     are still a few odd jumps when using keyboard focus navigation. */
+  switch (data->direction)
+    {
+    case GTK_DIR_UP:
+      /* If the y offset is > 0 we can discard this item. */
+      if (data->current_y_offset >= 0 || abs_x_offset > abs_y_offset)
+	return FALSE;
+
+      /* Compute a score (lower is best) and check if it is the best. */
+      data->current_score = abs_x_offset * 2 + abs_y_offset;
+      if (!data->best_item || data->current_score < data->best_score)
+	return TRUE;
+      break;
+
+    case GTK_DIR_DOWN:
+      /* If the y offset is < 0 we can discard this item. */
+      if (data->current_y_offset <= 0 || abs_x_offset > abs_y_offset)
+	return FALSE;
+
+      /* Compute a score (lower is best) and check if it is the best. */
+      data->current_score = abs_x_offset /** 2*/ + abs_y_offset;
+      if (!data->best_item || data->current_score < data->best_score)
+	return TRUE;
+      break;
+
+    case GTK_DIR_LEFT:
+      /* If the x offset is > 0 we can discard this item. */
+      if (data->current_x_offset >= 0 || abs_y_offset > abs_x_offset)
+	return FALSE;
+
+      /* Compute a score (lower is best) and check if it is the best. */
+      data->current_score = abs_y_offset * 2 + abs_x_offset;
+      if (!data->best_item || data->current_score < data->best_score)
+	return TRUE;
+      break;
+
+    case GTK_DIR_RIGHT:
+      /* If the x offset is < 0 we can discard this item. */
+      if (data->current_x_offset <= 0 || abs_y_offset > abs_x_offset)
+	return FALSE;
+
+      /* Compute a score (lower is best) and check if it is the best. */
+      data->current_score = abs_y_offset * 2 + abs_x_offset;
+      if (!data->best_item || data->current_score < data->best_score)
+	return TRUE;
+      break;
+
+    case GTK_DIR_TAB_BACKWARD:
+      /* We need to handle this differently depending on text direction. */
+      if (data->text_direction == GTK_TEXT_DIR_RTL)
+	{
+	  /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
+	     discard this item. */
+	  if (data->current_y_offset > 0
+	      || (data->current_y_offset == 0 && data->current_x_offset < 0))
+	    return FALSE;
+
+	  /* If the y offset is > the current best y offset, this is best. */
+	  if (!data->best_item || data->current_y_offset > data->best_y_offset)
+	    return TRUE;
+
+	  /* If the y offsets are the same, choose the largest x offset. */
+	  if (data->current_y_offset == data->best_y_offset
+	      && data->current_x_offset < data->best_x_offset)
+	    return TRUE;
+	}
+      else
+	{
+	  /* If the y offset is > 0, or it is 0 and the x offset > 0 we can
+	     discard this item. */
+	  if (data->current_y_offset > 0
+	      || (data->current_y_offset == 0 && data->current_x_offset > 0))
+	    return FALSE;
+
+	  /* If the y offset is > the current best y offset, this is best. */
+	  if (!data->best_item || data->current_y_offset > data->best_y_offset)
+	    return TRUE;
+
+	  /* If the y offsets are the same, choose the largest x offset. */
+	  if (data->current_y_offset == data->best_y_offset
+	      && data->current_x_offset > data->best_x_offset)
+	    return TRUE;
+	}
+      break;
+
+    case GTK_DIR_TAB_FORWARD:
+      /* We need to handle this differently depending on text direction. */
+      if (data->text_direction == GTK_TEXT_DIR_RTL)
+	{
+	  /* If the y offset is < 0, or it is 0 and the x offset > 0 we can
+	     discard this item. */
+	  if (data->current_y_offset < 0
+	      || (data->current_y_offset == 0 && data->current_x_offset > 0))
+	    return FALSE;
+
+	  /* If the y offset is < the current best y offset, this is best. */
+	  if (!data->best_item || data->current_y_offset < data->best_y_offset)
+	    return TRUE;
+
+	  /* If the y offsets are the same, choose the largest x offset. */
+	  if (data->current_y_offset == data->best_y_offset
+	      && data->current_x_offset > data->best_x_offset)
+	    return TRUE;
+	}
+      else
+	{
+	  /* If the y offset is < 0, or it is 0 and the x offset < 0 we can
+	     discard this item. */
+	  if (data->current_y_offset < 0
+	      || (data->current_y_offset == 0 && data->current_x_offset < 0))
+	    return FALSE;
+
+	  /* If the y offset is < the current best y offset, this is best. */
+	  if (!data->best_item || data->current_y_offset < data->best_y_offset)
+	    return TRUE;
+
+	  /* If the y offsets are the same, choose the smallest x offset. */
+	  if (data->current_y_offset == data->best_y_offset
+	      && data->current_x_offset < data->best_x_offset)
+	    return TRUE;
+	}
+      break;
+    }
+
+  return FALSE;
+}
+
+
+/* Recursively look for the next best item in the desired direction. */
+static void
+goo_canvas_focus_recurse (GooCanvas          *canvas,
+			  GooCanvasItem      *item,
+			  GooCanvasFocusData *data)
+{
+  GooCanvasItem *child;
+  gboolean can_focus = FALSE, is_best;
+  gint n_children, i;
+
+  /* If the item is not a possible candidate, just return. */
+  is_best = goo_canvas_focus_check_is_best (canvas, item, data);
+
+  if (is_best && goo_canvas_item_is_visible (item))
+    {
+      /* Check if the item can take the focus. */
+      if (GOO_IS_CANVAS_WIDGET (item))
+	{
+	  /* We have to assume that widget items can take focus, since any
+	     of their descendants may take the focus. */
+	  if (((GooCanvasWidget*) item)->widget)
+	    can_focus = TRUE;
+	}
+      else
+	{
+	  g_object_get (item, "can-focus", &can_focus, NULL);
+	}
+
+      /* If the item can take the focus itself, and it isn't the current
+	 focus item, save its score and return. (If it is a container it takes
+	 precedence over its children). */
+      if (can_focus && item != data->start_item)
+	{
+	  data->best_item = item;
+	  data->best_x_offset = data->current_x_offset;
+	  data->best_y_offset = data->current_y_offset;
+	  data->best_score = data->current_score;
+	  return;
+	}
+    }
+
+  /* If the item is a container, check the children recursively. */
+  n_children = goo_canvas_item_get_n_children (item);
+  if (n_children)
+    {
+      /* Check if we can skip the entire group. */
+      switch (data->direction)
+	{
+	case GTK_DIR_UP:
+	  /* If the group is below the bottom of the current focused item
+	     we can skip it. */
+	  if (data->current_bounds.y1 > data->start_bounds.y2)
+	    return;
+	  break;
+	case GTK_DIR_DOWN:
+	  /* If the group is above the top of the current focused item
+	     we can skip it. */
+	  if (data->current_bounds.y2 < data->start_bounds.y1)
+	    return;
+	  break;
+	case GTK_DIR_LEFT:
+	  /* If the group is to the right of the current focused item
+	     we can skip it. */
+	  if (data->current_bounds.x1 > data->start_bounds.x2)
+	    return;
+	  break;
+	case GTK_DIR_RIGHT:
+	  /* If the group is to the left of the current focused item
+	     we can skip it. */
+	  if (data->current_bounds.x2 < data->start_bounds.x1)
+	    return;
+	  break;
+	default:
+	  break;
+	}
+
+      for (i = 0; i < n_children; i++)
+	{
+	  child = goo_canvas_item_get_child (item, i);
+	  goo_canvas_focus_recurse (canvas, child, data);
+	}
+    }
+}
+
+
+/* FIXME: We could add support for a focus chain, like GtkContainer. */
+static gboolean
+goo_canvas_focus (GtkWidget        *widget,
+		  GtkDirectionType  direction)
+{
+  GooCanvas *canvas;
+  GooCanvasFocusData data;
+  GtkWidget *old_focus_child;
+  gboolean found_item;
+  gint try;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (widget), FALSE);
+
+  canvas = GOO_CANVAS (widget);
+
+  /* If keyboard navigation has been turned off for the canvas, return FALSE.*/
+  if (!gtk_widget_get_can_focus (widget))
+    return FALSE;
+
+  /* If a child widget has the focus, try moving the focus within that. */
+  old_focus_child = gtk_container_get_focus_child (GTK_CONTAINER (canvas));
+  if (old_focus_child && gtk_widget_child_focus (old_focus_child, direction))
+    return TRUE;
+
+  data.direction = direction;
+  data.text_direction = gtk_widget_get_direction (widget);
+  data.start_item = NULL;
+
+  if (gtk_widget_has_focus (GTK_WIDGET (canvas)))
+    data.start_item = canvas->focused_item;
+  else if (old_focus_child && GOO_IS_CANVAS_WIDGET (old_focus_child))
+    data.start_item = g_object_get_data (G_OBJECT (old_focus_child),
+					 "goo-canvas-item");
+
+  /* Keep looping until we find an item to focus or we fail. I've added a
+     limit on the number of tries just in case we get into an infinite loop. */
+  for (try = 1; try < 1000; try++)
+    {
+      /* Get the bounds of the currently focused item or widget. */
+      goo_canvas_get_start_bounds (canvas, &data);
+      data.start_center_x = (data.start_bounds.x1 + data.start_bounds.x2) / 2.0;
+      data.start_center_y = (data.start_bounds.y1 + data.start_bounds.y2) / 2.0;
+      data.best_item = NULL;
+
+      /* Recursively look for the next best item in the desired direction. */
+      goo_canvas_focus_recurse (canvas, canvas->root_item, &data);
+
+      /* If we found an item to focus, grab the focus and return TRUE. */
+      if (!data.best_item)
+	break;
+
+      if (GOO_IS_CANVAS_WIDGET (data.best_item))
+	{
+	  found_item = gtk_widget_child_focus (((GooCanvasWidget*) data.best_item)->widget, direction);
+	}
+      else
+	{
+	  found_item = TRUE;
+	  goo_canvas_grab_focus (canvas, data.best_item);
+	}
+
+      if (found_item)
+	{
+	  goo_canvas_scroll_to_item (canvas, data.best_item);
+	  return TRUE;
+	}
+
+      /* Try again from the last item tried. */
+      data.start_item = data.best_item;
+    }
+
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_register_widget_item:
+ * @canvas: a #GooCanvas.
+ * @witem: a #GooCanvasWidget item.
+ *
+ * This function should only be used by #GooCanvasWidget and subclass
+ * implementations.
+ *
+ * It registers a widget item with the canvas, so that the canvas can do the
+ * necessary actions to move and resize the widget as needed.
+ **/
+void
+goo_canvas_register_widget_item   (GooCanvas          *canvas,
+				   GooCanvasWidget    *witem)
+{
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_WIDGET (witem));
+
+  canvas->widget_items = g_list_append (canvas->widget_items, witem);
+}
+
+
+/**
+ * goo_canvas_unregister_widget_item:
+ * @canvas: a #GooCanvas.
+ * @witem: a #GooCanvasWidget item.
+ *
+ * This function should only be used by #GooCanvasWidget and subclass
+ * implementations.
+ *
+ * It unregisters a widget item from the canvas, when the item is no longer in
+ * the canvas.
+ **/
+void
+goo_canvas_unregister_widget_item (GooCanvas          *canvas,
+				   GooCanvasWidget    *witem)
+{
+  GList *tmp_list;
+  GooCanvasWidget *tmp_witem;
+
+  g_return_if_fail (GOO_IS_CANVAS (canvas));
+  g_return_if_fail (GOO_IS_CANVAS_WIDGET (witem));
+
+  tmp_list = canvas->widget_items;
+  while (tmp_list)
+    {
+      tmp_witem = tmp_list->data;
+      if (tmp_witem == witem)
+	break;
+      tmp_list = tmp_list->next;
+    }
+
+  if (tmp_list)
+    {
+      canvas->widget_items = g_list_remove_link (canvas->widget_items,
+						 tmp_list);
+      g_list_free_1 (tmp_list);
+    }
+}
+
+
+static void
+goo_canvas_forall (GtkContainer *container,
+		   gboolean      include_internals,
+		   GtkCallback   callback,
+		   gpointer      callback_data)
+{
+  GooCanvas *canvas;
+  GList *tmp_list;
+  GooCanvasWidget *witem;
+
+  g_return_if_fail (GOO_IS_CANVAS (container));
+  g_return_if_fail (callback != NULL);
+
+  canvas = GOO_CANVAS (container);
+
+  tmp_list = canvas->widget_items;
+  while (tmp_list)
+    {
+      witem = tmp_list->data;
+      tmp_list = tmp_list->next;
+
+      if (witem->widget)
+	(* callback) (witem->widget, callback_data);
+    }
+}
+
+
+static void
+goo_canvas_remove (GtkContainer *container,
+		   GtkWidget    *widget)
+{
+  GooCanvas *canvas;
+  GList *tmp_list;
+  GooCanvasWidget *witem;
+  GooCanvasItem *parent;
+  gint child_num;
+
+  g_return_if_fail (GOO_IS_CANVAS (container));
+
+  canvas = GOO_CANVAS (container);
+
+  tmp_list = canvas->widget_items;
+  while (tmp_list)
+    {
+      witem = tmp_list->data;
+      tmp_list = tmp_list->next;
+
+      if (witem->widget == widget)
+	{
+	  parent = goo_canvas_item_get_parent ((GooCanvasItem*) witem);
+	  child_num = goo_canvas_item_find_child (parent,
+						  (GooCanvasItem*) witem);
+	  goo_canvas_item_remove_child (parent, child_num);
+
+	  break;
+	}
+    }
+}
+
+
+static gboolean
+goo_canvas_query_tooltip (GtkWidget  *widget,
+			  gint        x,
+			  gint        y,
+			  gboolean    keyboard_tip,
+			  GtkTooltip *tooltip)
+{
+  GooCanvas *canvas = (GooCanvas*) widget;
+  GooCanvasItem *item = canvas->pointer_item, *parent;
+  gdouble item_x = x, item_y = y;
+  gboolean tip_set = FALSE, has_transform;
+  cairo_matrix_t transform;
+
+  if (!item)
+    return FALSE;
+
+  /* Convert from pixels to the item's coordinate space. */
+  goo_canvas_convert_from_pixels (canvas, &item_x, &item_y);
+  goo_canvas_convert_to_item_space (canvas, item, &item_x, &item_y);
+
+  for (;;)
+    {
+      g_signal_emit_by_name (item, "query-tooltip", item_x, item_y,
+			     keyboard_tip, tooltip, &tip_set);
+      if (tip_set)
+	return TRUE;
+
+      parent = goo_canvas_item_get_parent (item);
+      if (!parent)
+	break;
+
+      /* Convert x & y to the parent's coordinate space. */
+      has_transform = goo_canvas_item_get_transform_for_child (parent, item,
+							       &transform);
+      if (has_transform)
+	cairo_matrix_transform_point (&transform, &item_x, &item_y);
+
+      item = parent;
+    }
+
+  /* We call the parent method in case the canvas itself has a tooltip set. */
+  return GTK_WIDGET_CLASS (goo_canvas_parent_class)->query_tooltip (widget, x, y, keyboard_tip, tooltip);
+}
diff --git a/libgoocanvas/goocanvas.h b/libgoocanvas/goocanvas.h
new file mode 100644
index 0000000..8cea33f
--- /dev/null
+++ b/libgoocanvas/goocanvas.h
@@ -0,0 +1,334 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvas.h - the main canvas widget.
+ */
+#ifndef __GOO_CANVAS_H__
+#define __GOO_CANVAS_H__
+
+#include <gtk/gtk.h>
+#include <goocanvasenumtypes.h>
+#include <goocanvasellipse.h>
+#include <goocanvasgrid.h>
+#include <goocanvasgroup.h>
+#include <goocanvasimage.h>
+#include <goocanvaspath.h>
+#include <goocanvaspolyline.h>
+#include <goocanvasrect.h>
+#include <goocanvastable.h>
+#include <goocanvastext.h>
+#include <goocanvaswidget.h>
+
+G_BEGIN_DECLS
+
+
+#define GOO_TYPE_CANVAS            (goo_canvas_get_type ())
+#define GOO_CANVAS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS, GooCanvas))
+#define GOO_CANVAS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS, GooCanvasClass))
+#define GOO_IS_CANVAS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS))
+#define GOO_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS))
+#define GOO_CANVAS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS, GooCanvasClass))
+
+
+typedef struct _GooCanvasClass  GooCanvasClass;
+
+/**
+ * GooCanvas
+ *
+ * The #GooCanvas-struct struct contains private data only.
+ */
+struct _GooCanvas
+{
+  GtkContainer container;
+
+  /* The model for the root item, in model/view mode. */
+  GooCanvasItemModel *root_item_model;
+
+  /* The root canvas item. */
+  GooCanvasItem *root_item;
+
+  /* The bounds of the canvas, in canvas units (not pixels). */
+  GooCanvasBounds bounds;
+
+  /* The scale/zoom factors of the canvas. */
+  gdouble scale_x, scale_y;
+
+  /* The minimum of scale_x and scale_y, to compare with items' visibility
+     thresholds. */
+  gdouble scale;
+
+  /* Where the canvas is anchored (where it is displayed when it is smaller
+     than the entire window). */
+  GooCanvasAnchorType anchor;
+
+  /* Idle handler ID, for processing updates. */
+  guint idle_id;
+
+  /* This is TRUE if some item in the canvas needs an update. */
+  guint need_update : 1;
+
+  /* This is TRUE if all items in the canvas need to be updated. */
+  guint need_entire_subtree_update : 1;
+
+  /* This is TRUE if all layout should be done to the nearest integer. */
+  guint integer_layout : 1;
+
+  /* This is TRUE if the bounds are calculated automatically, using the bounds
+     of all the items in the canvas. */
+  guint automatic_bounds : 1;
+
+  /* This is TRUE if the automatic bounds are calculated from the origin. */
+  guint bounds_from_origin : 1;
+
+  /* This is TRUE if the background is cleared before painting the canvas. */
+  guint clear_background : 1;
+
+  /* This is TRUE if the canvas is completely redrawn when scrolled. It is
+     useful when there are sticky items to reduce flicker, but is slower. */
+  guint redraw_when_scrolled : 1;
+
+  /* If the canvas hasn't received the initial draw signal yet. */
+  guint before_initial_draw : 1;
+
+  /* GtkScrollablePolicy for each adjustment. */
+  guint hscroll_policy : 1;
+  guint vscroll_policy : 1;
+
+  /* This is the padding around the automatic bounds. */
+  gdouble bounds_padding;
+
+  /* The item that the mouse is over. */
+  GooCanvasItem *pointer_item;
+
+  /* The item that has the pointer grab, or NULL. */
+  GooCanvasItem *pointer_grab_item;
+
+  /* This is the item that the grab was started from. When the grab ends
+     we synthesize enter/leave notify events from this item. */
+  GooCanvasItem *pointer_grab_initial_item;
+
+  /* This is the mouse button that started an implicit pointer grab.
+     When the same button is released the implicit grab ends. */
+  guint pointer_grab_button;
+
+  /* The item that has the keyboard focus, or NULL. */
+  GooCanvasItem *focused_item;
+
+  /* The item that has the keyboard grab, or NULL. */
+  GooCanvasItem *keyboard_grab_item;
+
+  /* The synthesized event used for sending enter-notify and leave-notify
+     events to items. */
+  GdkEventCrossing crossing_event;
+
+  /* The main canvas window, which gets scrolled around. */
+  GdkWindow *canvas_window;
+
+  /* The offsets of the canvas within the canvas window, in pixels. These are
+     used when the canvas is smaller than the window size and the anchor is not
+     NORTH_WEST. */
+  gint canvas_x_offset;
+  gint canvas_y_offset;
+
+  /* The adjustments used for scrolling. */
+  GtkAdjustment *hadjustment;
+  GtkAdjustment *vadjustment;
+
+  /* Freezes any movement of the canvas window, until thawed. This is used
+     when we need to set both adjustments and don't want it to scroll twice. */
+  gint freeze_count;
+
+  /* A window temporarily mapped above the canvas to stop X from scrolling
+     the contents unnecessarily (i.e. when we zoom in/out). */
+  GdkWindow *tmp_window;
+
+  /* A hash table mapping canvas item models to canvas items. */
+  GHashTable *model_to_item;
+
+  /* The units of the canvas, which applies to all item coords. */
+  GtkUnit units;
+
+  /* The horizontal and vertical resolution of the display, in dots per inch.
+     This is only needed when units other than pixels are used. */
+  gdouble resolution_x, resolution_y;
+
+  /* The multiplers to convert from device units to pixels, taking into account
+     the canvas scale, the units setting and the display resolution. */
+  gdouble device_to_pixels_x, device_to_pixels_y;
+
+  /* The list of child widgets (using GooCanvasWidget items). */
+  GList *widget_items;
+};
+
+/**
+ * GooCanvasClass
+ * @create_item: a virtual method that subclasses may override to create custom
+ *  canvas items for item models.
+ * @item_created: signal emitted when a new canvas item has been created.
+ *  Applications can connect to this to setup signal handlers for the new item.
+ *
+ * The #GooCanvasClass-struct struct contains one virtual method that
+ * subclasses may override.
+ */
+struct _GooCanvasClass
+{
+  /*< private >*/
+  GtkContainerClass parent_class;
+
+  /* Virtual methods. */
+  /*< public >*/
+  GooCanvasItem* (* create_item)	    (GooCanvas          *canvas,
+					     GooCanvasItemModel *model);
+
+  /* Signals. */
+  void           (* item_created)	    (GooCanvas          *canvas,
+					     GooCanvasItem      *item,
+					     GooCanvasItemModel *model);
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+  void (*_goo_canvas_reserved5) (void);
+  void (*_goo_canvas_reserved6) (void);
+  void (*_goo_canvas_reserved7) (void);
+  void (*_goo_canvas_reserved8) (void);
+};
+
+
+GType           goo_canvas_get_type	    (void) G_GNUC_CONST;
+GtkWidget*      goo_canvas_new		    (void);
+
+GooCanvasItem*  goo_canvas_get_root_item    (GooCanvas		*canvas);
+void            goo_canvas_set_root_item    (GooCanvas		*canvas,
+					     GooCanvasItem      *item);
+
+GooCanvasItemModel* goo_canvas_get_root_item_model (GooCanvas	       *canvas);
+void                goo_canvas_set_root_item_model (GooCanvas	       *canvas,
+						    GooCanvasItemModel *model);
+
+GooCanvasItem*  goo_canvas_get_static_root_item    (GooCanvas		*canvas);
+void            goo_canvas_set_static_root_item    (GooCanvas		*canvas,
+						    GooCanvasItem      *item);
+
+GooCanvasItemModel* goo_canvas_get_static_root_item_model (GooCanvas	       *canvas);
+void                goo_canvas_set_static_root_item_model (GooCanvas	       *canvas,
+							   GooCanvasItemModel *model);
+
+GooCanvasItem*  goo_canvas_get_item	    (GooCanvas		*canvas,
+					     GooCanvasItemModel *model);
+GooCanvasItem*  goo_canvas_get_item_at	    (GooCanvas		*canvas,
+					     gdouble             x,
+					     gdouble             y,
+					     gboolean            is_pointer_event);
+GList*		goo_canvas_get_items_at	    (GooCanvas		*canvas,
+					     gdouble		 x,
+					     gdouble		 y,
+					     gboolean		 is_pointer_event);
+GList*		goo_canvas_get_items_in_area(GooCanvas		*canvas,
+					     const GooCanvasBounds *area,
+					     gboolean		 inside_area,
+					     gboolean            allow_overlaps,
+					     gboolean            include_containers);
+
+gdouble         goo_canvas_get_scale	    (GooCanvas		*canvas);
+void            goo_canvas_set_scale	    (GooCanvas		*canvas,
+					     gdouble             scale);
+
+void            goo_canvas_get_bounds	    (GooCanvas		*canvas,
+					     gdouble            *left,
+					     gdouble            *top,
+					     gdouble            *right,
+					     gdouble            *bottom);
+void            goo_canvas_set_bounds	    (GooCanvas		*canvas,
+					     gdouble             left,
+					     gdouble             top,
+					     gdouble             right,
+					     gdouble             bottom);
+
+void            goo_canvas_scroll_to	    (GooCanvas		*canvas,
+					     gdouble             left,
+					     gdouble             top);
+
+void            goo_canvas_grab_focus	    (GooCanvas		*canvas,
+					     GooCanvasItem	*item);
+
+void            goo_canvas_render	    (GooCanvas		   *canvas,
+					     cairo_t               *cr,
+					     const GooCanvasBounds *bounds,
+					     gdouble                scale);
+
+/*
+ * Coordinate conversion.
+ */
+void		goo_canvas_convert_to_pixels	   (GooCanvas       *canvas,
+						    gdouble         *x,
+						    gdouble         *y);
+void		goo_canvas_convert_from_pixels	   (GooCanvas       *canvas,
+						    gdouble         *x,
+						    gdouble         *y);
+
+void		goo_canvas_convert_to_item_space   (GooCanvas	    *canvas,
+						    GooCanvasItem   *item,
+						    gdouble         *x,
+						    gdouble         *y);
+void		goo_canvas_convert_from_item_space (GooCanvas       *canvas,
+						    GooCanvasItem   *item,
+						    gdouble         *x,
+						    gdouble         *y);
+void		goo_canvas_convert_bounds_to_item_space (GooCanvas           *canvas,
+							 GooCanvasItem       *item,
+							 GooCanvasBounds     *bounds);
+
+
+/*
+ * Pointer/keyboard grabbing & ungrabbing.
+ */
+GdkGrabStatus	goo_canvas_pointer_grab	    (GooCanvas		*canvas,
+					     GooCanvasItem	*item,
+					     GdkEventMask        event_mask,
+					     GdkCursor		*cursor,
+					     guint32             time);
+void		goo_canvas_pointer_ungrab   (GooCanvas		*canvas,
+					     GooCanvasItem	*item,
+					     guint32             time);
+GdkGrabStatus	goo_canvas_keyboard_grab    (GooCanvas		*canvas,
+					     GooCanvasItem	*item,
+					     gboolean            owner_events,
+					     guint32             time);
+void		goo_canvas_keyboard_ungrab  (GooCanvas		*canvas,
+					     GooCanvasItem	*item,
+					     guint32             time);
+
+
+/*
+ * Internal functions, mainly for canvas subclasses and item implementations.
+ */
+cairo_t*	goo_canvas_create_cairo_context	(GooCanvas *canvas);
+GooCanvasItem*	goo_canvas_create_item	    (GooCanvas          *canvas,
+					     GooCanvasItemModel *model);
+void		goo_canvas_unregister_item  (GooCanvas		*canvas,
+					     GooCanvasItemModel *model);
+void		goo_canvas_update	    (GooCanvas		*canvas);
+void		goo_canvas_request_update   (GooCanvas		*canvas);
+void		goo_canvas_request_redraw   (GooCanvas		*canvas,
+					     const GooCanvasBounds *bounds);
+void		goo_canvas_request_item_redraw   (GooCanvas		*canvas,
+						  const GooCanvasBounds *bounds,
+						  gboolean               is_static);
+gdouble         goo_canvas_get_default_line_width (GooCanvas    *canvas);
+
+
+void            goo_canvas_register_widget_item   (GooCanvas          *canvas,
+						   GooCanvasWidget    *witem);
+void            goo_canvas_unregister_widget_item (GooCanvas          *canvas,
+						   GooCanvasWidget    *witem);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_H__ */
diff --git a/libgoocanvas/goocanvasatk.c b/libgoocanvas/goocanvasatk.c
new file mode 100644
index 0000000..565f1bc
--- /dev/null
+++ b/libgoocanvas/goocanvasatk.c
@@ -0,0 +1,776 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Copyright 2001 Sun Microsystems Inc.
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasatk.c - the accessibility code. Most of this has been ported from
+ *                  the gail/libgnomecanvas & foocanvas code.
+ */
+#include <config.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include "goocanvas.h"
+
+
+/*
+ * GooCanvasItemAccessible.
+ */
+
+typedef AtkGObjectAccessible      GooCanvasItemAccessible;
+typedef AtkGObjectAccessibleClass GooCanvasItemAccessibleClass;
+
+#define GOO_TYPE_CANVAS_ITEM_ACCESSIBLE    (goo_canvas_item_accessible_get_type ())
+#define GOO_IS_CANVAS_ITEM_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), goo_canvas_item_accessible_get_type ()))
+
+static void goo_canvas_item_accessible_component_interface_init (AtkComponentIface *iface);
+static gint goo_canvas_item_accessible_get_index_in_parent (AtkObject *accessible);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasItemAccessible,
+			 goo_canvas_item_accessible,
+			 ATK_TYPE_GOBJECT_ACCESSIBLE,
+			 G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, goo_canvas_item_accessible_component_interface_init))
+
+
+/* Returns the extents of the item, in pixels relative to the main
+   canvas window. */
+static void
+goo_canvas_item_accessible_get_item_extents (GooCanvasItem *item,
+					     GdkRectangle  *rect)
+{
+  GooCanvas *canvas;
+  GooCanvasBounds bounds;
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (!canvas)
+    {
+      rect->x = rect->y = rect->width = rect->height = 0;
+      return;
+    }
+
+  /* Get the bounds in device units. */
+  goo_canvas_item_get_bounds (item, &bounds);
+
+  /* Static items are in pixels so don't need converting. */
+  if (!goo_canvas_item_get_is_static (item))
+    {
+      /* Convert to pixels within the entire canvas. */
+      goo_canvas_convert_to_pixels (canvas, &bounds.x1, &bounds.y1);
+      goo_canvas_convert_to_pixels (canvas, &bounds.x2, &bounds.y2);
+
+      /* Convert to pixels within the visible window. */
+      bounds.x1 -= gtk_adjustment_get_value (canvas->hadjustment);
+      bounds.y1 -= gtk_adjustment_get_value (canvas->vadjustment);
+      bounds.x2 -= gtk_adjustment_get_value (canvas->hadjustment);
+      bounds.y2 -= gtk_adjustment_get_value (canvas->vadjustment);
+    }
+
+  /* Round up or down to integers. */
+  rect->x = floor (bounds.x1);
+  rect->y = floor (bounds.y1);
+  rect->width = ceil (bounds.x1) - rect->x;
+  rect->height = ceil (bounds.y1) - rect->y;
+}
+
+
+/* This returns TRUE if the given rectangle intersects the canvas window.
+   The rectangle should be in pixels relative to the main canvas window. */
+static gboolean
+goo_canvas_item_accessible_is_item_in_window (GooCanvasItem *item,
+					      GdkRectangle  *rect)
+{
+  GtkWidget *widget;
+  GtkAllocation allocation;
+
+  widget = (GtkWidget*) goo_canvas_item_get_canvas (item);
+  if (!widget)
+    return FALSE;
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  if (rect->x + rect->width < 0 || rect->x > allocation.width
+      || rect->y + rect->height < 0 || rect->y > allocation.height)
+    return FALSE;
+
+  return TRUE;
+}
+
+
+static gboolean
+goo_canvas_item_accessible_is_item_on_screen (GooCanvasItem *item)
+{
+  GdkRectangle rect;
+
+  goo_canvas_item_accessible_get_item_extents (item, &rect);
+  return goo_canvas_item_accessible_is_item_in_window (item, &rect);
+}
+
+
+
+static void
+goo_canvas_item_accessible_get_extents (AtkComponent *component,
+					gint         *x,
+					gint         *y,
+					gint         *width,
+					gint         *height,
+					AtkCoordType  coord_type)
+{
+  GooCanvasItem *item;
+  GooCanvas *canvas;
+  GObject *object;
+  gint window_x, window_y;
+  gint toplevel_x, toplevel_y;
+  GdkRectangle rect;
+  GdkWindow *canvas_window, *window;
+
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (component));
+
+  *x = *y = G_MININT;
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (component));
+  if (!object)
+    return;
+
+  item = GOO_CANVAS_ITEM (object);
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (!canvas)
+    return;
+
+  canvas_window = gtk_widget_get_window (GTK_WIDGET (canvas));
+  if (!canvas_window)
+    return;
+
+  goo_canvas_item_accessible_get_item_extents (item, &rect);
+  *width = rect.width;
+  *height = rect.height;
+
+  if (!goo_canvas_item_accessible_is_item_in_window (item, &rect))
+    return;
+
+  gdk_window_get_origin (canvas_window, &window_x, &window_y);
+  *x = rect.x + window_x;
+  *y = rect.y + window_y;
+
+  if (coord_type == ATK_XY_WINDOW)
+    {
+      window = gdk_window_get_toplevel (canvas_window);
+      gdk_window_get_origin (window, &toplevel_x, &toplevel_y);
+      *x -= toplevel_x;
+      *y -= toplevel_y;
+    }
+}
+
+
+static gint
+goo_canvas_item_accessible_get_mdi_zorder (AtkComponent *component)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (component), -1);
+
+  return goo_canvas_item_accessible_get_index_in_parent (ATK_OBJECT (component));
+}
+
+
+static guint
+goo_canvas_item_accessible_add_focus_handler (AtkComponent    *component,
+					      AtkFocusHandler  handler)
+{
+  GSignalMatchType match_type;
+  GClosure *closure;
+  guint signal_id;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (component), 0);
+
+  match_type = G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC;
+  signal_id = g_signal_lookup ("focus-event", ATK_TYPE_OBJECT);
+
+  /* If the handler has already been added just return. */
+  if (g_signal_handler_find (component, match_type, signal_id, 0, NULL,
+			     (gpointer) handler, NULL))
+    return 0;
+
+  closure = g_cclosure_new (G_CALLBACK (handler), NULL, (GClosureNotify) NULL);
+  return g_signal_connect_closure_by_id (component, signal_id, 0, closure,
+					 FALSE);
+}
+
+
+static void
+goo_canvas_item_accessible_remove_focus_handler (AtkComponent *component,
+						 guint         handler_id)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (component));
+
+  g_signal_handler_disconnect (ATK_OBJECT (component), handler_id);
+}
+
+
+static gboolean
+goo_canvas_item_accessible_grab_focus (AtkComponent    *component)
+{
+  GooCanvasItem *item;
+  GooCanvas *canvas;
+  GtkWidget *toplevel;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (component), FALSE);
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (component));
+  if (!object)
+    return FALSE;
+
+  item = GOO_CANVAS_ITEM (object);
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (!canvas)
+    return FALSE;
+
+  goo_canvas_grab_focus (canvas, item);
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
+  if (gtk_widget_is_toplevel (toplevel))
+    gtk_window_present (GTK_WINDOW (toplevel));
+
+  return TRUE;
+}
+
+
+static void
+goo_canvas_item_accessible_component_interface_init (AtkComponentIface *iface)
+{
+  iface->get_extents          = goo_canvas_item_accessible_get_extents;
+  iface->get_mdi_zorder       = goo_canvas_item_accessible_get_mdi_zorder;
+  iface->add_focus_handler    = goo_canvas_item_accessible_add_focus_handler;
+  iface->remove_focus_handler = goo_canvas_item_accessible_remove_focus_handler;
+  iface->grab_focus           = goo_canvas_item_accessible_grab_focus;
+}
+
+
+static void
+goo_canvas_item_accessible_initialize (AtkObject *object,
+				       gpointer   data)
+{
+  if (ATK_OBJECT_CLASS (goo_canvas_item_accessible_parent_class)->initialize)
+    ATK_OBJECT_CLASS (goo_canvas_item_accessible_parent_class)->initialize (object, data);
+
+  object->role = ATK_ROLE_UNKNOWN;
+
+  /* FIXME: Maybe this should be ATK_LAYER_CANVAS. */
+  g_object_set_data (G_OBJECT (object), "atk-component-layer",
+		     GINT_TO_POINTER (ATK_LAYER_MDI));
+}
+
+
+static AtkObject*
+goo_canvas_item_accessible_get_parent (AtkObject *accessible)
+{
+  GooCanvasItem *item, *parent;
+  GooCanvas *canvas;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (accessible), NULL);
+
+  if (accessible->accessible_parent)
+    return accessible->accessible_parent;
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return NULL;
+
+  item = GOO_CANVAS_ITEM (object);
+  parent = goo_canvas_item_get_parent (item);
+
+  if (parent)
+    return atk_gobject_accessible_for_object (G_OBJECT (parent));
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (canvas)
+    return gtk_widget_get_accessible (GTK_WIDGET (canvas));
+
+  return NULL;
+}
+
+
+static gint
+goo_canvas_item_accessible_get_index_in_parent (AtkObject *accessible)
+{
+  GooCanvasItem *item, *parent;
+  GooCanvas *canvas;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (accessible), -1);
+
+  if (accessible->accessible_parent)
+    {
+      gint n_children, i;
+      gboolean found = FALSE;
+
+      n_children = atk_object_get_n_accessible_children (accessible->accessible_parent);
+      for (i = 0; i < n_children; i++)
+        {
+          AtkObject *child;
+
+          child = atk_object_ref_accessible_child (accessible->accessible_parent, i);
+          if (child == accessible)
+            found = TRUE;
+
+          g_object_unref (child);
+          if (found)
+            return i;
+        }
+      return -1;
+    }
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return -1;
+
+  item = GOO_CANVAS_ITEM (object);
+  parent = goo_canvas_item_get_parent (item);
+
+  if (parent)
+    return goo_canvas_item_find_child (parent, item);
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (canvas)
+    return 0;
+
+  return -1;
+}
+
+
+static gint
+goo_canvas_item_accessible_get_n_children (AtkObject *accessible)
+{
+  GooCanvasItem *item;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (accessible), 0);
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return 0;
+
+  item = GOO_CANVAS_ITEM (object);
+
+  return goo_canvas_item_get_n_children (item);
+}
+
+
+static AtkObject*
+goo_canvas_item_accessible_ref_child (AtkObject *accessible,
+				      gint       child_num)
+{
+  GooCanvasItem *item, *child;
+  AtkObject *atk_object;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (accessible), NULL);
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return NULL;
+
+  item = GOO_CANVAS_ITEM (object);
+
+  child = goo_canvas_item_get_child (item, child_num);
+  if (!child)
+    return NULL;
+
+  atk_object = atk_gobject_accessible_for_object (G_OBJECT (child));
+  g_object_ref (atk_object);
+
+  return atk_object;
+}
+
+
+static AtkStateSet*
+goo_canvas_item_accessible_ref_state_set (AtkObject *accessible)
+{
+  GooCanvasItem *item;
+  GooCanvas *canvas;
+  AtkStateSet *state_set;
+  GObject *object;
+  gboolean can_focus = FALSE;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM_ACCESSIBLE (accessible), NULL);
+
+  state_set = ATK_OBJECT_CLASS (goo_canvas_item_accessible_parent_class)->ref_state_set (accessible);
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (!object)
+    {
+      atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
+      return state_set;
+    }
+
+  item = GOO_CANVAS_ITEM (object);
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (!canvas)
+    return state_set;
+
+  if (goo_canvas_item_is_visible (item))
+    {
+      atk_state_set_add_state (state_set, ATK_STATE_VISIBLE);
+
+      if (goo_canvas_item_accessible_is_item_on_screen (item))
+	atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
+    }
+
+  g_object_get (item, "can-focus", &can_focus, NULL);
+
+  if (gtk_widget_get_can_focus (GTK_WIDGET (canvas)) && can_focus)
+    {
+      atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE);
+
+      if (gtk_widget_has_focus (GTK_WIDGET (canvas))
+	  && canvas->focused_item == item)
+	atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+    }
+
+  return state_set;
+}
+
+
+static void
+goo_canvas_item_accessible_class_init (GooCanvasItemAccessibleClass *klass)
+{
+  AtkObjectClass *aklass = (AtkObjectClass*) klass;
+
+  aklass->initialize          = goo_canvas_item_accessible_initialize;
+  aklass->get_parent          = goo_canvas_item_accessible_get_parent;
+  aklass->get_index_in_parent = goo_canvas_item_accessible_get_index_in_parent;
+  aklass->get_n_children      = goo_canvas_item_accessible_get_n_children;
+  aklass->ref_child           = goo_canvas_item_accessible_ref_child;
+  aklass->ref_state_set       = goo_canvas_item_accessible_ref_state_set;
+}
+
+
+static void
+goo_canvas_item_accessible_init (GooCanvasItemAccessible *accessible)
+{
+}
+
+
+static AtkObject *
+goo_canvas_item_accessible_new (GObject *object)
+{
+  AtkObject *accessible;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM (object), NULL);
+
+  accessible = g_object_new (goo_canvas_item_accessible_get_type (), NULL);
+  atk_object_initialize (accessible, object);
+
+  return accessible;
+}
+
+
+/*
+ * GooCanvasItemAccessibleFactory.
+ */
+
+typedef AtkObjectFactory      GooCanvasItemAccessibleFactory;
+typedef AtkObjectFactoryClass GooCanvasItemAccessibleFactoryClass;
+
+G_DEFINE_TYPE (GooCanvasItemAccessibleFactory,
+	       goo_canvas_item_accessible_factory,
+	       ATK_TYPE_OBJECT_FACTORY)
+
+static void
+goo_canvas_item_accessible_factory_class_init (GooCanvasItemAccessibleFactoryClass *klass)
+{
+  klass->create_accessible   = goo_canvas_item_accessible_new;
+  klass->get_accessible_type = goo_canvas_item_accessible_get_type;
+}
+
+static void
+goo_canvas_item_accessible_factory_init (GooCanvasItemAccessibleFactory *factory)
+{
+}
+
+
+
+/*
+ * GooCanvasWidgetAccessible.
+ */
+
+typedef AtkGObjectAccessible      GooCanvasWidgetAccessible;
+typedef AtkGObjectAccessibleClass GooCanvasWidgetAccessibleClass;
+
+#define GOO_IS_CANVAS_WIDGET_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), goo_canvas_widget_accessible_get_type ()))
+
+G_DEFINE_TYPE (GooCanvasWidgetAccessible, goo_canvas_widget_accessible,
+	       GOO_TYPE_CANVAS_ITEM_ACCESSIBLE)
+
+
+static void
+goo_canvas_widget_accessible_initialize (AtkObject *object,
+					 gpointer   data)
+{
+  if (ATK_OBJECT_CLASS (goo_canvas_widget_accessible_parent_class)->initialize)
+    ATK_OBJECT_CLASS (goo_canvas_widget_accessible_parent_class)->initialize (object, data);
+
+  object->role = ATK_ROLE_PANEL;
+}
+
+
+static gint 
+goo_canvas_widget_accessible_get_n_children (AtkObject *accessible)
+{
+  GooCanvasWidget *witem;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_WIDGET_ACCESSIBLE (accessible), 0);
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return 0;
+
+  witem = GOO_CANVAS_WIDGET (object);
+
+  return witem->widget ? 1 : 0;
+}
+
+static AtkObject *
+goo_canvas_widget_accessible_ref_child (AtkObject *accessible,
+					gint       child_num)
+{
+  GooCanvasWidget *witem;
+  AtkObject *atk_object;
+  GObject *object;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_WIDGET_ACCESSIBLE (accessible), NULL);
+
+  if (child_num != 0)
+    return NULL;
+
+  object = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+  if (object == NULL)
+    return NULL;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_WIDGET (object), NULL);
+
+  witem = GOO_CANVAS_WIDGET (object);
+
+  if (!witem->widget)
+    return NULL;
+
+  atk_object = gtk_widget_get_accessible (witem->widget);
+  g_object_ref (atk_object);
+
+  return atk_object;
+}
+
+
+static void
+goo_canvas_widget_accessible_class_init (GooCanvasWidgetAccessibleClass *klass)
+{
+  AtkObjectClass *aklass = (AtkObjectClass*) klass;
+
+  aklass->initialize     = goo_canvas_widget_accessible_initialize;
+  aklass->get_n_children = goo_canvas_widget_accessible_get_n_children;
+  aklass->ref_child      = goo_canvas_widget_accessible_ref_child;
+}
+
+
+static void
+goo_canvas_widget_accessible_init (GooCanvasWidgetAccessible *accessible)
+{
+}
+
+
+static AtkObject*
+goo_canvas_widget_accessible_new (GObject *object)
+{
+  AtkObject *accessible;
+
+  g_return_val_if_fail (GOO_IS_CANVAS_WIDGET (object), NULL);
+
+  accessible = g_object_new (goo_canvas_widget_accessible_get_type (), NULL);
+  atk_object_initialize (accessible, object);
+
+  return accessible;
+}
+
+
+/*
+ * GooCanvasWidgetAccessibleFactory.
+ */
+
+typedef AtkObjectFactory      GooCanvasWidgetAccessibleFactory;
+typedef AtkObjectFactoryClass GooCanvasWidgetAccessibleFactoryClass;
+
+G_DEFINE_TYPE (GooCanvasWidgetAccessibleFactory,
+	       goo_canvas_widget_accessible_factory,
+	       ATK_TYPE_OBJECT_FACTORY)
+
+static void
+goo_canvas_widget_accessible_factory_class_init (GooCanvasWidgetAccessibleFactoryClass *klass)
+{
+  klass->create_accessible   = goo_canvas_widget_accessible_new;
+  klass->get_accessible_type = goo_canvas_widget_accessible_get_type;
+}
+
+static void
+goo_canvas_widget_accessible_factory_init (GooCanvasWidgetAccessibleFactory *factory)
+{
+}
+
+
+/*
+ * GooCanvasAccessible.
+ */
+
+static gpointer accessible_parent_class = NULL;
+
+
+static void
+goo_canvas_accessible_initialize (AtkObject *object, 
+				  gpointer   data)
+{
+  if (ATK_OBJECT_CLASS (accessible_parent_class)->initialize) 
+    ATK_OBJECT_CLASS (accessible_parent_class)->initialize (object, data);
+
+  /* FIXME: Maybe this should be ATK_ROLE_CANVAS. */
+  object->role = ATK_ROLE_LAYERED_PANE;
+}
+
+
+static gint
+goo_canvas_accessible_get_n_children (AtkObject *object)
+{
+  GtkAccessible *accessible;
+  GtkWidget *widget;
+
+  accessible = GTK_ACCESSIBLE (object);
+  widget = gtk_accessible_get_widget (accessible);
+
+  /* Check if widget still exists. */
+  if (widget == NULL)
+    return 0;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (widget), 0);
+
+  if (goo_canvas_get_root_item (GOO_CANVAS (widget)))
+    return 1;
+
+  return 0;
+}
+
+static AtkObject*
+goo_canvas_accessible_ref_child (AtkObject *object,
+				 gint       child_num)
+{
+  GtkAccessible *accessible;
+  GtkWidget *widget;
+  GooCanvasItem *root;
+  AtkObject *atk_object;
+
+  /* Canvas only has one child, so return NULL if index is non zero */
+  if (child_num != 0)
+    return NULL;
+
+  accessible = GTK_ACCESSIBLE (object);
+  widget = gtk_accessible_get_widget (accessible);
+
+  /* Check if widget still exists. */
+  if (widget == NULL)
+    return NULL;
+
+  root = goo_canvas_get_root_item (GOO_CANVAS (widget));
+  if (!root)
+    return NULL;
+
+  atk_object = atk_gobject_accessible_for_object (G_OBJECT (root));
+  g_object_ref (atk_object);
+
+  return atk_object;
+}
+
+
+
+static void
+goo_canvas_accessible_class_init (AtkObjectClass *klass)
+{
+  accessible_parent_class = g_type_class_peek_parent (klass);
+
+  klass->initialize     = goo_canvas_accessible_initialize;
+  klass->get_n_children = goo_canvas_accessible_get_n_children;
+  klass->ref_child      = goo_canvas_accessible_ref_child;
+}
+
+
+static GType
+goo_canvas_accessible_get_type (void)
+{
+  static GType g_define_type_id = 0;
+
+  if (G_UNLIKELY (g_define_type_id == 0))
+    {
+      AtkObjectFactory *factory;
+      GType parent_atk_type;
+      GTypeQuery query;
+      GTypeInfo tinfo = { 0 };
+
+      /* Gail doesn't declare its classes publicly, so we have to do a query
+	 to find the size of the class and instances. */
+      factory = atk_registry_get_factory (atk_get_default_registry (),
+					  GTK_TYPE_WIDGET);
+      if (!factory)
+	return G_TYPE_INVALID;
+
+      parent_atk_type = atk_object_factory_get_accessible_type (factory);
+      if (!parent_atk_type)
+	return G_TYPE_INVALID;
+
+      g_type_query (parent_atk_type, &query);
+      tinfo.class_init = (GClassInitFunc) goo_canvas_accessible_class_init;
+      tinfo.class_size = query.class_size;
+      tinfo.instance_size = query.instance_size;
+      g_define_type_id = g_type_register_static (parent_atk_type,
+						 "GooCanvasAccessible",
+						 &tinfo, 0);
+    }
+
+  return g_define_type_id;
+}
+
+
+static AtkObject *
+goo_canvas_accessible_new (GObject *object)
+{
+  AtkObject *accessible;
+
+  g_return_val_if_fail (GOO_IS_CANVAS (object), NULL);
+
+  accessible = g_object_new (goo_canvas_accessible_get_type (), NULL);
+  atk_object_initialize (accessible, object);
+
+  return accessible;
+}
+
+
+/*
+ * GooCanvasAccessibleFactory.
+ */
+
+typedef AtkObjectFactory      GooCanvasAccessibleFactory;
+typedef AtkObjectFactoryClass GooCanvasAccessibleFactoryClass;
+
+G_DEFINE_TYPE (GooCanvasAccessibleFactory,
+	       goo_canvas_accessible_factory,
+	       ATK_TYPE_OBJECT_FACTORY)
+
+static void
+goo_canvas_accessible_factory_class_init (GooCanvasAccessibleFactoryClass *klass)
+{
+  klass->create_accessible   = goo_canvas_accessible_new;
+  klass->get_accessible_type = goo_canvas_accessible_get_type;
+}
+
+static void
+goo_canvas_accessible_factory_init (GooCanvasAccessibleFactory *factory)
+{
+}
diff --git a/libgoocanvas/goocanvasatk.h b/libgoocanvas/goocanvasatk.h
new file mode 100644
index 0000000..92aa4e5
--- /dev/null
+++ b/libgoocanvas/goocanvasatk.h
@@ -0,0 +1,22 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasatk.h - the accessibility code.
+ */
+#ifndef __GOO_CANVAS_ATK_H__
+#define __GOO_CANVAS_ATK_H__
+
+#include <gtk/gtk.h>
+
+
+G_BEGIN_DECLS
+
+GType    goo_canvas_accessible_factory_get_type  (void) G_GNUC_CONST;
+GType    goo_canvas_item_accessible_factory_get_type  (void) G_GNUC_CONST;
+GType    goo_canvas_widget_accessible_factory_get_type  (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_ATK_H__ */
diff --git a/libgoocanvas/goocanvasellipse.c b/libgoocanvas/goocanvasellipse.c
new file mode 100644
index 0000000..6653f50
--- /dev/null
+++ b/libgoocanvas/goocanvasellipse.c
@@ -0,0 +1,614 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasellipse.c - ellipse item.
+ */
+
+/**
+ * SECTION:goocanvasellipse
+ * @Title: GooCanvasEllipse
+ * @Short_Description: an ellipse item.
+ *
+ * GooCanvasEllipse represents an ellipse item.
+ *
+ * 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 #GooCanvasEllipse use goo_canvas_ellipse_new().
+ *
+ * To get or set the properties of an existing #GooCanvasEllipse, use
+ * g_object_get() and g_object_set().
+ *
+ * The ellipse can be specified either with the "center-x", "center-y",
+ * "radius-x" and "radius-y" properties, or with the "x", "y", "width" and
+ * "height" properties.
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasellipse.h"
+
+
+enum {
+  PROP_0,
+
+  PROP_CENTER_X,
+  PROP_CENTER_Y,
+  PROP_RADIUS_X,
+  PROP_RADIUS_Y,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT
+};
+
+
+static void canvas_item_interface_init      (GooCanvasItemIface  *iface);
+static void goo_canvas_ellipse_finalize     (GObject             *object);
+static void goo_canvas_ellipse_get_property (GObject             *object,
+					     guint                param_id,
+					     GValue              *value,
+					     GParamSpec          *pspec);
+static void goo_canvas_ellipse_set_property (GObject             *object,
+					     guint                param_id,
+					     const GValue        *value,
+					     GParamSpec          *pspec);
+static void goo_canvas_ellipse_create_path  (GooCanvasItemSimple *simple,
+					     cairo_t             *cr);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasEllipse, goo_canvas_ellipse,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_ellipse_install_common_properties (GObjectClass *gobject_class)
+{
+  g_object_class_install_property (gobject_class, PROP_CENTER_X,
+				   g_param_spec_double ("center-x",
+							_("Center X"),
+							_("The x coordinate of the center of the ellipse"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_CENTER_Y,
+				   g_param_spec_double ("center-y",
+							_("Center Y"),
+							_("The y coordinate of the center of the ellipse"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RADIUS_X,
+				   g_param_spec_double ("radius-x",
+							_("Radius X"),
+							_("The horizontal radius of the ellipse"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RADIUS_Y,
+				   g_param_spec_double ("radius-y",
+							_("Radius Y"),
+							_("The vertical radius of the ellipse"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the left side of the ellipse"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the top of the ellipse"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the ellipse"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the ellipse"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_ellipse_class_init (GooCanvasEllipseClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  gobject_class->finalize     = goo_canvas_ellipse_finalize;
+
+  gobject_class->get_property = goo_canvas_ellipse_get_property;
+  gobject_class->set_property = goo_canvas_ellipse_set_property;
+
+  simple_class->simple_create_path = goo_canvas_ellipse_create_path;
+
+  goo_canvas_ellipse_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_ellipse_init (GooCanvasEllipse *ellipse)
+{
+  ellipse->ellipse_data = g_slice_new0 (GooCanvasEllipseData);
+}
+
+
+/**
+ * goo_canvas_ellipse_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.
+ * @center_x: the x coordinate of the center of the ellipse.
+ * @center_y: the y coordinate of the center of the ellipse.
+ * @radius_x: the horizontal radius of the ellipse.
+ * @radius_y: the vertical radius of the ellipse.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new ellipse item.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create an ellipse centered at (100.0,
+ * 100.0), with a horizontal radius of 50.0 and a vertical radius of 30.0.
+ * It is drawn with a red outline with a width of 5.0 and filled with blue:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *ellipse = goo_canvas_ellipse_new (mygroup, 100.0, 100.0, 50.0, 30.0,
+ *                                                   "stroke-color", "red",
+ *                                                   "line-width", 5.0,
+ *                                                   "fill-color", "blue",
+ *                                                   NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new ellipse item.
+ **/
+GooCanvasItem*
+goo_canvas_ellipse_new (GooCanvasItem *parent,
+			gdouble        center_x,
+			gdouble        center_y,
+			gdouble        radius_x,
+			gdouble        radius_y,
+			...)
+{
+  GooCanvasItem *item;
+  GooCanvasEllipse *ellipse;
+  GooCanvasEllipseData *ellipse_data;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_ELLIPSE, NULL);
+  ellipse = (GooCanvasEllipse*) item;
+
+  ellipse_data = ellipse->ellipse_data;
+  ellipse_data->center_x = center_x;
+  ellipse_data->center_y = center_y;
+  ellipse_data->radius_x = radius_x;
+  ellipse_data->radius_y = radius_y;
+
+  va_start (var_args, radius_y);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_ellipse_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasEllipse *ellipse = (GooCanvasEllipse*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    g_slice_free (GooCanvasEllipseData, ellipse->ellipse_data);
+  ellipse->ellipse_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_ellipse_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_ellipse_get_common_property (GObject              *object,
+					GooCanvasEllipseData *ellipse_data,
+					guint                 prop_id,
+					GValue               *value,
+					GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_CENTER_X:
+      g_value_set_double (value, ellipse_data->center_x);
+      break;
+    case PROP_CENTER_Y:
+      g_value_set_double (value, ellipse_data->center_y);
+      break;
+    case PROP_RADIUS_X:
+      g_value_set_double (value, ellipse_data->radius_x);
+      break;
+    case PROP_RADIUS_Y:
+      g_value_set_double (value, ellipse_data->radius_y);
+      break;
+    case PROP_X:
+      g_value_set_double (value, ellipse_data->center_x - ellipse_data->radius_x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, ellipse_data->center_y - ellipse_data->radius_y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, 2.0 * ellipse_data->radius_x);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, 2.0 * ellipse_data->radius_y);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_ellipse_get_property (GObject              *object,
+				 guint                 prop_id,
+				 GValue               *value,
+				 GParamSpec           *pspec)
+{
+  GooCanvasEllipse *ellipse = (GooCanvasEllipse*) object;
+
+  goo_canvas_ellipse_get_common_property (object, ellipse->ellipse_data,
+					  prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_ellipse_set_common_property (GObject              *object,
+					GooCanvasEllipseData *ellipse_data,
+					guint                 prop_id,
+					const GValue         *value,
+					GParamSpec           *pspec)
+{
+  gdouble x, y;
+
+  switch (prop_id)
+    {
+    case PROP_CENTER_X:
+      ellipse_data->center_x = g_value_get_double (value);
+      g_object_notify (object, "x");
+      break;
+    case PROP_CENTER_Y:
+      ellipse_data->center_y = g_value_get_double (value);
+      g_object_notify (object, "y");
+      break;
+    case PROP_RADIUS_X:
+      ellipse_data->radius_x = g_value_get_double (value);
+      g_object_notify (object, "width");
+      break;
+    case PROP_RADIUS_Y:
+      ellipse_data->radius_y = g_value_get_double (value);
+      g_object_notify (object, "height");
+      break;
+    case PROP_X:
+      ellipse_data->center_x = g_value_get_double (value) + ellipse_data->radius_x;
+      g_object_notify (object, "center-x");
+      break;
+    case PROP_Y:
+      ellipse_data->center_y = g_value_get_double (value) + ellipse_data->radius_y;
+      g_object_notify (object, "center-y");
+      break;
+    case PROP_WIDTH:
+      /* Calculate the current x coordinate. */
+      x = ellipse_data->center_x - ellipse_data->radius_x;
+      /* Calculate the new radius_x, which is half the width. */
+      ellipse_data->radius_x = g_value_get_double (value) / 2.0;
+      /* Now calculate the new center_x. */
+      ellipse_data->center_x = x + ellipse_data->radius_x;
+
+      g_object_notify (object, "center-x");
+      g_object_notify (object, "radius-x");
+      break;
+    case PROP_HEIGHT:
+      /* Calculate the current y coordinate. */
+      y = ellipse_data->center_y - ellipse_data->radius_y;
+      /* Calculate the new radius_y, which is half the height. */
+      ellipse_data->radius_y = g_value_get_double (value) / 2.0;
+      /* Now calculate the new center_y. */
+      ellipse_data->center_y = y + ellipse_data->radius_y;
+
+      g_object_notify (object, "center-y");
+      g_object_notify (object, "radius-y");
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_ellipse_set_property (GObject              *object,
+				 guint                 prop_id,
+				 const GValue         *value,
+				 GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasEllipse *ellipse = (GooCanvasEllipse*) object;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_ellipse_set_common_property (object, ellipse->ellipse_data,
+					  prop_id, value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_ellipse_create_path (GooCanvasItemSimple *simple,
+				cairo_t             *cr)
+{
+  GooCanvasEllipse *ellipse = (GooCanvasEllipse*) simple;
+  GooCanvasEllipseData *ellipse_data = ellipse->ellipse_data;
+
+  cairo_new_path (cr);
+  cairo_save (cr);
+  cairo_translate (cr, ellipse_data->center_x, ellipse_data->center_y);
+  cairo_scale (cr, ellipse_data->radius_x, ellipse_data->radius_y);
+  cairo_arc (cr, 0.0, 0.0, 1.0, 0.0, 2.0 * M_PI);
+  cairo_restore (cr);
+}
+
+
+
+static void
+goo_canvas_ellipse_set_model    (GooCanvasItem      *item,
+				 GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasEllipse *ellipse = (GooCanvasEllipse*) item;
+  GooCanvasEllipseModel *emodel = (GooCanvasEllipseModel*) model;
+
+  /* If our ellipse_data was allocated, free it. */
+  if (!simple->model)
+    g_slice_free (GooCanvasEllipseData, ellipse->ellipse_data);
+
+  /* Now use the new model's ellipse_data instead. */
+  ellipse->ellipse_data = &emodel->ellipse_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model      = goo_canvas_ellipse_set_model;
+}
+
+
+/**
+ * SECTION:goocanvasellipsemodel
+ * @Title: GooCanvasEllipseModel
+ * @Short_Description: a model for ellipse items.
+ *
+ * GooCanvasEllipseModel represents a model for ellipse items.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "stroke-color", "fill-color" and "line-width".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasEllipseModel use goo_canvas_ellipse_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasEllipseModel, use
+ * g_object_get() and g_object_set().
+ *
+ * The ellipse can be specified either with the "center-x", "center-y",
+ * "radius-x" and "radius-y" properties, or with the "x", "y", "width" and
+ * "height" properties.
+ *
+ * To respond to events such as mouse clicks on the ellipse you must connect
+ * to the signal handlers of the corresponding #GooCanvasEllipse objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_ellipse_model_finalize     (GObject            *object);
+static void goo_canvas_ellipse_model_get_property (GObject            *object,
+						   guint               param_id,
+						   GValue             *value,
+						   GParamSpec         *pspec);
+static void goo_canvas_ellipse_model_set_property (GObject            *object,
+						   guint               param_id,
+						   const GValue       *value,
+						   GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasEllipseModel, goo_canvas_ellipse_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_ellipse_model_class_init (GooCanvasEllipseModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = goo_canvas_ellipse_model_finalize;
+
+  gobject_class->get_property = goo_canvas_ellipse_model_get_property;
+  gobject_class->set_property = goo_canvas_ellipse_model_set_property;
+
+  goo_canvas_ellipse_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_ellipse_model_init (GooCanvasEllipseModel *emodel)
+{
+
+}
+
+
+/**
+ * goo_canvas_ellipse_model_new:
+ * @parent: the parent model, 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.
+ * @center_x: the x coordinate of the center of the ellipse.
+ * @center_y: the y coordinate of the center of the ellipse.
+ * @radius_x: the horizontal radius of the ellipse.
+ * @radius_y: the vertical radius of the ellipse.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new ellipse model.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create an ellipse centered at (100.0,
+ * 100.0), with a horizontal radius of 50.0 and a vertical radius of 30.0.
+ * It is drawn with a red outline with a width of 5.0 and filled with blue:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *ellipse = goo_canvas_ellipse_model_new (mygroup, 100.0, 100.0, 50.0, 30.0,
+ *                                                              "stroke-color", "red",
+ *                                                              "line-width", 5.0,
+ *                                                              "fill-color", "blue",
+ *                                                              NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new ellipse model.
+ **/
+GooCanvasItemModel*
+goo_canvas_ellipse_model_new (GooCanvasItemModel *parent,
+			      gdouble             center_x,
+			      gdouble             center_y,
+			      gdouble             radius_x,
+			      gdouble             radius_y,
+			      ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasEllipseModel *emodel;
+  GooCanvasEllipseData *ellipse_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_ELLIPSE_MODEL, NULL);
+  emodel = (GooCanvasEllipseModel*) model;
+
+  ellipse_data = &emodel->ellipse_data;
+  ellipse_data->center_x = center_x;
+  ellipse_data->center_y = center_y;
+  ellipse_data->radius_x = radius_x;
+  ellipse_data->radius_y = radius_y;
+
+  va_start (var_args, radius_y);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_ellipse_model_finalize (GObject *object)
+{
+  /*GooCanvasEllipseModel *emodel = (GooCanvasEllipseModel*) object;*/
+
+  G_OBJECT_CLASS (goo_canvas_ellipse_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_ellipse_model_get_property (GObject              *object,
+				       guint                 prop_id,
+				       GValue               *value,
+				       GParamSpec           *pspec)
+{
+  GooCanvasEllipseModel *emodel = (GooCanvasEllipseModel*) object;
+
+  goo_canvas_ellipse_get_common_property (object, &emodel->ellipse_data,
+					  prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_ellipse_model_set_property (GObject              *object,
+				       guint                 prop_id,
+				       const GValue         *value,
+				       GParamSpec           *pspec)
+{
+  GooCanvasEllipseModel *emodel = (GooCanvasEllipseModel*) object;
+
+  goo_canvas_ellipse_set_common_property (object, &emodel->ellipse_data,
+					  prop_id, value, pspec);
+  g_signal_emit_by_name (emodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_ellipse_model_create_item (GooCanvasItemModel *model,
+				      GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_ELLIPSE, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_ellipse_model_create_item;
+}
diff --git a/libgoocanvas/goocanvasellipse.h b/libgoocanvas/goocanvasellipse.h
new file mode 100644
index 0000000..0b15263
--- /dev/null
+++ b/libgoocanvas/goocanvasellipse.h
@@ -0,0 +1,120 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasellipse.h - ellipse item.
+ */
+#ifndef __GOO_CANVAS_ELLIPSE_H__
+#define __GOO_CANVAS_ELLIPSE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasEllipseData   GooCanvasEllipseData;
+struct _GooCanvasEllipseData
+{
+  gdouble center_x, center_y, radius_x, radius_y;
+};
+
+
+#define GOO_TYPE_CANVAS_ELLIPSE            (goo_canvas_ellipse_get_type ())
+#define GOO_CANVAS_ELLIPSE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ELLIPSE, GooCanvasEllipse))
+#define GOO_CANVAS_ELLIPSE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_ELLIPSE, GooCanvasEllipseClass))
+#define GOO_IS_CANVAS_ELLIPSE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ELLIPSE))
+#define GOO_IS_CANVAS_ELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_ELLIPSE))
+#define GOO_CANVAS_ELLIPSE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_ELLIPSE, GooCanvasEllipseClass))
+
+
+typedef struct _GooCanvasEllipse       GooCanvasEllipse;
+typedef struct _GooCanvasEllipseClass  GooCanvasEllipseClass;
+
+/**
+ * GooCanvasEllipse
+ *
+ * The #GooCanvasEllipse-struct struct contains private data only.
+ */
+struct _GooCanvasEllipse
+{
+  GooCanvasItemSimple parent_object;
+
+  GooCanvasEllipseData *ellipse_data;
+};
+
+struct _GooCanvasEllipseClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_ellipse_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_ellipse_new	 (GooCanvasItem      *parent,
+						  gdouble             center_x,
+						  gdouble             center_y,
+						  gdouble             radius_x,
+						  gdouble             radius_y,
+						  ...);
+
+
+
+#define GOO_TYPE_CANVAS_ELLIPSE_MODEL            (goo_canvas_ellipse_model_get_type ())
+#define GOO_CANVAS_ELLIPSE_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ELLIPSE_MODEL, GooCanvasEllipseModel))
+#define GOO_CANVAS_ELLIPSE_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_ELLIPSE_MODEL, GooCanvasEllipseModelClass))
+#define GOO_IS_CANVAS_ELLIPSE_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ELLIPSE_MODEL))
+#define GOO_IS_CANVAS_ELLIPSE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_ELLIPSE_MODEL))
+#define GOO_CANVAS_ELLIPSE_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_ELLIPSE_MODEL, GooCanvasEllipseModelClass))
+
+
+typedef struct _GooCanvasEllipseModel       GooCanvasEllipseModel;
+typedef struct _GooCanvasEllipseModelClass  GooCanvasEllipseModelClass;
+
+/**
+ * GooCanvasEllipseModel
+ *
+ * The #GooCanvasEllipseModel-struct struct contains private data only.
+ */
+struct _GooCanvasEllipseModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasEllipseData ellipse_data;
+};
+
+struct _GooCanvasEllipseModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_ellipse_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_ellipse_model_new (GooCanvasItemModel *parent,
+						  gdouble             center_x,
+						  gdouble             center_y,
+						  gdouble             radius_x,
+						  gdouble             radius_y,
+						  ...);
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_ELLIPSE_H__ */
diff --git a/libgoocanvas/goocanvasgrid.c b/libgoocanvas/goocanvasgrid.c
new file mode 100644
index 0000000..80190dd
--- /dev/null
+++ b/libgoocanvas/goocanvasgrid.c
@@ -0,0 +1,1170 @@
+/*
+ * GooCanvas. Copyright (C) 2005-8 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasgrid.c - a grid item.
+ */
+
+/**
+ * SECTION:goocanvasgrid
+ * @Title: GooCanvasGrid
+ * @Short_Description: a grid item.
+ *
+ * GooCanvasGrid represents a grid item.
+ * A grid consists of a number of equally-spaced horizontal and vertical
+ * grid lines, plus an optional border.
+ *
+ * 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 #GooCanvasGrid use goo_canvas_grid_new().
+ *
+ * To get or set the properties of an existing #GooCanvasGrid, use
+ * g_object_get() and g_object_set().
+ *
+ * The grid's position and size is specified with the #GooCanvasGrid:x,
+ * #GooCanvasGrid:y, #GooCanvasGrid:width and #GooCanvasGrid:height properties.
+ *
+ * The #GooCanvasGrid:x-step and #GooCanvasGrid:y-step properties specify the 
+ * distance between grid lines. The  #GooCanvasGrid:x-offset and
+ * #GooCanvasGrid:y-offset properties specify the distance before the first
+ * grid lines.
+ *
+ * The horizontal or vertical grid lines can be hidden using the
+ * #GooCanvasGrid:show-horz-grid-lines and #GooCanvasGrid:show-vert-grid-lines
+ * properties.
+ *
+ * The width of the border can be set using the #GooCanvasGrid:border-width
+ * property. The border is drawn outside the area specified with the
+ * #GooCanvasGrid:x, #GooCanvasGrid:y, #GooCanvasGrid:width and
+ * #GooCanvasGrid:height properties.
+ *
+ * Other properties allow the colors and widths of the grid lines to be set.
+ * The grid line color and width properties override the standard
+ * #GooCanvasItemSimple:stroke-color and #GooCanvasItemSimple:line-width
+ * properties, enabling different styles for horizontal and vertical grid lines.
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include "goocanvas.h"
+
+
+enum {
+  PROP_0,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_X_STEP,
+  PROP_Y_STEP,
+  PROP_X_OFFSET,
+  PROP_Y_OFFSET,
+  PROP_HORZ_GRID_LINE_WIDTH,
+  PROP_VERT_GRID_LINE_WIDTH,
+  PROP_HORZ_GRID_LINE_PATTERN,
+  PROP_VERT_GRID_LINE_PATTERN,
+  PROP_BORDER_WIDTH,
+  PROP_BORDER_PATTERN,
+  PROP_SHOW_HORZ_GRID_LINES,
+  PROP_SHOW_VERT_GRID_LINES,
+  PROP_VERT_GRID_LINES_ON_TOP,
+
+  /* Convenience properties. */
+  PROP_HORZ_GRID_LINE_COLOR,
+  PROP_HORZ_GRID_LINE_COLOR_RGBA,
+  PROP_HORZ_GRID_LINE_PIXBUF,
+  PROP_VERT_GRID_LINE_COLOR,
+  PROP_VERT_GRID_LINE_COLOR_RGBA,
+  PROP_VERT_GRID_LINE_PIXBUF,
+  PROP_BORDER_COLOR,
+  PROP_BORDER_COLOR_RGBA,
+  PROP_BORDER_PIXBUF
+};
+
+
+GooCanvasItemIface *goo_canvas_grid_parent_iface;
+
+static void canvas_item_interface_init      (GooCanvasItemIface  *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasGrid, goo_canvas_grid,
+                         GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_grid_install_common_properties (GObjectClass *gobject_class)
+{
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the grid"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the grid"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the grid"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the grid"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X_STEP,
+				   g_param_spec_double ("x-step",
+							"X Step",
+							_("The distance between the vertical grid lines"),
+							0.0, G_MAXDOUBLE, 10.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y_STEP,
+				   g_param_spec_double ("y-step",
+							"Y Step",
+							_("The distance between the horizontal grid lines"),
+							0.0, G_MAXDOUBLE, 10.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X_OFFSET,
+				   g_param_spec_double ("x-offset",
+							"X Offset",
+							_("The distance before the first vertical grid line"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y_OFFSET,
+				   g_param_spec_double ("y-offset",
+							"Y Offset",
+							_("The distance before the first horizontal grid line"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_WIDTH,
+                                   g_param_spec_double ("horz-grid-line-width",
+                                                        _("Horizontal Grid Line Width"),
+                                                        _("The width of the horizontal grid lines"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+                                                        G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_WIDTH,
+                                   g_param_spec_double ("vert-grid-line-width",
+							_("Vertical Grid Line Width"),
+							_("The width of the vertical grid lines"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_PATTERN,
+                                   g_param_spec_boxed ("horz-grid-line-pattern",
+						       _("Horizontal Grid Line Pattern"),
+						       _("The cairo pattern to paint the horizontal grid lines with"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_PATTERN,
+                                   g_param_spec_boxed ("vert-grid-line-pattern",
+						       _("Vertical Grid Line Pattern"),
+						       _("The cairo pattern to paint the vertical grid lines with"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_BORDER_WIDTH,
+				   g_param_spec_double ("border-width",
+							_("Border Width"),
+							_("The width of the border around the grid"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_BORDER_PATTERN,
+                                   g_param_spec_boxed ("border-pattern",
+						       _("Border Pattern"),
+						       _("The cairo pattern to paint the border with"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_SHOW_HORZ_GRID_LINES,
+                                   g_param_spec_boolean ("show-horz-grid-lines",
+							 _("Show Horizontal Grid Lines"),
+							 _("If the horizontal grid lines are shown"),
+							 TRUE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_SHOW_VERT_GRID_LINES,
+                                   g_param_spec_boolean ("show-vert-grid-lines",
+							 _("Show Vertical Grid Lines"),
+							 _("If the vertical grid lines are shown"),
+							 TRUE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINES_ON_TOP,
+                                   g_param_spec_boolean ("vert-grid-lines-on-top",
+							 _("Vertical Grid Lines On Top"),
+							 _("If the vertical grid lines are painted above the horizontal grid lines"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+
+  /* Convenience properties - some are writable only. */
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_COLOR,
+				   g_param_spec_string ("horz-grid-line-color",
+							_("Horizontal Grid Line Color"),
+							_("The color to use for the horizontal grid lines"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_COLOR_RGBA,
+				   g_param_spec_uint ("horz-grid-line-color-rgba",
+						      _("Horizontal Grid Line Color RGBA"),
+						      _("The color to use for the horizontal grid lines, specified as a 32-bit integer value"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_PIXBUF,
+                                   g_param_spec_object ("horz-grid-line-pixbuf",
+							_("Horizontal Grid Line Pixbuf"),
+							_("The pixbuf to use to draw the horizontal grid lines"),
+                                                        GDK_TYPE_PIXBUF,
+                                                        G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_COLOR,
+				   g_param_spec_string ("vert-grid-line-color",
+							_("Vertical Grid Line Color"),
+							_("The color to use for the vertical grid lines"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_COLOR_RGBA,
+				   g_param_spec_uint ("vert-grid-line-color-rgba",
+						      _("Vertical Grid Line Color RGBA"),
+						      _("The color to use for the vertical grid lines, specified as a 32-bit integer value"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_PIXBUF,
+                                   g_param_spec_object ("vert-grid-line-pixbuf",
+							_("Vertical Grid Line Pixbuf"),
+							_("The pixbuf to use to draw the vertical grid lines"),
+                                                        GDK_TYPE_PIXBUF,
+                                                        G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_BORDER_COLOR,
+				   g_param_spec_string ("border-color",
+							_("Border Color"),
+							_("The color to use for the border"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_BORDER_COLOR_RGBA,
+				   g_param_spec_uint ("border-color-rgba",
+						      _("Border Color RGBA"),
+						      _("The color to use for the border, specified as a 32-bit integer value"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_BORDER_PIXBUF,
+                                   g_param_spec_object ("border-pixbuf",
+							_("Border Pixbuf"),
+							_("The pixbuf to use to draw the border"),
+                                                        GDK_TYPE_PIXBUF,
+                                                        G_PARAM_WRITABLE));
+}
+
+
+/* This initializes the common grid data. */
+static void
+goo_canvas_grid_init_data (GooCanvasGridData *grid_data)
+{
+  grid_data->x = 0.0;
+  grid_data->y = 0.0;
+  grid_data->width = 0.0;
+  grid_data->height = 0.0;
+  grid_data->x_step = 10.0;
+  grid_data->y_step = 10.0;
+  grid_data->x_offset = 0.0;
+  grid_data->y_offset = 0.0;
+  grid_data->horz_grid_line_width = -1.0;
+  grid_data->vert_grid_line_width = -1.0;
+  grid_data->horz_grid_line_pattern = NULL;
+  grid_data->vert_grid_line_pattern = NULL;
+  grid_data->border_width = -1.0;
+  grid_data->border_pattern = NULL;
+  grid_data->show_horz_grid_lines = TRUE;
+  grid_data->show_vert_grid_lines = TRUE;
+  grid_data->vert_grid_lines_on_top = FALSE;
+}
+
+
+/* This frees the contents of the grid data, but not the struct itself. */
+static void
+goo_canvas_grid_free_data (GooCanvasGridData *grid_data)
+{
+
+}
+
+
+static void
+goo_canvas_grid_init (GooCanvasGrid *grid)
+{
+  grid->grid_data = g_slice_new0 (GooCanvasGridData);
+  goo_canvas_grid_init_data (grid->grid_data);
+}
+
+
+/**
+ * goo_canvas_grid_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.
+ * @x: the x coordinate of the left of the grid.
+ * @y: the y coordinate of the top of the grid.
+ * @width: the width of the grid.
+ * @height: the height of the grid.
+ * @x_step: the distance between the vertical grid lines.
+ * @y_step: the distance between the horizontal grid lines.
+ * @x_offset: the distance before the first vertical grid line.
+ * @y_offset: the distance before the first horizontal grid line.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new grid item.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a grid:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *grid = goo_canvas_grid_new (mygroup, 100.0, 100.0, 400.0, 200.0,
+ *                                             20.0, 20.0, 10.0, 10.0,
+ *                                             "horz-grid-line-width", 4.0,
+ *                                             "horz-grid-line-color", "yellow",
+ *                                             "vert-grid-line-width", 2.0,
+ *                                             "vert-grid-line-color", "red",
+ *                                             "border-width", 3.0,
+ *                                             "border-color", "white",
+ *                                             "fill-color", "blue",
+ *                                             NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new grid item.
+ **/
+GooCanvasItem*
+goo_canvas_grid_new (GooCanvasItem      *parent,
+		     gdouble             x,
+		     gdouble             y,
+		     gdouble             width,
+		     gdouble             height,
+		     gdouble             x_step,
+		     gdouble             y_step,
+		     gdouble             x_offset,
+		     gdouble             y_offset,
+		     ...)
+{
+  GooCanvasItem *item;
+  GooCanvasGrid *grid;
+  GooCanvasGridData *grid_data;
+  va_list var_args;
+  const char *first_property;
+
+  item = g_object_new (GOO_TYPE_CANVAS_GRID, NULL);
+  grid = (GooCanvasGrid*) item;
+
+  grid_data = grid->grid_data;
+  grid_data->x = x;
+  grid_data->y = y;
+  grid_data->width = width;
+  grid_data->height = height;
+  grid_data->x_step = x_step;
+  grid_data->y_step = y_step;
+  grid_data->x_offset = x_offset;
+  grid_data->y_offset = y_offset;
+
+  va_start (var_args, y_offset);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist (G_OBJECT (item), first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_grid_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasGrid *grid = (GooCanvasGrid*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    {
+      goo_canvas_grid_free_data (grid->grid_data);
+      g_slice_free (GooCanvasGridData, grid->grid_data);
+    }
+  grid->grid_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_grid_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_grid_get_common_property (GObject              *object,
+				     GooCanvasGridData    *grid_data,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      g_value_set_double (value, grid_data->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, grid_data->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, grid_data->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, grid_data->height);
+      break;
+    case PROP_X_STEP:
+      g_value_set_double (value, grid_data->x_step);
+      break;
+    case PROP_Y_STEP:
+      g_value_set_double (value, grid_data->y_step);
+      break;
+    case PROP_X_OFFSET:
+      g_value_set_double (value, grid_data->x_offset);
+      break;
+    case PROP_Y_OFFSET:
+      g_value_set_double (value, grid_data->y_offset);
+      break;
+    case PROP_HORZ_GRID_LINE_WIDTH:
+      g_value_set_double (value, grid_data->horz_grid_line_width);
+      break;
+    case PROP_VERT_GRID_LINE_WIDTH:
+      g_value_set_double (value, grid_data->vert_grid_line_width);
+      break;
+    case PROP_HORZ_GRID_LINE_PATTERN:
+      g_value_set_boxed (value, grid_data->horz_grid_line_pattern);
+      break;
+    case PROP_VERT_GRID_LINE_PATTERN:
+      g_value_set_boxed (value, grid_data->vert_grid_line_pattern);
+      break;
+    case PROP_BORDER_WIDTH:
+      g_value_set_double (value, grid_data->border_width);
+      break;
+    case PROP_BORDER_PATTERN:
+      g_value_set_boxed (value, grid_data->border_pattern);
+      break;
+    case PROP_SHOW_HORZ_GRID_LINES:
+      g_value_set_boolean (value, grid_data->show_horz_grid_lines);
+      break;
+    case PROP_SHOW_VERT_GRID_LINES:
+      g_value_set_boolean (value, grid_data->show_vert_grid_lines);
+      break;
+    case PROP_VERT_GRID_LINES_ON_TOP:
+      g_value_set_boolean (value, grid_data->vert_grid_lines_on_top);
+      break;
+
+  /* Convenience properties. */
+    case PROP_HORZ_GRID_LINE_COLOR_RGBA:
+      goo_canvas_get_rgba_value_from_pattern (grid_data->horz_grid_line_pattern, value);
+      break;
+    case PROP_VERT_GRID_LINE_COLOR_RGBA:
+      goo_canvas_get_rgba_value_from_pattern (grid_data->vert_grid_line_pattern, value);
+      break;
+    case PROP_BORDER_COLOR_RGBA:
+      goo_canvas_get_rgba_value_from_pattern (grid_data->border_pattern, value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_grid_get_property (GObject              *object,
+			      guint                 prop_id,
+			      GValue               *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasGrid *grid = (GooCanvasGrid*) object;
+
+  goo_canvas_grid_get_common_property (object, grid->grid_data,
+					  prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_grid_set_common_property (GObject              *object,
+				     GooCanvasGridData    *grid_data,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      grid_data->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      grid_data->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      grid_data->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      grid_data->height = g_value_get_double (value);
+      break;
+    case PROP_X_STEP:
+      grid_data->x_step = g_value_get_double (value);
+      break;
+    case PROP_Y_STEP:
+      grid_data->y_step = g_value_get_double (value);
+      break;
+    case PROP_X_OFFSET:
+      grid_data->x_offset = g_value_get_double (value);
+      break;
+    case PROP_Y_OFFSET:
+      grid_data->y_offset = g_value_get_double (value);
+      break;
+    case PROP_HORZ_GRID_LINE_WIDTH:
+      grid_data->horz_grid_line_width = g_value_get_double (value);
+      break;
+    case PROP_VERT_GRID_LINE_WIDTH:
+      grid_data->vert_grid_line_width = g_value_get_double (value);
+      break;
+    case PROP_HORZ_GRID_LINE_PATTERN:
+      cairo_pattern_destroy (grid_data->horz_grid_line_pattern);
+      grid_data->horz_grid_line_pattern = g_value_get_boxed (value);
+      cairo_pattern_reference (grid_data->horz_grid_line_pattern);
+      break;
+    case PROP_VERT_GRID_LINE_PATTERN:
+      cairo_pattern_destroy (grid_data->vert_grid_line_pattern);
+      grid_data->vert_grid_line_pattern = g_value_get_boxed (value);
+      cairo_pattern_reference (grid_data->vert_grid_line_pattern);
+      break;
+    case PROP_BORDER_WIDTH:
+      grid_data->border_width = g_value_get_double (value);
+      break;
+    case PROP_BORDER_PATTERN:
+      cairo_pattern_destroy (grid_data->border_pattern);
+      grid_data->border_pattern = g_value_get_boxed (value);
+      cairo_pattern_reference (grid_data->border_pattern);
+      break;
+    case PROP_SHOW_HORZ_GRID_LINES:
+      grid_data->show_horz_grid_lines = g_value_get_boolean (value);
+      break;
+    case PROP_SHOW_VERT_GRID_LINES:
+      grid_data->show_vert_grid_lines = g_value_get_boolean (value);
+      break;
+    case PROP_VERT_GRID_LINES_ON_TOP:
+      grid_data->vert_grid_lines_on_top = g_value_get_boolean (value);
+      break;
+
+  /* Convenience properties. */
+    case PROP_HORZ_GRID_LINE_COLOR:
+      cairo_pattern_destroy (grid_data->horz_grid_line_pattern);
+      grid_data->horz_grid_line_pattern = goo_canvas_create_pattern_from_color_value (value);
+      break;
+    case PROP_HORZ_GRID_LINE_COLOR_RGBA:
+      cairo_pattern_destroy (grid_data->horz_grid_line_pattern);
+      grid_data->horz_grid_line_pattern = goo_canvas_create_pattern_from_rgba_value (value);
+      break;
+    case PROP_HORZ_GRID_LINE_PIXBUF:
+      cairo_pattern_destroy (grid_data->horz_grid_line_pattern);
+      grid_data->horz_grid_line_pattern = goo_canvas_create_pattern_from_pixbuf_value (value);
+      break;
+
+    case PROP_VERT_GRID_LINE_COLOR:
+      cairo_pattern_destroy (grid_data->vert_grid_line_pattern);
+      grid_data->vert_grid_line_pattern = goo_canvas_create_pattern_from_color_value (value);
+      break;
+    case PROP_VERT_GRID_LINE_COLOR_RGBA:
+      cairo_pattern_destroy (grid_data->vert_grid_line_pattern);
+      grid_data->vert_grid_line_pattern = goo_canvas_create_pattern_from_rgba_value (value);
+      break;
+    case PROP_VERT_GRID_LINE_PIXBUF:
+      cairo_pattern_destroy (grid_data->vert_grid_line_pattern);
+      grid_data->vert_grid_line_pattern = goo_canvas_create_pattern_from_pixbuf_value (value);
+      break;
+
+    case PROP_BORDER_COLOR:
+      cairo_pattern_destroy (grid_data->border_pattern);
+      grid_data->border_pattern = goo_canvas_create_pattern_from_color_value (value);
+      break;
+    case PROP_BORDER_COLOR_RGBA:
+      cairo_pattern_destroy (grid_data->border_pattern);
+      grid_data->border_pattern = goo_canvas_create_pattern_from_rgba_value (value);
+      break;
+    case PROP_BORDER_PIXBUF:
+      cairo_pattern_destroy (grid_data->border_pattern);
+      grid_data->border_pattern = goo_canvas_create_pattern_from_pixbuf_value (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_grid_set_property (GObject              *object,
+			      guint                 prop_id,
+			      const GValue         *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasGrid *grid = (GooCanvasGrid*) object;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_grid_set_common_property (object, grid->grid_data,
+					  prop_id, value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_grid_update  (GooCanvasItemSimple *simple,
+			 cairo_t             *cr)
+{
+  GooCanvasGrid *grid = (GooCanvasGrid*) simple;
+  GooCanvasGridData *grid_data = grid->grid_data;
+  gdouble border_width = 0.0;
+
+  /* We can quickly compute the bounds as being just the grid's size
+     plus the border width around each edge. */
+  if (grid_data->border_width > 0.0)
+    border_width = grid_data->border_width;
+
+  simple->bounds.x1 = grid_data->x - border_width;
+  simple->bounds.y1 = grid_data->y - border_width;
+  simple->bounds.x2 = grid_data->x + grid_data->width + border_width;
+  simple->bounds.y2 = grid_data->y + grid_data->height + border_width;
+}
+
+
+static gdouble
+calculate_start_position (gdouble start_pos,
+			  gdouble step,
+			  gdouble redraw_start_pos,
+			  gdouble line_width)
+{
+  gdouble n = 0.0, result;
+
+  /* We want the first position where pos + line_width/2 >= redraw_start_pos.
+     i.e. start_pos + (n * step) + (line_width / 2) >= redraw_start_pos,
+     or   (n * step) >= redraw_start_pos - start_pos - (line_width / 2),
+     or   n >= (redraw_start_pos - start_pos - (line_width / 2) / step). */
+  if (step > 0.0)
+    n = ceil (((redraw_start_pos - start_pos - (line_width / 2.0))) / step);
+
+  if (n <= 0.0)
+    result = start_pos;
+  else
+    result = start_pos + (n * step);
+
+  return result;
+}
+
+
+static void
+paint_vertical_lines (GooCanvasItemSimple   *simple,
+		      cairo_t               *cr,
+		      const GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGrid *grid = (GooCanvasGrid*) simple;
+  GooCanvasGridData *grid_data = grid->grid_data;
+  double x, max_x, max_y, max_bounds_x, line_width;
+  gboolean has_stroke;
+
+  if (!grid_data->show_vert_grid_lines)
+    return;
+
+  max_x = grid_data->x + grid_data->width;
+  max_y = grid_data->y + grid_data->height;
+
+  has_stroke = goo_canvas_style_set_stroke_options (simple_data->style, cr);
+  line_width = goo_canvas_item_simple_get_line_width (simple);
+
+  /* If the grid's vertical grid line pattern/color has been set, use that.
+     If not, and we don't have a stroke color just return. */
+  if (grid_data->vert_grid_line_pattern)
+    cairo_set_source (cr, grid_data->vert_grid_line_pattern);
+  else if (!has_stroke)
+    return;
+
+  /* If the grid's vertical grid line width has been set, use that. */
+  if (grid_data->vert_grid_line_width > 0.0)
+    {
+      line_width = grid_data->vert_grid_line_width;
+      cairo_set_line_width (cr, line_width);
+    }
+
+  cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+
+  /* Calculate the first grid line that intersects the bounds to redraw. */
+  x = calculate_start_position (grid_data->x + grid_data->x_offset,
+				grid_data->x_step, bounds->x1, line_width);
+
+  /* Calculate the last possible line position. */
+  max_bounds_x = bounds->x2 + (line_width / 2.0);
+  max_x = MIN (max_x, max_bounds_x);
+
+  /* Add on a tiny fraction of step to avoid any double comparison issues. */
+  max_x += grid_data->x_step * 0.00001;
+
+  while (x <= max_x)
+    {
+      cairo_move_to (cr, x, grid_data->y);
+      cairo_line_to (cr, x, max_y);
+      cairo_stroke (cr);
+
+      /* Avoid an infinite loop. */
+      if (grid_data->x_step <= 0.0)
+        break;
+
+      x += grid_data->x_step;
+    }
+}
+
+
+static void
+paint_horizontal_lines (GooCanvasItemSimple   *simple,
+			cairo_t               *cr,
+			const GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGrid *grid = (GooCanvasGrid*) simple;
+  GooCanvasGridData *grid_data = grid->grid_data;
+  double y, max_x, max_y, max_bounds_y, line_width;
+  gboolean has_stroke;
+
+  if (!grid_data->show_horz_grid_lines)
+    return;
+
+  max_x = grid_data->x + grid_data->width;
+  max_y = grid_data->y + grid_data->height;
+
+  has_stroke = goo_canvas_style_set_stroke_options (simple_data->style, cr);
+  line_width = goo_canvas_item_simple_get_line_width (simple);
+
+  /* If the grid's horizontal grid line pattern/color has been set, use that.
+     If not, and we don't have a stroke color just return. */
+  if (grid_data->horz_grid_line_pattern)
+    cairo_set_source (cr, grid_data->horz_grid_line_pattern);
+  else if (!has_stroke)
+    return;
+
+  /* If the grid's horizontal grid line width has been set, use that. */
+  if (grid_data->horz_grid_line_width > 0.0)
+    {
+      line_width = grid_data->horz_grid_line_width;
+      cairo_set_line_width (cr, grid_data->horz_grid_line_width);
+    }
+
+  cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+
+  /* Calculate the first grid line that intersects the bounds to redraw. */
+  y = calculate_start_position (grid_data->y + grid_data->y_offset,
+				grid_data->y_step, bounds->y1, line_width);
+
+  /* Calculate the last possible line position. */
+  max_bounds_y = bounds->y2 + (line_width / 2.0);
+  max_y = MIN (max_y, max_bounds_y);
+
+  /* Add on a tiny fraction of step to avoid any double comparison issues. */
+  max_y += grid_data->y_step * 0.00001;
+
+  while (y <= max_y)
+    {
+      cairo_move_to (cr, grid_data->x, y);
+      cairo_line_to (cr, max_x, y);
+      cairo_stroke (cr);
+
+      /* Avoid an infinite loop. */
+      if (grid_data->y_step <= 0.0)
+        break;
+
+      y += grid_data->y_step;
+    }
+}
+
+
+static void
+goo_canvas_grid_paint (GooCanvasItemSimple   *simple,
+		       cairo_t               *cr,
+		       const GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGrid *grid = (GooCanvasGrid*) simple;
+  GooCanvasGridData *grid_data = grid->grid_data;
+  GooCanvasBounds redraw_bounds = *bounds;
+  gdouble half_border_width;
+
+  /* Paint the background in the fill pattern/color, if one is set. */
+  if (goo_canvas_style_set_fill_options (simple_data->style, cr))
+    {
+      cairo_rectangle (cr, grid_data->x, grid_data->y,
+		       grid_data->width, grid_data->height);
+      cairo_fill (cr);
+    }
+
+  /* Clip to the grid's area while painting the grid lines. */
+  cairo_save (cr);
+  cairo_rectangle (cr, grid_data->x, grid_data->y,
+		   grid_data->width, grid_data->height);
+  cairo_clip (cr);
+
+  /* Convert the bounds to be redrawn from device space to item space. */
+  goo_canvas_convert_bounds_to_item_space (simple->canvas,
+					   (GooCanvasItem*) simple,
+					   &redraw_bounds);
+
+  /* Paint the grid lines, in the required order. */
+  if (grid_data->vert_grid_lines_on_top)
+    {
+      paint_horizontal_lines (simple, cr, &redraw_bounds);
+      paint_vertical_lines (simple, cr, &redraw_bounds);
+    }
+  else
+    {
+      paint_vertical_lines (simple, cr, &redraw_bounds);
+      paint_horizontal_lines (simple, cr, &redraw_bounds);
+    }
+
+  cairo_restore (cr);
+
+  /* Paint the border. */
+  if (grid_data->border_width > 0)
+    {
+      if (grid_data->border_pattern)
+	cairo_set_source (cr, grid_data->border_pattern);
+      else
+	goo_canvas_style_set_stroke_options (simple_data->style, cr);
+
+      cairo_set_line_width (cr, grid_data->border_width);
+      half_border_width = grid_data->border_width / 2.0;
+      cairo_rectangle (cr, grid_data->x - half_border_width,
+		       grid_data->y - half_border_width,
+		       grid_data->width + grid_data->border_width,
+		       grid_data->height + grid_data->border_width);
+      cairo_stroke (cr);
+    }
+}
+
+
+static void
+goo_canvas_grid_set_model    (GooCanvasItem      *item,
+			      GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGrid *grid = (GooCanvasGrid*) item;
+  GooCanvasGridModel *gmodel = (GooCanvasGridModel*) model;
+
+  /* If our grid_data was allocated, free it. */
+  if (!simple->model)
+    {
+      goo_canvas_grid_free_data (grid->grid_data);
+      g_slice_free (GooCanvasGridData, grid->grid_data);
+    }
+
+  /* Now use the new model's grid_data instead. */
+  grid->grid_data = &gmodel->grid_data;
+
+  /* Let the parent class do the rest. */
+  goo_canvas_grid_parent_iface->set_model (item, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model      = goo_canvas_grid_set_model;
+}
+
+
+static void
+goo_canvas_grid_class_init (GooCanvasGridClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  goo_canvas_grid_parent_iface = g_type_interface_peek (goo_canvas_grid_parent_class, GOO_TYPE_CANVAS_ITEM);
+
+  gobject_class->finalize    = goo_canvas_grid_finalize;
+
+  gobject_class->get_property = goo_canvas_grid_get_property;
+  gobject_class->set_property = goo_canvas_grid_set_property;
+
+  simple_class->simple_update      = goo_canvas_grid_update;
+  simple_class->simple_paint       = goo_canvas_grid_paint;
+
+  goo_canvas_grid_install_common_properties (gobject_class);
+}
+
+
+
+/**
+ * SECTION:goocanvasgridmodel
+ * @Title: GooCanvasGridModel
+ * @Short_Description: a model for grid items.
+ *
+ * GooCanvasGridModel represents a model for grid items.
+ * A grid consists of a number of equally-spaced horizontal and vertical
+ * grid lines, plus an optional border.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "stroke-color", "fill-color" and "line-width".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasGridModel use goo_canvas_grid_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasGridModel, use
+ * g_object_get() and g_object_set().
+ *
+ * To respond to events such as mouse clicks on the grid you must connect
+ * to the signal handlers of the corresponding #GooCanvasGrid objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ *
+ * The grid's position and size is specified with the #GooCanvasGridModel:x,
+ * #GooCanvasGridModel:y, #GooCanvasGridModel:width and
+ * #GooCanvasGridModel:height properties.
+ *
+ * The #GooCanvasGridModel:x-step and #GooCanvasGridModel:y-step properties
+ * specify the distance between grid lines. The  #GooCanvasGridModel:x-offset
+ * and #GooCanvasGridModel:y-offset properties specify the distance before the
+ * first grid lines.
+ *
+ * The horizontal or vertical grid lines can be hidden using the
+ * #GooCanvasGridModel:show-horz-grid-lines and
+ * #GooCanvasGridModel:show-vert-grid-lines properties.
+ *
+ * The width of the border can be set using the #GooCanvasGridModel:border-width
+ * property. The border is drawn outside the area specified with the
+ * #GooCanvasGridModel:x, #GooCanvasGridModel:y, #GooCanvasGridModel:width and
+ * #GooCanvasGridModel:height properties.
+ *
+ * Other properties allow the colors and widths of the grid lines to be set.
+ * The grid line color and width properties override the standard
+ * #GooCanvasItemModelSimple:stroke-color and
+ * #GooCanvasItemModelSimple:line-width properties, enabling different styles
+ * for horizontal and vertical grid lines.
+ */
+
+GooCanvasItemModelIface *goo_canvas_grid_model_parent_iface;
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasGridModel, goo_canvas_grid_model,
+                         GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_grid_model_init (GooCanvasGridModel *gmodel)
+{
+  goo_canvas_grid_init_data (&gmodel->grid_data);
+}
+
+
+/**
+ * goo_canvas_grid_model_new:
+ * @parent: the parent model, 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.
+ * @x: the x coordinate of the left of the grid.
+ * @y: the y coordinate of the top of the grid.
+ * @width: the width of the grid.
+ * @height: the height of the grid.
+ * @x_step: the distance between the vertical grid lines.
+ * @y_step: the distance between the horizontal grid lines.
+ * @x_offset: the distance before the first vertical grid line.
+ * @y_offset: the distance before the first horizontal grid line.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new grid model.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a grid:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *grid = goo_canvas_grid_model_new (mygroup, 100.0, 100.0, 400.0, 200.0,
+ *                                                        20.0, 20.0, 10.0, 10.0,
+ *                                                        "horz-grid-line-width", 4.0,
+ *                                                        "horz-grid-line-color", "yellow",
+ *                                                        "vert-grid-line-width", 2.0,
+ *                                                        "vert-grid-line-color", "red",
+ *                                                        "border-width", 3.0,
+ *                                                        "border-color", "white",
+ *                                                        "fill-color", "blue",
+ *                                                        NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new grid model.
+ **/
+GooCanvasItemModel*
+goo_canvas_grid_model_new (GooCanvasItemModel *parent,
+			   gdouble             x,
+			   gdouble             y,
+			   gdouble             width,
+			   gdouble             height,
+			   gdouble             x_step,
+			   gdouble             y_step,
+			   gdouble             x_offset,
+			   gdouble             y_offset,
+			   ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasGridModel *gmodel;
+  GooCanvasGridData *grid_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_GRID_MODEL, NULL);
+  gmodel = (GooCanvasGridModel*) model;
+
+  grid_data = &gmodel->grid_data;
+  grid_data->x = x;
+  grid_data->y = y;
+  grid_data->width = width;
+  grid_data->height = height;
+  grid_data->x_step = x_step;
+  grid_data->y_step = y_step;
+  grid_data->x_offset = x_offset;
+  grid_data->y_offset = y_offset;
+
+  va_start (var_args, y_offset);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_grid_model_finalize (GObject *object)
+{
+  GooCanvasGridModel *gmodel = (GooCanvasGridModel*) object;
+
+  goo_canvas_grid_free_data (&gmodel->grid_data);
+
+  G_OBJECT_CLASS (goo_canvas_grid_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_grid_model_get_property (GObject              *object,
+				    guint                 prop_id,
+				    GValue               *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasGridModel *gmodel = (GooCanvasGridModel*) object;
+
+  goo_canvas_grid_get_common_property (object, &gmodel->grid_data,
+					  prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_grid_model_set_property (GObject              *object,
+				    guint                 prop_id,
+				    const GValue         *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasGridModel *gmodel = (GooCanvasGridModel*) object;
+
+  goo_canvas_grid_set_common_property (object, &gmodel->grid_data,
+					  prop_id, value, pspec);
+  g_signal_emit_by_name (gmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_grid_model_create_item (GooCanvasItemModel *model,
+				   GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_GRID, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_grid_model_create_item;
+}
+
+
+static void
+goo_canvas_grid_model_class_init (GooCanvasGridModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  goo_canvas_grid_model_parent_iface = g_type_interface_peek (goo_canvas_grid_model_parent_class, GOO_TYPE_CANVAS_ITEM_MODEL);
+
+  gobject_class->finalize     = goo_canvas_grid_model_finalize;
+
+  gobject_class->get_property = goo_canvas_grid_model_get_property;
+  gobject_class->set_property = goo_canvas_grid_model_set_property;
+
+  goo_canvas_grid_install_common_properties (gobject_class);
+}
+
+
diff --git a/libgoocanvas/goocanvasgrid.h b/libgoocanvas/goocanvasgrid.h
new file mode 100644
index 0000000..fa70e2e
--- /dev/null
+++ b/libgoocanvas/goocanvasgrid.h
@@ -0,0 +1,153 @@
+/*
+ * GooCanvas. Copyright (C) 2005-8 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasgrid.h - a grid item.
+ */
+#ifndef __GOO_CANVAS_GRID_H__
+#define __GOO_CANVAS_GRID_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasGridData   GooCanvasGridData;
+struct _GooCanvasGridData
+{
+  /* The area that the grid covers. */
+  gdouble x, y, width, height;
+
+  /* The distance between grid lines. */
+  gdouble x_step, y_step;
+
+  /* The offset before the first grid line. */
+  gdouble x_offset, y_offset;
+
+  /* The widths of the grid lines, or -ve to use item's stroke width. */
+  gdouble horz_grid_line_width, vert_grid_line_width;
+
+  /* The color/pattern for the grid lines, or NULL to use the stroke color. */
+  cairo_pattern_t *horz_grid_line_pattern, *vert_grid_line_pattern;
+
+  /* The width of the border around the grid, or -1 for no border. */
+  gdouble border_width;
+
+  /* The color/pattern for the border, or NULL to use the stroke color. */
+  cairo_pattern_t *border_pattern;
+
+  /* If the horizontal and vertical grid lines should be shown. */
+  guint show_horz_grid_lines : 1;
+  guint show_vert_grid_lines : 1;
+
+  /* If vertical grid lines are drawn on top. */
+  guint vert_grid_lines_on_top : 1;
+};
+
+
+#define GOO_TYPE_CANVAS_GRID            (goo_canvas_grid_get_type ())
+#define GOO_CANVAS_GRID(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_GRID, GooCanvasGrid))
+#define GOO_CANVAS_GRID_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_GRID, GooCanvasGridClass))
+#define GOO_IS_CANVAS_GRID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_GRID))
+#define GOO_IS_CANVAS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_GRID))
+#define GOO_CANVAS_GRID_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_GRID, GooCanvasGridClass))
+
+
+typedef struct _GooCanvasGrid       GooCanvasGrid;
+typedef struct _GooCanvasGridClass  GooCanvasGridClass;
+
+/**
+ * GooCanvasGrid
+ *
+ * The #GooCanvasGrid-struct struct contains private data only.
+ */
+struct _GooCanvasGrid
+{
+  GooCanvasItemSimple parent_object;
+
+  GooCanvasGridData *grid_data;
+};
+
+struct _GooCanvasGridClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType          goo_canvas_grid_get_type      (void) G_GNUC_CONST;
+GooCanvasItem* goo_canvas_grid_new           (GooCanvasItem      *parent,
+					      gdouble             x,
+					      gdouble             y,
+					      gdouble             width,
+					      gdouble             height,
+					      gdouble             x_step,
+					      gdouble             y_step,
+					      gdouble             x_offset,
+					      gdouble             y_offset,
+					      ...);
+
+
+
+#define GOO_TYPE_CANVAS_GRID_MODEL            (goo_canvas_grid_model_get_type ())
+#define GOO_CANVAS_GRID_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_GRID_MODEL, GooCanvasGridModel))
+#define GOO_CANVAS_GRID_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_GRID_MODEL, GooCanvasGridModelClass))
+#define GOO_IS_CANVAS_GRID_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_GRID_MODEL))
+#define GOO_IS_CANVAS_GRID_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_GRID_MODEL))
+#define GOO_CANVAS_GRID_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_GRID_MODEL, GooCanvasGridModelClass))
+
+
+typedef struct _GooCanvasGridModel       GooCanvasGridModel;
+typedef struct _GooCanvasGridModelClass  GooCanvasGridModelClass;
+
+/**
+ * GooCanvasGridModel
+ *
+ * The #GooCanvasGridModel-struct struct contains private data only.
+ */
+struct _GooCanvasGridModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasGridData grid_data;
+};
+
+struct _GooCanvasGridModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_grid_model_get_type (void) G_GNUC_CONST;
+GooCanvasItemModel* goo_canvas_grid_model_new      (GooCanvasItemModel *parent,
+						    gdouble             x,
+						    gdouble             y,
+						    gdouble             width,
+						    gdouble             height,
+						    gdouble             x_step,
+						    gdouble             y_step,
+						    gdouble             x_offset,
+						    gdouble             y_offset,
+						    ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_GRID_H__ */
diff --git a/libgoocanvas/goocanvasgroup.c b/libgoocanvas/goocanvasgroup.c
new file mode 100644
index 0000000..a41423a
--- /dev/null
+++ b/libgoocanvas/goocanvasgroup.c
@@ -0,0 +1,1082 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasgroup.c - group item.
+ */
+
+/**
+ * SECTION:goocanvasgroup
+ * @Title: GooCanvasGroup
+ * @Short_Description: a group of items.
+ *
+ * #GooCanvasGroup represents a group of items. Groups can be nested to
+ * any depth, to create a hierarchy of items. Items are ordered within each
+ * group, with later items being displayed above earlier items.
+ *
+ * #GooCanvasGroup is a subclass of #GooCanvasItemSimple and so
+ * inherits all of the style properties such as "stroke-color", "fill-color"
+ * and "line-width". Setting a style property on a #GooCanvasGroup will affect
+ * all children of the #GooCanvasGroup (unless the children override the
+ * property setting).
+ *
+ * #GooCanvasGroup implements the #GooCanvasItem interface, so you can use
+ * the #GooCanvasItem functions such as goo_canvas_item_raise() and
+ * goo_canvas_item_rotate(), and the properties such as "visibility" and
+ * "pointer-events".
+ *
+ * If the #GooCanvasGroup:width and #GooCanvasGroup:height properties are
+ * set to positive values then the group is clipped to the given size.
+ *
+ * To create a #GooCanvasGroup use goo_canvas_group_new().
+ *
+ * To get or set the properties of an existing #GooCanvasGroup, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include "goocanvasgroup.h"
+#include "goocanvasitemmodel.h"
+#include "goocanvas.h"
+#include "goocanvasmarshal.h"
+#include "goocanvasatk.h"
+
+typedef struct _GooCanvasGroupPrivate GooCanvasGroupPrivate;
+struct _GooCanvasGroupPrivate {
+  gdouble x;
+  gdouble y;
+  gdouble width;
+  gdouble height;
+};
+
+#define GOO_CANVAS_GROUP_GET_PRIVATE(group)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((group), GOO_TYPE_CANVAS_GROUP, GooCanvasGroupPrivate))
+#define GOO_CANVAS_GROUP_MODEL_GET_PRIVATE(group)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((group), GOO_TYPE_CANVAS_GROUP_MODEL, GooCanvasGroupPrivate))
+
+enum {
+  PROP_0,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT
+};
+
+static void goo_canvas_group_dispose	  (GObject            *object);
+static void goo_canvas_group_finalize     (GObject            *object);
+static void goo_canvas_group_get_property (GObject            *object,
+                                           guint               prop_id,
+                                           GValue             *value,
+                                           GParamSpec         *pspec);
+static void goo_canvas_group_set_property (GObject            *object,
+                                           guint               prop_id,
+                                           const GValue       *value,
+                                           GParamSpec         *pspec);
+static void canvas_item_interface_init    (GooCanvasItemIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasGroup, goo_canvas_group,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+static void
+goo_canvas_group_install_common_properties (GObjectClass *gobject_class)
+{
+  g_object_class_install_property (gobject_class, PROP_X,
+                                  g_param_spec_double ("x",
+                                                       "X",
+                                                       _("The x coordinate of the group"),
+                                                       -G_MAXDOUBLE,
+                                                       G_MAXDOUBLE, 0.0,
+                                                       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+                                  g_param_spec_double ("y",
+                                                       "Y",
+                                                       _("The y coordinate of the group"),
+                                                       -G_MAXDOUBLE,
+                                                       G_MAXDOUBLE, 0.0,
+                                                       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+                                  g_param_spec_double ("width",
+                                                       _("Width"),
+                                                       _("The width of the group, or -1 to use the default width"),
+                                                       -G_MAXDOUBLE,
+                                                       G_MAXDOUBLE, -1.0,
+                                                       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+                                  g_param_spec_double ("height",
+                                                       _("Height"),
+                                                       _("The height of the group, or -1 to use the default height"),
+                                                       -G_MAXDOUBLE,
+                                                       G_MAXDOUBLE, -1.0,
+                                                       G_PARAM_READWRITE));
+}
+
+static void
+goo_canvas_group_class_init (GooCanvasGroupClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasGroupPrivate));
+
+  gobject_class->dispose  = goo_canvas_group_dispose;
+  gobject_class->finalize = goo_canvas_group_finalize;
+  gobject_class->get_property = goo_canvas_group_get_property;
+  gobject_class->set_property = goo_canvas_group_set_property;
+
+  /* Register our accessible factory, but only if accessibility is enabled. */
+  if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
+    {
+      atk_registry_set_factory_type (atk_get_default_registry (),
+				     GOO_TYPE_CANVAS_GROUP,
+				     goo_canvas_item_accessible_factory_get_type ());
+    }
+
+  goo_canvas_group_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_group_init (GooCanvasGroup *group)
+{
+  GooCanvasGroupPrivate* priv = GOO_CANVAS_GROUP_GET_PRIVATE (group);
+
+  group->items = g_ptr_array_sized_new (8);
+
+  priv->x = 0.0;
+  priv->y = 0.0;
+  priv->width = -1.0;
+  priv->height = -1.0;
+}
+
+
+/**
+ * goo_canvas_group_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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new group item.
+ * 
+ * Return value: a new group item.
+ **/
+GooCanvasItem*
+goo_canvas_group_new (GooCanvasItem *parent,
+		      ...)
+{
+  GooCanvasItem *item;
+  GooCanvasGroup *group;
+  va_list var_args;
+  const char *first_property;
+
+  item = g_object_new (GOO_TYPE_CANVAS_GROUP, NULL);
+  group = (GooCanvasGroup*) item;
+
+  va_start (var_args, parent);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist (G_OBJECT (item), first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_group_dispose (GObject *object)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) object;
+  gint i;
+
+  /* Unref all the items in the group. */
+  for (i = 0; i < group->items->len; i++)
+    {
+      GooCanvasItem *item = group->items->pdata[i];
+      goo_canvas_item_set_parent (item, NULL);
+      g_object_unref (item);
+    }
+
+  g_ptr_array_set_size (group->items, 0);
+
+  G_OBJECT_CLASS (goo_canvas_group_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_group_finalize (GObject *object)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) object;
+
+  g_ptr_array_free (group->items, TRUE);
+
+  G_OBJECT_CLASS (goo_canvas_group_parent_class)->finalize (object);
+}
+
+
+/* Gets the private data to use, from the model or from the item itself. */
+static GooCanvasGroupPrivate*
+goo_canvas_group_get_private (GooCanvasGroup *group)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) group;
+
+  if (simple->model)
+    return GOO_CANVAS_GROUP_MODEL_GET_PRIVATE (simple->model);
+  else
+    return GOO_CANVAS_GROUP_GET_PRIVATE (group);
+}
+
+
+static void
+goo_canvas_group_get_common_property (GObject               *object,
+                                      GooCanvasGroupPrivate *priv,
+                                      guint                  prop_id,
+                                      GValue                *value,
+                                      GParamSpec            *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      g_value_set_double (value, priv->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, priv->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, priv->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, priv->height);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+goo_canvas_group_get_property (GObject               *object,
+                               guint                  prop_id,
+                               GValue                *value,
+                               GParamSpec            *pspec)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) object;
+  GooCanvasGroupPrivate *priv = goo_canvas_group_get_private (group);
+
+  goo_canvas_group_get_common_property (object, priv, prop_id, value, pspec);
+}
+
+static void
+goo_canvas_group_set_common_property (GObject               *object,
+                                      GooCanvasGroupPrivate *priv,
+                                      guint                  prop_id,
+                                      const GValue          *value,
+                                      GParamSpec            *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      priv->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      priv->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      priv->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      priv->height = g_value_get_double (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+goo_canvas_group_set_property (GObject                  *object,
+                               guint                     prop_id,
+                               const GValue             *value,
+                               GParamSpec               *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasGroup *group = (GooCanvasGroup*) object;
+  GooCanvasGroupPrivate *priv = goo_canvas_group_get_private (group);
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_group_set_common_property (object, priv, prop_id, value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+static void
+goo_canvas_group_add_child     (GooCanvasItem  *item,
+				GooCanvasItem  *child,
+				gint            position)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  AtkObject *atk_obj, *child_atk_obj;
+
+  g_object_ref (child);
+
+  if (position >= 0)
+    {
+      goo_canvas_util_ptr_array_insert (group->items, child, position);
+    }
+  else
+    {
+      position = group->items->len;
+      g_ptr_array_add (group->items, child);
+    }
+
+  goo_canvas_item_set_parent (child, item);
+  goo_canvas_item_set_is_static (child, simple->simple_data->is_static);
+
+  /* Emit the "children_changed" ATK signal, if ATK is enabled. */
+  atk_obj = atk_gobject_accessible_for_object (G_OBJECT (item));
+  if (!ATK_IS_NO_OP_OBJECT (atk_obj))
+    {
+      child_atk_obj = atk_gobject_accessible_for_object (G_OBJECT (child));
+      g_signal_emit_by_name (atk_obj, "children_changed::add",
+			     position, child_atk_obj);
+    }
+
+  goo_canvas_item_request_update (item);
+}
+
+
+static void
+goo_canvas_group_move_child    (GooCanvasItem  *item,
+				gint	        old_position,
+				gint            new_position)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasItem *child;
+  GooCanvasBounds bounds;
+
+  /* Request a redraw of the item's bounds. */
+  child = group->items->pdata[old_position];
+  if (simple->canvas)
+    {
+      goo_canvas_item_get_bounds (child, &bounds);
+      goo_canvas_request_item_redraw (simple->canvas, &bounds,
+				      simple->simple_data->is_static);
+    }
+
+  goo_canvas_util_ptr_array_move (group->items, old_position, new_position);
+
+  goo_canvas_item_request_update (item);
+}
+
+
+static void
+goo_canvas_group_remove_child  (GooCanvasItem  *item,
+				gint            child_num)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasItem *child;
+  GooCanvasBounds bounds;
+  AtkObject *atk_obj, *child_atk_obj;
+
+  g_return_if_fail (child_num < group->items->len);
+
+  /* Request a redraw of the item's bounds. */
+  child = group->items->pdata[child_num];
+  if (simple->canvas)
+    {
+      goo_canvas_item_get_bounds (child, &bounds);
+      goo_canvas_request_item_redraw (simple->canvas, &bounds,
+				      simple->simple_data->is_static);
+    }
+
+  /* Emit the "children_changed" ATK signal, if ATK is enabled. */
+  atk_obj = atk_gobject_accessible_for_object (G_OBJECT (item));
+  if (!ATK_IS_NO_OP_OBJECT (atk_obj))
+    {
+      child_atk_obj = atk_gobject_accessible_for_object (G_OBJECT (child));
+      g_signal_emit_by_name (atk_obj, "children_changed::remove",
+			     child_num, child_atk_obj);
+    }
+
+  g_ptr_array_remove_index (group->items, child_num);
+
+  goo_canvas_item_set_parent (child, NULL);
+  g_object_unref (child);
+
+  goo_canvas_item_request_update (item);
+}
+
+
+static gint
+goo_canvas_group_get_n_children (GooCanvasItem  *item)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+
+  return group->items->len;
+}
+
+
+static GooCanvasItem*
+goo_canvas_group_get_child   (GooCanvasItem       *item,
+			      gint                 child_num)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+
+  if (child_num < group->items->len)
+    return group->items->pdata[child_num];
+  return NULL;
+}
+
+
+/* This is only used to set the canvas of the root group. It isn't normally
+   needed by apps. */
+static void
+goo_canvas_group_set_canvas  (GooCanvasItem *item,
+			      GooCanvas     *canvas)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  gint i;
+
+  if (simple->canvas == canvas)
+    return;
+
+  simple->canvas = canvas;
+
+  /* Recursively set the canvas of all child items. */
+  for (i = 0; i < group->items->len; i++)
+    {
+      GooCanvasItem *item = group->items->pdata[i];
+      goo_canvas_item_set_canvas (item, canvas);
+    }
+}
+
+
+static void
+goo_canvas_group_set_is_static  (GooCanvasItem *item,
+				 gboolean       is_static)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  gint i;
+
+  if (simple_data->is_static == is_static)
+    return;
+
+  simple_data->is_static = is_static;
+
+  /* Recursively set the canvas of all child items. */
+  for (i = 0; i < group->items->len; i++)
+    {
+      GooCanvasItem *item = group->items->pdata[i];
+      goo_canvas_item_set_is_static (item, is_static);
+    }
+}
+
+
+static void
+on_model_child_added (GooCanvasGroupModel *model,
+		      gint                 position,
+		      GooCanvasGroup      *group)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) group;
+  GooCanvasItem *item = (GooCanvasItem*) group;
+  GooCanvasItemModel *child_model;
+  GooCanvasItem *child;
+
+  /* Create a canvas item for the model. */
+  child_model = goo_canvas_item_model_get_child ((GooCanvasItemModel*) model,
+						 position);
+  child = goo_canvas_create_item (simple->canvas, child_model);
+  goo_canvas_item_add_child (item, child, position);
+  g_object_unref (child);
+}
+
+
+static void
+on_model_child_moved (GooCanvasGroupModel *model,
+		      gint                 old_position,
+		      gint                 new_position,
+		      GooCanvasGroup      *group)
+{
+  goo_canvas_item_move_child ((GooCanvasItem*) group, old_position,
+			       new_position);
+}
+
+
+static void
+on_model_child_removed (GooCanvasGroupModel *model,
+			gint                 child_num,
+			GooCanvasGroup      *group)
+{
+  goo_canvas_item_remove_child ((GooCanvasItem*) group, child_num);
+}
+
+
+static void
+goo_canvas_group_set_model (GooCanvasItem       *item,
+			    GooCanvasItemModel  *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  gint n_children, i;
+
+  /* Do the default GooCanvasItemSimple code first. */
+  goo_canvas_item_simple_set_model (simple, model);
+
+  /* Now add our own handlers. */
+  g_signal_connect (model, "child-added",
+		    G_CALLBACK (on_model_child_added), group);
+  g_signal_connect (model, "child-moved",
+		    G_CALLBACK (on_model_child_moved), group);
+  g_signal_connect (model, "child-removed",
+		    G_CALLBACK (on_model_child_removed), group);
+
+  /* Recursively create child items for any children. */
+  n_children = goo_canvas_item_model_get_n_children (model);
+  for (i = 0; i < n_children; i++)
+    on_model_child_added ((GooCanvasGroupModel*) simple->model, i, group);
+}
+
+
+static void
+goo_canvas_group_request_update  (GooCanvasItem *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+
+  if (!simple->need_update)
+    {
+      simple->need_update = TRUE;
+
+      if (simple->parent)
+	goo_canvas_item_request_update (simple->parent);
+      else if (simple->canvas)
+	goo_canvas_request_update (simple->canvas);
+    }
+}
+
+
+static void
+goo_canvas_group_update  (GooCanvasItem   *item,
+			  gboolean         entire_tree,
+			  cairo_t         *cr,
+			  GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasGroupPrivate *priv = goo_canvas_group_get_private (group);
+  GooCanvasBounds child_bounds;
+  gboolean initial_bounds = TRUE;
+  gint i;
+
+  if (entire_tree || simple->need_update)
+    {
+      if (simple->need_entire_subtree_update)
+	entire_tree = TRUE;
+
+      simple->need_update = FALSE;
+      simple->need_entire_subtree_update = FALSE;
+
+      goo_canvas_item_simple_check_style (simple);
+
+      simple->bounds.x1 = simple->bounds.y1 = 0.0;
+      simple->bounds.x2 = simple->bounds.y2 = 0.0;
+
+      cairo_save (cr);
+      if (simple->simple_data->transform)
+        cairo_transform (cr, simple->simple_data->transform);
+
+      cairo_translate (cr, priv->x, priv->y);
+
+      for (i = 0; i < group->items->len; i++)
+        {
+          GooCanvasItem *child = group->items->pdata[i];
+
+          goo_canvas_item_update (child, entire_tree, cr, &child_bounds);
+          
+          /* If the child has non-empty bounds, compute the union. */
+          if (child_bounds.x1 < child_bounds.x2
+              && child_bounds.y1 < child_bounds.y2)
+            {
+              if (initial_bounds)
+                {
+                  simple->bounds.x1 = child_bounds.x1;
+                  simple->bounds.y1 = child_bounds.y1;
+                  simple->bounds.x2 = child_bounds.x2;
+                  simple->bounds.y2 = child_bounds.y2;
+                  initial_bounds = FALSE;
+                }
+              else
+                {
+                  simple->bounds.x1 = MIN (simple->bounds.x1, child_bounds.x1);
+                  simple->bounds.y1 = MIN (simple->bounds.y1, child_bounds.y1);
+                  simple->bounds.x2 = MAX (simple->bounds.x2, child_bounds.x2);
+                  simple->bounds.y2 = MAX (simple->bounds.y2, child_bounds.y2);
+                }
+            }
+        }
+
+      cairo_restore (cr);
+    }
+
+  *bounds = simple->bounds;
+}
+
+
+static GList*
+goo_canvas_group_get_items_at (GooCanvasItem  *item,
+			       gdouble         x,
+			       gdouble         y,
+			       cairo_t        *cr,
+			       gboolean        is_pointer_event,
+			       gboolean        parent_visible,
+			       GList          *found_items)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasGroupPrivate *priv = goo_canvas_group_get_private (group);
+  gboolean visible = parent_visible;
+  int i;
+
+  if (simple->need_update)
+    goo_canvas_item_ensure_updated (item);
+
+  /* Skip the item if the point isn't in the item's bounds. */
+  if (simple->bounds.x1 > x || simple->bounds.x2 < x
+      || simple->bounds.y1 > y || simple->bounds.y2 < y)
+    return found_items;
+
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && simple->canvas->scale < simple_data->visibility_threshold))
+    visible = FALSE;
+
+  /* Check if the group should receive events. */
+  if (is_pointer_event
+      && (simple_data->pointer_events == GOO_CANVAS_EVENTS_NONE
+	  || ((simple_data->pointer_events & GOO_CANVAS_EVENTS_VISIBLE_MASK)
+	      && !visible)))
+    return found_items;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  cairo_translate (cr, priv->x, priv->y);
+
+  /* If the group has a clip path, check if the point is inside it. */
+  if (simple_data->clip_path_commands)
+    {
+      double user_x = x, user_y = y;
+
+      cairo_device_to_user (cr, &user_x, &user_y);
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      if (!cairo_in_fill (cr, user_x, user_y))
+	{
+	  cairo_restore (cr);
+	  return found_items;
+	}
+    }
+
+  if (priv->width > 0.0 && priv->height > 0.0)
+    {
+      double user_x = x, user_y = y;
+
+      cairo_device_to_user (cr, &user_x, &user_y);
+      if (user_x < 0.0 || user_x >= priv->width
+	  || user_y < 0.0 || user_y >= priv->height)
+	{
+	  cairo_restore (cr);
+	  return found_items;
+	}
+    }
+
+  /* Step up from the bottom of the children to the top, adding any items
+     found to the start of the list. */
+  for (i = 0; i < group->items->len; i++)
+    {
+      GooCanvasItem *child = group->items->pdata[i];
+
+      found_items = goo_canvas_item_get_items_at (child, x, y, cr,
+						  is_pointer_event, visible,
+						  found_items);
+    }
+  cairo_restore (cr);
+
+  return found_items;
+}
+
+
+static void
+goo_canvas_group_paint (GooCanvasItem         *item,
+			cairo_t               *cr,
+			const GooCanvasBounds *bounds,
+			gdouble                scale)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasGroupPrivate *priv = goo_canvas_group_get_private (group);
+  gint i;
+
+  /* Skip the item if the bounds don't intersect the expose rectangle. */
+  if (simple->bounds.x1 > bounds->x2 || simple->bounds.x2 < bounds->x1
+      || simple->bounds.y1 > bounds->y2 || simple->bounds.y2 < bounds->y1)
+    return;
+
+  /* Check if the item should be visible. */
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && scale < simple_data->visibility_threshold))
+    return;
+
+  /* Paint all the items in the group. */
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  cairo_translate (cr, priv->x, priv->y);
+
+  /* Clip with the group's clip path, if it is set. */
+  if (simple_data->clip_path_commands)
+    {
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      cairo_clip (cr);
+    }
+
+  if (priv->width > 0.0 && priv->height > 0.0)
+    {
+      cairo_rectangle (cr, 0.0, 0.0, priv->width, priv->height);
+      cairo_clip (cr);
+    }
+
+  for (i = 0; i < group->items->len; i++)
+    {
+      GooCanvasItem *child = group->items->pdata[i];
+      goo_canvas_item_paint (child, cr, bounds, scale);
+    }
+  cairo_restore (cr);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_canvas     = goo_canvas_group_set_canvas;
+  iface->get_n_children = goo_canvas_group_get_n_children;
+  iface->get_child      = goo_canvas_group_get_child;
+  iface->request_update = goo_canvas_group_request_update;
+
+  iface->add_child      = goo_canvas_group_add_child;
+  iface->move_child     = goo_canvas_group_move_child;
+  iface->remove_child   = goo_canvas_group_remove_child;
+
+  iface->get_items_at	= goo_canvas_group_get_items_at;
+  iface->update         = goo_canvas_group_update;
+  iface->paint          = goo_canvas_group_paint;
+
+  iface->set_model      = goo_canvas_group_set_model;
+  iface->set_is_static  = goo_canvas_group_set_is_static;
+}
+
+
+/**
+ * SECTION:goocanvasgroupmodel
+ * @Title: GooCanvasGroupModel
+ * @Short_Description: a model for a group of items.
+ *
+ * #GooCanvasGroupModel represents a group of items. Groups can be nested to
+ * any depth, to create a hierarchy of items. Items are ordered within each
+ * group, with later items being displayed above earlier items.
+ *
+ * #GooCanvasGroupModel is a subclass of #GooCanvasItemModelSimple and so
+ * inherits all of the style properties such as "stroke-color", "fill-color"
+ * and "line-width". Setting a style property on a #GooCanvasGroupModel will
+ * affect all children of the #GooCanvasGroupModel (unless the children
+ * override the property setting).
+ *
+ * #GooCanvasGroupModel implements the #GooCanvasItemModel interface, so you
+ * can use the #GooCanvasItemModel functions such as
+ * goo_canvas_item_model_raise() and goo_canvas_item_model_rotate(), and the
+ * properties such as "visibility" and "pointer-events".
+ *
+ * To create a #GooCanvasGroupModel use goo_canvas_group_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasGroupModel, use
+ * g_object_get() and g_object_set().
+ *
+ * To respond to events such as mouse clicks on the group you must connect
+ * to the signal handlers of the corresponding #GooCanvasGroup objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_group_model_dispose      (GObject            *object);
+static void goo_canvas_group_model_finalize     (GObject            *object);
+static void goo_canvas_group_model_get_property (GObject            *object,
+                                                 guint               prop_id,
+                                                 GValue             *value,
+                                                 GParamSpec         *pspec);
+static void goo_canvas_group_model_set_property (GObject            *object,
+                                                 guint               prop_id,
+                                                 const GValue       *value,
+                                                 GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasGroupModel, goo_canvas_group_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_group_model_class_init (GooCanvasGroupModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasGroupPrivate));
+
+  gobject_class->dispose  = goo_canvas_group_model_dispose;
+  gobject_class->finalize = goo_canvas_group_model_finalize;
+  gobject_class->get_property  = goo_canvas_group_model_get_property;
+  gobject_class->set_property = goo_canvas_group_model_set_property;
+
+  goo_canvas_group_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_group_model_init (GooCanvasGroupModel *gmodel)
+{
+  GooCanvasGroupPrivate *priv = GOO_CANVAS_GROUP_MODEL_GET_PRIVATE (gmodel);
+  gmodel->children = g_ptr_array_sized_new (8);
+
+  priv->x = 0.0;
+  priv->y = 0.0;
+  priv->width = -1.0;
+  priv->height = -1.0;
+}
+
+
+/**
+ * goo_canvas_group_model_new:
+ * @parent: the parent model, 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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new group item.
+ * 
+ * Return value: a new group model.
+ **/
+GooCanvasItemModel*
+goo_canvas_group_model_new (GooCanvasItemModel *parent,
+			    ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasGroupModel *gmodel;
+  va_list var_args;
+  const char *first_property;
+
+  model = g_object_new (GOO_TYPE_CANVAS_GROUP_MODEL, NULL);
+  gmodel = (GooCanvasGroupModel*) model;
+
+  va_start (var_args, parent);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist (G_OBJECT (model), first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_group_model_dispose (GObject *object)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) object;
+  gint i;
+
+  /* Unref all the items in the group. */
+  for (i = 0; i < gmodel->children->len; i++)
+    {
+      GooCanvasItemModel *child = gmodel->children->pdata[i];
+      goo_canvas_item_model_set_parent (child, NULL);
+      g_object_unref (child);
+    }
+
+  g_ptr_array_set_size (gmodel->children, 0);
+
+  G_OBJECT_CLASS (goo_canvas_group_model_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_group_model_finalize (GObject *object)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) object;
+
+  g_ptr_array_free (gmodel->children, TRUE);
+
+  G_OBJECT_CLASS (goo_canvas_group_model_parent_class)->finalize (object);
+}
+
+static void goo_canvas_group_model_get_property (GObject            *object,
+                                                 guint               prop_id,
+                                                 GValue             *value,
+                                                 GParamSpec         *pspec)
+{
+  GooCanvasGroupModel *model = (GooCanvasGroupModel*) object;
+  GooCanvasGroupPrivate *priv = GOO_CANVAS_GROUP_MODEL_GET_PRIVATE (model);
+
+  goo_canvas_group_get_common_property (object, priv, prop_id, value, pspec);
+}
+
+static void goo_canvas_group_model_set_property (GObject            *object,
+                                                 guint               prop_id,
+                                                 const GValue       *value,
+                                                 GParamSpec         *pspec)
+{
+  GooCanvasGroupModel *model = (GooCanvasGroupModel*) object;
+  GooCanvasGroupPrivate *priv = GOO_CANVAS_GROUP_MODEL_GET_PRIVATE (model);
+
+  goo_canvas_group_set_common_property (object, priv, prop_id, value, pspec);
+  g_signal_emit_by_name (model, "changed", TRUE);
+}
+
+extern void _goo_canvas_item_model_emit_child_added (GooCanvasItemModel *model,
+						     gint                position);
+
+static void
+goo_canvas_group_model_add_child     (GooCanvasItemModel *model,
+				      GooCanvasItemModel *child,
+				      gint                position)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+
+  g_object_ref (child);
+
+  if (position >= 0)
+    {
+      goo_canvas_util_ptr_array_insert (gmodel->children, child, position);
+    }
+  else
+    {
+      position = gmodel->children->len;
+      g_ptr_array_add (gmodel->children, child);
+    }
+
+  goo_canvas_item_model_set_parent (child, model);
+
+  _goo_canvas_item_model_emit_child_added (model, position);
+}
+
+
+static void
+goo_canvas_group_model_move_child    (GooCanvasItemModel *model,
+				      gint	          old_position,
+				      gint                new_position)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+
+  goo_canvas_util_ptr_array_move (gmodel->children, old_position,
+				  new_position);
+
+  g_signal_emit_by_name (gmodel, "child-moved", old_position, new_position);
+}
+
+
+static void
+goo_canvas_group_model_remove_child  (GooCanvasItemModel *model,
+				      gint                child_num)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+  GooCanvasItemModel *child;
+
+  child = gmodel->children->pdata[child_num];
+  goo_canvas_item_model_set_parent (child, NULL);
+
+  g_ptr_array_remove_index (gmodel->children, child_num);
+
+  g_signal_emit_by_name (gmodel, "child-removed", child_num);
+
+  g_object_unref (child);
+}
+
+
+static gint
+goo_canvas_group_model_get_n_children (GooCanvasItemModel  *model)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+
+  return gmodel->children->len;
+}
+
+
+static GooCanvasItemModel*
+goo_canvas_group_model_get_child   (GooCanvasItemModel  *model,
+				    gint                 child_num)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+
+  if (child_num < gmodel->children->len)
+    return gmodel->children->pdata[child_num];
+  return NULL;
+}
+
+
+static GooCanvasItem*
+goo_canvas_group_model_create_item (GooCanvasItemModel *model,
+				    GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = goo_canvas_group_new (NULL, NULL);
+  /* Note that we set the canvas before the model, since we may need the
+     canvas to create any child items. */
+  goo_canvas_item_set_canvas (item, canvas);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->add_child      = goo_canvas_group_model_add_child;
+  iface->move_child     = goo_canvas_group_model_move_child;
+  iface->remove_child   = goo_canvas_group_model_remove_child;
+  iface->get_n_children = goo_canvas_group_model_get_n_children;
+  iface->get_child      = goo_canvas_group_model_get_child;
+
+  iface->create_item    = goo_canvas_group_model_create_item;
+}
+
+
diff --git a/libgoocanvas/goocanvasgroup.h b/libgoocanvas/goocanvasgroup.h
new file mode 100644
index 0000000..75e28cd
--- /dev/null
+++ b/libgoocanvas/goocanvasgroup.h
@@ -0,0 +1,109 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasgroup.h - group item.
+ */
+#ifndef __GOO_CANVAS_GROUP_H__
+#define __GOO_CANVAS_GROUP_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+#include "goocanvasutils.h"
+
+G_BEGIN_DECLS
+
+
+#define GOO_TYPE_CANVAS_GROUP            (goo_canvas_group_get_type ())
+#define GOO_CANVAS_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_GROUP, GooCanvasGroup))
+#define GOO_CANVAS_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_GROUP, GooCanvasGroupClass))
+#define GOO_IS_CANVAS_GROUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_GROUP))
+#define GOO_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_GROUP))
+#define GOO_CANVAS_GROUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_GROUP, GooCanvasGroupClass))
+
+
+typedef struct _GooCanvasGroup            GooCanvasGroup;
+typedef struct _GooCanvasGroupClass       GooCanvasGroupClass;
+
+typedef struct _GooCanvasGroupModel       GooCanvasGroupModel;
+typedef struct _GooCanvasGroupModelClass  GooCanvasGroupModelClass;
+
+/**
+ * GooCanvasGroup
+ *
+ * The #GooCanvasGroup-struct struct contains private data only.
+ */
+struct _GooCanvasGroup
+{
+  GooCanvasItemSimple parent_object;
+
+  /* An array of pointers to GooCanvasItems. The first element is at the
+     bottom of the display stack and the last element is at the top. */
+  GPtrArray *items;
+};
+
+struct _GooCanvasGroupClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType          goo_canvas_group_get_type    (void) G_GNUC_CONST;
+GooCanvasItem* goo_canvas_group_new         (GooCanvasItem  *parent,
+					     ...);
+
+
+
+#define GOO_TYPE_CANVAS_GROUP_MODEL            (goo_canvas_group_model_get_type ())
+#define GOO_CANVAS_GROUP_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_GROUP_MODEL, GooCanvasGroupModel))
+#define GOO_CANVAS_GROUP_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_GROUP_MODEL, GooCanvasGroupModelClass))
+#define GOO_IS_CANVAS_GROUP_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_GROUP_MODEL))
+#define GOO_IS_CANVAS_GROUP_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_GROUP_MODEL))
+#define GOO_CANVAS_GROUP_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_GROUP_MODEL, GooCanvasGroupModelClass))
+
+
+
+/**
+ * GooCanvasGroupModel
+ *
+ * The #GooCanvasGroupModel-struct struct contains private data only.
+ */
+struct _GooCanvasGroupModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  /* An array of pointers to GooCanvasItemModels. The first element is at the
+     bottom of the display stack and the last element is at the top. */
+  GPtrArray *children;
+};
+
+struct _GooCanvasGroupModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_group_model_get_type (void) G_GNUC_CONST;
+GooCanvasItemModel* goo_canvas_group_model_new      (GooCanvasItemModel  *parent,
+						     ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_GROUP_H__ */
diff --git a/libgoocanvas/goocanvasimage.c b/libgoocanvas/goocanvasimage.c
new file mode 100644
index 0000000..5ecc7db
--- /dev/null
+++ b/libgoocanvas/goocanvasimage.c
@@ -0,0 +1,738 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasimage.c - image item.
+ */
+
+/**
+ * SECTION:goocanvasimage
+ * @Title: GooCanvasImage
+ * @Short_Description: an image item.
+ *
+ * GooCanvasImage represents an image item.
+ *
+ * It is a subclass of #GooCanvasItemSimple and so inherits all of the style
+ * properties such as "operator" and "pointer-events".
+ *
+ * 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 #GooCanvasImage use goo_canvas_image_new().
+ *
+ * To get or set the properties of an existing #GooCanvasImage, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include "goocanvasimage.h"
+#include "goocanvas.h"
+#include "goocanvasutils.h"
+
+
+typedef struct _GooCanvasImagePrivate GooCanvasImagePrivate;
+struct _GooCanvasImagePrivate {
+  gboolean scale_to_fit;
+  gdouble alpha;
+};
+
+#define GOO_CANVAS_IMAGE_GET_PRIVATE(image)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((image), GOO_TYPE_CANVAS_IMAGE, GooCanvasImagePrivate))
+#define GOO_CANVAS_IMAGE_MODEL_GET_PRIVATE(image)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((image), GOO_TYPE_CANVAS_IMAGE_MODEL, GooCanvasImagePrivate))
+
+
+enum {
+  PROP_0,
+
+  PROP_PATTERN,
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_SCALE_TO_FIT,
+  PROP_ALPHA,
+
+  /* Convenience properties. */
+  PROP_PIXBUF
+};
+
+static void goo_canvas_image_dispose      (GObject            *object);
+static void goo_canvas_image_finalize     (GObject            *object);
+static void canvas_item_interface_init    (GooCanvasItemIface *iface);
+static void goo_canvas_image_get_property (GObject            *object,
+					   guint               param_id,
+					   GValue             *value,
+					   GParamSpec         *pspec);
+static void goo_canvas_image_set_property (GObject            *object,
+					   guint               param_id,
+					   const GValue       *value,
+					   GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasImage, goo_canvas_image,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_image_install_common_properties (GObjectClass *gobject_class)
+{
+  g_object_class_install_property (gobject_class, PROP_PATTERN,
+                                   g_param_spec_boxed ("pattern",
+						       _("Pattern"),
+						       _("The cairo pattern to paint"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the image"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the image"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the image"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the image"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_SCALE_TO_FIT, 
+                                   g_param_spec_boolean ("scale-to-fit",
+							 _("Scale To Fit"),
+							 _("If the image is scaled to fit the width and height settings"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_ALPHA,
+				   g_param_spec_double ("alpha",
+							_("Alpha"),
+							_("The opacity of the image, 0.0 is fully transparent, and 1.0 is opaque."),
+							0.0, 1.0, 1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_PIXBUF,
+				   g_param_spec_object ("pixbuf",
+							_("Pixbuf"),
+							_("The GdkPixbuf to display"),
+							GDK_TYPE_PIXBUF,
+							G_PARAM_WRITABLE));
+}
+
+
+/* Gets the private data to use, from the model or from the item itself. */
+static GooCanvasImagePrivate*
+goo_canvas_image_get_private (gpointer object)
+{
+  GooCanvasItemSimple *simple;
+
+  if (GOO_IS_CANVAS_IMAGE (object))
+    {
+      simple = (GooCanvasItemSimple*) object;
+      if (simple->model)
+	return GOO_CANVAS_IMAGE_MODEL_GET_PRIVATE (simple->model);
+      else
+	return GOO_CANVAS_IMAGE_GET_PRIVATE (object);
+    }
+  else
+    {
+      return GOO_CANVAS_IMAGE_MODEL_GET_PRIVATE (object);
+    }
+}
+
+
+static void
+goo_canvas_image_init (GooCanvasImage *image)
+{
+  GooCanvasImagePrivate *priv = GOO_CANVAS_IMAGE_GET_PRIVATE (image);
+
+  image->image_data = g_slice_new0 (GooCanvasImageData);
+
+  priv->alpha = 1.0;
+}
+
+
+/**
+ * goo_canvas_image_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.
+ * @pixbuf: the #GdkPixbuf containing the image data, or %NULL.
+ * @x: the x coordinate of the image.
+ * @y: the y coordinate of the image.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new image item.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create an image at (100.0, 100.0), using
+ * the given pixbuf at its natural width and height:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *image = goo_canvas_image_new (mygroup, pixbuf, 100.0, 100.0,
+ *                                               NULL);
+ * </programlisting></informalexample>
+ *
+ * Returns: a new image item.
+ **/
+GooCanvasItem*
+goo_canvas_image_new (GooCanvasItem *parent,
+		      GdkPixbuf     *pixbuf,
+		      gdouble        x,
+		      gdouble        y,
+		      ...)
+{
+  GooCanvasItem *item;
+  GooCanvasImage *image;
+  GooCanvasImageData *image_data;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_IMAGE, NULL);
+  image = (GooCanvasImage*) item;
+
+  image_data = image->image_data;
+  image_data->x = x;
+  image_data->y = y;
+
+  if (pixbuf)
+    {
+      image_data->pattern = goo_canvas_cairo_pattern_from_pixbuf (pixbuf);
+      image_data->width = gdk_pixbuf_get_width (pixbuf);
+      image_data->height = gdk_pixbuf_get_height (pixbuf);
+    }
+
+  va_start (var_args, y);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_image_dispose (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasImage *image = (GooCanvasImage*) object;
+
+  if (!simple->model)
+    {
+      cairo_pattern_destroy (image->image_data->pattern);
+      image->image_data->pattern = NULL;
+    }
+
+  G_OBJECT_CLASS (goo_canvas_image_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_image_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasImage *image = (GooCanvasImage*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    g_slice_free (GooCanvasImageData, image->image_data);
+  image->image_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_image_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_image_get_common_property (GObject              *object,
+				      GooCanvasImageData   *image_data,
+				      guint                 prop_id,
+				      GValue               *value,
+				      GParamSpec           *pspec)
+{
+  GooCanvasImagePrivate *priv = goo_canvas_image_get_private (object);
+
+  switch (prop_id)
+    {
+    case PROP_PATTERN:
+      g_value_set_boxed (value, image_data->pattern);
+      break;
+    case PROP_X:
+      g_value_set_double (value, image_data->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, image_data->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, image_data->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, image_data->height);
+      break;
+    case PROP_SCALE_TO_FIT:
+      g_value_set_boolean (value, priv->scale_to_fit);
+      break;
+    case PROP_ALPHA:
+      g_value_set_double (value, priv->alpha);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+
+static void
+goo_canvas_image_get_property (GObject              *object,
+			       guint                 prop_id,
+			       GValue               *value,
+			       GParamSpec           *pspec)
+{
+  GooCanvasImage *image = (GooCanvasImage*) object;
+
+  goo_canvas_image_get_common_property (object, image->image_data, prop_id,
+					value, pspec);
+}
+
+
+static gboolean
+goo_canvas_image_set_common_property (GObject              *object,
+				      GooCanvasImageData   *image_data,
+				      guint                 prop_id,
+				      const GValue         *value,
+				      GParamSpec           *pspec)
+{
+  GooCanvasImagePrivate *priv = goo_canvas_image_get_private (object);
+  GdkPixbuf *pixbuf;
+  gboolean recompute_bounds = TRUE;
+
+  switch (prop_id)
+    {
+    case PROP_PATTERN:
+      cairo_pattern_destroy (image_data->pattern);
+      image_data->pattern = g_value_get_boxed (value);
+      cairo_pattern_reference (image_data->pattern);
+      break;
+    case PROP_X:
+      image_data->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      image_data->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      image_data->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      image_data->height = g_value_get_double (value);
+      break;
+    case PROP_SCALE_TO_FIT:
+      priv->scale_to_fit = g_value_get_boolean (value);
+      break;
+    case PROP_PIXBUF:
+      cairo_pattern_destroy (image_data->pattern);
+      pixbuf = g_value_get_object (value);
+      image_data->pattern = pixbuf ? goo_canvas_cairo_pattern_from_pixbuf (pixbuf) : NULL;
+      image_data->width = pixbuf ? gdk_pixbuf_get_width (pixbuf) : 0;
+      image_data->height = pixbuf ? gdk_pixbuf_get_height (pixbuf) : 0;
+      break;
+    case PROP_ALPHA:
+      priv->alpha = g_value_get_double (value);
+      recompute_bounds = FALSE;
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+
+  return recompute_bounds;
+}
+
+
+static void
+goo_canvas_image_set_property (GObject              *object,
+			       guint                 prop_id,
+			       const GValue         *value,
+			       GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasImage *image = (GooCanvasImage*) object;
+  gboolean recompute_bounds;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  recompute_bounds = goo_canvas_image_set_common_property (object,
+							   image->image_data,
+							   prop_id,
+							   value, pspec);
+  goo_canvas_item_simple_changed (simple, recompute_bounds);
+}
+
+
+static gboolean
+goo_canvas_image_is_item_at (GooCanvasItemSimple *simple,
+			     gdouble              x,
+			     gdouble              y,
+			     cairo_t             *cr,
+			     gboolean             is_pointer_event)
+{
+  GooCanvasImage *image = (GooCanvasImage*) simple;
+  GooCanvasImageData *image_data = image->image_data;
+
+  if (x < image_data->x || (x > image_data->x + image_data->width)
+      || y < image_data->y || (y > image_data->y + image_data->height))
+    return FALSE;
+
+  return TRUE;
+}
+
+
+static void
+goo_canvas_image_update  (GooCanvasItemSimple  *simple,
+			  cairo_t              *cr)
+{
+  GooCanvasImage *image = (GooCanvasImage*) simple;
+  GooCanvasImageData *image_data = image->image_data;
+
+  /* Compute the new bounds. */
+  simple->bounds.x1 = image_data->x;
+  simple->bounds.y1 = image_data->y;
+  simple->bounds.x2 = image_data->x + image_data->width;
+  simple->bounds.y2 = image_data->y + image_data->height;
+}
+
+
+static void
+goo_canvas_image_paint (GooCanvasItemSimple   *simple,
+			cairo_t               *cr,
+			const GooCanvasBounds *bounds)
+{
+  GooCanvasImagePrivate *priv = goo_canvas_image_get_private (simple);
+  GooCanvasImage *image = (GooCanvasImage*) simple;
+  GooCanvasImageData *image_data = image->image_data;
+  cairo_matrix_t matrix = { 1, 0, 0, 1, 0, 0 };
+  cairo_surface_t *surface;
+  gdouble width, height;
+
+  if (!image_data->pattern)
+    return;
+
+#if 1
+  if (priv->scale_to_fit)
+    {
+      if (cairo_pattern_get_surface (image_data->pattern, &surface)
+	  == CAIRO_STATUS_SUCCESS
+	  && cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE)
+	{
+	  width = cairo_image_surface_get_width (surface);
+	  height = cairo_image_surface_get_height (surface);
+	  cairo_matrix_scale (&matrix, width / image_data->width,
+			      height / image_data->height);
+	}
+    }
+
+  cairo_matrix_translate (&matrix, -image_data->x, -image_data->y);
+
+  cairo_pattern_set_matrix (image_data->pattern, &matrix);
+  goo_canvas_style_set_fill_options (simple->simple_data->style, cr);
+  cairo_set_source (cr, image_data->pattern);
+  cairo_rectangle (cr, image_data->x, image_data->y,
+		   image_data->width, image_data->height);
+  /* To have better performance, we don't use cairo_paint_with_alpha if
+   * the image is not transparent at all. */
+  if (priv->alpha != 1.0)
+    cairo_paint_with_alpha (cr, priv->alpha);
+  else
+    cairo_fill (cr);
+#else
+  /* Using cairo_paint() used to be much slower than cairo_fill(), though
+     they seem similar now. I'm not sure if it matters which we use. */
+  cairo_matrix_init_translate (&matrix, -image_data->x, -image_data->y);
+  cairo_pattern_set_matrix (image_data->pattern, &matrix);
+  goo_canvas_style_set_fill_options (simple->simple_data->style, cr);
+  cairo_set_source (cr, image_data->pattern);
+  cairo_paint (cr);
+#endif
+}
+
+
+static void
+goo_canvas_image_set_model    (GooCanvasItem      *item,
+			       GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasImage *image = (GooCanvasImage*) item;
+  GooCanvasImageModel *imodel = (GooCanvasImageModel*) model;
+
+  /* If our data was allocated, free it. */
+  if (!simple->model)
+    {
+      cairo_pattern_destroy (image->image_data->pattern);
+      g_slice_free (GooCanvasImageData, image->image_data);
+    }
+
+  /* Now use the new model's data instead. */
+  image->image_data = &imodel->image_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model   = goo_canvas_image_set_model;
+}
+
+
+static void
+goo_canvas_image_class_init (GooCanvasImageClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasImagePrivate));
+
+  gobject_class->dispose  = goo_canvas_image_dispose;
+  gobject_class->finalize = goo_canvas_image_finalize;
+
+  gobject_class->get_property = goo_canvas_image_get_property;
+  gobject_class->set_property = goo_canvas_image_set_property;
+
+  simple_class->simple_update      = goo_canvas_image_update;
+  simple_class->simple_paint       = goo_canvas_image_paint;
+  simple_class->simple_is_item_at  = goo_canvas_image_is_item_at;
+
+  goo_canvas_image_install_common_properties (gobject_class);
+}
+
+
+
+/**
+ * SECTION:goocanvasimagemodel
+ * @Title: GooCanvasImageModel
+ * @Short_Description: a model for image items.
+ *
+ * GooCanvasImageModel represent a model for image items.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "operator" and "pointer-events".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasImageModel use goo_canvas_image_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasImageModel, use
+ * g_object_get() and g_object_set().
+ *
+ * To respond to events such as mouse clicks on the image you must connect
+ * to the signal handlers of the corresponding #GooCanvasImage objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_image_model_dispose      (GObject            *object);
+static void goo_canvas_image_model_get_property (GObject            *object,
+						 guint               param_id,
+						 GValue             *value,
+						 GParamSpec         *pspec);
+static void goo_canvas_image_model_set_property (GObject            *object,
+						 guint               param_id,
+						 const GValue       *value,
+						 GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasImageModel, goo_canvas_image_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_image_model_class_init (GooCanvasImageModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasImagePrivate));
+
+  gobject_class->dispose      = goo_canvas_image_model_dispose;
+
+  gobject_class->get_property = goo_canvas_image_model_get_property;
+  gobject_class->set_property = goo_canvas_image_model_set_property;
+
+  goo_canvas_image_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_image_model_init (GooCanvasImageModel *emodel)
+{
+  GooCanvasImagePrivate *priv = GOO_CANVAS_IMAGE_MODEL_GET_PRIVATE (emodel);
+
+  priv->alpha = 1.0;
+}
+
+
+/**
+ * goo_canvas_image_model_new:
+ * @parent: the parent model, 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.
+ * @pixbuf: the #GdkPixbuf containing the image data, or %NULL.
+ * @x: the x coordinate of the image.
+ * @y: the y coordinate of the image.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new image model.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create an image at (100.0, 100.0), using
+ * the given pixbuf at its natural width and height:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *image = goo_canvas_image_model_new (mygroup, pixbuf, 100.0, 100.0,
+ *                                                          NULL);
+ * </programlisting></informalexample>
+ *
+ * Returns: a new image model.
+ **/
+GooCanvasItemModel*
+goo_canvas_image_model_new (GooCanvasItemModel *parent,
+			    GdkPixbuf          *pixbuf,
+			    gdouble             x,
+			    gdouble             y,
+			    ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasImageModel *imodel;
+  GooCanvasImageData *image_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_IMAGE_MODEL, NULL);
+  imodel = (GooCanvasImageModel*) model;
+
+  image_data = &imodel->image_data;
+  image_data->x = x;
+  image_data->y = y;
+
+  if (pixbuf)
+    {
+      image_data->pattern = goo_canvas_cairo_pattern_from_pixbuf (pixbuf);
+      image_data->width = gdk_pixbuf_get_width (pixbuf);
+      image_data->height = gdk_pixbuf_get_height (pixbuf);
+    }
+
+  va_start (var_args, y);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_image_model_dispose (GObject *object)
+{
+  GooCanvasImageModel *imodel = (GooCanvasImageModel*) object;
+
+  cairo_pattern_destroy (imodel->image_data.pattern);
+  imodel->image_data.pattern = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_image_model_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_image_model_get_property (GObject              *object,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasImageModel *imodel = (GooCanvasImageModel*) object;
+
+  goo_canvas_image_get_common_property (object, &imodel->image_data, prop_id,
+					value, pspec);
+}
+
+
+static void
+goo_canvas_image_model_set_property (GObject              *object,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasImageModel *imodel = (GooCanvasImageModel*) object;
+  gboolean recompute_bounds;
+
+  recompute_bounds = goo_canvas_image_set_common_property (object,
+							   &imodel->image_data,
+							   prop_id,
+							   value, pspec);
+  g_signal_emit_by_name (imodel, "changed", recompute_bounds);
+}
+
+
+static GooCanvasItem*
+goo_canvas_image_model_create_item (GooCanvasItemModel *model,
+				    GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_IMAGE, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_image_model_create_item;
+}
+
diff --git a/libgoocanvas/goocanvasimage.h b/libgoocanvas/goocanvasimage.h
new file mode 100644
index 0000000..a97364f
--- /dev/null
+++ b/libgoocanvas/goocanvasimage.h
@@ -0,0 +1,121 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasimage.h - image item.
+ */
+#ifndef __GOO_CANVAS_IMAGE_H__
+#define __GOO_CANVAS_IMAGE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasImageData   GooCanvasImageData;
+struct _GooCanvasImageData
+{
+  cairo_pattern_t *pattern;
+
+  gdouble x, y, width, height;
+};
+
+
+#define GOO_TYPE_CANVAS_IMAGE            (goo_canvas_image_get_type ())
+#define GOO_CANVAS_IMAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_IMAGE, GooCanvasImage))
+#define GOO_CANVAS_IMAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_IMAGE, GooCanvasImageClass))
+#define GOO_IS_CANVAS_IMAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_IMAGE))
+#define GOO_IS_CANVAS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_IMAGE))
+#define GOO_CANVAS_IMAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_IMAGE, GooCanvasImageClass))
+
+
+typedef struct _GooCanvasImage       GooCanvasImage;
+typedef struct _GooCanvasImageClass  GooCanvasImageClass;
+
+/**
+ * GooCanvasImage
+ *
+ * The #GooCanvasImage-struct struct contains private data only.
+ */
+struct _GooCanvasImage
+{
+  GooCanvasItemSimple parent_object;
+
+  GooCanvasImageData *image_data;
+};
+
+struct _GooCanvasImageClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_image_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_image_new       (GooCanvasItem      *parent,
+						GdkPixbuf          *pixbuf,
+						gdouble             x,
+						gdouble             y,
+						...);
+
+
+
+#define GOO_TYPE_CANVAS_IMAGE_MODEL            (goo_canvas_image_model_get_type ())
+#define GOO_CANVAS_IMAGE_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_IMAGE_MODEL, GooCanvasImageModel))
+#define GOO_CANVAS_IMAGE_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_IMAGE_MODEL, GooCanvasImageModelClass))
+#define GOO_IS_CANVAS_IMAGE_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_IMAGE_MODEL))
+#define GOO_IS_CANVAS_IMAGE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_IMAGE_MODEL))
+#define GOO_CANVAS_IMAGE_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_IMAGE_MODEL, GooCanvasImageModelClass))
+
+
+typedef struct _GooCanvasImageModel       GooCanvasImageModel;
+typedef struct _GooCanvasImageModelClass  GooCanvasImageModelClass;
+
+/**
+ * GooCanvasImageModel
+ *
+ * The #GooCanvasImageModel-struct struct contains private data only.
+ */
+struct _GooCanvasImageModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasImageData image_data;
+};
+
+struct _GooCanvasImageModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_image_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_image_model_new (GooCanvasItemModel *parent,
+						GdkPixbuf          *pixbuf,
+						gdouble             x,
+						gdouble             y,
+						...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_IMAGE_H__ */
diff --git a/libgoocanvas/goocanvasitem.c b/libgoocanvas/goocanvasitem.c
new file mode 100644
index 0000000..57db899
--- /dev/null
+++ b/libgoocanvas/goocanvasitem.c
@@ -0,0 +1,2344 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitem.c - interface for canvas items & groups.
+ */
+
+/**
+ * SECTION:goocanvasitem
+ * @Title: GooCanvasItem
+ * @Short_Description: the interface for canvas items.
+ *
+ * #GooCanvasItem defines the interface that canvas items must implement,
+ * and contains methods for operating on canvas items.
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gobject/gobjectnotifyqueue.c>
+#include <gobject/gvaluecollector.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include "goocanvasitem.h"
+#include "goocanvas.h"
+#include "goocanvasutils.h"
+#include "goocanvasmarshal.h"
+
+
+static GParamSpecPool       *_goo_canvas_item_child_property_pool = NULL;
+static GObjectNotifyContext *_goo_canvas_item_child_property_notify_context = NULL;
+static const char *animation_key = "GooCanvasItemAnimation";
+
+enum {
+  /* Mouse events. */
+  ENTER_NOTIFY_EVENT,
+  LEAVE_NOTIFY_EVENT,
+  MOTION_NOTIFY_EVENT,
+  BUTTON_PRESS_EVENT,
+  BUTTON_RELEASE_EVENT,
+
+  /* Keyboard events. */
+  FOCUS_IN_EVENT,
+  FOCUS_OUT_EVENT,
+  KEY_PRESS_EVENT,
+  KEY_RELEASE_EVENT,
+
+  /* Miscellaneous signals. */
+  GRAB_BROKEN_EVENT,
+  CHILD_NOTIFY,
+  ANIMATION_FINISHED,
+  SCROLL_EVENT,
+  QUERY_TOOLTIP,
+
+  LAST_SIGNAL
+};
+
+static guint canvas_item_signals[LAST_SIGNAL] = { 0 };
+
+static void goo_canvas_item_base_init (gpointer g_class);
+extern void _goo_canvas_style_init (void);
+
+
+GType
+goo_canvas_item_get_type (void)
+{
+  static GType canvas_item_type = 0;
+
+  if (!canvas_item_type)
+    {
+      static const GTypeInfo canvas_item_info =
+      {
+        sizeof (GooCanvasItemIface), /* class_size */
+	goo_canvas_item_base_init,   /* base_init */
+	NULL,			     /* base_finalize */
+      };
+
+      canvas_item_type = g_type_register_static (G_TYPE_INTERFACE,
+						 "GooCanvasItem",
+						 &canvas_item_info, 0);
+
+      g_type_interface_add_prerequisite (canvas_item_type, G_TYPE_OBJECT);
+    }
+
+  return canvas_item_type;
+}
+
+
+static void
+child_property_notify_dispatcher (GObject     *object,
+				  guint        n_pspecs,
+				  GParamSpec **pspecs)
+{
+  guint i;
+
+  for (i = 0; i < n_pspecs; i++)
+    g_signal_emit (object, canvas_item_signals[CHILD_NOTIFY],
+		   g_quark_from_string (pspecs[i]->name), pspecs[i]);
+}
+
+
+static void
+goo_canvas_item_base_init (gpointer g_iface)
+{
+  static GObjectNotifyContext cpn_context = { 0, NULL, NULL };
+  static gboolean initialized = FALSE;
+  
+  if (!initialized)
+    {
+      GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+      _goo_canvas_item_child_property_pool = g_param_spec_pool_new (TRUE);
+
+      cpn_context.quark_notify_queue = g_quark_from_static_string ("GooCanvasItem-child-property-notify-queue");
+      cpn_context.dispatcher = child_property_notify_dispatcher;
+      _goo_canvas_item_child_property_notify_context = &cpn_context;
+
+      /* Mouse events. */
+
+      /**
+       * GooCanvasItem::enter-notify-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when the mouse enters an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[ENTER_NOTIFY_EVENT] =
+	g_signal_new ("enter_notify_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       enter_notify_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::leave-notify-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when the mouse leaves an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[LEAVE_NOTIFY_EVENT] =
+	g_signal_new ("leave_notify_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       leave_notify_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::motion-notify-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when the mouse moves within an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[MOTION_NOTIFY_EVENT] =
+	g_signal_new ("motion_notify_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       motion_notify_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::button-press-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when a mouse button is pressed in an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[BUTTON_PRESS_EVENT] =
+	g_signal_new ("button_press_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       button_press_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::button-release-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when a mouse button is released in an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[BUTTON_RELEASE_EVENT] =
+	g_signal_new ("button_release_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       button_release_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+
+      /* Keyboard events. */
+
+      /**
+       * GooCanvasItem::focus-in-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data.
+       *
+       * Emitted when the item receives the keyboard focus.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[FOCUS_IN_EVENT] =
+	g_signal_new ("focus_in_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       focus_in_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::focus-out-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data.
+       *
+       * Emitted when the item loses the keyboard focus.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[FOCUS_OUT_EVENT] =
+	g_signal_new ("focus_out_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       focus_out_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::key-press-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data.
+       *
+       * Emitted when a key is pressed and the item has the keyboard
+       * focus.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[KEY_PRESS_EVENT] =
+	g_signal_new ("key_press_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       key_press_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::key-release-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data.
+       *
+       * Emitted when a key is released and the item has the keyboard
+       * focus.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[KEY_RELEASE_EVENT] =
+	g_signal_new ("key_release_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       key_release_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::query-tooltip:
+       * @item: the item which received the signal.
+       * @x: the x coordinate of the mouse.
+       * @y: the y coordinate of the mouse.
+       * @keyboard_mode: %TRUE if the tooltip was triggered using the keyboard.
+       * @tooltip: a #GtkTooltip.
+       *
+       * Emitted when the mouse has paused over the item for a certain amount
+       * of time, or the tooltip was requested via the keyboard.
+       *
+       * Note that if @keyboard_mode is %TRUE, the values of @x and @y are
+       * undefined and should not be used.
+       *
+       * If the item wants to display a tooltip it should update @tooltip
+       * and return %TRUE.
+       *
+       * Returns: %TRUE if the item has set a tooltip to show.
+       */
+      canvas_item_signals[QUERY_TOOLTIP] =
+	g_signal_new ("query-tooltip",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface, query_tooltip),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__DOUBLE_DOUBLE_BOOLEAN_OBJECT,
+		      G_TYPE_BOOLEAN, 4,
+		      G_TYPE_DOUBLE,
+		      G_TYPE_DOUBLE,
+		      G_TYPE_BOOLEAN,
+		      GTK_TYPE_TOOLTIP);
+
+      /**
+       * GooCanvasItem::grab-broken-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data.
+       *
+       * Emitted when the item's keyboard or pointer grab was lost
+       * unexpectedly.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[GRAB_BROKEN_EVENT] =
+	g_signal_new ("grab_broken_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       grab_broken_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      /**
+       * GooCanvasItem::child-notify
+       * @item: the item that received the signal.
+       * @pspec: the #GParamSpec of the changed child property.
+       *
+       * Emitted for each child property that has changed.
+       * The signal's detail holds the property name. 
+       */
+      canvas_item_signals[CHILD_NOTIFY] =
+	g_signal_new ("child_notify",
+		      iface_type,
+		      G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
+		      G_STRUCT_OFFSET (GooCanvasItemIface, child_notify),
+		      NULL, NULL,
+		      g_cclosure_marshal_VOID__PARAM,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_PARAM);
+
+      /**
+       * GooCanvasItem::animation-finished
+       * @item: the item that received the signal.
+       * @stopped: if the animation was explicitly stopped.
+       *
+       * Emitted when the item animation has finished.
+       */
+      canvas_item_signals[ANIMATION_FINISHED] =
+	g_signal_new ("animation-finished",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface, animation_finished),
+		      NULL, NULL,
+		      g_cclosure_marshal_VOID__BOOLEAN,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_BOOLEAN);
+
+      /**
+       * GooCanvasItem::scroll-event
+       * @item: the item that received the signal.
+       * @target_item: the target of the event.
+       * @event: the event data. The x & y fields contain the mouse position
+       *  in the item's coordinate space. The x_root & y_root fields contain
+       *  the same coordinates converted to the canvas coordinate space.
+       *
+       * Emitted when a button in the 4 to 7 range is pressed. Wheel mice are
+       * usually configured to generate button press events for buttons 4 and 5
+       * when the wheel is turned in an item.
+       *
+       * Returns: %TRUE to stop the signal emission, or %FALSE to let it
+       *  continue.
+       */
+      canvas_item_signals[SCROLL_EVENT] =
+	g_signal_new ("scroll_event",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemIface,
+				       scroll_event),
+		      goo_canvas_boolean_handled_accumulator, NULL,
+		      goo_canvas_marshal_BOOLEAN__OBJECT_BOXED,
+		      G_TYPE_BOOLEAN, 2,
+		      GOO_TYPE_CANVAS_ITEM,
+		      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_object ("parent",
+								_("Parent"),
+								_("The parent item"),
+								GOO_TYPE_CANVAS_ITEM,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_enum ("visibility",
+							      _("Visibility"),
+							      _("When the canvas item is visible"),
+							      GOO_TYPE_CANVAS_ITEM_VISIBILITY,
+							      GOO_CANVAS_ITEM_VISIBLE,
+							      G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_double ("visibility-threshold",
+								_("Visibility Threshold"),
+								_("The scale threshold at which the item becomes visible"),
+								0.0,
+								G_MAXDOUBLE,
+								0.0,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_boxed ("transform",
+							       _("Transform"),
+							       _("The transformation matrix of the item"),
+							       GOO_TYPE_CAIRO_MATRIX,
+							       G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_flags ("pointer-events",
+							       _("Pointer Events"),
+							       _("Specifies when the item receives pointer events"),
+							       GOO_TYPE_CANVAS_POINTER_EVENTS,
+							       GOO_CANVAS_EVENTS_VISIBLE_PAINTED,
+							       G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("title",
+								_("Title"),
+								_("A short context-rich description of the item for use by assistive technologies"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("description",
+								_("Description"),
+								_("A description of the item for use by assistive technologies"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_boolean ("can-focus",
+								 _("Can Focus"),
+								 _("If the item can take the keyboard focus"),
+								 FALSE,
+								 G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("tooltip",
+								_("Tooltip"),
+								_("The tooltip to display for the item"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      _goo_canvas_style_init ();
+
+      initialized = TRUE;
+    }
+}
+
+
+/**
+ * goo_canvas_item_get_canvas:
+ * @item: a #GooCanvasItem.
+ * 
+ * Returns the #GooCanvas containing the given #GooCanvasItem.
+ * 
+ * Returns: the #GooCanvas.
+ **/
+GooCanvas*
+goo_canvas_item_get_canvas (GooCanvasItem *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->get_canvas)
+    {
+      return iface->get_canvas (item);
+    }
+  else
+    {
+      GooCanvasItem *parent = iface->get_parent (item);
+
+      if (parent)
+	return goo_canvas_item_get_canvas (parent);
+      return NULL;
+    }
+}
+
+
+/**
+ * goo_canvas_item_set_canvas:
+ * @item: a #GooCanvasItem.
+ * @canvas: a #GooCanvas
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically container items such as #GooCanvasGroup.
+ *
+ * It sets the canvas of the item.
+ **/
+void
+goo_canvas_item_set_canvas     (GooCanvasItem   *item,
+				GooCanvas       *canvas)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->set_canvas)
+    iface->set_canvas (item, canvas);
+}
+
+
+/**
+ * goo_canvas_item_add_child:
+ * @item: the container to add the item to.
+ * @child: the item to add.
+ * @position: the position of the item, or -1 to place it last (at the top of
+ *  the stacking order).
+ * 
+ * Adds a child item to a container item at the given stack position.
+ **/
+void
+goo_canvas_item_add_child      (GooCanvasItem       *item,
+				GooCanvasItem       *child,
+				gint                 position)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  g_return_if_fail (iface->add_child != NULL);
+  g_return_if_fail (item != child);
+
+  iface->add_child (item, child, position);
+}
+
+
+/**
+ * goo_canvas_item_move_child:
+ * @item: a container item.
+ * @old_position: the current position of the child item.
+ * @new_position: the new position of the child item.
+ * 
+ * Moves a child item to a new stack position within the container.
+ **/
+void
+goo_canvas_item_move_child     (GooCanvasItem       *item,
+				gint                 old_position,
+				gint                 new_position)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  g_return_if_fail (iface->move_child != NULL);
+
+  iface->move_child (item, old_position, new_position);
+}
+
+
+/**
+ * goo_canvas_item_remove_child:
+ * @item: a container item.
+ * @child_num: the position of the child item to remove.
+ * 
+ * Removes the child item at the given position.
+ **/
+void
+goo_canvas_item_remove_child   (GooCanvasItem       *item,
+				gint                 child_num)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  g_return_if_fail (iface->remove_child != NULL);
+
+  iface->remove_child (item, child_num);
+}
+
+
+/**
+ * goo_canvas_item_find_child:
+ * @item: a container item.
+ * @child: the child item to find.
+ * 
+ * Attempts to find the given child item with the container's stack.
+ * 
+ * Returns: the position of the given @child item, or -1 if it isn't found.
+ **/
+gint
+goo_canvas_item_find_child     (GooCanvasItem *item,
+				GooCanvasItem *child)
+{
+  GooCanvasItem *tmp;
+  int n_children, i;
+
+  /* Find the current position of item and above. */
+  n_children = goo_canvas_item_get_n_children (item);
+  for (i = 0; i < n_children; i++)
+    {
+      tmp = goo_canvas_item_get_child (item, i);
+      if (child == tmp)
+	return i;
+    }
+  return -1;
+}
+
+
+/**
+ * goo_canvas_item_is_container:
+ * @item: an item.
+ * 
+ * Tests to see if the given item is a container.
+ * 
+ * Returns: %TRUE if the item is a container.
+ **/
+gboolean
+goo_canvas_item_is_container (GooCanvasItem       *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_n_children ? TRUE : FALSE;
+}
+
+
+/**
+ * goo_canvas_item_get_n_children:
+ * @item: a container item.
+ * 
+ * Gets the number of children of the container.
+ * 
+ * Returns: the number of children.
+ **/
+gint
+goo_canvas_item_get_n_children (GooCanvasItem       *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_n_children ? iface->get_n_children (item) : 0;
+}
+
+
+/**
+ * goo_canvas_item_get_child:
+ * @item: a container item.
+ * @child_num: the position of a child in the container's stack.
+ * 
+ * Gets the child item at the given stack position.
+ * 
+ * Returns: the child item at the given stack position, or %NULL if @child_num
+ * is out of range.
+ **/
+GooCanvasItem*
+goo_canvas_item_get_child (GooCanvasItem       *item,
+			   gint                 child_num)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_child ? iface->get_child (item, child_num) : NULL;
+}
+
+
+/**
+ * goo_canvas_item_get_parent:
+ * @item: an item.
+ * 
+ * Gets the parent of the given item.
+ * 
+ * Returns: the parent item, or %NULL if the item has no parent.
+ **/
+GooCanvasItem*
+goo_canvas_item_get_parent  (GooCanvasItem *item)
+{
+  g_return_val_if_fail (GOO_IS_CANVAS_ITEM (item), NULL);
+
+  return GOO_CANVAS_ITEM_GET_IFACE (item)->get_parent (item);
+}
+
+
+/**
+ * goo_canvas_item_set_parent:
+ * @item: an item.
+ * @parent: the new parent item.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items (specifically container items such as #GooCanvasGroup).
+ * It sets the parent of the child item.
+ * <!--PARAMETERS-->
+ * <note><para>
+ * This function cannot be used to add an item to a group
+ * or to change the parent of an item.
+ * To do that use the #GooCanvasItem:parent property.
+ * </para></note>
+ **/
+void
+goo_canvas_item_set_parent (GooCanvasItem *item,
+			    GooCanvasItem *parent)
+{
+  GOO_CANVAS_ITEM_GET_IFACE (item)->set_parent (item, parent);
+}
+
+
+/**
+ * goo_canvas_item_get_is_static:
+ * @item: an item.
+ * 
+ * Returns %TRUE if the item is static. Static items do not move or change
+ * size when the canvas is scrolled or the scale changes.
+ *
+ * Returns: %TRUE if the item is static.
+ **/
+gboolean
+goo_canvas_item_get_is_static	(GooCanvasItem		*item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->get_is_static)
+    return iface->get_is_static (item);
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_item_set_is_static:
+ * @item: an item.
+ * @is_static: if the item is static.
+ * 
+ * Notifies the item that it is static. Static items do not move or change
+ * size when the canvas is scrolled or the scale changes.
+ *
+ * Container items such as #GooCanvasGroup should call this function when
+ * children are added, to notify children whether they are static or not.
+ * Containers should also pass on any changes in their own status to children.
+ **/
+void
+goo_canvas_item_set_is_static	(GooCanvasItem		*item,
+				 gboolean		 is_static)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->set_is_static)
+    iface->set_is_static (item, is_static);
+}
+
+
+/**
+ * goo_canvas_item_remove:
+ * @item: an item.
+ * 
+ * Removes an item from its parent. If the item is in a canvas it will be
+ * removed.
+ *
+ * This would normally also result in the item being freed.
+ **/
+void
+goo_canvas_item_remove (GooCanvasItem *item)
+{
+  GooCanvasItem *parent;
+  gint child_num;
+
+  parent = goo_canvas_item_get_parent (item);
+  if (!parent)
+    return;
+
+  child_num = goo_canvas_item_find_child (parent, item);
+  if (child_num == -1)
+    return;
+
+  goo_canvas_item_remove_child (parent, child_num);
+}
+
+
+/**
+ * goo_canvas_item_raise:
+ * @item: an item.
+ * @above: the item to raise @item above, or %NULL to raise @item to the top
+ *  of the stack.
+ * 
+ * Raises an item in the stacking order.
+ **/
+void
+goo_canvas_item_raise          (GooCanvasItem *item,
+				GooCanvasItem *above)
+{
+  GooCanvasItem *parent, *child;
+  int n_children, i, item_pos = -1, above_pos = -1;
+
+  parent = goo_canvas_item_get_parent (item);
+  if (!parent || item == above)
+    return;
+
+  /* Find the current position of item and above. */
+  n_children = goo_canvas_item_get_n_children (parent);
+  for (i = 0; i < n_children; i++)
+    {
+      child = goo_canvas_item_get_child (parent, i);
+      if (child == item)
+	item_pos = i;
+      if (child == above)
+	above_pos = i;
+    }
+
+  /* If above is NULL we raise the item to the top of the stack. */
+  if (!above)
+    above_pos = n_children - 1;
+
+  g_return_if_fail (item_pos != -1);
+  g_return_if_fail (above_pos != -1);
+
+  /* Only move the item if the new position is higher in the stack. */
+  if (above_pos > item_pos)
+    goo_canvas_item_move_child (parent, item_pos, above_pos);
+}
+
+
+/**
+ * goo_canvas_item_lower:
+ * @item: an item.
+ * @below: the item to lower @item below, or %NULL to lower @item to the
+ *  bottom of the stack.
+ * 
+ * Lowers an item in the stacking order.
+ **/
+void
+goo_canvas_item_lower          (GooCanvasItem *item,
+				GooCanvasItem *below)
+{
+  GooCanvasItem *parent, *child;
+  int n_children, i, item_pos = -1, below_pos = -1;
+
+  parent = goo_canvas_item_get_parent (item);
+  if (!parent || item == below)
+    return;
+
+  /* Find the current position of item and below. */
+  n_children = goo_canvas_item_get_n_children (parent);
+  for (i = 0; i < n_children; i++)
+    {
+      child = goo_canvas_item_get_child (parent, i);
+      if (child == item)
+	item_pos = i;
+      if (child == below)
+	below_pos = i;
+    }
+
+  /* If below is NULL we lower the item to the bottom of the stack. */
+  if (!below)
+    below_pos = 0;
+
+  g_return_if_fail (item_pos != -1);
+  g_return_if_fail (below_pos != -1);
+
+  /* Only move the item if the new position is lower in the stack. */
+  if (below_pos < item_pos)
+    goo_canvas_item_move_child (parent, item_pos, below_pos);
+}
+
+
+/**
+ * goo_canvas_item_get_transform:
+ * @item: an item.
+ * @transform: the place to store the transform.
+ * 
+ * Gets the transformation matrix of an item.
+ * 
+ * Returns: %TRUE if a transform is set.
+ **/
+gboolean
+goo_canvas_item_get_transform  (GooCanvasItem   *item,
+				cairo_matrix_t  *transform)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_transform ? iface->get_transform (item, transform) : FALSE;
+}
+
+
+/**
+ * goo_canvas_item_get_transform_for_child:
+ * @item: an item.
+ * @child: a child of @item.
+ * @transform: the place to store the transform.
+ * 
+ * Gets the transformation matrix of an item combined with any special
+ * transform needed for the given child. These special transforms are used
+ * by layout items such as #GooCanvasTable.
+ * 
+ * Returns: %TRUE if a transform is set.
+ **/
+gboolean
+goo_canvas_item_get_transform_for_child  (GooCanvasItem  *item,
+					  GooCanvasItem  *child,
+					  cairo_matrix_t *transform)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (child && iface->get_transform_for_child)
+    return iface->get_transform_for_child (item, child, transform);
+
+  /* We fallback to the standard get_transform method. */
+  if (iface->get_transform)
+    return iface->get_transform (item, transform);
+
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_item_set_transform:
+ * @item: an item.
+ * @transform: the new transformation matrix, or %NULL to reset the
+ *  transformation to the identity matrix.
+ * 
+ * Sets the transformation matrix of an item.
+ **/
+void
+goo_canvas_item_set_transform  (GooCanvasItem        *item,
+				const cairo_matrix_t *transform)
+{
+  GOO_CANVAS_ITEM_GET_IFACE (item)->set_transform (item, transform);
+}
+
+
+/**
+ * goo_canvas_item_get_simple_transform:
+ * @item: an item.
+ * @x: returns the x coordinate of the origin of the item's coordinate space.
+ * @y: returns the y coordinate of the origin of the item's coordinate space.
+ * @scale: returns the scale of the item.
+ * @rotation: returns the clockwise rotation of the item, in degrees (0-360).
+ * 
+ * This function can be used to get the position, scale and rotation of an
+ * item, providing that the item has a simple transformation matrix
+ * (e.g. set with goo_canvas_item_set_simple_transform(), or using a
+ * combination of simple translate, scale and rotate operations). If the item
+ * has a complex transformation matrix the results will be incorrect.
+ * 
+ * Returns: %TRUE if a transform is set.
+ **/
+gboolean
+goo_canvas_item_get_simple_transform (GooCanvasItem   *item,
+				      gdouble         *x,
+				      gdouble         *y,
+				      gdouble         *scale,
+				      gdouble         *rotation)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t matrix = { 1, 0, 0, 1, 0, 0 };
+  double x1 = 1.0, y1 = 0.0, radians;
+  gboolean has_transform = FALSE;
+
+  if (iface->get_transform)
+    has_transform = iface->get_transform (item, &matrix);
+
+  if (!has_transform)
+    {
+      *x = *y = *rotation = 0.0;
+      *scale = 1.0;
+      return FALSE;
+    }
+
+  *x = matrix.x0;
+  *y = matrix.y0;
+
+  matrix.x0 = 0.0;
+  matrix.y0 = 0.0;
+
+  cairo_matrix_transform_point (&matrix, &x1, &y1);
+  *scale = sqrt (x1 * x1 + y1 * y1);
+  radians = atan2 (y1, x1);
+  *rotation = radians * (180 / M_PI);
+  if (*rotation < 0)
+    *rotation += 360;
+
+  return TRUE;
+}
+
+
+/**
+ * goo_canvas_item_set_simple_transform:
+ * @item: an item.
+ * @x: the x coordinate of the origin of the item's coordinate space.
+ * @y: the y coordinate of the origin of the item's coordinate space.
+ * @scale: the scale of the item.
+ * @rotation: the clockwise rotation of the item, in degrees.
+ * 
+ * A convenience function to set the item's transformation matrix.
+ **/
+void
+goo_canvas_item_set_simple_transform (GooCanvasItem   *item,
+				      gdouble          x,
+				      gdouble          y,
+				      gdouble          scale,
+				      gdouble          rotation)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  cairo_matrix_translate (&new_matrix, x, y);
+  cairo_matrix_scale (&new_matrix, scale, scale);
+  cairo_matrix_rotate (&new_matrix, rotation * (M_PI  / 180));
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_translate:
+ * @item: an item.
+ * @tx: the amount to move the origin in the horizontal direction.
+ * @ty: the amount to move the origin in the vertical direction.
+ * 
+ * Translates the origin of the item's coordinate system by the given amounts.
+ **/
+void
+goo_canvas_item_translate      (GooCanvasItem *item,
+				gdouble        tx,
+				gdouble        ty)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  iface->get_transform (item, &new_matrix);
+  cairo_matrix_translate (&new_matrix, tx, ty);
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_scale:
+ * @item: an item.
+ * @sx: the amount to scale the horizontal axis.
+ * @sy: the amount to scale the vertical axis.
+ * 
+ * Scales the item's coordinate system by the given amounts.
+ **/
+void
+goo_canvas_item_scale          (GooCanvasItem *item,
+				gdouble        sx,
+				gdouble        sy)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  iface->get_transform (item, &new_matrix);
+  cairo_matrix_scale (&new_matrix, sx, sy);
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_rotate:
+ * @item: an item.
+ * @degrees: the clockwise angle of rotation.
+ * @cx: the x coordinate of the origin of the rotation.
+ * @cy: the y coordinate of the origin of the rotation.
+ * 
+ * Rotates the item's coordinate system by the given amount, about the given
+ * origin.
+ **/
+void
+goo_canvas_item_rotate         (GooCanvasItem *item,
+				gdouble        degrees,
+				gdouble        cx,
+				gdouble        cy)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (item, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_rotate (&new_matrix, radians);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_skew_x:
+ * @item: an item.
+ * @degrees: the skew angle.
+ * @cx: the x coordinate of the origin of the skew transform.
+ * @cy: the y coordinate of the origin of the skew transform.
+ * 
+ * Skews the item's coordinate system along the x axis by the given amount,
+ * about the given origin.
+ **/
+void
+goo_canvas_item_skew_x         (GooCanvasItem *item,
+				gdouble        degrees,
+				gdouble        cx,
+				gdouble        cy)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t tmp, new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (item, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_init (&tmp, 1, 0, tan (radians), 1, 0, 0);
+  cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_skew_y:
+ * @item: an item.
+ * @degrees: the skew angle.
+ * @cx: the x coordinate of the origin of the skew transform.
+ * @cy: the y coordinate of the origin of the skew transform.
+ * 
+ * Skews the item's coordinate system along the y axis by the given amount,
+ * about the given origin.
+ **/
+void
+goo_canvas_item_skew_y         (GooCanvasItem *item,
+				gdouble        degrees,
+				gdouble        cx,
+				gdouble        cy)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  cairo_matrix_t tmp, new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (item, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_init (&tmp, 1, tan (radians), 0, 1, 0, 0);
+  cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (item, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_get_style:
+ * @item: an item.
+ * 
+ * Gets the item's style. If the item doesn't have its own style it will return
+ * its parent's style.
+ * 
+ * Returns: the item's style.
+ **/
+GooCanvasStyle*
+goo_canvas_item_get_style      (GooCanvasItem   *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_style ? iface->get_style (item) : NULL;
+}
+
+
+/**
+ * goo_canvas_item_set_style:
+ * @item: an item.
+ * @style: a style.
+ * 
+ * Sets the item's style, by copying the properties from the given style.
+ **/
+void
+goo_canvas_item_set_style      (GooCanvasItem   *item,
+				GooCanvasStyle  *style)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->set_style)
+    iface->set_style (item, style);
+}
+
+
+typedef struct _GooCanvasItemAnimation  GooCanvasItemAnimation;
+struct _GooCanvasItemAnimation
+{
+  GooCanvasAnimateType type;
+  GooCanvasItem *item;
+  GooCanvasItemModel *model;
+  int step, total_steps;
+  cairo_matrix_t start;
+  gdouble x_start, y_start, scale_start, radians_start;
+  gdouble x_step, y_step, scale_step, radians_step;
+  gboolean absolute;
+  gboolean forward;
+  guint timeout_id;
+};
+
+
+static void
+goo_canvas_item_free_animation (GooCanvasItemAnimation *anim)
+{
+  if (anim->timeout_id)
+    {
+      g_source_remove (anim->timeout_id);
+      anim->timeout_id = 0;
+    }
+
+  g_free (anim);
+}
+
+
+static gboolean
+goo_canvas_item_animate_cb (GooCanvasItemAnimation *anim)
+{
+  GooCanvasItem *item = anim->item;
+  GooCanvasItemModel *model = anim->model;
+  GooCanvasAnimateType type = anim->type;
+  GooCanvasItemIface *iface = NULL;
+  GooCanvasItemModelIface *model_iface = NULL;
+  cairo_matrix_t new_matrix;
+  gboolean keep_source = TRUE;
+  gdouble scale;
+  gint step;
+
+  GDK_THREADS_ENTER ();
+
+  if (model)
+    model_iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  else
+    iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (++anim->step > anim->total_steps)
+    {
+      switch (type)
+	{
+	case GOO_CANVAS_ANIMATE_RESET:
+	  /* Reset the transform to the initial value. */
+	  if (model)
+	    model_iface->set_transform (model, &anim->start);
+	  else
+	    iface->set_transform (item, &anim->start);
+
+	  /* Fall through.. */
+	case GOO_CANVAS_ANIMATE_FREEZE:
+	  keep_source = FALSE;
+	  anim->timeout_id = 0;
+	  /* This will result in a call to goo_canvas_item_free_animation()
+	     above. We've set the timeout_id to 0 so it isn't removed twice. */
+	  if (model)
+	    {
+	      g_object_set_data (G_OBJECT (model), animation_key, NULL);
+	      g_signal_emit_by_name (model, "animation-finished", FALSE);
+	    }
+	  else
+	    {
+	      g_object_set_data (G_OBJECT (item), animation_key, NULL);
+	      g_signal_emit_by_name (item, "animation-finished", FALSE);
+	    }
+	  break;
+
+	case GOO_CANVAS_ANIMATE_RESTART:
+	  anim->step = 0;
+	  break;
+
+	case GOO_CANVAS_ANIMATE_BOUNCE:
+	  anim->forward = !anim->forward;
+	  anim->step = 1;
+	  break;
+	}
+    }
+
+  if (keep_source)
+    {
+      step = anim->forward ? anim->step : anim->total_steps - anim->step;
+
+      if (anim->absolute)
+	{
+	  cairo_matrix_init_identity (&new_matrix);
+	  scale = anim->scale_start + anim->scale_step * step;
+	  cairo_matrix_translate (&new_matrix,
+				  anim->x_start + anim->x_step * step,
+				  anim->y_start + anim->y_step * step);
+	  cairo_matrix_scale (&new_matrix, scale, scale);
+	  cairo_matrix_rotate (&new_matrix,
+			       anim->radians_start + anim->radians_step * step);
+	}
+      else
+	{
+	  new_matrix = anim->start;
+	  scale = 1 + anim->scale_step * step;
+	  cairo_matrix_translate (&new_matrix, anim->x_step * step,
+				  anim->y_step * step);
+	  cairo_matrix_scale (&new_matrix, scale, scale);
+	  cairo_matrix_rotate (&new_matrix, anim->radians_step * step);
+	}
+
+      if (model)
+	model_iface->set_transform (model, &new_matrix);
+      else
+	iface->set_transform (item, &new_matrix);
+    }
+
+  GDK_THREADS_LEAVE ();
+
+  /* Return FALSE to remove the timeout handler when we are finished. */
+  return keep_source;
+}
+
+
+void
+_goo_canvas_item_animate_internal (GooCanvasItem       *item,
+				   GooCanvasItemModel  *model,
+				   gdouble              x,
+				   gdouble              y,
+				   gdouble              scale,
+				   gdouble              degrees,
+				   gboolean             absolute,
+				   gint                 duration,
+				   gint                 step_time,
+				   GooCanvasAnimateType type)
+{
+  GObject *object;
+  cairo_matrix_t matrix = { 1, 0, 0, 1, 0, 0 };
+  GooCanvasItemAnimation *anim;
+
+  if (item)
+    {
+      GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+      iface->get_transform (item, &matrix);
+      object = (GObject*) item;
+    }
+  else
+    {
+      GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+      iface->get_transform (model, &matrix);
+      object = (GObject*) model;
+    }
+
+  anim = g_new (GooCanvasItemAnimation, 1);
+  anim->type = type;
+  anim->item = item;
+  anim->model = model;
+  anim->step = 0;
+  anim->total_steps = duration / step_time;
+  anim->start = matrix;
+  anim->absolute = absolute;
+  anim->forward = TRUE;
+
+  /* For absolute animation we have to try to calculate the current position,
+     scale and rotation. */
+  if (absolute)
+    {
+      cairo_matrix_t tmp_matrix = anim->start;
+      double x1 = 1.0, y1 = 0.0;
+
+      anim->x_start = tmp_matrix.x0;
+      anim->y_start = tmp_matrix.y0;
+
+      tmp_matrix.x0 = 0.0;
+      tmp_matrix.y0 = 0.0;
+
+      cairo_matrix_transform_point (&tmp_matrix, &x1, &y1);
+      anim->scale_start = sqrt (x1 * x1 + y1 * y1);
+      anim->radians_start = atan2 (y1, x1);
+
+      anim->x_step = (x - anim->x_start) / anim->total_steps;
+      anim->y_step = (y - anim->y_start) / anim->total_steps;
+      anim->scale_step = (scale - anim->scale_start) / anim->total_steps;
+      anim->radians_step = (degrees * (M_PI / 180) - anim->radians_start) / anim->total_steps;
+    }
+  else
+    {
+      anim->x_step = x / anim->total_steps;
+      anim->y_step = y / anim->total_steps;
+      anim->scale_step = (scale - 1.0) / anim->total_steps;
+      anim->radians_step = (degrees * (M_PI / 180)) / anim->total_steps;
+    }
+
+
+  /* Store a pointer to the new animation in the item. This will automatically
+     stop any current animation and free it. */
+  g_object_set_data_full (object, animation_key, anim,
+			  (GDestroyNotify) goo_canvas_item_free_animation);
+
+  anim->timeout_id = g_timeout_add (step_time,
+				    (GSourceFunc) goo_canvas_item_animate_cb,
+				    anim);
+}
+
+
+/**
+ * goo_canvas_item_animate:
+ * @item: an item.
+ * @x: the final x coordinate.
+ * @y: the final y coordinate.
+ * @scale: the final scale.
+ * @degrees: the final rotation. This can be negative to rotate anticlockwise,
+ *  and can also be greater than 360 to rotate a number of times.
+ * @absolute: if the @x, @y, @scale and @degrees values are absolute, or
+ *  relative to the current transform. Note that absolute animations only work
+ *  if the item currently has a simple transform. If the item has a shear or
+ *  some other complicated transform it may result in strange animations.
+ * @duration: the duration of the animation, in milliseconds (1/1000ths of a
+ *  second).
+ * @step_time: the time between each animation step, in milliseconds.
+ * @type: specifies what happens when the animation finishes.
+ * 
+ * Animates an item from its current position to the given offsets, scale
+ * and rotation.
+ **/
+void
+goo_canvas_item_animate        (GooCanvasItem *item,
+				gdouble        x,
+				gdouble        y,
+				gdouble        scale,
+				gdouble        degrees,
+				gboolean       absolute,
+				gint           duration,
+				gint           step_time,
+				GooCanvasAnimateType type)
+{
+  _goo_canvas_item_animate_internal (item, NULL, x, y, scale, degrees,
+				     absolute, duration, step_time, type);
+}
+
+
+/**
+ * goo_canvas_item_stop_animation:
+ * @item: an item.
+ * 
+ * Stops any current animation for the given item, leaving it at its current
+ * position.
+ **/
+void
+goo_canvas_item_stop_animation (GooCanvasItem *item)
+{
+  /* This will result in a call to goo_canvas_item_free_animation() above. */
+  g_object_set_data (G_OBJECT (item), animation_key, NULL);
+
+  g_signal_emit_by_name (item, "animation-finished", TRUE);
+}
+
+
+
+
+/**
+ * goo_canvas_item_request_update:
+ * @item: a #GooCanvasItem.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items.
+ *
+ * It requests that an update of the item is scheduled. It will be performed
+ * as soon as the application is idle, and before the canvas is redrawn.
+ **/
+void
+goo_canvas_item_request_update  (GooCanvasItem *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->request_update)
+    iface->request_update (item);
+  else
+    goo_canvas_item_request_update (iface->get_parent (item));
+}
+
+
+/**
+ * goo_canvas_item_get_bounds:
+ * @item: a #GooCanvasItem.
+ * @bounds: a #GooCanvasBounds to return the bounds in.
+ * 
+ * Gets the bounds of the item.
+ *
+ * Note that the bounds includes the entire fill and stroke extents of the
+ * item, whether they are painted or not.
+ **/
+void
+goo_canvas_item_get_bounds  (GooCanvasItem   *item,
+			     GooCanvasBounds *bounds)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  iface->get_bounds (item, bounds);
+}
+
+
+/**
+ * goo_canvas_item_get_items_at:
+ * @item: a #GooCanvasItem.
+ * @x: the x coordinate of the point.
+ * @y: the y coordinate of the point.
+ * @cr: a cairo contect.
+ * @is_pointer_event: %TRUE if the "pointer-events" properties of items should
+ *  be used to determine which parts of the item are tested.
+ * @parent_is_visible: %TRUE if the parent item is visible (which
+ *  implies that all ancestors are also visible).
+ * @found_items: the list of items found so far.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically container items such as #GooCanvasGroup.
+ *
+ * It gets the items at the given point.
+ * 
+ * Returns: the @found_items list, with any more found items added onto
+ *  the start of the list, leaving the top item first.
+ **/
+GList*
+goo_canvas_item_get_items_at (GooCanvasItem  *item,
+			      gdouble         x,
+			      gdouble         y,
+			      cairo_t        *cr,
+			      gboolean        is_pointer_event,
+			      gboolean        parent_is_visible,
+			      GList          *found_items)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->get_items_at)
+    return iface->get_items_at (item, x, y, cr, is_pointer_event,
+				parent_is_visible, found_items);
+  else
+    return found_items;
+}
+
+
+/**
+ * goo_canvas_item_is_visible:
+ * @item: a #GooCanvasItem.
+ * 
+ * Checks if the item is visible.
+ *
+ * This entails checking the item's own visibility setting, as well as those
+ * of its ancestors.
+ *
+ * Note that the item may be scrolled off the screen and so may not
+ * be actually visible to the user.
+ * 
+ * Returns: %TRUE if the item is visible.
+ **/
+gboolean
+goo_canvas_item_is_visible  (GooCanvasItem   *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+  GooCanvasItem *parent;
+
+  if (iface->is_visible)
+    return iface->is_visible (item);
+
+  /* If the item doesn't implement the is_visible method we assume it is
+     visible and check its ancestors. */
+  parent = goo_canvas_item_get_parent (item);
+  if (parent)
+    return goo_canvas_item_is_visible (parent);
+
+  return TRUE;
+}
+
+
+/**
+ * goo_canvas_item_get_model:
+ * @item: a #GooCanvasItem.
+ * 
+ * Gets the model of the given canvas item.
+ * 
+ * Returns: the item's model, or %NULL if it has no model.
+ **/
+GooCanvasItemModel*
+goo_canvas_item_get_model	  (GooCanvasItem   *item)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_model ? iface->get_model (item) : NULL;
+}
+
+
+/**
+ * goo_canvas_item_set_model:
+ * @item: a #GooCanvasItem.
+ * @model: a #GooCanvasItemModel.
+ * 
+ * Sets the model of the given canvas item.
+ **/
+void
+goo_canvas_item_set_model	  (GooCanvasItem      *item,
+				   GooCanvasItemModel *model)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->set_model)
+    iface->set_model (item, model);
+}
+
+
+/**
+ * goo_canvas_item_ensure_updated:
+ * @item: a #GooCanvasItem.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items.
+ *
+ * It updates the canvas immediately, if an update is scheduled.
+ * This ensures that all item bounds are up-to-date.
+ **/
+void
+goo_canvas_item_ensure_updated (GooCanvasItem *item)
+{
+  GooCanvas *canvas;
+
+  canvas = goo_canvas_item_get_canvas (item);
+  if (canvas)
+    goo_canvas_update (canvas);
+}
+
+
+/**
+ * goo_canvas_item_update:
+ * @item: a #GooCanvasItem.
+ * @entire_tree: if the entire subtree should be updated.
+ * @cr: a cairo context.
+ * @bounds: a #GooCanvasBounds to return the new bounds in.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically container items such as #GooCanvasGroup.
+ *
+ * Updates the item, if needed, and any children.
+ **/
+void
+goo_canvas_item_update      (GooCanvasItem   *item,
+			     gboolean         entire_tree,
+			     cairo_t         *cr,
+			     GooCanvasBounds *bounds)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  iface->update (item, entire_tree, cr, bounds);
+}
+
+
+/**
+ * goo_canvas_item_paint:
+ * @item: a #GooCanvasItem.
+ * @cr: a cairo context.
+ * @bounds: the bounds that need to be repainted, in device space.
+ * @scale: the scale to use to determine whether an item should be painted.
+ *  See #GooCanvasItem:visibility-threshold.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically container items such as #GooCanvasGroup.
+ *
+ * It paints the item and all children if they intersect the given bounds.
+ *
+ * Note that the @scale argument may be different to the current scale in the
+ * #GooCanvasItem, e.g. when the canvas is being printed.
+ **/
+void
+goo_canvas_item_paint (GooCanvasItem         *item,
+		       cairo_t               *cr,
+		       const GooCanvasBounds *bounds,
+		       gdouble                scale)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  iface->paint (item, cr, bounds, scale);
+}
+
+
+/**
+ * goo_canvas_item_get_requested_area:
+ * @item: a #GooCanvasItem.
+ * @cr: a cairo context.
+ * @requested_area: a #GooCanvasBounds to return the requested area in, in the
+ *  parent's coordinate space.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout items such as #GooCanvasTable.
+ *
+ * It gets the requested area of a child item.
+ * 
+ * Returns: %TRUE if the item should be allocated space.
+ **/
+gboolean
+goo_canvas_item_get_requested_area (GooCanvasItem    *item,
+				    cairo_t          *cr,
+				    GooCanvasBounds  *requested_area)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  return iface->get_requested_area (item, cr, requested_area);
+}
+
+
+/**
+ * goo_canvas_item_get_requested_height:
+ * @item: a #GooCanvasItem.
+ * @cr: a cairo context.
+ * @width: the width that the item may be allocated.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout items such as #GooCanvasTable.
+ *
+ * It gets the requested height of a child item, assuming it is allocated the
+ * given width. This is useful for text items whose requested height may change
+ * depending on the allocated width.
+ * 
+ * Returns: the requested height of the item, given the allocated width,
+ *  or %-1 if the item doesn't support this method or its height doesn't
+ *  change when allocated different widths.
+ **/
+gdouble
+goo_canvas_item_get_requested_height (GooCanvasItem       *item,
+				      cairo_t		  *cr,
+				      gdouble              width)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  if (iface->get_requested_height)
+    return iface->get_requested_height (item, cr, width);
+  else
+    return -1;
+}
+
+
+/**
+ * goo_canvas_item_allocate_area:
+ * @item: a #GooCanvasItem.
+ * @cr: a cairo context.
+ * @requested_area: the area that the item originally requested, in the
+ *  parent's coordinate space.
+ * @allocated_area: the area that the item has been allocated, in the parent's
+ *  coordinate space.
+ * @x_offset: the x offset of the allocated area from the requested area in
+ *  the device coordinate space.
+ * @y_offset: the y offset of the allocated area from the requested area in
+ *  the device coordinate space.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout items such as #GooCanvasTable.
+ *
+ * It allocates an area to a child #GooCanvasItem.
+ *
+ * Note that the parent layout item will use a transform to move each of its
+ * children for the layout, so there is no need for the child item to
+ * reposition itself. It only needs to recalculate its device bounds.
+ *
+ * To help recalculate the item's device bounds, the @x_offset and @y_offset
+ * of the child item's allocated position from its requested position are
+ * provided. Simple items can just add these to their bounds.
+ **/
+void
+goo_canvas_item_allocate_area      (GooCanvasItem         *item,
+				    cairo_t               *cr,
+				    const GooCanvasBounds *requested_area,
+				    const GooCanvasBounds *allocated_area,
+				    gdouble                x_offset,
+				    gdouble                y_offset)
+{
+  GooCanvasItemIface *iface = GOO_CANVAS_ITEM_GET_IFACE (item);
+
+  iface->allocate_area (item, cr, requested_area, allocated_area,
+			x_offset, y_offset);
+}
+
+
+/*
+ * Child Properties.
+ */
+static inline void
+item_get_child_property (GObject      *object,
+			 GObject      *child,
+			 GParamSpec   *pspec,
+			 GValue       *value,
+			 gboolean      is_model)
+{
+  GObjectClass *class;
+
+  class = g_type_class_peek (pspec->owner_type);
+
+  if (is_model)
+    {
+      GooCanvasItemModelIface *iface;
+
+      iface = g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM_MODEL);
+      iface->get_child_property ((GooCanvasItemModel*) object,
+				 (GooCanvasItemModel*) child,
+				 pspec->param_id, value, pspec);
+    }
+  else
+    {
+      GooCanvasItemIface *iface;
+
+      iface = g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM);
+      iface->get_child_property ((GooCanvasItem*) object,
+				 (GooCanvasItem*) child,
+				 pspec->param_id, value, pspec);
+    }
+}
+
+
+void
+_goo_canvas_item_get_child_property_internal (GObject              *object,
+					      GObject              *child,
+					      const gchar          *property_name,
+					      GValue               *value,
+					      GParamSpecPool       *property_pool,
+					      gboolean              is_model)
+{
+  GParamSpec *pspec;
+
+  g_object_ref (object);
+  g_object_ref (child);
+  pspec = g_param_spec_pool_lookup (property_pool, property_name,
+				    G_OBJECT_TYPE (object), TRUE);
+  if (!pspec)
+    g_warning ("%s: class `%s' has no child property named `%s'",
+	       G_STRLOC,
+	       G_OBJECT_TYPE_NAME (object),
+	       property_name);
+  else if (!(pspec->flags & G_PARAM_READABLE))
+    g_warning ("%s: child property `%s' of class `%s' is not readable",
+	       G_STRLOC,
+	       pspec->name,
+	       G_OBJECT_TYPE_NAME (object));
+  else
+    {
+      GValue *prop_value, tmp_value = { 0, };
+
+      /* auto-conversion of the callers value type
+       */
+      if (G_VALUE_TYPE (value) == G_PARAM_SPEC_VALUE_TYPE (pspec))
+	{
+	  g_value_reset (value);
+	  prop_value = value;
+	}
+      else if (!g_value_type_transformable (G_PARAM_SPEC_VALUE_TYPE (pspec), G_VALUE_TYPE (value)))
+	{
+	  g_warning ("can't retrieve child property `%s' of type `%s' as value of type `%s'",
+		     pspec->name,
+		     g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+		     G_VALUE_TYPE_NAME (value));
+	  g_object_unref (child);
+	  g_object_unref (object);
+	  return;
+	}
+      else
+	{
+	  g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+	  prop_value = &tmp_value;
+	}
+      item_get_child_property (object, child, pspec, prop_value, is_model);
+      if (prop_value != value)
+	{
+	  g_value_transform (prop_value, value);
+	  g_value_unset (&tmp_value);
+	}
+    }
+  g_object_unref (child);
+  g_object_unref (object);
+}
+
+
+void
+_goo_canvas_item_get_child_properties_internal (GObject              *object,
+						GObject              *child,
+						va_list	              var_args,
+						GParamSpecPool       *property_pool,
+						GObjectNotifyContext *notify_context,
+						gboolean              is_model)
+{
+  g_object_ref (object);
+  g_object_ref (child);
+
+  for (;;)
+    {
+      GValue value = { 0, };
+      GParamSpec *pspec;
+      gchar *name, *error = NULL;
+
+      name = va_arg (var_args, gchar*);
+      if (!name)
+	break;
+
+      pspec = g_param_spec_pool_lookup (property_pool, name,
+					G_OBJECT_TYPE (object), TRUE);
+      if (!pspec)
+	{
+	  g_warning ("%s: class `%s' has no child property named `%s'",
+		     G_STRLOC, G_OBJECT_TYPE_NAME (object), name);
+	  break;
+	}
+      if (!(pspec->flags & G_PARAM_READABLE))
+	{
+	  g_warning ("%s: child property `%s' of class `%s' is not readable",
+		     G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (object));
+	  break;
+	}
+      g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+      item_get_child_property (object, child, pspec, &value, is_model);
+      G_VALUE_LCOPY (&value, var_args, 0, &error);
+      if (error)
+	{
+	  g_warning ("%s: %s", G_STRLOC, error);
+	  g_free (error);
+	  g_value_unset (&value);
+	  break;
+	}
+      g_value_unset (&value);
+    }
+
+  g_object_unref (child);
+  g_object_unref (object);
+}
+
+
+static inline void
+canvas_item_set_child_property (GObject            *object,
+				GObject            *child,
+				GParamSpec         *pspec,
+				const GValue       *value,
+				GObjectNotifyQueue *nqueue,
+				gboolean            is_model)
+{
+  GValue tmp_value = { 0, };
+
+  /* provide a copy to work from, convert (if necessary) and validate */
+  g_value_init (&tmp_value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+  if (!g_value_transform (value, &tmp_value))
+    g_warning ("unable to set child property `%s' of type `%s' from value of type `%s'",
+	       pspec->name,
+	       g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)),
+	       G_VALUE_TYPE_NAME (value));
+  else if (g_param_value_validate (pspec, &tmp_value) && !(pspec->flags & G_PARAM_LAX_VALIDATION))
+    {
+      gchar *contents = g_strdup_value_contents (value);
+
+      g_warning ("value \"%s\" of type `%s' is invalid for property `%s' of type `%s'",
+		 contents,
+		 G_VALUE_TYPE_NAME (value),
+		 pspec->name,
+		 g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
+      g_free (contents);
+    }
+  else
+    {
+      GObjectClass *class = g_type_class_peek (pspec->owner_type);
+
+      if (is_model)
+	{
+	  GooCanvasItemModelIface *iface;
+
+	  iface = g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM_MODEL);
+	  iface->set_child_property ((GooCanvasItemModel*) object,
+				     (GooCanvasItemModel*) child,
+				     pspec->param_id, &tmp_value, pspec);
+	}
+      else
+	{
+	  GooCanvasItemIface *iface;
+
+	  iface = g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM);
+	  iface->set_child_property ((GooCanvasItem*) object,
+				     (GooCanvasItem*) child,
+				     pspec->param_id, &tmp_value, pspec);
+	}
+
+      g_object_notify_queue_add (G_OBJECT (child), nqueue, pspec);
+    }
+  g_value_unset (&tmp_value);
+}
+
+
+void
+_goo_canvas_item_set_child_property_internal (GObject              *object,
+					      GObject              *child,
+					      const gchar          *property_name,
+					      const GValue         *value,
+					      GParamSpecPool       *property_pool,
+					      GObjectNotifyContext *notify_context,
+					      gboolean              is_model)
+{
+  GObjectNotifyQueue *nqueue;
+  GParamSpec *pspec;
+
+  g_object_ref (object);
+  g_object_ref (child);
+
+  nqueue = g_object_notify_queue_freeze (child, notify_context);
+  pspec = g_param_spec_pool_lookup (property_pool, property_name,
+				    G_OBJECT_TYPE (object), TRUE);
+  if (!pspec)
+    g_warning ("%s: class `%s' has no child property named `%s'",
+	       G_STRLOC,
+	       G_OBJECT_TYPE_NAME (object),
+	       property_name);
+  else if (!(pspec->flags & G_PARAM_WRITABLE))
+    g_warning ("%s: child property `%s' of class `%s' is not writable",
+	       G_STRLOC,
+	       pspec->name,
+	       G_OBJECT_TYPE_NAME (object));
+  else
+    {
+      canvas_item_set_child_property (object, child, pspec,
+				      value, nqueue, is_model);
+    }
+  g_object_notify_queue_thaw (child, nqueue);
+  g_object_unref (object);
+  g_object_unref (child);
+}
+
+
+void
+_goo_canvas_item_set_child_properties_internal (GObject              *object,
+						GObject              *child,
+						va_list	              var_args,
+						GParamSpecPool       *property_pool,
+						GObjectNotifyContext *notify_context,
+						gboolean              is_model)
+{
+  GObjectNotifyQueue *nqueue;
+
+  g_object_ref (object);
+  g_object_ref (child);
+
+  nqueue = g_object_notify_queue_freeze (child, notify_context);
+
+  for (;;)
+    {
+      GValue value = { 0, };
+      GParamSpec *pspec;
+      gchar *name, *error = NULL;
+
+      name = va_arg (var_args, gchar*);
+      if (!name)
+	break;
+
+      pspec = g_param_spec_pool_lookup (property_pool, name,
+					G_OBJECT_TYPE (object), TRUE);
+      if (!pspec)
+	{
+	  g_warning ("%s: class `%s' has no child property named `%s'",
+		     G_STRLOC, G_OBJECT_TYPE_NAME (object), name);
+	  break;
+	}
+      if (!(pspec->flags & G_PARAM_WRITABLE))
+	{
+	  g_warning ("%s: child property `%s' of class `%s' is not writable",
+		     G_STRLOC, pspec->name, G_OBJECT_TYPE_NAME (object));
+	  break;
+	}
+      g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+      G_VALUE_COLLECT (&value, var_args, 0, &error);
+      if (error)
+	{
+	  g_warning ("%s: %s", G_STRLOC, error);
+	  g_free (error);
+
+	  /* we purposely leak the value here, it might not be
+	   * in a sane state if an error condition occoured
+	   */
+	  break;
+	}
+      canvas_item_set_child_property (object, child, pspec, &value, nqueue,
+				      is_model);
+      g_value_unset (&value);
+    }
+  g_object_notify_queue_thaw (G_OBJECT (child), nqueue);
+
+  g_object_unref (object);
+  g_object_unref (child);
+}
+
+
+/**
+ * goo_canvas_item_get_child_property:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @property_name: the name of the child property to get.
+ * @value: a location to return the value.
+ * 
+ * Gets a child property of @child.
+ **/
+void
+goo_canvas_item_get_child_property (GooCanvasItem *item,
+				    GooCanvasItem *child,
+				    const gchar   *property_name,
+				    GValue        *value)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (child));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (G_IS_VALUE (value));
+
+  _goo_canvas_item_get_child_property_internal ((GObject*) item, (GObject*) child, property_name, value, _goo_canvas_item_child_property_pool, FALSE);
+}
+
+
+/**
+ * goo_canvas_item_set_child_property:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @property_name: the name of the child property to set.
+ * @value: the value to set the property to.
+ * 
+ * Sets a child property of @child.
+ **/
+void
+goo_canvas_item_set_child_property (GooCanvasItem   *item,
+				    GooCanvasItem   *child,
+				    const gchar     *property_name,
+				    const GValue    *value)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (child));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (G_IS_VALUE (value));
+
+  _goo_canvas_item_set_child_property_internal ((GObject*) item, (GObject*) child, property_name, value, _goo_canvas_item_child_property_pool, _goo_canvas_item_child_property_notify_context, FALSE);
+}
+
+
+/**
+ * goo_canvas_item_get_child_properties_valist:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @var_args: pairs of property names and value pointers, and a terminating
+ *  %NULL.
+ * 
+ * Gets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_get_child_properties_valist (GooCanvasItem   *item,
+					     GooCanvasItem   *child,
+					     va_list	      var_args)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (child));
+
+  _goo_canvas_item_get_child_properties_internal ((GObject*) item, (GObject*) child, var_args, _goo_canvas_item_child_property_pool, _goo_canvas_item_child_property_notify_context, FALSE);
+}
+
+
+/**
+ * goo_canvas_item_set_child_properties_valist:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @var_args: pairs of property names and values, and a terminating %NULL.
+ * 
+ * Sets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_set_child_properties_valist (GooCanvasItem   *item,
+					     GooCanvasItem   *child,
+					     va_list	      var_args)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (item));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM (child));
+
+  _goo_canvas_item_set_child_properties_internal ((GObject*) item, (GObject*) child, var_args, _goo_canvas_item_child_property_pool, _goo_canvas_item_child_property_notify_context, FALSE);
+}
+
+
+/**
+ * goo_canvas_item_get_child_properties:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @...: pairs of property names and value pointers, and a terminating %NULL.
+ * 
+ * Gets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_get_child_properties        (GooCanvasItem   *item,
+					     GooCanvasItem   *child,
+					     ...)
+{
+  va_list var_args;
+  
+  va_start (var_args, child);
+  goo_canvas_item_get_child_properties_valist (item, child, var_args);
+  va_end (var_args);
+}
+
+
+/**
+ * goo_canvas_item_set_child_properties:
+ * @item: a #GooCanvasItem.
+ * @child: a child #GooCanvasItem.
+ * @...: pairs of property names and values, and a terminating %NULL.
+ * 
+ * Sets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_set_child_properties        (GooCanvasItem   *item,
+					     GooCanvasItem   *child,
+					     ...)
+{
+  va_list var_args;
+  
+  va_start (var_args, child);
+  goo_canvas_item_set_child_properties_valist (item, child, var_args);
+  va_end (var_args);
+}
+
+
+
+/**
+ * goo_canvas_item_class_install_child_property:
+ * @iclass: a #GObjectClass
+ * @property_id: the id for the property
+ * @pspec: the #GParamSpec for the property
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout container items such as #GooCanvasTable.
+ *
+ * It installs a child property on a canvas item class. 
+ **/
+void
+goo_canvas_item_class_install_child_property (GObjectClass *iclass,
+					      guint         property_id,
+					      GParamSpec   *pspec)
+{
+  g_return_if_fail (G_IS_OBJECT_CLASS (iclass));
+  g_return_if_fail (G_IS_PARAM_SPEC (pspec));
+  g_return_if_fail (property_id > 0);
+
+  if (g_param_spec_pool_lookup (_goo_canvas_item_child_property_pool,
+				pspec->name, G_OBJECT_CLASS_TYPE (iclass),
+				FALSE))
+    {
+      g_warning (G_STRLOC ": class `%s' already contains a child property named `%s'",
+		 G_OBJECT_CLASS_NAME (iclass), pspec->name);
+      return;
+    }
+  g_param_spec_ref (pspec);
+  g_param_spec_sink (pspec);
+  pspec->param_id = property_id;
+  g_param_spec_pool_insert (_goo_canvas_item_child_property_pool, pspec,
+			    G_OBJECT_CLASS_TYPE (iclass));
+}
+
+/**
+ * goo_canvas_item_class_find_child_property:
+ * @iclass: a #GObjectClass
+ * @property_name: the name of the child property to find
+ * @returns: the #GParamSpec of the child property or %NULL if @class has no
+ *   child property with that name.
+ *
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout container items such as #GooCanvasTable.
+ *
+ * It finds a child property of a canvas item class by name.
+ */
+GParamSpec*
+goo_canvas_item_class_find_child_property (GObjectClass *iclass,
+					   const gchar  *property_name)
+{
+  g_return_val_if_fail (G_IS_OBJECT_CLASS (iclass), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  return g_param_spec_pool_lookup (_goo_canvas_item_child_property_pool,
+				   property_name, G_OBJECT_CLASS_TYPE (iclass),
+				   TRUE);
+}
+
+/**
+ * goo_canvas_item_class_list_child_properties:
+ * @iclass: a #GObjectClass
+ * @n_properties: location to return the number of child properties found
+ * @returns: a newly allocated array of #GParamSpec*. The array must be 
+ *           freed with g_free().
+ *
+ * This function is only intended to be used when implementing new canvas
+ * items, specifically layout container items such as #GooCanvasTable.
+ *
+ * It returns all child properties of a canvas item class.
+ */
+GParamSpec**
+goo_canvas_item_class_list_child_properties (GObjectClass *iclass,
+					     guint        *n_properties)
+{
+  GParamSpec **pspecs;
+  guint n;
+
+  g_return_val_if_fail (G_IS_OBJECT_CLASS (iclass), NULL);
+
+  pspecs = g_param_spec_pool_list (_goo_canvas_item_child_property_pool,
+				   G_OBJECT_CLASS_TYPE (iclass), &n);
+  if (n_properties)
+    *n_properties = n;
+
+  return pspecs;
+}
diff --git a/libgoocanvas/goocanvasitem.h b/libgoocanvas/goocanvasitem.h
new file mode 100644
index 0000000..76c61ec
--- /dev/null
+++ b/libgoocanvas/goocanvasitem.h
@@ -0,0 +1,479 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitem.h - interface for canvas items & groups.
+ */
+#ifndef __GOO_CANVAS_ITEM_H__
+#define __GOO_CANVAS_ITEM_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasstyle.h"
+
+G_BEGIN_DECLS
+
+
+/**
+ * GooCanvasAnimateType
+ * @GOO_CANVAS_ANIMATE_FREEZE: the item remains in the final position.
+ * @GOO_CANVAS_ANIMATE_RESET: the item is moved back to the initial position.
+ * @GOO_CANVAS_ANIMATE_RESTART: the animation is restarted from the initial
+ *  position.
+ * @GOO_CANVAS_ANIMATE_BOUNCE: the animation bounces back and forth between the
+ *  start and end positions.
+ *
+ * #GooCanvasAnimateType is used to specify what happens when the end of an
+ * animation is reached.
+ */
+typedef enum
+{
+  GOO_CANVAS_ANIMATE_FREEZE,
+  GOO_CANVAS_ANIMATE_RESET,
+  GOO_CANVAS_ANIMATE_RESTART,
+  GOO_CANVAS_ANIMATE_BOUNCE
+} GooCanvasAnimateType;
+
+
+/**
+ * GooCanvasBounds
+ * @x1: the left edge.
+ * @y1: the top edge.
+ * @x2: the right edge.
+ * @y2: the bottom edge.
+ *
+ * #GooCanvasBounds represents the bounding box of an item in the canvas.
+ */
+typedef struct _GooCanvasBounds GooCanvasBounds;
+struct _GooCanvasBounds
+{
+  gdouble x1, y1, x2, y2;
+};
+
+GType goo_canvas_bounds_get_type (void) G_GNUC_CONST;
+#define GOO_TYPE_CANVAS_BOUNDS (goo_canvas_bounds_get_type ())
+
+
+#define GOO_TYPE_CANVAS_ITEM            (goo_canvas_item_get_type ())
+#define GOO_CANVAS_ITEM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ITEM, GooCanvasItem))
+#define GOO_IS_CANVAS_ITEM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ITEM))
+#define GOO_CANVAS_ITEM_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GOO_TYPE_CANVAS_ITEM, GooCanvasItemIface))
+
+
+/* Workaround for circular dependencies. Include this file first. */
+typedef struct _GooCanvas           GooCanvas;
+typedef struct _GooCanvasItemModel  GooCanvasItemModel;
+
+
+/**
+ * GooCanvasItem
+ *
+ * #GooCanvasItem is a typedef used for objects that implement the
+ * #GooCanvasItem interface.
+ *
+ * (There is no actual #GooCanvasItem struct, since it is only an interface.
+ * But using '#GooCanvasItem' is more helpful than using '#GObject'.)
+ */
+typedef struct _GooCanvasItem       GooCanvasItem;
+
+
+/**
+ * GooCanvasItemIface
+ * @get_canvas: returns the canvas the item is in.
+ * @set_canvas: sets the canvas the item is in.
+ * @get_n_children: returns the number of children of the item.
+ * @get_child: returns the child at the given index.
+ * @request_update: requests that an update is scheduled.
+ * @add_child: adds a child.
+ * @move_child: moves a child up or down the stacking order.
+ * @remove_child: removes a child.
+ * @get_child_property: gets a child property of a given child item,
+ *  e.g. the "row" or "column" property of an item in a #GooCanvasTable.
+ * @set_child_property: sets a child property for a given child item.
+ * @get_transform_for_child: gets the transform used to lay out a given child.
+ * @get_parent: gets the item's parent.
+ * @set_parent: sets the item's parent.
+ * @get_bounds: gets the bounds of the item.
+ * @get_items_at: gets all the items at the given point.
+ * @update: updates the item, if needed. It recalculates the bounds of the item
+ *  and requests redraws of parts of the canvas if necessary.
+ * @paint: renders the item to the given cairo context.
+ * @get_requested_area: returns the requested area of the item, in its parent's
+ *  coordinate space. This is only used for items in layout containers such as
+ *  #GooCanvasTable.
+ * @allocate_area: allocates the item's area, in its parent's coordinate space.
+ *  The item must recalculate its bounds and request redraws of parts of the
+ *  canvas if necessary. This is only used for items in layout containers such
+ *  as #GooCanvasTable.
+ * @get_transform: gets the item's transformation matrix.
+ * @set_transform: sets the item's transformation matrix.
+ * @get_style: gets the item's style.
+ * @set_style: sets the item's style.
+ * @is_visible: returns %TRUE if the item is currently visible.
+ * @get_is_static: returns %TRUE if the item is static.
+ * @set_is_static: notifies the item whether it is static or not.
+ * @get_requested_height: returns the requested height of the item,
+ *  given a particular allocated width, using the parent's coordinate space.
+ * @get_model: gets the model that the canvas item is viewing.
+ * @set_model: sets the model that the canvas item will view.
+ * @enter_notify_event: signal emitted when the mouse enters the item.
+ * @leave_notify_event: signal emitted when the mouse leaves the item.
+ * @motion_notify_event: signal emitted when the mouse moves within the item.
+ * @button_press_event: signal emitted when a mouse button is pressed within
+ *  the item.
+ * @button_release_event: signal emitted when a mouse button is released.
+ * @focus_in_event: signal emitted when the item receices the keyboard focus.
+ * @focus_out_event: signal emitted when the item loses the keyboard focus.
+ * @key_press_event: signal emitted when a key is pressed.
+ * @key_release_event: signal emitted when a key is released.
+ * @grab_broken_event: signal emitted when a grab that the item has is lost.
+ * @child_notify: signal emitted when a child property is changed.
+ * @query_tooltip: signal emitted to query the tooltip of an item.
+ * @animation_finished: signal emitted when the item's animation has finished.
+ * @scroll_event: signal emitted when the mouse wheel is activated within
+ * the item.
+ *
+ * #GooCanvasItemIFace holds the virtual methods that make up the
+ * #GooCanvasItem interface.
+ *
+ * Simple canvas items only need to implement the get_parent(), set_parent(),
+ * get_bounds(), get_items_at(), update() and paint() methods (and also
+ * get_requested_area() and allocate_area() if they are going to be used
+ * inside a layout container like #GooCanvasTable).
+ *
+ * Items that support transforms should also implement get_transform() and
+ * set_transform(). Items that support styles should implement get_style()
+ * and set_style().
+ *
+ * Container items must implement get_canvas(), set_canvas(),
+ * get_n_children(), get_child() and request_update(). Containers that support
+ * dynamic changes to their children should implement add_child(),
+ * move_child() and remove_child(). Layout containers like #GooCanvasTable
+ * may implement get_child_property(), set_child_property() and
+ * get_transform_for_child().
+ */
+typedef struct _GooCanvasItemIface  GooCanvasItemIface;
+
+struct _GooCanvasItemIface
+{
+  /*< private >*/
+  GTypeInterface base_iface;
+
+  /*< public >*/
+  /* Virtual methods that group items must implement. */
+  GooCanvas*		(* get_canvas)			(GooCanvasItem		*item);
+  void			(* set_canvas)			(GooCanvasItem		*item,
+							 GooCanvas		*canvas);
+  gint			(* get_n_children)		(GooCanvasItem		*item);
+  GooCanvasItem*	(* get_child)			(GooCanvasItem		*item,
+							 gint			 child_num);
+  void			(* request_update)		(GooCanvasItem		*item);
+
+  /* Virtual methods that group items may implement. */
+  void			(* add_child)			(GooCanvasItem		*item,
+							 GooCanvasItem		*child,
+							 gint			 position);
+  void			(* move_child)			(GooCanvasItem		*item,
+							 gint			 old_position,
+							 gint			 new_position);
+  void			(* remove_child)		(GooCanvasItem		*item,
+							 gint			 child_num);
+  void			(* get_child_property)		(GooCanvasItem		*item,
+							 GooCanvasItem		*child,
+							 guint			 property_id,
+							 GValue			*value,
+							 GParamSpec		*pspec);
+  void			(* set_child_property)		(GooCanvasItem		*item,
+							 GooCanvasItem		*child,
+							 guint			 property_id,
+							 const GValue		*value,
+							 GParamSpec		*pspec);
+  gboolean		(* get_transform_for_child)	(GooCanvasItem		*item,
+							 GooCanvasItem		*child,
+							 cairo_matrix_t		*transform);
+
+  /* Virtual methods that all canvas items must implement. */
+  GooCanvasItem*	(* get_parent)			(GooCanvasItem		*item);
+  void			(* set_parent)			(GooCanvasItem		*item,
+							 GooCanvasItem		*parent);
+  void			(* get_bounds)			(GooCanvasItem		*item,
+							 GooCanvasBounds	*bounds);
+  GList*		(* get_items_at)		(GooCanvasItem		*item,
+							 gdouble		 x,
+							 gdouble		 y,
+							 cairo_t		*cr,
+							 gboolean		 is_pointer_event,
+							 gboolean		 parent_is_visible,
+							 GList                  *found_items);
+  void			(* update)			(GooCanvasItem		*item,
+							 gboolean		 entire_tree,
+							 cairo_t		*cr,
+							 GooCanvasBounds	*bounds);
+  void			(* paint)			(GooCanvasItem		*item,
+							 cairo_t		*cr,
+							 const GooCanvasBounds	*bounds,
+							 gdouble		 scale);
+
+  gboolean		(* get_requested_area)		(GooCanvasItem		*item,
+							 cairo_t		*cr,
+							 GooCanvasBounds	*requested_area);
+  void			(* allocate_area)		(GooCanvasItem		*item,
+							 cairo_t		*cr,
+							 const GooCanvasBounds	*requested_area,
+							 const GooCanvasBounds	*allocated_area,
+							 gdouble		 x_offset,
+							 gdouble		 y_offset);
+
+  /* Virtual methods that canvas items may implement. */
+  gboolean		(* get_transform)		(GooCanvasItem		*item,
+							 cairo_matrix_t		*transform);
+  void			(* set_transform)		(GooCanvasItem		*item,
+							 const cairo_matrix_t	*transform);
+  GooCanvasStyle*	(* get_style)			(GooCanvasItem		*item);
+  void			(* set_style)			(GooCanvasItem		*item,
+							 GooCanvasStyle		*style);
+  gboolean		(* is_visible)			(GooCanvasItem		*item);
+  gdouble               (* get_requested_height)	(GooCanvasItem		*item,
+							 cairo_t		*cr,
+							 gdouble		 width);
+
+  /* Virtual methods that model/view items must implement. */
+  GooCanvasItemModel*	(* get_model)			(GooCanvasItem		*item);
+  void			(* set_model)			(GooCanvasItem		*item,
+							 GooCanvasItemModel	*model);
+
+
+  /* Signals. */
+  gboolean		(* enter_notify_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventCrossing	*event);
+  gboolean		(* leave_notify_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventCrossing	*event);
+  gboolean		(* motion_notify_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventMotion		*event);
+  gboolean		(* button_press_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventButton		*event);
+  gboolean		(* button_release_event)	(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventButton		*event);
+  gboolean		(* focus_in_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventFocus		*event);
+  gboolean		(* focus_out_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventFocus		*event);
+  gboolean		(* key_press_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventKey		*event);
+  gboolean		(* key_release_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventKey		*event);
+  gboolean		(* grab_broken_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventGrabBroken	*event);
+  void			(* child_notify)		(GooCanvasItem		*item,
+							 GParamSpec		*pspec);
+  gboolean		(* query_tooltip)		(GooCanvasItem		*item,
+							 gdouble		 x,
+							 gdouble		 y,
+							 gboolean		 keyboard_tooltip,
+							 GtkTooltip		*tooltip);
+
+  gboolean		(* get_is_static)		(GooCanvasItem		*item);
+  void			(* set_is_static)		(GooCanvasItem		*item,
+							 gboolean		 is_static);
+
+  void			(* animation_finished)		(GooCanvasItem           *item,
+							 gboolean                 stopped);
+
+  gboolean		(* scroll_event)		(GooCanvasItem		*item,
+							 GooCanvasItem		*target,
+							 GdkEventScroll		*event);
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType              goo_canvas_item_get_type       (void) G_GNUC_CONST;
+
+
+/*
+ * Group functions - these should only be called on container items.
+ */
+gint               goo_canvas_item_get_n_children (GooCanvasItem   *item);
+GooCanvasItem*     goo_canvas_item_get_child      (GooCanvasItem   *item,
+						   gint             child_num);
+gint               goo_canvas_item_find_child     (GooCanvasItem   *item,
+						   GooCanvasItem   *child);
+void               goo_canvas_item_add_child      (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   gint             position);
+void               goo_canvas_item_move_child     (GooCanvasItem   *item,
+						   gint             old_position,
+						   gint             new_position);
+void               goo_canvas_item_remove_child   (GooCanvasItem   *item,
+						   gint             child_num);
+
+void  goo_canvas_item_get_child_property	  (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   const gchar     *property_name,
+						   GValue          *value);
+void  goo_canvas_item_set_child_property	  (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   const gchar     *property_name,
+						   const GValue    *value);
+void  goo_canvas_item_get_child_properties        (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   ...) G_GNUC_NULL_TERMINATED;
+void  goo_canvas_item_set_child_properties        (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   ...) G_GNUC_NULL_TERMINATED;
+void  goo_canvas_item_get_child_properties_valist (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   va_list	    var_args);
+void  goo_canvas_item_set_child_properties_valist (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   va_list	    var_args);
+
+gboolean goo_canvas_item_get_transform_for_child  (GooCanvasItem   *item,
+						   GooCanvasItem   *child,
+						   cairo_matrix_t  *transform);
+
+
+/*
+ * Item functions - these are safe to call on all items.
+ */
+GooCanvas*         goo_canvas_item_get_canvas     (GooCanvasItem   *item);
+void               goo_canvas_item_set_canvas     (GooCanvasItem   *item,
+						   GooCanvas       *canvas);
+GooCanvasItem*     goo_canvas_item_get_parent     (GooCanvasItem   *item);
+void               goo_canvas_item_set_parent	  (GooCanvasItem   *item,
+						   GooCanvasItem   *parent);
+void               goo_canvas_item_remove         (GooCanvasItem   *item);
+gboolean           goo_canvas_item_is_container   (GooCanvasItem   *item);
+
+void               goo_canvas_item_raise          (GooCanvasItem   *item,
+						   GooCanvasItem   *above);
+void               goo_canvas_item_lower          (GooCanvasItem   *item,
+						   GooCanvasItem   *below);
+
+gboolean           goo_canvas_item_get_transform  (GooCanvasItem   *item,
+						   cairo_matrix_t  *transform);
+void               goo_canvas_item_set_transform  (GooCanvasItem         *item,
+						   const cairo_matrix_t  *transform);
+gboolean	   goo_canvas_item_get_simple_transform (GooCanvasItem   *item,
+							 gdouble         *x,
+							 gdouble         *y,
+							 gdouble         *scale,
+							 gdouble         *rotation);
+void               goo_canvas_item_set_simple_transform (GooCanvasItem   *item,
+							 gdouble          x,
+							 gdouble          y,
+							 gdouble          scale,
+							 gdouble          rotation);
+
+void               goo_canvas_item_translate      (GooCanvasItem   *item,
+						   gdouble          tx,
+						   gdouble          ty);
+void               goo_canvas_item_scale          (GooCanvasItem   *item,
+						   gdouble          sx,
+						   gdouble          sy);
+void               goo_canvas_item_rotate         (GooCanvasItem   *item,
+						   gdouble          degrees,
+						   gdouble          cx,
+						   gdouble          cy);
+void               goo_canvas_item_skew_x         (GooCanvasItem   *item,
+						   gdouble          degrees,
+						   gdouble          cx,
+						   gdouble          cy);
+void               goo_canvas_item_skew_y         (GooCanvasItem   *item,
+						   gdouble          degrees,
+						   gdouble          cx,
+						   gdouble          cy);
+
+GooCanvasStyle*    goo_canvas_item_get_style      (GooCanvasItem   *item);
+void               goo_canvas_item_set_style      (GooCanvasItem   *item,
+						   GooCanvasStyle  *style);
+
+void               goo_canvas_item_animate        (GooCanvasItem   *item,
+						   gdouble           x,
+						   gdouble           y,
+						   gdouble           scale,
+						   gdouble           degrees,
+						   gboolean          absolute,
+						   gint             duration,
+						   gint             step_time,
+						   GooCanvasAnimateType type);
+void               goo_canvas_item_stop_animation (GooCanvasItem   *item);
+
+
+
+void               goo_canvas_item_get_bounds	  (GooCanvasItem   *item,
+						   GooCanvasBounds *bounds);
+GList*		   goo_canvas_item_get_items_at   (GooCanvasItem   *item,
+						   gdouble          x,
+						   gdouble          y,
+						   cairo_t         *cr,
+						   gboolean         is_pointer_event,
+						   gboolean         parent_is_visible,
+						   GList           *found_items);
+gboolean           goo_canvas_item_is_visible     (GooCanvasItem   *item);
+
+GooCanvasItemModel* goo_canvas_item_get_model	  (GooCanvasItem      *item);
+void                goo_canvas_item_set_model	  (GooCanvasItem      *item,
+						   GooCanvasItemModel *model);
+
+void               goo_canvas_item_request_update (GooCanvasItem   *item);
+void		   goo_canvas_item_ensure_updated (GooCanvasItem   *item);
+void               goo_canvas_item_update         (GooCanvasItem   *item,
+						   gboolean         entire_tree,
+						   cairo_t         *cr,
+						   GooCanvasBounds *bounds);
+void               goo_canvas_item_paint          (GooCanvasItem         *item,
+						   cairo_t               *cr,
+						   const GooCanvasBounds *bounds,
+						   gdouble                scale);
+
+gboolean	   goo_canvas_item_get_requested_area (GooCanvasItem	*item,
+						       cairo_t          *cr,
+						       GooCanvasBounds  *requested_area);
+gdouble            goo_canvas_item_get_requested_height (GooCanvasItem  *item,
+							 cairo_t	*cr,
+							 gdouble         width);
+void		   goo_canvas_item_allocate_area      (GooCanvasItem	     *item,
+						       cairo_t               *cr,
+						       const GooCanvasBounds *requested_area,
+						       const GooCanvasBounds *allocated_area,
+						       gdouble                x_offset,
+						       gdouble                y_offset);
+
+gboolean	   goo_canvas_item_get_is_static	(GooCanvasItem		*item);
+void		   goo_canvas_item_set_is_static	(GooCanvasItem		*item,
+							 gboolean		 is_static);
+
+
+/*
+ * Functions to support child properties when implementing new canvas items.
+ */
+void         goo_canvas_item_class_install_child_property (GObjectClass *iclass,
+							   guint	 property_id,
+							   GParamSpec	*pspec);
+GParamSpec*  goo_canvas_item_class_find_child_property	  (GObjectClass	*iclass,
+							   const gchar	*property_name);
+GParamSpec** goo_canvas_item_class_list_child_properties  (GObjectClass	*iclass,
+							   guint	*n_properties);
+
+
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_ITEM_H__ */
diff --git a/libgoocanvas/goocanvasitemmodel.c b/libgoocanvas/goocanvasitemmodel.c
new file mode 100644
index 0000000..b70405a
--- /dev/null
+++ b/libgoocanvas/goocanvasitemmodel.c
@@ -0,0 +1,1187 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitem.c - interface for canvas items & groups.
+ */
+
+/**
+ * SECTION:goocanvasitemmodel
+ * @Title: GooCanvasItemModel
+ * @Short_Description: the interface for canvas item models.
+ *
+ * #GooCanvasItemModel defines the interface that models for canvas items must
+ * implement, and contains methods for operating on canvas item models.
+ *
+ * <note><para>
+ * 	The Model/View canvas feature may be removed in a future version of
+ * 	GooCanvas.
+ * </para></note>
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gobject/gobjectnotifyqueue.c>
+#include <gobject/gvaluecollector.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include <goocanvasenumtypes.h>
+#include "goocanvasitemmodel.h"
+#include "goocanvasutils.h"
+#include "goocanvasmarshal.h"
+
+
+static GParamSpecPool       *_goo_canvas_item_model_child_property_pool = NULL;
+static GObjectNotifyContext *_goo_canvas_item_model_child_property_notify_context = NULL;
+static const char *animation_key = "GooCanvasItemAnimation";
+
+enum {
+  CHILD_ADDED,
+  CHILD_MOVED,
+  CHILD_REMOVED,
+  CHANGED,
+
+  CHILD_NOTIFY,
+  ANIMATION_FINISHED,
+
+  LAST_SIGNAL
+};
+
+static guint item_model_signals[LAST_SIGNAL] = { 0 };
+
+static void goo_canvas_item_model_base_init (gpointer g_class);
+extern void _goo_canvas_style_init (void);
+extern void _goo_canvas_item_get_child_property_internal (GObject              *object,
+							  GObject              *child,
+							  const gchar          *property_name,
+							  GValue               *value,
+							  GParamSpecPool       *property_pool,
+							  gboolean              is_model);
+
+
+GType
+goo_canvas_item_model_get_type (void)
+{
+  static GType item_model_type = 0;
+
+  if (!item_model_type)
+    {
+      static const GTypeInfo item_model_info =
+      {
+        sizeof (GooCanvasItemModelIface),  /* class_size */
+	goo_canvas_item_model_base_init,   /* base_init */
+	NULL,			           /* base_finalize */
+      };
+
+      item_model_type = g_type_register_static (G_TYPE_INTERFACE,
+						"GooCanvasItemModel",
+						&item_model_info, 0);
+
+      g_type_interface_add_prerequisite (item_model_type, G_TYPE_OBJECT);
+    }
+
+  return item_model_type;
+}
+
+
+
+static void
+child_property_notify_dispatcher (GObject     *object,
+				  guint        n_pspecs,
+				  GParamSpec **pspecs)
+{
+  guint i;
+
+  for (i = 0; i < n_pspecs; i++)
+    g_signal_emit (object, item_model_signals[CHILD_NOTIFY],
+		   g_quark_from_string (pspecs[i]->name), pspecs[i]);
+}
+
+
+static void
+goo_canvas_item_model_base_init (gpointer g_iface)
+{
+  static GObjectNotifyContext cpn_context = { 0, NULL, NULL };
+  static gboolean initialized = FALSE;
+  
+  if (!initialized)
+    {
+      GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
+
+      _goo_canvas_item_model_child_property_pool = g_param_spec_pool_new (TRUE);
+
+      cpn_context.quark_notify_queue = g_quark_from_static_string ("GooCanvasItemModel-child-property-notify-queue");
+      cpn_context.dispatcher = child_property_notify_dispatcher;
+      _goo_canvas_item_model_child_property_notify_context = &cpn_context;
+
+      /**
+       * GooCanvasItemModel::child-added
+       * @model: the item model that received the signal.
+       * @child_num: the index of the new child.
+       *
+       * Emitted when a child has been added.
+       */
+      item_model_signals[CHILD_ADDED] =
+	g_signal_new ("child-added",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, child_added),
+		      NULL, NULL,
+		      goo_canvas_marshal_VOID__INT,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_INT);
+
+      /**
+       * GooCanvasItemModel::child-moved
+       * @model: the item model that received the signal.
+       * @old_child_num: the old index of the child.
+       * @new_child_num: the new index of the child.
+       *
+       * Emitted when a child has been moved in the stacking order.
+       */
+      item_model_signals[CHILD_MOVED] =
+	g_signal_new ("child-moved",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, child_moved),
+		      NULL, NULL,
+		      goo_canvas_marshal_VOID__INT_INT,
+		      G_TYPE_NONE, 2,
+		      G_TYPE_INT, G_TYPE_INT);
+
+      /**
+       * GooCanvasItemModel::child-removed
+       * @model: the item model that received the signal.
+       * @child_num: the index of the child that was removed.
+       *
+       * Emitted when a child has been removed.
+       */
+      item_model_signals[CHILD_REMOVED] =
+	g_signal_new ("child-removed",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, child_removed),
+		      NULL, NULL,
+		      goo_canvas_marshal_VOID__INT,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_INT);
+
+      /**
+       * GooCanvasItemModel::changed
+       * @model: the item model that received the signal.
+       * @recompute_bounds: if the bounds of the item need to be recomputed.
+       *
+       * Emitted when the item model has been changed.
+       */
+      item_model_signals[CHANGED] =
+	g_signal_new ("changed",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, changed),
+		      NULL, NULL,
+		      goo_canvas_marshal_VOID__BOOLEAN,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_BOOLEAN);
+
+      /**
+       * GooCanvasItemModel::child-notify
+       * @item: the item model that received the signal.
+       * @pspec: the #GParamSpec of the changed child property.
+       *
+       * Emitted for each child property that has changed.
+       * The signal's detail holds the property name. 
+       */
+      item_model_signals[CHILD_NOTIFY] =
+	g_signal_new ("child_notify",
+		      iface_type,
+		      G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, child_notify),
+		      NULL, NULL,
+		      g_cclosure_marshal_VOID__PARAM,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_PARAM);
+
+      /**
+       * GooCanvasItemModel::animation-finished
+       * @item: the item model that received the signal.
+       * @stopped: if the animation was explicitly stopped.
+       *
+       * Emitted when the item model animation has finished.
+       */
+      item_model_signals[ANIMATION_FINISHED] =
+	g_signal_new ("animation-finished",
+		      iface_type,
+		      G_SIGNAL_RUN_LAST,
+		      G_STRUCT_OFFSET (GooCanvasItemModelIface, animation_finished),
+		      NULL, NULL,
+		      g_cclosure_marshal_VOID__BOOLEAN,
+		      G_TYPE_NONE, 1,
+		      G_TYPE_BOOLEAN);
+
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_object ("parent",
+								_("Parent"),
+								_("The parent item model"),
+								GOO_TYPE_CANVAS_ITEM_MODEL,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_enum ("visibility",
+							      _("Visibility"),
+							      _("When the canvas item is visible"),
+							      GOO_TYPE_CANVAS_ITEM_VISIBILITY,
+							      GOO_CANVAS_ITEM_VISIBLE,
+							      G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_double ("visibility-threshold",
+								_("Visibility Threshold"),
+								_("The scale threshold at which the item becomes visible"),
+								0.0,
+								G_MAXDOUBLE,
+								0.0,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_boxed ("transform",
+							       _("Transform"),
+							       _("The transformation matrix of the item"),
+							       GOO_TYPE_CAIRO_MATRIX,
+							       G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_flags ("pointer-events",
+							       _("Pointer Events"),
+							       _("Specifies when the item receives pointer events"),
+							       GOO_TYPE_CANVAS_POINTER_EVENTS,
+							       GOO_CANVAS_EVENTS_VISIBLE_PAINTED,
+							       G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("title",
+								_("Title"),
+								_("A short context-rich description of the item for use by assistive technologies"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("description",
+								_("Description"),
+								_("A description of the item for use by assistive technologies"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_boolean ("can-focus",
+								 _("Can Focus"),
+								 _("If the item can take the keyboard focus"),
+								 FALSE,
+								 G_PARAM_READWRITE));
+
+      g_object_interface_install_property (g_iface,
+					   g_param_spec_string ("tooltip",
+								_("Tooltip"),
+								_("The tooltip to display for the item"),
+								NULL,
+								G_PARAM_READWRITE));
+
+      _goo_canvas_style_init ();
+
+      initialized = TRUE;
+    }
+}
+
+
+/**
+ * goo_canvas_item_model_add_child:
+ * @model: an item model.
+ * @child: the child to add.
+ * @position: the position of the child, or -1 to place it last (at the top of
+ *  the stacking order).
+ * 
+ * Adds a child at the given stack position.
+ **/
+void
+goo_canvas_item_model_add_child      (GooCanvasItemModel  *model,
+				      GooCanvasItemModel  *child,
+				      gint                 position)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  g_return_if_fail (iface->add_child != NULL);
+  g_return_if_fail (model != child);
+
+  iface->add_child (model, child, position);
+}
+
+
+/**
+ * goo_canvas_item_model_move_child:
+ * @model: an item model.
+ * @old_position: the current position of the child.
+ * @new_position: the new position of the child.
+ * 
+ * Moves a child to a new stack position.
+ **/
+void
+goo_canvas_item_model_move_child     (GooCanvasItemModel  *model,
+				      gint                 old_position,
+				      gint                 new_position)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  g_return_if_fail (iface->move_child != NULL);
+
+  iface->move_child (model, old_position, new_position);
+}
+
+
+/**
+ * goo_canvas_item_model_remove_child:
+ * @model: an item model.
+ * @child_num: the position of the child to remove.
+ * 
+ * Removes the child at the given position.
+ **/
+void
+goo_canvas_item_model_remove_child   (GooCanvasItemModel  *model,
+				      gint                 child_num)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  g_return_if_fail (iface->remove_child != NULL);
+
+  iface->remove_child (model, child_num);
+}
+
+
+/**
+ * goo_canvas_item_model_find_child:
+ * @model: an item model.
+ * @child: the child to find.
+ * 
+ * Attempts to find the given child with the container's stack.
+ * 
+ * Returns: the position of the given @child, or -1 if it isn't found.
+ **/
+gint
+goo_canvas_item_model_find_child     (GooCanvasItemModel *model,
+				      GooCanvasItemModel *child)
+{
+  GooCanvasItemModel *item;
+  int n_children, i;
+
+  /* Find the current position of item and above. */
+  n_children = goo_canvas_item_model_get_n_children (model);
+  for (i = 0; i < n_children; i++)
+    {
+      item = goo_canvas_item_model_get_child (model, i);
+      if (child == item)
+	return i;
+    }
+  return -1;
+}
+
+
+/**
+ * goo_canvas_item_model_is_container:
+ * @model: an item model.
+ * 
+ * Tests to see if the given item model is a container.
+ * 
+ * Returns: %TRUE if the item model is a container.
+ **/
+gboolean
+goo_canvas_item_model_is_container (GooCanvasItemModel       *model)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  return iface->get_n_children ? TRUE : FALSE;
+}
+
+
+/**
+ * goo_canvas_item_model_get_n_children:
+ * @model: an item model.
+ * 
+ * Gets the number of children of the container.
+ * 
+ * Returns: the number of children.
+ **/
+gint
+goo_canvas_item_model_get_n_children (GooCanvasItemModel       *model)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  return iface->get_n_children ? iface->get_n_children (model) : 0;
+}
+
+
+/**
+ * goo_canvas_item_model_get_child:
+ * @model: an item model.
+ * @child_num: the position of a child in the container's stack.
+ * 
+ * Gets the child at the given stack position.
+ * 
+ * Returns: the child at the given stack position, or %NULL if @child_num
+ * is out of range.
+ **/
+GooCanvasItemModel*
+goo_canvas_item_model_get_child (GooCanvasItemModel  *model,
+				 gint                 child_num)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  return iface->get_child ? iface->get_child (model, child_num) : NULL;
+}
+
+
+/**
+ * goo_canvas_item_model_get_parent:
+ * @model: an item model.
+ * 
+ * Gets the parent of the given model.
+ * 
+ * Returns: the parent model, or %NULL if the model has no parent.
+ **/
+GooCanvasItemModel*
+goo_canvas_item_model_get_parent  (GooCanvasItemModel *model)
+{
+  return GOO_CANVAS_ITEM_MODEL_GET_IFACE (model)->get_parent (model);
+}
+
+
+/**
+ * goo_canvas_item_model_set_parent:
+ * @model: an item model.
+ * @parent: the new parent item model.
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * item models (specifically container models such as #GooCanvasGroupModel).
+ * It sets the parent of the child model.
+ * <!--PARAMETERS-->
+ * <note><para>
+ * This function cannot be used to add a model to a group
+ * or to change the parent of a model.
+ * To do that use the #GooCanvasItemModel:parent property.
+ * </para></note>
+ **/
+void
+goo_canvas_item_model_set_parent (GooCanvasItemModel *model,
+				  GooCanvasItemModel *parent)
+{
+  GOO_CANVAS_ITEM_MODEL_GET_IFACE (model)->set_parent (model, parent);
+}
+
+
+/**
+ * goo_canvas_item_model_remove:
+ * @model: an item model.
+ * 
+ * Removes a model from its parent. If the model is in a canvas it will be
+ * removed.
+ *
+ * This would normally also result in the model being freed.
+ **/
+void
+goo_canvas_item_model_remove         (GooCanvasItemModel *model)
+{
+  GooCanvasItemModel *parent;
+  gint child_num;
+
+  parent = goo_canvas_item_model_get_parent (model);
+  if (!parent)
+    return;
+
+  child_num = goo_canvas_item_model_find_child (parent, model);
+  if (child_num == -1)
+    return;
+
+  goo_canvas_item_model_remove_child (parent, child_num);
+}
+
+
+/**
+ * goo_canvas_item_model_raise:
+ * @model: an item model.
+ * @above: the item model to raise @model above, or %NULL to raise @model to the top
+ *  of the stack.
+ * 
+ * Raises a model in the stacking order.
+ **/
+void
+goo_canvas_item_model_raise          (GooCanvasItemModel *model,
+				      GooCanvasItemModel *above)
+{
+  GooCanvasItemModel *parent, *child;
+  int n_children, i, model_pos = -1, above_pos = -1;
+
+  parent = goo_canvas_item_model_get_parent (model);
+  if (!parent || model == above)
+    return;
+
+  /* Find the current position of model and above. */
+  n_children = goo_canvas_item_model_get_n_children (parent);
+  for (i = 0; i < n_children; i++)
+    {
+      child = goo_canvas_item_model_get_child (parent, i);
+      if (child == model)
+	model_pos = i;
+      if (child == above)
+	above_pos = i;
+    }
+
+  /* If above is NULL we raise the model to the top of the stack. */
+  if (!above)
+    above_pos = n_children - 1;
+
+  g_return_if_fail (model_pos != -1);
+  g_return_if_fail (above_pos != -1);
+
+  /* Only move the model if the new position is higher in the stack. */
+  if (above_pos > model_pos)
+    goo_canvas_item_model_move_child (parent, model_pos, above_pos);
+}
+
+
+/**
+ * goo_canvas_item_model_lower:
+ * @model: an item model.
+ * @below: the item model to lower @model below, or %NULL to lower @model to the
+ *  bottom of the stack.
+ * 
+ * Lowers a model in the stacking order.
+ **/
+void
+goo_canvas_item_model_lower          (GooCanvasItemModel *model,
+				      GooCanvasItemModel *below)
+{
+  GooCanvasItemModel *parent, *child;
+  int n_children, i, model_pos = -1, below_pos = -1;
+
+  parent = goo_canvas_item_model_get_parent (model);
+  if (!parent || model == below)
+    return;
+
+  /* Find the current position of model and below. */
+  n_children = goo_canvas_item_model_get_n_children (parent);
+  for (i = 0; i < n_children; i++)
+    {
+      child = goo_canvas_item_model_get_child (parent, i);
+      if (child == model)
+	model_pos = i;
+      if (child == below)
+	below_pos = i;
+    }
+
+  /* If below is NULL we lower the model to the bottom of the stack. */
+  if (!below)
+    below_pos = 0;
+
+  g_return_if_fail (model_pos != -1);
+  g_return_if_fail (below_pos != -1);
+
+  /* Only move the model if the new position is lower in the stack. */
+  if (below_pos < model_pos)
+    goo_canvas_item_model_move_child (parent, model_pos, below_pos);
+}
+
+
+/**
+ * goo_canvas_item_model_get_transform:
+ * @model: an item model.
+ * @transform: the place to store the transform.
+ * 
+ * Gets the transformation matrix of an item model.
+ * 
+ * Returns: %TRUE if a transform is set.
+ **/
+gboolean
+goo_canvas_item_model_get_transform  (GooCanvasItemModel *model,
+				      cairo_matrix_t     *transform)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  return iface->get_transform ? iface->get_transform (model, transform) : FALSE;
+}
+
+
+/**
+ * goo_canvas_item_model_set_transform:
+ * @model: an item model.
+ * @transform: the new transformation matrix, or %NULL to reset the
+ *  transformation to the identity matrix.
+ * 
+ * Sets the transformation matrix of an item model.
+ **/
+void
+goo_canvas_item_model_set_transform  (GooCanvasItemModel   *model,
+				      const cairo_matrix_t *transform)
+{
+  GOO_CANVAS_ITEM_MODEL_GET_IFACE (model)->set_transform (model, transform);
+}
+
+
+/**
+ * goo_canvas_item_model_get_simple_transform:
+ * @model: an item model.
+ * @x: returns the x coordinate of the origin of the model's coordinate space.
+ * @y: returns the y coordinate of the origin of the model's coordinate space.
+ * @scale: returns the scale of the model.
+ * @rotation: returns the clockwise rotation of the model, in degrees (0-360).
+ * 
+ * This function can be used to get the position, scale and rotation of an
+ * item model, providing that the model has a simple transformation matrix
+ * (e.g. set with goo_canvas_item_model_set_simple_transform(), or using a
+ * combination of simple translate, scale and rotate operations). If the model
+ * has a complex transformation matrix the results will be incorrect.
+ * 
+ * Returns: %TRUE if a transform is set.
+ **/
+gboolean
+goo_canvas_item_model_get_simple_transform (GooCanvasItemModel *model,
+					    gdouble            *x,
+					    gdouble            *y,
+					    gdouble            *scale,
+					    gdouble            *rotation)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t matrix = { 1, 0, 0, 1, 0, 0 };
+  double x1 = 1.0, y1 = 0.0, radians;
+  gboolean has_transform = FALSE;
+
+  if (iface->get_transform)
+    has_transform = iface->get_transform (model, &matrix);
+
+  if (!has_transform)
+    {
+      *x = *y = *rotation = 0.0;
+      *scale = 1.0;
+      return FALSE;
+    }
+
+  *x = matrix.x0;
+  *y = matrix.y0;
+
+  matrix.x0 = 0.0;
+  matrix.y0 = 0.0;
+
+  cairo_matrix_transform_point (&matrix, &x1, &y1);
+  *scale = sqrt (x1 * x1 + y1 * y1);
+  radians = atan2 (y1, x1);
+  *rotation = radians * (180 / M_PI);
+  if (*rotation < 0)
+    *rotation += 360;
+
+  return TRUE;
+}
+
+
+/**
+ * goo_canvas_item_model_set_simple_transform:
+ * @model: an item model.
+ * @x: the x coordinate of the origin of the model's coordinate space.
+ * @y: the y coordinate of the origin of the model's coordinate space.
+ * @scale: the scale of the model.
+ * @rotation: the clockwise rotation of the model, in degrees.
+ * 
+ * A convenience function to set the item model's transformation matrix.
+ **/
+void
+goo_canvas_item_model_set_simple_transform (GooCanvasItemModel *model,
+					    gdouble             x,
+					    gdouble             y,
+					    gdouble             scale,
+					    gdouble             rotation)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  cairo_matrix_translate (&new_matrix, x, y);
+  cairo_matrix_scale (&new_matrix, scale, scale);
+  cairo_matrix_rotate (&new_matrix, rotation * (M_PI  / 180));
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_translate:
+ * @model: an item model.
+ * @tx: the amount to move the origin in the horizontal direction.
+ * @ty: the amount to move the origin in the vertical direction.
+ * 
+ * Translates the origin of the model's coordinate system by the given amounts.
+ **/
+void
+goo_canvas_item_model_translate      (GooCanvasItemModel *model,
+				      gdouble             tx,
+				      gdouble             ty)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  iface->get_transform (model, &new_matrix);
+  cairo_matrix_translate (&new_matrix, tx, ty);
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_scale:
+ * @model: an item model.
+ * @sx: the amount to scale the horizontal axis.
+ * @sy: the amount to scale the vertical axis.
+ * 
+ * Scales the model's coordinate system by the given amounts.
+ **/
+void
+goo_canvas_item_model_scale          (GooCanvasItemModel *model,
+				      gdouble             sx,
+				      gdouble             sy)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+
+  iface->get_transform (model, &new_matrix);
+  cairo_matrix_scale (&new_matrix, sx, sy);
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_rotate:
+ * @model: an item model.
+ * @degrees: the clockwise angle of rotation.
+ * @cx: the x coordinate of the origin of the rotation.
+ * @cy: the y coordinate of the origin of the rotation.
+ * 
+ * Rotates the model's coordinate system by the given amount, about the given
+ * origin.
+ **/
+void
+goo_canvas_item_model_rotate         (GooCanvasItemModel *model,
+				      gdouble             degrees,
+				      gdouble             cx,
+				      gdouble             cy)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (model, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_rotate (&new_matrix, radians);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_skew_x:
+ * @model: an item model.
+ * @degrees: the skew angle.
+ * @cx: the x coordinate of the origin of the skew transform.
+ * @cy: the y coordinate of the origin of the skew transform.
+ * 
+ * Skews the model's coordinate system along the x axis by the given amount,
+ * about the given origin.
+ **/
+void
+goo_canvas_item_model_skew_x         (GooCanvasItemModel *model,
+				      gdouble             degrees,
+				      gdouble             cx,
+				      gdouble             cy)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t tmp, new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (model, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_init (&tmp, 1, 0, tan (radians), 1, 0, 0);
+  cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_skew_y:
+ * @model: an item model.
+ * @degrees: the skew angle.
+ * @cx: the x coordinate of the origin of the skew transform.
+ * @cy: the y coordinate of the origin of the skew transform.
+ * 
+ * Skews the model's coordinate system along the y axis by the given amount,
+ * about the given origin.
+ **/
+void
+goo_canvas_item_model_skew_y         (GooCanvasItemModel *model,
+				      gdouble             degrees,
+				      gdouble             cx,
+				      gdouble             cy)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+  cairo_matrix_t tmp, new_matrix = { 1, 0, 0, 1, 0, 0 };
+  double radians = degrees * (M_PI / 180);
+
+  iface->get_transform (model, &new_matrix);
+  cairo_matrix_translate (&new_matrix, cx, cy);
+  cairo_matrix_init (&tmp, 1, tan (radians), 0, 1, 0, 0);
+  cairo_matrix_multiply (&new_matrix, &tmp, &new_matrix);
+  cairo_matrix_translate (&new_matrix, -cx, -cy);
+  iface->set_transform (model, &new_matrix);
+}
+
+
+/**
+ * goo_canvas_item_model_get_style:
+ * @model: an item model.
+ * 
+ * Gets the model's style. If the model doesn't have its own style it will
+ * return its parent's style.
+ * 
+ * Returns: the model's style.
+ **/
+GooCanvasStyle*
+goo_canvas_item_model_get_style      (GooCanvasItemModel   *model)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  return iface->get_style ? iface->get_style (model) : NULL;
+}
+
+
+/**
+ * goo_canvas_item_model_set_style:
+ * @model: an item model.
+ * @style: a style.
+ * 
+ * Sets the model's style, by copying the properties from the given style.
+ **/
+void
+goo_canvas_item_model_set_style      (GooCanvasItemModel *model,
+				      GooCanvasStyle     *style)
+{
+  GooCanvasItemModelIface *iface = GOO_CANVAS_ITEM_MODEL_GET_IFACE (model);
+
+  if (iface->set_style)
+    iface->set_style (model, style);
+}
+
+
+extern void _goo_canvas_item_animate_internal (GooCanvasItem       *item,
+					       GooCanvasItemModel  *model,
+					       gdouble              x,
+					       gdouble              y,
+					       gdouble              scale,
+					       gdouble              degrees,
+					       gboolean             absolute,
+					       gint                 duration,
+					       gint                 step_time,
+					       GooCanvasAnimateType type);
+
+/**
+ * goo_canvas_item_model_animate:
+ * @model: an item model.
+ * @x: the final x coordinate.
+ * @y: the final y coordinate.
+ * @scale: the final scale.
+ * @degrees: the final rotation. This can be negative to rotate anticlockwise,
+ *  and can also be greater than 360 to rotate a number of times.
+ * @absolute: if the @x, @y, @scale and @degrees values are absolute, or
+ *  relative to the current transform. Note that absolute animations only work
+ *  if the model currently has a simple transform. If the model has a shear or
+ *  some other complicated transform it may result in strange animations.
+ * @duration: the duration of the animation, in milliseconds (1/1000ths of a
+ *  second).
+ * @step_time: the time between each animation step, in milliseconds.
+ * @type: specifies what happens when the animation finishes.
+ * 
+ * Animates a model from its current position to the given offsets, scale
+ * and rotation.
+ **/
+void
+goo_canvas_item_model_animate        (GooCanvasItemModel  *model,
+				      gdouble              x,
+				      gdouble              y,
+				      gdouble              scale,
+				      gdouble              degrees,
+				      gboolean             absolute,
+				      gint                 duration,
+				      gint                 step_time,
+				      GooCanvasAnimateType type)
+{
+  _goo_canvas_item_animate_internal (NULL, model, x, y, scale, degrees,
+				     absolute, duration, step_time, type);
+}
+
+
+/**
+ * goo_canvas_item_model_stop_animation:
+ * @model: an item model.
+ * 
+ * Stops any current animation for the given model, leaving it at its current
+ * position.
+ **/
+void
+goo_canvas_item_model_stop_animation (GooCanvasItemModel *model)
+{
+  /* This will result in a call to goo_canvas_item_free_animation() above. */
+  g_object_set_data (G_OBJECT (model), animation_key, NULL);
+
+  g_signal_emit_by_name (model, "animation-finished", TRUE);
+}
+
+
+
+/*
+ * Child Properties.
+ */
+extern void _goo_canvas_item_set_child_property_internal (GObject *object, GObject *child, const gchar *property_name, const GValue *value, GParamSpecPool *property_pool, GObjectNotifyContext *notify_context, gboolean is_model);
+
+extern void _goo_canvas_item_get_child_properties_internal (GObject *object, GObject *child, va_list var_args, GParamSpecPool *property_pool, GObjectNotifyContext *notify_context, gboolean is_model);
+
+extern void _goo_canvas_item_set_child_properties_internal (GObject *object, GObject *child, va_list var_args, GParamSpecPool *property_pool, GObjectNotifyContext *notify_context, gboolean is_model);
+
+
+/**
+ * goo_canvas_item_model_get_child_property:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @property_name: the name of the child property to get.
+ * @value: a location to return the value.
+ * 
+ * Gets a child property of @child.
+ **/
+void
+goo_canvas_item_model_get_child_property (GooCanvasItemModel *model,
+					  GooCanvasItemModel *child,
+					  const gchar        *property_name,
+					  GValue             *value)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (child));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (G_IS_VALUE (value));
+
+  _goo_canvas_item_get_child_property_internal ((GObject*) model, (GObject*) child, property_name, value, _goo_canvas_item_model_child_property_pool, TRUE);
+}
+
+
+/**
+ * goo_canvas_item_model_set_child_property:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @property_name: the name of the child property to set.
+ * @value: the value to set the property to.
+ * 
+ * Sets a child property of @child.
+ **/
+void
+goo_canvas_item_model_set_child_property (GooCanvasItemModel *model,
+					  GooCanvasItemModel *child,
+					  const gchar        *property_name,
+					  const GValue       *value)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (child));
+  g_return_if_fail (property_name != NULL);
+  g_return_if_fail (G_IS_VALUE (value));
+
+  _goo_canvas_item_set_child_property_internal ((GObject*) model, (GObject*) child, property_name, value, _goo_canvas_item_model_child_property_pool, _goo_canvas_item_model_child_property_notify_context, TRUE);
+}
+
+
+/**
+ * goo_canvas_item_model_get_child_properties_valist:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @var_args: pairs of property names and value pointers, and a terminating
+ *  %NULL.
+ * 
+ * Gets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_model_get_child_properties_valist (GooCanvasItemModel *model,
+						   GooCanvasItemModel *child,
+						   va_list	       var_args)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (child));
+
+  _goo_canvas_item_get_child_properties_internal ((GObject*) model, (GObject*) child, var_args, _goo_canvas_item_model_child_property_pool, _goo_canvas_item_model_child_property_notify_context, TRUE);
+}
+
+
+/**
+ * goo_canvas_item_model_set_child_properties_valist:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @var_args: pairs of property names and values, and a terminating %NULL.
+ * 
+ * Sets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_model_set_child_properties_valist (GooCanvasItemModel *model,
+						   GooCanvasItemModel *child,
+						   va_list	       var_args)
+{
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (model));
+  g_return_if_fail (GOO_IS_CANVAS_ITEM_MODEL (child));
+
+  _goo_canvas_item_set_child_properties_internal ((GObject*) model, (GObject*) child, var_args, _goo_canvas_item_model_child_property_pool, _goo_canvas_item_model_child_property_notify_context, TRUE);
+}
+
+
+/**
+ * goo_canvas_item_model_get_child_properties:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @...: pairs of property names and value pointers, and a terminating %NULL.
+ * 
+ * Gets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_model_get_child_properties  (GooCanvasItemModel   *model,
+					     GooCanvasItemModel   *child,
+					     ...)
+{
+  va_list var_args;
+  
+  va_start (var_args, child);
+  goo_canvas_item_model_get_child_properties_valist (model, child, var_args);
+  va_end (var_args);
+}
+
+
+/**
+ * goo_canvas_item_model_set_child_properties:
+ * @model: a #GooCanvasItemModel.
+ * @child: a child #GooCanvasItemModel.
+ * @...: pairs of property names and values, and a terminating %NULL.
+ * 
+ * Sets the values of one or more child properties of @child.
+ **/
+void
+goo_canvas_item_model_set_child_properties  (GooCanvasItemModel   *model,
+					     GooCanvasItemModel   *child,
+					     ...)
+{
+  va_list var_args;
+  
+  va_start (var_args, child);
+  goo_canvas_item_model_set_child_properties_valist (model, child, var_args);
+  va_end (var_args);
+}
+
+
+
+/**
+ * goo_canvas_item_model_class_install_child_property:
+ * @mclass: a #GObjectClass
+ * @property_id: the id for the property
+ * @pspec: the #GParamSpec for the property
+ * 
+ * This function is only intended to be used when implementing new canvas
+ * item models, specifically layout container item models such as
+ * #GooCanvasTableModel.
+ *
+ * It installs a child property on a canvas item class. 
+ **/
+void
+goo_canvas_item_model_class_install_child_property (GObjectClass *mclass,
+						    guint         property_id,
+						    GParamSpec   *pspec)
+{
+  g_return_if_fail (G_IS_OBJECT_CLASS (mclass));
+  g_return_if_fail (G_IS_PARAM_SPEC (pspec));
+  g_return_if_fail (property_id > 0);
+
+  if (g_param_spec_pool_lookup (_goo_canvas_item_model_child_property_pool,
+				pspec->name, G_OBJECT_CLASS_TYPE (mclass),
+				FALSE))
+    {
+      g_warning (G_STRLOC ": class `%s' already contains a child property named `%s'",
+		 G_OBJECT_CLASS_NAME (mclass), pspec->name);
+      return;
+    }
+  g_param_spec_ref (pspec);
+  g_param_spec_sink (pspec);
+  pspec->param_id = property_id;
+  g_param_spec_pool_insert (_goo_canvas_item_model_child_property_pool, pspec,
+			    G_OBJECT_CLASS_TYPE (mclass));
+}
+
+/**
+ * goo_canvas_item_model_class_find_child_property:
+ * @mclass: a #GObjectClass
+ * @property_name: the name of the child property to find
+ * @returns: the #GParamSpec of the child property or %NULL if @class has no
+ *   child property with that name.
+ *
+ * This function is only intended to be used when implementing new canvas
+ * item models, specifically layout container item models such as
+ * #GooCanvasTableModel.
+ *
+ * It finds a child property of a canvas item class by name.
+ */
+GParamSpec*
+goo_canvas_item_model_class_find_child_property (GObjectClass *mclass,
+						 const gchar  *property_name)
+{
+  g_return_val_if_fail (G_IS_OBJECT_CLASS (mclass), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  return g_param_spec_pool_lookup (_goo_canvas_item_model_child_property_pool,
+				   property_name, G_OBJECT_CLASS_TYPE (mclass),
+				   TRUE);
+}
+
+/**
+ * goo_canvas_item_model_class_list_child_properties:
+ * @mclass: a #GObjectClass
+ * @n_properties: location to return the number of child properties found
+ * @returns: a newly allocated array of #GParamSpec*. The array must be 
+ *           freed with g_free().
+ *
+ * This function is only intended to be used when implementing new canvas
+ * item models, specifically layout container item models such as
+ * #GooCanvasTableModel.
+ *
+ * It returns all child properties of a canvas item class.
+ */
+GParamSpec**
+goo_canvas_item_model_class_list_child_properties (GObjectClass *mclass,
+						   guint        *n_properties)
+{
+  GParamSpec **pspecs;
+  guint n;
+
+  g_return_val_if_fail (G_IS_OBJECT_CLASS (mclass), NULL);
+
+  pspecs = g_param_spec_pool_list (_goo_canvas_item_model_child_property_pool,
+				   G_OBJECT_CLASS_TYPE (mclass), &n);
+  if (n_properties)
+    *n_properties = n;
+
+  return pspecs;
+}
+
+
+void
+_goo_canvas_item_model_emit_child_added (GooCanvasItemModel *model,
+					 gint                position)
+{
+  g_signal_emit (model, item_model_signals[CHILD_ADDED], 0, position);
+}
+
+
+void
+_goo_canvas_item_model_emit_changed (GooCanvasItemModel *model,
+				     gboolean            recompute_bounds)
+{
+  g_signal_emit (model, item_model_signals[CHANGED], 0, recompute_bounds);
+}
diff --git a/libgoocanvas/goocanvasitemmodel.h b/libgoocanvas/goocanvasitemmodel.h
new file mode 100644
index 0000000..c32f7ef
--- /dev/null
+++ b/libgoocanvas/goocanvasitemmodel.h
@@ -0,0 +1,275 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitemmodel.h - interface for canvas item models.
+ */
+#ifndef __GOO_CANVAS_ITEM_MODEL_H__
+#define __GOO_CANVAS_ITEM_MODEL_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitem.h"
+
+G_BEGIN_DECLS
+
+
+#define GOO_TYPE_CANVAS_ITEM_MODEL            (goo_canvas_item_model_get_type ())
+#define GOO_CANVAS_ITEM_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ITEM_MODEL, GooCanvasItemModel))
+#define GOO_IS_CANVAS_ITEM_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ITEM_MODEL))
+#define GOO_CANVAS_ITEM_MODEL_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GOO_TYPE_CANVAS_ITEM_MODEL, GooCanvasItemModelIface))
+
+
+/**
+ * GooCanvasItemModel
+ *
+ * #GooCanvasItemModel is a typedef used for objects that implement the
+ * #GooCanvasItemModel interface.
+ *
+ * (There is no actual #GooCanvasItemModel struct, since it is only an interface.
+ * But using '#GooCanvasItemModel' is more helpful than using '#GObject'.)
+ */
+/* The typedef is in goocanvasitem.h */
+/*typedef struct _GooCanvasItemModel       GooCanvasItemModel;*/
+
+
+/**
+ * GooCanvasItemModelIface
+ * @get_n_children: returns the number of children of the model.
+ * @get_child: returns the child at the given index.
+ * @add_child: adds a child.
+ * @move_child: moves a child up or down the stacking order.
+ * @remove_child: removes a child.
+ * @get_child_property: gets a child property of a given child model,
+ *  e.g. the "row" or "column" property of a model in a #GooCanvasTableModel.
+ * @set_child_property: sets a child property for a given child model.
+ * @get_parent: gets the model's parent.
+ * @set_parent: sets the model's parent.
+ * @create_item: creates a default canvas item to view the model.
+ * @get_transform: gets the model's transformation matrix.
+ * @set_transform: sets the model's transformation matrix.
+ * @get_style: gets the model's style.
+ * @set_style: sets the model's style.
+ * @child_added: signal emitted when a child is added.
+ * @child_moved: signal emitted when a child is moved in the stacking order.
+ * @child_removed: signal emitted when a child is removed.
+ * @changed: signal emitted when the model has changed.
+ * @child_notify: signal emitted when a child property has changed.
+ * @animation_finished: signal emitted when the model's animation has finished.
+ *
+ * #GooCanvasItemModelIFace holds the virtual methods that make up the
+ * #GooCanvasItemModel interface.
+ *
+ * Simple item models only need to implement the get_parent(), set_parent()
+ * and create_item() methods.
+ *
+ * Items that support transforms should also implement get_transform() and
+ * set_transform(). Items that support styles should implement get_style()
+ * and set_style().
+ *
+ * Container items must implement get_n_children() and get_child().
+ * Containers that support dynamic changes to their children should implement
+ * add_child(), move_child() and remove_child().
+ * Layout containers like #GooCanvasTable may implement
+ * get_child_property() and set_child_property().
+ */
+typedef struct _GooCanvasItemModelIface  GooCanvasItemModelIface;
+
+struct _GooCanvasItemModelIface
+{
+  /*< private >*/
+  GTypeInterface base_iface;
+
+  /*< public >*/
+  /* Virtual methods that group models must implement. */
+  gint		       (* get_n_children)		(GooCanvasItemModel	*model);
+  GooCanvasItemModel*  (* get_child)			(GooCanvasItemModel	*model,
+							 gint			 child_num);
+
+  /* Virtual methods that group models may implement. */
+  void                 (* add_child)			(GooCanvasItemModel	*model,
+							 GooCanvasItemModel	*child,
+							 gint			 position);
+  void                 (* move_child)			(GooCanvasItemModel	*model,
+							 gint			 old_position,
+							 gint			 new_position);
+  void                 (* remove_child)			(GooCanvasItemModel	*model,
+							 gint			 child_num);
+  void                 (* get_child_property)		(GooCanvasItemModel	*model,
+							 GooCanvasItemModel	*child,
+							 guint			 property_id,
+							 GValue			*value,
+							 GParamSpec		*pspec);
+  void                 (* set_child_property)		(GooCanvasItemModel	*item,
+							 GooCanvasItemModel	*child,
+							 guint			 property_id,
+							 const GValue		*value,
+							 GParamSpec		*pspec);
+
+  /* Virtual methods that all item models must implement. */
+  GooCanvasItemModel*  (* get_parent)			(GooCanvasItemModel	*model);
+  void                 (* set_parent)			(GooCanvasItemModel	*model,
+							 GooCanvasItemModel	*parent);
+
+  GooCanvasItem*       (* create_item)			(GooCanvasItemModel	*model,
+							 GooCanvas		*canvas);
+
+  /* Virtual methods that all item models may implement. */
+  gboolean             (* get_transform)		(GooCanvasItemModel	*model,
+							 cairo_matrix_t         *transform);
+  void                 (* set_transform)		(GooCanvasItemModel	*model,
+							 const cairo_matrix_t	*transform);
+  GooCanvasStyle*      (* get_style)			(GooCanvasItemModel	*model);
+  void                 (* set_style)			(GooCanvasItemModel	*model,
+							 GooCanvasStyle		*style);
+
+  /* Signals. */
+  void                 (* child_added)			(GooCanvasItemModel	*model,
+							 gint			 child_num);
+  void                 (* child_moved)			(GooCanvasItemModel	*model,
+							 gint			 old_child_num,
+							 gint			 new_child_num);
+  void                 (* child_removed)		(GooCanvasItemModel	*model,
+							 gint			 child_num);
+  void                 (* changed)			(GooCanvasItemModel	*model,
+							 gboolean		 recompute_bounds);
+  void                 (* child_notify)			(GooCanvasItemModel	*model,
+							 GParamSpec		*pspec);
+
+  void		       (* animation_finished)		(GooCanvasItemModel     *model,
+							 gboolean                stopped);
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+  void (*_goo_canvas_reserved5) (void);
+  void (*_goo_canvas_reserved6) (void);
+  void (*_goo_canvas_reserved7) (void);
+};
+
+
+GType               goo_canvas_item_model_get_type       (void) G_GNUC_CONST;
+
+
+/*
+ * Group functions - these should only be called on container models.
+ */
+gint                goo_canvas_item_model_get_n_children (GooCanvasItemModel *model);
+GooCanvasItemModel* goo_canvas_item_model_get_child      (GooCanvasItemModel *model,
+							  gint                child_num);
+void                goo_canvas_item_model_add_child      (GooCanvasItemModel *model,
+							  GooCanvasItemModel *child,
+							  gint                position);
+void                goo_canvas_item_model_move_child     (GooCanvasItemModel *model,
+							  gint                old_position,
+							  gint                new_position);
+void                goo_canvas_item_model_remove_child   (GooCanvasItemModel *model,
+							  gint                child_num);
+gint                goo_canvas_item_model_find_child     (GooCanvasItemModel *model,
+							  GooCanvasItemModel *child);
+
+void     goo_canvas_item_model_get_child_property	 (GooCanvasItemModel   *model,
+							  GooCanvasItemModel   *child,
+							  const gchar          *property_name,
+							  GValue               *value);
+void     goo_canvas_item_model_set_child_property	 (GooCanvasItemModel   *model,
+							  GooCanvasItemModel   *child,
+							  const gchar          *property_name,
+							  const GValue         *value);
+void     goo_canvas_item_model_get_child_properties	 (GooCanvasItemModel   *model,
+							  GooCanvasItemModel   *child,
+							  ...) G_GNUC_NULL_TERMINATED;
+void     goo_canvas_item_model_set_child_properties	 (GooCanvasItemModel   *model,
+							  GooCanvasItemModel   *child,
+							  ...) G_GNUC_NULL_TERMINATED;
+void     goo_canvas_item_model_get_child_properties_valist (GooCanvasItemModel   *model,
+							    GooCanvasItemModel   *child,
+							    va_list	         var_args);
+void     goo_canvas_item_model_set_child_properties_valist (GooCanvasItemModel   *model,
+							    GooCanvasItemModel   *child,
+							    va_list	         var_args);
+
+
+/*
+ * Model functions - these are safe to call on all models.
+ */
+GooCanvasItemModel* goo_canvas_item_model_get_parent     (GooCanvasItemModel *model);
+void                goo_canvas_item_model_set_parent	 (GooCanvasItemModel *model,
+							  GooCanvasItemModel *parent);
+void                goo_canvas_item_model_remove         (GooCanvasItemModel *model);
+gboolean            goo_canvas_item_model_is_container   (GooCanvasItemModel *model);
+
+void                goo_canvas_item_model_raise          (GooCanvasItemModel *model,
+							  GooCanvasItemModel *above);
+void                goo_canvas_item_model_lower          (GooCanvasItemModel *model,
+							  GooCanvasItemModel *below);
+
+gboolean            goo_canvas_item_model_get_transform  (GooCanvasItemModel *model,
+							  cairo_matrix_t     *transform);
+void                goo_canvas_item_model_set_transform  (GooCanvasItemModel   *model,
+							  const cairo_matrix_t *transform);
+gboolean	    goo_canvas_item_model_get_simple_transform (GooCanvasItemModel *model,
+								gdouble            *x,
+								gdouble            *y,
+								gdouble            *scale,
+								gdouble            *rotation);
+void                goo_canvas_item_model_set_simple_transform (GooCanvasItemModel *model,
+								gdouble             x,
+								gdouble             y,
+								gdouble             scale,
+								gdouble             rotation);
+
+void                goo_canvas_item_model_translate      (GooCanvasItemModel *model,
+							  gdouble             tx,
+							  gdouble             ty);
+void                goo_canvas_item_model_scale          (GooCanvasItemModel *model,
+							  gdouble             sx,
+							  gdouble             sy);
+void                goo_canvas_item_model_rotate         (GooCanvasItemModel *model,
+							  gdouble             degrees,
+							  gdouble             cx,
+							  gdouble             cy);
+void                goo_canvas_item_model_skew_x         (GooCanvasItemModel *model,
+							  gdouble             degrees,
+							  gdouble             cx,
+							  gdouble             cy);
+void                goo_canvas_item_model_skew_y         (GooCanvasItemModel *model,
+							  gdouble             degrees,
+							  gdouble             cx,
+							  gdouble             cy);
+
+GooCanvasStyle*     goo_canvas_item_model_get_style      (GooCanvasItemModel *model);
+void                goo_canvas_item_model_set_style      (GooCanvasItemModel *model,
+							  GooCanvasStyle  *style);
+
+void                goo_canvas_item_model_animate        (GooCanvasItemModel *model,
+							  gdouble             x,
+							  gdouble             y,
+							  gdouble             scale,
+							  gdouble             degrees,
+							  gboolean            absolute,
+							  gint                duration,
+							  gint                step_time,
+							  GooCanvasAnimateType type);
+void                goo_canvas_item_model_stop_animation (GooCanvasItemModel *model);
+
+
+/*
+ * Functions to support child properties when implementing new canvas items.
+ */
+void         goo_canvas_item_model_class_install_child_property (GObjectClass *mclass,
+								 guint	 property_id,
+								 GParamSpec	*pspec);
+GParamSpec*  goo_canvas_item_model_class_find_child_property	(GObjectClass	*mclass,
+								 const gchar	*property_name);
+GParamSpec** goo_canvas_item_model_class_list_child_properties  (GObjectClass	*mclass,
+								 guint	*n_properties);
+
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_ITEM_MODEL_H__ */
diff --git a/libgoocanvas/goocanvasitemsimple.c b/libgoocanvas/goocanvasitemsimple.c
new file mode 100644
index 0000000..8d3f001
--- /dev/null
+++ b/libgoocanvas/goocanvasitemsimple.c
@@ -0,0 +1,2106 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitemsimple.c - abstract base class for canvas items.
+ */
+
+/**
+ * SECTION:goocanvasitemsimple
+ * @Title: GooCanvasItemSimple
+ * @Short_Description: the base class for the standard canvas items.
+ * @Stability_Level: 
+ * @See_Also: 
+ *
+ * #GooCanvasItemSimple is used as a base class for all of the standard canvas
+ * items. It can also be used as the base class for new custom canvas items.
+ *
+ * It provides default implementations for many of the #GooCanvasItem
+ * methods.
+ *
+ * For very simple items, all that is needed is to implement the create_path()
+ * method. (#GooCanvasEllipse, #GooCanvasRect and #GooCanvasPath do this.)
+ *
+ * More complicated items need to implement the update(), paint() and
+ * is_item_at() methods instead. (#GooCanvasImage, #GooCanvasPolyline,
+ * #GooCanvasText and #GooCanvasWidget do this.) They may also need to
+ * override some of the other GooCanvasItem methods such as set_canvas(),
+ * set_parent() or allocate_area() if special code is needed. (#GooCanvasWidget
+ * does this to make sure the #GtkWidget is embedded in the #GooCanvas widget
+ * correctly.)
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasprivate.h"
+#include "goocanvasitemsimple.h"
+#include "goocanvas.h"
+#include "goocanvasatk.h"
+
+
+enum {
+  PROP_0,
+
+  /* Basic drawing properties. */
+  PROP_STROKE_PATTERN,
+  PROP_FILL_PATTERN,
+  PROP_FILL_RULE,
+  PROP_OPERATOR,
+  PROP_ANTIALIAS,
+
+  /* Line style & width properties. */
+  PROP_LINE_WIDTH,
+  PROP_LINE_CAP,
+  PROP_LINE_JOIN,
+  PROP_LINE_JOIN_MITER_LIMIT,
+  PROP_LINE_DASH,
+
+  /* Font properties. */
+  PROP_FONT,
+  PROP_FONT_DESC,
+  PROP_HINT_METRICS,
+
+  /* Convenience properties. */
+  PROP_STROKE_COLOR,
+  PROP_STROKE_COLOR_RGBA,
+  PROP_STROKE_PIXBUF,
+  PROP_FILL_COLOR,
+  PROP_FILL_COLOR_RGBA,
+  PROP_FILL_PIXBUF,
+
+  /* Other properties. Note that the order here is important PROP_TRANSFORM
+     must be the first non-style property. see set_property(). */
+  PROP_TRANSFORM,
+  PROP_PARENT,
+  PROP_VISIBILITY,
+  PROP_VISIBILITY_THRESHOLD,
+  PROP_POINTER_EVENTS,
+  PROP_TITLE,
+  PROP_DESCRIPTION,
+  PROP_CAN_FOCUS,
+  PROP_CLIP_PATH,
+  PROP_CLIP_FILL_RULE,
+  PROP_TOOLTIP
+};
+
+static gboolean accessibility_enabled = FALSE;
+
+static void canvas_item_interface_init          (GooCanvasItemIface   *iface);
+static void goo_canvas_item_simple_dispose      (GObject              *object);
+static void goo_canvas_item_simple_finalize     (GObject              *object);
+static void goo_canvas_item_simple_get_property (GObject              *object,
+						 guint                 prop_id,
+						 GValue               *value,
+						 GParamSpec           *pspec);
+static void goo_canvas_item_simple_set_property (GObject              *object,
+						 guint                 prop_id,
+						 const GValue         *value,
+						 GParamSpec           *pspec);
+
+static void     goo_canvas_item_simple_default_create_path (GooCanvasItemSimple   *simple,
+							    cairo_t               *cr);
+static void     goo_canvas_item_simple_default_update      (GooCanvasItemSimple   *simple,
+							    cairo_t               *cr);
+static void     goo_canvas_item_simple_default_paint       (GooCanvasItemSimple   *simple,
+							    cairo_t               *cr,
+							    const GooCanvasBounds *bounds);
+static gboolean goo_canvas_item_simple_default_is_item_at  (GooCanvasItemSimple   *simple,
+							    double                 x,
+							    double                 y,
+							    cairo_t               *cr,
+							    gboolean               is_pointer_event);
+
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasItemSimple, goo_canvas_item_simple,
+			 G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_item_simple_install_common_properties (GObjectClass *gobject_class)
+{
+  /* Basic drawing properties. */
+  g_object_class_install_property (gobject_class, PROP_STROKE_PATTERN,
+                                   g_param_spec_boxed ("stroke-pattern",
+						       _("Stroke Pattern"),
+						       _("The pattern to use to paint the perimeter of the item, or NULL disable painting"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_FILL_PATTERN,
+                                   g_param_spec_boxed ("fill-pattern",
+						       _("Fill Pattern"),
+						       _("The pattern to use to paint the interior of the item, or NULL to disable painting"),
+						       GOO_TYPE_CAIRO_PATTERN,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_FILL_RULE,
+				   g_param_spec_enum ("fill-rule",
+						      _("Fill Rule"),
+						      _("The fill rule used to determine which parts of the item are filled"),
+						      GOO_TYPE_CAIRO_FILL_RULE,
+						      CAIRO_FILL_RULE_WINDING,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_OPERATOR,
+				   g_param_spec_enum ("operator",
+						      _("Operator"),
+						      _("The compositing operator to use"),
+						      GOO_TYPE_CAIRO_OPERATOR,
+						      CAIRO_OPERATOR_OVER,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_ANTIALIAS,
+				   g_param_spec_enum ("antialias",
+						      _("Antialias"),
+						      _("The antialiasing mode to use"),
+						      GOO_TYPE_CAIRO_ANTIALIAS,
+						      CAIRO_ANTIALIAS_GRAY,
+						      G_PARAM_READWRITE));
+
+  /* Line style & width properties. */
+  g_object_class_install_property (gobject_class, PROP_LINE_WIDTH,
+				   g_param_spec_double ("line-width",
+							_("Line Width"),
+							_("The line width to use for the item's perimeter"),
+							0.0, G_MAXDOUBLE, 2.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_LINE_CAP,
+				   g_param_spec_enum ("line-cap",
+						      _("Line Cap"),
+						      _("The line cap style to use"),
+						      GOO_TYPE_CAIRO_LINE_CAP,
+						      CAIRO_LINE_CAP_BUTT,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_LINE_JOIN,
+				   g_param_spec_enum ("line-join",
+						      _("Line Join"),
+						      _("The line join style to use"),
+						      GOO_TYPE_CAIRO_LINE_JOIN,
+						      CAIRO_LINE_JOIN_MITER,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_LINE_JOIN_MITER_LIMIT,
+				   g_param_spec_double ("line-join-miter-limit",
+							_("Miter Limit"),
+							_("The smallest angle to use with miter joins, in degrees. Bevel joins will be used below this limit"),
+							0.0, G_MAXDOUBLE, 10.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_LINE_DASH,
+				   g_param_spec_boxed ("line-dash",
+						       _("Line Dash"),
+						       _("The dash pattern to use"),
+						       GOO_TYPE_CANVAS_LINE_DASH,
+						       G_PARAM_READWRITE));
+
+  /* Font properties. */
+  g_object_class_install_property (gobject_class, PROP_FONT,
+				   g_param_spec_string ("font",
+							_("Font"),
+							_("The base font to use for the text"),
+							NULL,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_FONT_DESC,
+				   g_param_spec_boxed ("font-desc",
+						       _("Font Description"),
+						       _("The attributes specifying which font to use"),
+						       PANGO_TYPE_FONT_DESCRIPTION,
+						       G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HINT_METRICS,
+				   g_param_spec_enum ("hint-metrics",
+						      _("Hint Metrics"),
+						      _("The hinting to be used for font metrics"),
+						      GOO_TYPE_CAIRO_HINT_METRICS,
+						      CAIRO_HINT_METRICS_OFF,
+						      G_PARAM_READWRITE));
+
+  /* Convenience properties - writable only. */
+  g_object_class_install_property (gobject_class, PROP_STROKE_COLOR,
+				   g_param_spec_string ("stroke-color",
+							_("Stroke Color"),
+							_("The color to use for the item's perimeter. To disable painting set the 'stroke-pattern' property to NULL"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_STROKE_COLOR_RGBA,
+				   g_param_spec_uint ("stroke-color-rgba",
+						      _("Stroke Color RGBA"),
+						      _("The color to use for the item's perimeter, specified as a 32-bit integer value. To disable painting set the 'stroke-pattern' property to NULL"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_STROKE_PIXBUF,
+                                   g_param_spec_object ("stroke-pixbuf",
+							_("Stroke Pixbuf"),
+							_("The pixbuf to use to draw the item's perimeter. To disable painting set the 'stroke-pattern' property to NULL"),
+                                                        GDK_TYPE_PIXBUF,
+                                                        G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_FILL_COLOR,
+				   g_param_spec_string ("fill-color",
+							_("Fill Color"),
+							_("The color to use to paint the interior of the item. To disable painting set the 'fill-pattern' property to NULL"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_FILL_COLOR_RGBA,
+				   g_param_spec_uint ("fill-color-rgba",
+						      _("Fill Color RGBA"),
+						      _("The color to use to paint the interior of the item, specified as a 32-bit integer value. To disable painting set the 'fill-pattern' property to NULL"),
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_FILL_PIXBUF,
+                                   g_param_spec_object ("fill-pixbuf",
+							_("Fill Pixbuf"),
+							_("The pixbuf to use to paint the interior of the item. To disable painting set the 'fill-pattern' property to NULL"),
+                                                        GDK_TYPE_PIXBUF,
+                                                        G_PARAM_WRITABLE));
+
+  /* Other properties. */
+  g_object_class_override_property (gobject_class, PROP_PARENT,
+				    "parent");
+
+  g_object_class_override_property (gobject_class, PROP_VISIBILITY,
+				    "visibility");
+
+  g_object_class_override_property (gobject_class, PROP_VISIBILITY_THRESHOLD,
+				    "visibility-threshold");
+
+  g_object_class_override_property (gobject_class, PROP_TRANSFORM,
+				    "transform");
+
+  g_object_class_override_property (gobject_class, PROP_POINTER_EVENTS,
+				    "pointer-events");
+
+  g_object_class_override_property (gobject_class, PROP_TITLE,
+				    "title");
+
+  g_object_class_override_property (gobject_class, PROP_DESCRIPTION,
+				    "description");
+
+  g_object_class_override_property (gobject_class, PROP_CAN_FOCUS,
+				    "can-focus");
+
+  g_object_class_override_property (gobject_class, PROP_TOOLTIP,
+				    "tooltip");
+
+  /**
+   * GooCanvasItemSimple:clip-path
+   *
+   * The sequence of commands describing the clip path of the item, specified
+   * as a string using the same syntax
+   * as in the <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable Vector
+   * Graphics (SVG)</ulink> path element.
+   */
+  /**
+   * GooCanvasItemModelSimple:clip-path
+   *
+   * The sequence of commands describing the clip path of the item, specified
+   * as a string using the same syntax
+   * as in the <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable Vector
+   * Graphics (SVG)</ulink> path element.
+   */
+  g_object_class_install_property (gobject_class, PROP_CLIP_PATH,
+				   g_param_spec_string ("clip-path",
+							_("Clip Path"),
+							_("The sequence of path commands specifying the clip path"),
+							NULL,
+							G_PARAM_WRITABLE));
+
+  g_object_class_install_property (gobject_class, PROP_CLIP_FILL_RULE,
+				   g_param_spec_enum ("clip-fill-rule",
+						      _("Clip Fill Rule"),
+						      _("The fill rule used to determine which parts of the item are clipped"),
+						      GOO_TYPE_CAIRO_FILL_RULE,
+						      CAIRO_FILL_RULE_WINDING,
+						      G_PARAM_READWRITE));
+
+}
+
+
+static void
+goo_canvas_item_simple_class_init (GooCanvasItemSimpleClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->dispose  = goo_canvas_item_simple_dispose;
+  gobject_class->finalize = goo_canvas_item_simple_finalize;
+
+  gobject_class->get_property = goo_canvas_item_simple_get_property;
+  gobject_class->set_property = goo_canvas_item_simple_set_property;
+
+  /* Register our accessible factory, but only if accessibility is enabled. */
+  if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
+    {
+      accessibility_enabled = TRUE;
+      atk_registry_set_factory_type (atk_get_default_registry (),
+				     GOO_TYPE_CANVAS_ITEM_SIMPLE,
+				     goo_canvas_item_accessible_factory_get_type ());
+    }
+
+  goo_canvas_item_simple_install_common_properties (gobject_class);
+
+  klass->simple_create_path = goo_canvas_item_simple_default_create_path;
+  klass->simple_update      = goo_canvas_item_simple_default_update;
+  klass->simple_paint       = goo_canvas_item_simple_default_paint;
+  klass->simple_is_item_at  = goo_canvas_item_simple_default_is_item_at;
+}
+
+
+static void
+goo_canvas_item_simple_init (GooCanvasItemSimple *item)
+{
+  GooCanvasBounds *bounds = &item->bounds;
+
+  bounds->x1 = bounds->y1 = bounds->x2 = bounds->y2 = 0.0;
+  item->simple_data = g_slice_new0 (GooCanvasItemSimpleData);
+  item->simple_data->visibility = GOO_CANVAS_ITEM_VISIBLE;
+  item->simple_data->pointer_events = GOO_CANVAS_EVENTS_VISIBLE_PAINTED;
+  item->simple_data->clip_fill_rule = CAIRO_FILL_RULE_WINDING;
+  item->need_update = TRUE;
+  item->need_entire_subtree_update = TRUE;
+}
+
+
+static void
+goo_canvas_item_simple_reset_model (GooCanvasItemSimple *simple)
+{
+  if (simple->model)
+    {
+      g_signal_handlers_disconnect_matched (simple->model, G_SIGNAL_MATCH_DATA,
+					    0, 0, NULL, NULL, simple);
+      g_object_unref (simple->model);
+      simple->model = NULL;
+      simple->simple_data = NULL;
+    }
+}
+
+
+/* Frees the contents of the GooCanvasItemSimpleData, but not the struct. */
+static void
+goo_canvas_item_simple_free_data (GooCanvasItemSimpleData *simple_data)
+{
+  if (simple_data)
+    {
+      if (simple_data->style)
+	{
+	  g_object_unref (simple_data->style);
+	  simple_data->style = NULL;
+	}
+
+      if (simple_data->clip_path_commands)
+	{
+	  g_array_free (simple_data->clip_path_commands, TRUE);
+	  simple_data->clip_path_commands = NULL;
+	}
+
+      g_slice_free (cairo_matrix_t, simple_data->transform);
+      simple_data->transform = NULL;
+    }
+}
+
+
+static void
+goo_canvas_item_simple_dispose (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+
+  /* Remove the view from the GooCanvas hash table. */
+  if (simple->canvas && simple->model)
+    goo_canvas_unregister_item (simple->canvas,
+				(GooCanvasItemModel*) simple->model);
+
+  goo_canvas_item_simple_reset_model (simple);
+  goo_canvas_item_simple_free_data (simple->simple_data);
+
+  G_OBJECT_CLASS (goo_canvas_item_simple_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_item_simple_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+
+  g_slice_free (GooCanvasItemSimpleData, simple->simple_data);
+  simple->simple_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_item_simple_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_item_simple_get_common_property (GObject                 *object,
+					    GooCanvasItemSimpleData *simple_data,
+					    GooCanvas               *canvas,
+					    guint                    prop_id,
+					    GValue                  *value,
+					    GParamSpec              *pspec)
+{
+  GooCanvasStyle *style = simple_data->style;
+  GValue *svalue;
+  gdouble line_width = 2.0;
+  gchar *font = NULL;
+
+  switch (prop_id)
+    {
+      /* Basic drawing properties. */
+    case PROP_STROKE_PATTERN:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_stroke_pattern_id);
+      g_value_set_boxed (value, svalue ? svalue->data[0].v_pointer : NULL);
+      break;
+    case PROP_FILL_PATTERN:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_fill_pattern_id);
+      g_value_set_boxed (value, svalue ? svalue->data[0].v_pointer : NULL);
+      break;
+    case PROP_FILL_RULE:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_fill_rule_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_FILL_RULE_WINDING);
+      break;
+    case PROP_OPERATOR:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_operator_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_OPERATOR_OVER);
+      break;
+    case PROP_ANTIALIAS:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_antialias_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_ANTIALIAS_GRAY);
+      break;
+
+      /* Line style & width properties. */
+    case PROP_LINE_WIDTH:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_line_width_id);
+      if (svalue)
+	line_width = svalue->data[0].v_double;
+      else if (canvas)
+	line_width = goo_canvas_get_default_line_width (canvas);
+      g_value_set_double (value, line_width);
+      break;
+    case PROP_LINE_CAP:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_line_cap_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_LINE_CAP_BUTT);
+      break;
+    case PROP_LINE_JOIN:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_line_join_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_LINE_JOIN_MITER);
+      break;
+    case PROP_LINE_JOIN_MITER_LIMIT:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_line_join_miter_limit_id);
+      g_value_set_double (value, svalue ? svalue->data[0].v_double : 10.0);
+      break;
+    case PROP_LINE_DASH:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_line_dash_id);
+      g_value_set_boxed (value, svalue ? svalue->data[0].v_pointer : NULL);
+      break;
+
+      /* Font properties. */
+    case PROP_FONT:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_font_desc_id);
+      if (svalue)
+	font = pango_font_description_to_string (svalue->data[0].v_pointer);
+      g_value_set_string (value, font);
+      g_free (font);
+      break;
+    case PROP_FONT_DESC:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_font_desc_id);
+      g_value_set_boxed (value, svalue ? svalue->data[0].v_pointer : NULL);
+      break;
+    case PROP_HINT_METRICS:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_hint_metrics_id);
+      g_value_set_enum (value, svalue ? svalue->data[0].v_long : CAIRO_HINT_METRICS_OFF);
+      break;
+
+      /* Convenience properties. */
+    case PROP_STROKE_COLOR_RGBA:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_stroke_pattern_id);
+      if (svalue)
+	goo_canvas_get_rgba_value_from_pattern (svalue->data[0].v_pointer,
+						value);
+      break;
+    case PROP_FILL_COLOR_RGBA:
+      svalue = goo_canvas_style_get_property (style, goo_canvas_style_fill_pattern_id);
+      if (svalue)
+	goo_canvas_get_rgba_value_from_pattern (svalue->data[0].v_pointer,
+						value);
+      break;
+
+      /* Other properties. */
+    case PROP_TRANSFORM:
+      g_value_set_boxed (value, simple_data->transform);
+      break;
+    case PROP_VISIBILITY:
+      g_value_set_enum (value, simple_data->visibility);
+      break;
+    case PROP_VISIBILITY_THRESHOLD:
+      g_value_set_double (value, simple_data->visibility_threshold);
+      break;
+    case PROP_POINTER_EVENTS:
+      g_value_set_flags (value, simple_data->pointer_events);
+      break;
+    case PROP_CAN_FOCUS:
+      g_value_set_boolean (value, simple_data->can_focus);
+      break;
+    case PROP_CLIP_FILL_RULE:
+      g_value_set_enum (value, simple_data->clip_fill_rule);
+      break;
+    case PROP_TOOLTIP:
+      g_value_set_string (value, simple_data->tooltip);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_item_simple_get_property (GObject              *object,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  AtkObject *accessible;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT:
+      g_value_set_object (value, simple->parent);
+      break;
+    case PROP_TITLE:
+      accessible = atk_gobject_accessible_for_object (object);
+      g_value_set_string (value, atk_object_get_name (accessible));
+      break;
+    case PROP_DESCRIPTION:
+      accessible = atk_gobject_accessible_for_object (object);
+      g_value_set_string (value, atk_object_get_description (accessible));
+      break;
+    default:
+      goo_canvas_item_simple_get_common_property (object, simple_data,
+						  simple->canvas, prop_id,
+						  value, pspec);
+      break;
+    }
+}
+
+
+static gboolean
+goo_canvas_item_simple_set_common_property (GObject                 *object,
+					    GooCanvasItemSimpleData *simple_data,
+					    guint                    prop_id,
+					    const GValue            *value,
+					    GParamSpec              *pspec)
+{
+  GooCanvasStyle *style;
+  cairo_pattern_t *pattern;
+  gboolean recompute_bounds = FALSE;
+  cairo_matrix_t *transform;
+  GValue tmpval = { 0 };
+  const char *font_name, *path_data;
+  PangoFontDescription *font_desc = NULL;
+
+  /* See if we need to create our own style. */
+  if (prop_id < PROP_TRANSFORM)
+    {
+      if (!simple_data->style)
+	{
+	  simple_data->style = goo_canvas_style_new ();
+	}
+      else if (!simple_data->own_style)
+	{
+	  g_object_unref (simple_data->style);
+	  simple_data->style = goo_canvas_style_new ();
+	}
+      simple_data->own_style = TRUE;
+    }
+
+  style = simple_data->style;
+
+  switch (prop_id)
+    {
+      /* Basic drawing properties. */
+    case PROP_STROKE_PATTERN:
+      goo_canvas_style_set_property (style, goo_canvas_style_stroke_pattern_id, value);
+      break;
+    case PROP_FILL_PATTERN:
+      goo_canvas_style_set_property (style, goo_canvas_style_fill_pattern_id, value);
+      break;
+    case PROP_FILL_RULE:
+      goo_canvas_style_set_property (style, goo_canvas_style_fill_rule_id, value);
+      break;
+    case PROP_OPERATOR:
+      goo_canvas_style_set_property (style, goo_canvas_style_operator_id, value);
+      break;
+    case PROP_ANTIALIAS:
+      goo_canvas_style_set_property (style, goo_canvas_style_antialias_id, value);
+      break;
+
+      /* Line style & width properties. */
+    case PROP_LINE_WIDTH:
+      goo_canvas_style_set_property (style, goo_canvas_style_line_width_id, value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_LINE_CAP:
+      goo_canvas_style_set_property (style, goo_canvas_style_line_cap_id, value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_LINE_JOIN:
+      goo_canvas_style_set_property (style, goo_canvas_style_line_join_id, value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_LINE_JOIN_MITER_LIMIT:
+      goo_canvas_style_set_property (style, goo_canvas_style_line_join_miter_limit_id,
+				     value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_LINE_DASH:
+      goo_canvas_style_set_property (style, goo_canvas_style_line_dash_id, value);
+      recompute_bounds = TRUE;
+      break;
+
+      /* Font properties. */
+    case PROP_FONT:
+      font_name = g_value_get_string (value);
+      if (font_name)
+	font_desc = pango_font_description_from_string (font_name);
+      g_value_init (&tmpval, PANGO_TYPE_FONT_DESCRIPTION);
+      g_value_take_boxed (&tmpval, font_desc);
+      goo_canvas_style_set_property (style, goo_canvas_style_font_desc_id, &tmpval);
+      g_value_unset (&tmpval);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_FONT_DESC:
+      goo_canvas_style_set_property (style, goo_canvas_style_font_desc_id, value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_HINT_METRICS:
+      goo_canvas_style_set_property (style, goo_canvas_style_hint_metrics_id, value);
+      recompute_bounds = TRUE;
+      break;
+
+      /* Convenience properties. */
+    case PROP_STROKE_COLOR:
+      pattern = goo_canvas_create_pattern_from_color_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_stroke_pattern_id, pattern);
+      break;
+    case PROP_STROKE_COLOR_RGBA:
+      pattern = goo_canvas_create_pattern_from_rgba_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_stroke_pattern_id, pattern);
+      break;
+    case PROP_STROKE_PIXBUF:
+      pattern = goo_canvas_create_pattern_from_pixbuf_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_stroke_pattern_id, pattern);
+      break;
+
+    case PROP_FILL_COLOR:
+      pattern = goo_canvas_create_pattern_from_color_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_fill_pattern_id, pattern);
+      break;
+    case PROP_FILL_COLOR_RGBA:
+      pattern = goo_canvas_create_pattern_from_rgba_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_fill_pattern_id, pattern);
+      break;
+    case PROP_FILL_PIXBUF:
+      pattern = goo_canvas_create_pattern_from_pixbuf_value (value);
+      goo_canvas_set_style_property_from_pattern (style, goo_canvas_style_fill_pattern_id, pattern);
+      break;
+
+      /* Other properties. */
+    case PROP_TRANSFORM:
+      g_slice_free (cairo_matrix_t, simple_data->transform);
+      transform = g_value_get_boxed (value);
+      simple_data->transform = goo_cairo_matrix_copy (transform);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_VISIBILITY:
+      simple_data->visibility = g_value_get_enum (value);
+      break;
+    case PROP_VISIBILITY_THRESHOLD:
+      simple_data->visibility_threshold = g_value_get_double (value);
+      break;
+    case PROP_POINTER_EVENTS:
+      simple_data->pointer_events = g_value_get_flags (value);
+      break;
+    case PROP_CAN_FOCUS:
+      simple_data->can_focus = g_value_get_boolean (value);
+      break;
+    case PROP_CLIP_PATH:
+      if (simple_data->clip_path_commands)
+	g_array_free (simple_data->clip_path_commands, TRUE);
+      path_data = g_value_get_string (value);
+      if (path_data)
+	simple_data->clip_path_commands = goo_canvas_parse_path_data (path_data);
+      else
+	simple_data->clip_path_commands = NULL;
+      recompute_bounds = TRUE;
+      break;
+    case PROP_CLIP_FILL_RULE:
+      simple_data->clip_fill_rule = g_value_get_enum (value);
+      recompute_bounds = TRUE;
+      break;
+    case PROP_TOOLTIP:
+      simple_data->tooltip = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+
+  return recompute_bounds;
+}
+
+
+static void
+goo_canvas_item_simple_set_property (GObject              *object,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasItem *item = (GooCanvasItem*) object;
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasItem *parent;
+  AtkObject *accessible;
+  gboolean recompute_bounds;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  switch (prop_id)
+    {
+    case PROP_PARENT:
+      parent = g_value_get_object (value);
+      goo_canvas_item_remove (item);
+      goo_canvas_item_add_child (parent, item, -1);
+      break;
+    case PROP_TITLE:
+      accessible = atk_gobject_accessible_for_object (object);
+      atk_object_set_name (accessible, g_value_get_string (value));
+      break;
+    case PROP_DESCRIPTION:
+      accessible = atk_gobject_accessible_for_object (object);
+      atk_object_set_description (accessible, g_value_get_string (value));
+      break;
+    default:
+      recompute_bounds = goo_canvas_item_simple_set_common_property (object,
+								     simple_data,
+								     prop_id,
+								     value,
+								     pspec);
+      goo_canvas_item_simple_changed (simple, recompute_bounds);
+      break;
+    }
+}
+
+
+static GooCanvas*
+goo_canvas_item_simple_get_canvas  (GooCanvasItem *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  return simple->canvas;
+}
+
+
+static void
+goo_canvas_item_simple_set_canvas  (GooCanvasItem *item,
+				    GooCanvas     *canvas)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  simple->canvas = canvas;
+}
+
+
+static GooCanvasItem*
+goo_canvas_item_simple_get_parent (GooCanvasItem   *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  return simple->parent;
+}
+
+
+static void
+goo_canvas_item_simple_set_parent (GooCanvasItem  *item,
+				   GooCanvasItem  *parent)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvas *canvas;
+
+  simple->parent = parent;
+  canvas = parent ? goo_canvas_item_get_canvas (parent) : NULL;
+  goo_canvas_item_set_canvas (item, canvas);
+  simple->need_update = TRUE;
+  simple->need_entire_subtree_update = TRUE;
+}
+
+
+/**
+ * goo_canvas_item_simple_changed:
+ * @item: a #GooCanvasItemSimple.
+ * @recompute_bounds: if the item's bounds need to be recomputed.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple.
+ *
+ * It is used as a callback for the "changed" signal of the item models.
+ * It requests an update or redraw of the item as appropriate.
+ **/
+void
+goo_canvas_item_simple_changed    (GooCanvasItemSimple *item,
+				   gboolean             recompute_bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (recompute_bounds)
+    {
+      item->need_entire_subtree_update = TRUE;
+      if (!item->need_update)
+	{
+	  goo_canvas_item_request_update ((GooCanvasItem*) item);
+
+	  /* Do this after requesting an update, since GooCanvasGroup will
+	     ignore the update request if we do this first. */
+	  item->need_update = TRUE;
+	}
+    }
+  else
+    {
+      if (item->canvas)
+	goo_canvas_request_item_redraw (item->canvas, &item->bounds, simple_data->is_static);
+    }
+}
+
+
+static gboolean
+goo_canvas_item_simple_get_transform (GooCanvasItem       *item,
+				      cairo_matrix_t      *matrix)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (!simple_data->transform)
+    return FALSE;
+
+  *matrix = *simple_data->transform;
+  return TRUE;
+}
+
+
+static void
+goo_canvas_item_simple_set_transform (GooCanvasItem        *item,
+				      const cairo_matrix_t *transform)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (transform)
+    {
+      if (!simple_data->transform)
+	simple_data->transform = g_slice_new (cairo_matrix_t);
+
+      *simple_data->transform = *transform;
+    }
+  else
+    {
+      g_slice_free (cairo_matrix_t, simple_data->transform);
+      simple_data->transform = NULL;
+    }
+
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static GooCanvasStyle*
+goo_canvas_item_simple_get_style (GooCanvasItem       *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+
+  return simple->simple_data->style;
+}
+
+
+static void
+goo_canvas_item_simple_set_style (GooCanvasItem  *item,
+				  GooCanvasStyle *style)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (simple_data->style)
+    g_object_unref (simple_data->style);
+
+  if (style)
+    {
+      simple_data->style = goo_canvas_style_copy (style);
+      simple_data->own_style = TRUE;
+    }
+  else
+    {
+      simple_data->style = NULL;
+      simple_data->own_style = FALSE;
+    }
+
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_item_simple_get_bounds  (GooCanvasItem   *item,
+				    GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+
+  if (simple->need_update)
+    goo_canvas_item_ensure_updated (item);
+    
+  *bounds = simple->bounds;
+}
+
+
+static GList*
+goo_canvas_item_simple_get_items_at (GooCanvasItem  *item,
+				     gdouble         x,
+				     gdouble         y,
+				     cairo_t        *cr,
+				     gboolean        is_pointer_event,
+				     gboolean        parent_visible,
+				     GList          *found_items)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (item);
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  double user_x = x, user_y = y;
+  cairo_matrix_t matrix;
+  gboolean add_item = FALSE;
+
+  if (simple->need_update)
+    goo_canvas_item_ensure_updated (item);
+
+  /* Skip the item if the point isn't in the item's bounds. */
+  if (simple->bounds.x1 > x || simple->bounds.x2 < x
+      || simple->bounds.y1 > y || simple->bounds.y2 < y)
+    return found_items;
+
+  /* Check if the item should receive events. */
+  if (is_pointer_event)
+    {
+      if (simple_data->pointer_events == GOO_CANVAS_EVENTS_NONE)
+	return found_items;
+      if (simple_data->pointer_events & GOO_CANVAS_EVENTS_VISIBLE_MASK
+	  && (!parent_visible
+	      || simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+	      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+		  && simple->canvas->scale < simple_data->visibility_threshold)))
+	return found_items;
+    }
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  cairo_device_to_user (cr, &user_x, &user_y);
+
+  /* Remove any current translation, to avoid the 16-bit cairo limit. */
+  cairo_get_matrix (cr, &matrix);
+  matrix.x0 = matrix.y0 = 0.0;
+  cairo_set_matrix (cr, &matrix);
+
+  /* If the item has a clip path, check if the point is inside it. */
+  if (simple_data->clip_path_commands)
+    {
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      if (!cairo_in_fill (cr, user_x, user_y))
+	{
+	  cairo_restore (cr);
+	  return found_items;
+	}
+    }
+
+  add_item = class->simple_is_item_at (simple, user_x, user_y, cr,
+                                       is_pointer_event);
+
+  cairo_restore (cr);
+
+  if (add_item)
+    return g_list_prepend (found_items, item);
+  else
+    return found_items;
+}
+
+
+static gboolean
+goo_canvas_item_simple_default_is_item_at (GooCanvasItemSimple *simple,
+					   double               x,
+					   double               y,
+					   cairo_t             *cr,
+					   gboolean             is_pointer_event)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (simple);
+
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasPointerEvents pointer_events = GOO_CANVAS_EVENTS_ALL;
+
+  if (is_pointer_event)
+    pointer_events = simple_data->pointer_events;
+
+  /* Use the virtual method subclasses define to create the path. */
+  class->simple_create_path (simple, cr);
+
+  if (goo_canvas_item_simple_check_in_path (simple, x, y, cr, pointer_events))
+    return TRUE;
+
+  return FALSE;
+}
+
+
+static gboolean
+goo_canvas_item_simple_is_visible  (GooCanvasItem   *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && simple->canvas->scale < simple_data->visibility_threshold))
+    return FALSE;
+
+  if (simple->parent)
+    return goo_canvas_item_is_visible (simple->parent);
+
+  return TRUE;
+}
+
+
+static gboolean
+goo_canvas_item_simple_get_is_static  (GooCanvasItem   *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  return simple_data->is_static;
+}
+
+
+static void
+goo_canvas_item_simple_set_is_static  (GooCanvasItem   *item,
+				       gboolean         is_static)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  simple_data->is_static = is_static;
+}
+
+
+/**
+ * goo_canvas_item_simple_check_style:
+ * @item: a #GooCanvasItemSimple.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple,
+ * typically in their update() or get_requested_area() methods.
+ *
+ * It ensures that the item's style is setup correctly. If the item has its
+ * own #GooCanvasStyle it makes sure the parent is set correctly. If it
+ * doesn't have its own style it uses the parent item's style.
+ **/
+void
+goo_canvas_item_simple_check_style (GooCanvasItemSimple *item)
+{
+  GooCanvasItemSimpleData *simple_data = item->simple_data;
+  GooCanvasStyle *parent_style = NULL;
+
+  if (item->parent)
+    parent_style = goo_canvas_item_get_style (item->parent);
+
+  if (simple_data->own_style)
+    {
+      goo_canvas_style_set_parent (simple_data->style, parent_style);
+    }
+  else if (simple_data->style != parent_style)
+    {
+      /* The item doesn't have its own style so we use the parent's (though
+	 that may also be NULL). */
+      if (simple_data->style)
+	g_object_unref (simple_data->style);
+
+      simple_data->style = parent_style;
+
+      if (parent_style)
+	g_object_ref (parent_style);
+    }
+}
+
+
+static void
+goo_canvas_item_simple_update_internal  (GooCanvasItemSimple *simple,
+					 cairo_t             *cr)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (simple);
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasBounds tmp_bounds;
+  cairo_matrix_t transform;
+
+  simple->need_update = FALSE;
+
+  goo_canvas_item_simple_check_style (simple);
+
+  cairo_get_matrix (cr, &transform);
+
+  class->simple_update (simple, cr);
+
+  /* Modify the extents by the item's clip path. */
+  if (simple_data->clip_path_commands)
+    {
+      cairo_identity_matrix (cr);
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      cairo_fill_extents (cr, &tmp_bounds.x1, &tmp_bounds.y1,
+			  &tmp_bounds.x2, &tmp_bounds.y2);
+      simple->bounds.x1 = MAX (simple->bounds.x1, tmp_bounds.x1);
+      simple->bounds.y1 = MAX (simple->bounds.y1, tmp_bounds.y1);
+      simple->bounds.x2 = MIN (simple->bounds.x2, tmp_bounds.x2);
+      simple->bounds.y2 = MIN (simple->bounds.y2, tmp_bounds.y2);
+
+      if (simple->bounds.x1 > simple->bounds.x2)
+	simple->bounds.x2 = simple->bounds.x1;
+      if (simple->bounds.y1 > simple->bounds.y2)
+	simple->bounds.y2 = simple->bounds.y1;
+    }
+
+  cairo_set_matrix (cr, &transform);
+}
+
+
+static void
+goo_canvas_item_simple_default_update (GooCanvasItemSimple   *simple,
+				       cairo_t               *cr)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (simple);
+
+  /* Use the identity matrix to get the bounds completely in user space. */
+  cairo_identity_matrix (cr);
+
+  class->simple_create_path (simple, cr);
+  goo_canvas_item_simple_get_path_bounds (simple, cr, &simple->bounds);
+}
+
+
+static void
+goo_canvas_item_simple_update  (GooCanvasItem   *item,
+				gboolean         entire_tree,
+				cairo_t         *cr,
+				GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  cairo_matrix_t matrix;
+  double x_offset, y_offset;
+
+  if (entire_tree || simple->need_update)
+    {
+      /* Request a redraw of the existing bounds. */
+      goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+
+      cairo_save (cr);
+      if (simple_data->transform)
+	cairo_transform (cr, simple_data->transform);
+
+      /* Remove any current translation, to avoid the 16-bit cairo limit. */
+      cairo_get_matrix (cr, &matrix);
+      x_offset = matrix.x0;
+      y_offset = matrix.y0;
+      matrix.x0 = matrix.y0 = 0.0;
+      cairo_set_matrix (cr, &matrix);
+
+      goo_canvas_item_simple_update_internal (simple, cr);
+
+      goo_canvas_item_simple_user_bounds_to_device (simple, cr,
+						    &simple->bounds);
+
+      /* Add the translation back to the bounds. */
+      simple->bounds.x1 += x_offset;
+      simple->bounds.y1 += y_offset;
+      simple->bounds.x2 += x_offset;
+      simple->bounds.y2 += y_offset;
+
+      cairo_restore (cr);
+
+      /* Request a redraw of the new bounds. */
+      goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+    }
+
+  *bounds = simple->bounds;
+}
+
+
+static gboolean
+goo_canvas_item_simple_get_requested_area (GooCanvasItem    *item,
+					   cairo_t          *cr,
+					   GooCanvasBounds  *requested_area)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  cairo_matrix_t matrix;
+  double x_offset, y_offset;
+
+  /* Request a redraw of the existing bounds. */
+  goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  /* Remove any current translation, to avoid the 16-bit cairo limit. */
+  cairo_get_matrix (cr, &matrix);
+  x_offset = matrix.x0;
+  y_offset = matrix.y0;
+  matrix.x0 = matrix.y0 = 0.0;
+  cairo_set_matrix (cr, &matrix);
+
+  goo_canvas_item_simple_update_internal (simple, cr);
+
+  if (simple->simple_data->visibility == GOO_CANVAS_ITEM_HIDDEN)
+    {
+      simple->bounds.x1 = simple->bounds.x2 = 0.0;
+      simple->bounds.y1 = simple->bounds.y2 = 0.0;
+      cairo_restore (cr);
+      return FALSE;
+    }
+
+  /* FIXME: Maybe optimize by just converting the offsets to user space
+     and adding them? */
+
+  /* Convert to device space. */
+  cairo_user_to_device (cr, &simple->bounds.x1, &simple->bounds.y1);
+  cairo_user_to_device (cr, &simple->bounds.x2, &simple->bounds.y2);
+
+  /* Add the translation back to the bounds. */
+  simple->bounds.x1 += x_offset;
+  simple->bounds.y1 += y_offset;
+  simple->bounds.x2 += x_offset;
+  simple->bounds.y2 += y_offset;
+
+  /* Restore the item's proper transformation matrix. */
+  matrix.x0 = x_offset;
+  matrix.y0 = y_offset;
+  cairo_set_matrix (cr, &matrix);
+
+  /* Convert back to user space. */
+  cairo_device_to_user (cr, &simple->bounds.x1, &simple->bounds.y1);
+  cairo_device_to_user (cr, &simple->bounds.x2, &simple->bounds.y2);
+
+
+  /* Copy the user bounds to the requested area. */
+  *requested_area = simple->bounds;
+
+  /* Convert to the parent's coordinate space. */
+  goo_canvas_item_simple_user_bounds_to_parent (simple, cr, requested_area);
+
+  /* Convert the item's bounds to device space. */
+  goo_canvas_item_simple_user_bounds_to_device (simple, cr, &simple->bounds);
+
+  cairo_restore (cr);
+
+  return TRUE;
+}
+
+
+static void
+goo_canvas_item_simple_allocate_area      (GooCanvasItem         *item,
+					   cairo_t               *cr,
+					   const GooCanvasBounds *requested_area,
+					   const GooCanvasBounds *allocated_area,
+					   gdouble                x_offset,
+					   gdouble                y_offset)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  /* Simple items can't resize at all, so we just adjust the bounds x & y
+     positions here, and let the item be clipped if necessary. */
+  simple->bounds.x1 += x_offset;
+  simple->bounds.y1 += y_offset;
+  simple->bounds.x2 += x_offset;
+  simple->bounds.y2 += y_offset;
+
+  /* Request a redraw of the new bounds. */
+  goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+}
+
+
+static void
+goo_canvas_item_simple_paint (GooCanvasItem         *item,
+			      cairo_t               *cr,
+			      const GooCanvasBounds *bounds,
+			      gdouble                scale)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (item);
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  /* Skip the item if the bounds don't intersect the expose rectangle. */
+  if (simple->bounds.x1 > bounds->x2 || simple->bounds.x2 < bounds->x1
+      || simple->bounds.y1 > bounds->y2 || simple->bounds.y2 < bounds->y1)
+    return;
+
+  /* Check if the item should be visible. */
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && scale < simple_data->visibility_threshold))
+    return;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  /* Clip with the item's clip path, if it is set. */
+  if (simple_data->clip_path_commands)
+    {
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      cairo_clip (cr);
+    }
+
+  class->simple_paint (simple, cr, bounds);
+
+  cairo_restore (cr);
+}
+
+
+static void
+goo_canvas_item_simple_default_paint (GooCanvasItemSimple   *simple,
+				      cairo_t               *cr,
+				      const GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimpleClass *class = GOO_CANVAS_ITEM_SIMPLE_GET_CLASS (simple);
+
+  class->simple_create_path (simple, cr);
+  goo_canvas_item_simple_paint_path (simple, cr);
+}
+
+
+static void
+goo_canvas_item_simple_default_create_path (GooCanvasItemSimple   *simple,
+					    cairo_t               *cr)
+{
+  /* Do nothing. */
+}
+
+
+static void
+goo_canvas_item_simple_title_changed (GooCanvasItemModelSimple *smodel,
+				      GParamSpec               *pspec,
+				      GooCanvasItemSimple      *item)
+{
+  AtkObject *accessible;
+
+  accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
+  atk_object_set_name (accessible, smodel->title);
+}
+
+
+static void
+goo_canvas_item_simple_description_changed (GooCanvasItemModelSimple *smodel,
+					    GParamSpec               *pspec,
+					    GooCanvasItemSimple      *item)
+{
+  AtkObject *accessible;
+
+  accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
+  atk_object_set_description (accessible, smodel->description);
+}
+
+
+static void
+goo_canvas_item_simple_setup_accessibility (GooCanvasItemSimple      *item)
+{
+  GooCanvasItemModelSimple *smodel = item->model;
+  AtkObject *accessible;
+
+  accessible = atk_gobject_accessible_for_object (G_OBJECT (item));
+  if (!ATK_IS_NO_OP_OBJECT (accessible))
+    {
+      if (smodel->title)
+	atk_object_set_name (accessible, smodel->title);
+      if (smodel->description)
+	atk_object_set_description (accessible, smodel->description);
+
+      g_signal_connect (smodel, "notify::title",
+			G_CALLBACK (goo_canvas_item_simple_title_changed),
+			item);
+      g_signal_connect (smodel, "notify::description",
+			G_CALLBACK (goo_canvas_item_simple_description_changed),
+			item);
+    }
+}
+
+
+static void
+goo_canvas_item_model_simple_changed (GooCanvasItemModelSimple *smodel,
+				      gboolean                  recompute_bounds,
+				      GooCanvasItemSimple      *simple)
+{
+  goo_canvas_item_simple_changed (simple, recompute_bounds);
+}
+
+
+static GooCanvasItemModel*
+goo_canvas_item_simple_get_model    (GooCanvasItem      *item)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  return (GooCanvasItemModel*) simple->model;
+}
+
+
+/**
+ * goo_canvas_item_simple_set_model:
+ * @item: a #GooCanvasItemSimple.
+ * @model: the model that @item will view.
+ * 
+ * This function should be called by subclasses of #GooCanvasItemSimple
+ * in their set_model() method.
+ **/
+void
+goo_canvas_item_simple_set_model (GooCanvasItemSimple  *item,
+				  GooCanvasItemModel   *model)
+{
+  g_return_if_fail (model != NULL);
+
+  goo_canvas_item_simple_reset_model (item);
+  goo_canvas_item_simple_free_data (item->simple_data);
+  g_slice_free (GooCanvasItemSimpleData, item->simple_data);
+
+  item->model = g_object_ref (model);
+  item->simple_data = &item->model->simple_data;
+
+  if (accessibility_enabled)
+    goo_canvas_item_simple_setup_accessibility (item);
+
+  g_signal_connect (model, "changed",
+		    G_CALLBACK (goo_canvas_item_model_simple_changed),
+		    item);
+}
+
+
+static void
+goo_canvas_item_simple_set_model_internal    (GooCanvasItem      *item,
+					      GooCanvasItemModel *model)
+{
+  goo_canvas_item_simple_set_model ((GooCanvasItemSimple*) item, model);
+}
+
+
+static gboolean
+goo_canvas_item_simple_query_tooltip (GooCanvasItem  *item,
+				      gdouble         x,
+				      gdouble         y,
+				      gboolean        keyboard_tip,
+				      GtkTooltip     *tooltip)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+
+  if (simple_data->tooltip)
+    {
+      gtk_tooltip_set_markup (tooltip, simple_data->tooltip);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->get_canvas         = goo_canvas_item_simple_get_canvas;
+  iface->set_canvas         = goo_canvas_item_simple_set_canvas;
+
+  iface->get_parent	    = goo_canvas_item_simple_get_parent;
+  iface->set_parent	    = goo_canvas_item_simple_set_parent;
+  iface->get_bounds         = goo_canvas_item_simple_get_bounds;
+  iface->get_items_at	    = goo_canvas_item_simple_get_items_at;
+  iface->update             = goo_canvas_item_simple_update;
+  iface->get_requested_area = goo_canvas_item_simple_get_requested_area;
+  iface->allocate_area      = goo_canvas_item_simple_allocate_area;
+  iface->paint              = goo_canvas_item_simple_paint;
+
+  iface->get_transform      = goo_canvas_item_simple_get_transform;
+  iface->set_transform      = goo_canvas_item_simple_set_transform;
+  iface->get_style          = goo_canvas_item_simple_get_style;
+  iface->set_style          = goo_canvas_item_simple_set_style;
+  iface->is_visible         = goo_canvas_item_simple_is_visible;
+  iface->get_is_static	    = goo_canvas_item_simple_get_is_static;
+  iface->set_is_static	    = goo_canvas_item_simple_set_is_static;
+
+  iface->get_model          = goo_canvas_item_simple_get_model;
+  iface->set_model          = goo_canvas_item_simple_set_model_internal;
+
+  iface->query_tooltip	    = goo_canvas_item_simple_query_tooltip;
+}
+
+
+/**
+ * goo_canvas_item_simple_paint_path:
+ * @item: a #GooCanvasItemSimple.
+ * @cr: a cairo context.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple.
+ *
+ * It paints the current path, using the item's style settings.
+ **/
+void
+goo_canvas_item_simple_paint_path (GooCanvasItemSimple *item,
+				   cairo_t             *cr)
+{
+  GooCanvasStyle *style = item->simple_data->style;
+
+  if (goo_canvas_style_set_fill_options (style, cr))
+    cairo_fill_preserve (cr);
+
+  if (goo_canvas_style_set_stroke_options (style, cr))
+    cairo_stroke (cr);
+
+  cairo_new_path (cr);
+}
+
+
+/* Returns the bounds of the path, using the item's stroke and fill options,
+   in device coords. Note that the bounds include both the stroke and the
+   fill extents, even if they will not be painted. (We need this to handle
+   the "pointer-events" property.) */
+/**
+ * goo_canvas_item_simple_get_path_bounds:
+ * @item: a #GooCanvasItemSimple.
+ * @cr: a cairo context.
+ * @bounds: the #GooCanvasBounds struct to store the resulting bounding box.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple,
+ * typically in their update() or get_requested_area() methods.
+ *
+ * It calculates the bounds of the current path, using the item's style
+ * settings, and stores the results in the given #GooCanvasBounds struct.
+ *
+ * The returned bounds contains the bounding box of the path in device space,
+ * converted to user space coordinates. To calculate the bounds completely in
+ * user space, use cairo_identity_matrix() to temporarily reset the current
+ * transformation matrix to the identity matrix.
+ **/
+void
+goo_canvas_item_simple_get_path_bounds (GooCanvasItemSimple *item,
+					cairo_t             *cr,
+					GooCanvasBounds     *bounds)
+{
+  GooCanvasStyle *style = item->simple_data->style;
+  GooCanvasBounds fill_bounds, stroke_bounds;
+
+  /* Calculate the filled extents. */
+  goo_canvas_style_set_fill_options (style, cr);
+  cairo_fill_extents (cr, &fill_bounds.x1, &fill_bounds.y1,
+		      &fill_bounds.x2, &fill_bounds.y2);
+
+  /* Check the stroke. */
+  goo_canvas_style_set_stroke_options (style, cr);
+  cairo_stroke_extents (cr, &stroke_bounds.x1, &stroke_bounds.y1,
+			&stroke_bounds.x2, &stroke_bounds.y2);
+
+  /* Workaround for cairo < 1.4.0. It used to just return odd values
+     if the path had empty bounds. This fix will work, but only if there is
+     no transform currently set, since cairo will convert to user space. */
+  if (cairo_version () < CAIRO_VERSION_ENCODE (1, 4, 0))
+    {
+      if (fill_bounds.x1 == 32767.0 && fill_bounds.x2 == -32768.0)
+	fill_bounds.x1 = fill_bounds.x2 = 0.0;
+      if (stroke_bounds.x1 == 32767.0 && stroke_bounds.x2 == -32768.0)
+	stroke_bounds.x1 = stroke_bounds.x2 = 0.0;
+    }
+
+  if (fill_bounds.x1 == 0.0 && fill_bounds.x2 == 0.0)
+    {
+      /* The fill bounds are empty so just use the stroke bounds.
+	 If the stroke bounds are also empty the bounds will be all 0.0. */
+      bounds->x1 = MIN (stroke_bounds.x1, stroke_bounds.x2);
+      bounds->x2 = MAX (stroke_bounds.x1, stroke_bounds.x2);
+      bounds->y1 = MIN (stroke_bounds.y1, stroke_bounds.y2);
+      bounds->y2 = MAX (stroke_bounds.y1, stroke_bounds.y2);
+    }
+  else if (stroke_bounds.x1 == 0.0 && stroke_bounds.x2 == 0.0)
+    {
+      /* The stroke bounds are empty so just use the fill bounds. */
+      bounds->x1 = MIN (fill_bounds.x1, fill_bounds.x2);
+      bounds->x2 = MAX (fill_bounds.x1, fill_bounds.x2);
+      bounds->y1 = MIN (fill_bounds.y1, fill_bounds.y2);
+      bounds->y2 = MAX (fill_bounds.y1, fill_bounds.y2);
+    }
+  else
+    {
+      /* Both fill & stoke bounds are non-empty so combine them. */
+      bounds->x1 = MIN (fill_bounds.x1, fill_bounds.x2);
+      bounds->x2 = MAX (fill_bounds.x1, fill_bounds.x2);
+      bounds->y1 = MIN (fill_bounds.y1, fill_bounds.y2);
+      bounds->y2 = MAX (fill_bounds.y1, fill_bounds.y2);
+
+      bounds->x1 = MIN (bounds->x1, stroke_bounds.x1);
+      bounds->x1 = MIN (bounds->x1, stroke_bounds.x2);
+
+      bounds->x2 = MAX (bounds->x2, stroke_bounds.x1);
+      bounds->x2 = MAX (bounds->x2, stroke_bounds.x2);
+
+      bounds->y1 = MIN (bounds->y1, stroke_bounds.y1);
+      bounds->y1 = MIN (bounds->y1, stroke_bounds.y2);
+
+      bounds->y2 = MAX (bounds->y2, stroke_bounds.y1);
+      bounds->y2 = MAX (bounds->y2, stroke_bounds.y2);
+    }
+}
+
+
+/**
+ * goo_canvas_item_simple_user_bounds_to_device:
+ * @item: a #GooCanvasItemSimple.
+ * @cr: a cairo context.
+ * @bounds: the bounds of the item, in the item's coordinate space.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple,
+ * typically in their update() or get_requested_area() methods.
+ *
+ * It converts the item's bounds to a bounding box in the canvas (device)
+ * coordinate space.
+ **/
+void
+goo_canvas_item_simple_user_bounds_to_device (GooCanvasItemSimple *item,
+					      cairo_t             *cr,
+					      GooCanvasBounds     *bounds)
+{
+  GooCanvasBounds tmp_bounds = *bounds, tmp_bounds2 = *bounds;
+
+  /* Convert the top-left and bottom-right corners to device coords. */
+  cairo_user_to_device (cr, &tmp_bounds.x1, &tmp_bounds.y1);
+  cairo_user_to_device (cr, &tmp_bounds.x2, &tmp_bounds.y2);
+
+  /* Now convert the top-right and bottom-left corners. */
+  cairo_user_to_device (cr, &tmp_bounds2.x1, &tmp_bounds2.y2);
+  cairo_user_to_device (cr, &tmp_bounds2.x2, &tmp_bounds2.y1);
+
+  /* Calculate the minimum x coordinate seen and put in x1. */
+  bounds->x1 = MIN (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x1);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x2);
+
+  /* Calculate the maximum x coordinate seen and put in x2. */
+  bounds->x2 = MAX (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x1);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x2);
+
+  /* Calculate the minimum y coordinate seen and put in y1. */
+  bounds->y1 = MIN (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y1);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y2);
+
+  /* Calculate the maximum y coordinate seen and put in y2. */
+  bounds->y2 = MAX (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y1);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y2);
+}
+
+
+/**
+ * goo_canvas_item_simple_user_bounds_to_parent:
+ * @item: a #GooCanvasItemSimple.
+ * @cr: a cairo context.
+ * @bounds: the bounds of the item, in the item's coordinate space.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple,
+ * typically in their get_requested_area() method.
+ *
+ * It converts the item's bounds to a bounding box in its parent's coordinate
+ * space. If the item has no transformation matrix set then no conversion is
+ * needed.
+ **/
+void
+goo_canvas_item_simple_user_bounds_to_parent (GooCanvasItemSimple *item,
+					      cairo_t             *cr,
+					      GooCanvasBounds     *bounds)
+{
+  GooCanvasItemSimpleData *simple_data = item->simple_data;
+  cairo_matrix_t *transform = simple_data->transform;
+  GooCanvasBounds tmp_bounds, tmp_bounds2;
+
+  if (!transform)
+    return;
+
+  tmp_bounds = tmp_bounds2 = *bounds;
+
+  /* Convert the top-left and bottom-right corners to parent coords. */
+  cairo_matrix_transform_point (transform, &tmp_bounds.x1, &tmp_bounds.y1);
+  cairo_matrix_transform_point (transform, &tmp_bounds.x2, &tmp_bounds.y2);
+
+  /* Now convert the top-right and bottom-left corners. */
+  cairo_matrix_transform_point (transform, &tmp_bounds2.x1, &tmp_bounds2.y2);
+  cairo_matrix_transform_point (transform, &tmp_bounds2.x2, &tmp_bounds2.y1);
+
+  /* Calculate the minimum x coordinate seen and put in x1. */
+  bounds->x1 = MIN (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x1);
+  bounds->x1 = MIN (bounds->x1, tmp_bounds2.x2);
+
+  /* Calculate the maximum x coordinate seen and put in x2. */
+  bounds->x2 = MAX (tmp_bounds.x1, tmp_bounds.x2);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x1);
+  bounds->x2 = MAX (bounds->x2, tmp_bounds2.x2);
+
+  /* Calculate the minimum y coordinate seen and put in y1. */
+  bounds->y1 = MIN (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y1);
+  bounds->y1 = MIN (bounds->y1, tmp_bounds2.y2);
+
+  /* Calculate the maximum y coordinate seen and put in y2. */
+  bounds->y2 = MAX (tmp_bounds.y1, tmp_bounds.y2);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y1);
+  bounds->y2 = MAX (bounds->y2, tmp_bounds2.y2);
+}
+
+
+/**
+ * goo_canvas_item_simple_check_in_path:
+ * @item: a #GooCanvasItemSimple.
+ * @x: the x coordinate of the point.
+ * @y: the y coordinate of the point.
+ * @cr: a cairo context.
+ * @pointer_events: specifies which parts of the path to check.
+ * 
+ * This function is intended to be used by subclasses of #GooCanvasItemSimple.
+ *
+ * It checks if the given point is in the current path, using the item's
+ * style settings.
+ * 
+ * Returns: %TRUE if the given point is in the current path.
+ **/
+gboolean
+goo_canvas_item_simple_check_in_path (GooCanvasItemSimple   *item,
+				      gdouble                x,
+				      gdouble                y,
+				      cairo_t               *cr,
+				      GooCanvasPointerEvents pointer_events)
+{
+  GooCanvasStyle *style = item->simple_data->style;
+  gboolean do_fill, do_stroke;
+
+  /* Check the filled path, if required. */
+  if (pointer_events & GOO_CANVAS_EVENTS_FILL_MASK)
+    {
+      do_fill = goo_canvas_style_set_fill_options (style, cr);
+      if (!(pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK) || do_fill)
+	{
+	  if (cairo_in_fill (cr, x, y))
+	    return TRUE;
+	}
+    }
+
+  /* Check the stroke, if required. */
+  if (pointer_events & GOO_CANVAS_EVENTS_STROKE_MASK)
+    {
+      do_stroke = goo_canvas_style_set_stroke_options (style, cr);
+      if (!(pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK) || do_stroke)
+	{
+	  if (cairo_in_stroke (cr, x, y))
+	    return TRUE;
+	}
+    }
+
+  return FALSE;
+}
+
+
+/**
+ * goo_canvas_item_simple_get_line_width:
+ * @item: a #GooCanvasItemSimple.
+ * 
+ * Gets the item's line width.
+ * 
+ * Returns: the item's line width.
+ **/
+gdouble
+goo_canvas_item_simple_get_line_width (GooCanvasItemSimple   *item)
+{
+  GValue *value;
+
+  value = goo_canvas_style_get_property (item->simple_data->style,
+					 goo_canvas_style_line_width_id);
+  if (value)
+    return value->data[0].v_double;
+  else if (item->canvas)
+    return goo_canvas_get_default_line_width (item->canvas);
+  else
+    return 2.0;
+}
+
+
+/**
+ * SECTION:goocanvasitemmodelsimple
+ * @Title: GooCanvasItemModelSimple
+ * @Short_Description: the base class for the standard canvas item models.
+ * @Stability_Level: 
+ * @See_Also: 
+ *
+ * #GooCanvasItemModelSimple is used as a base class for the standard canvas
+ * item models. It can also be used as the base class for new custom canvas
+ * item models.
+ *
+ * <note><para>
+ * 	The Model/View canvas feature may be removed in a future version of
+ * 	GooCanvas.
+ * </para></note>
+ * 
+ * It provides default implementations for many of the #GooCanvasItemModel
+ * methods.
+ *
+ * Subclasses of #GooCanvasItemModelSimple only need to implement the
+ * create_item() method of the #GooCanvasItemModel interface, to create
+ * the default canvas item to view the item model.
+ *
+ */
+
+
+static void item_model_interface_init  (GooCanvasItemModelIface *iface);
+static void goo_canvas_item_model_simple_dispose  (GObject *object);
+static void goo_canvas_item_model_simple_finalize (GObject *object);
+static void goo_canvas_item_model_simple_get_property (GObject              *object,
+						       guint                 prop_id,
+						       GValue               *value,
+						       GParamSpec           *pspec);
+static void goo_canvas_item_model_simple_set_property (GObject              *object,
+						       guint                 prop_id,
+						       const GValue         *value,
+						       GParamSpec           *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasItemModelSimple,
+			 goo_canvas_item_model_simple, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_item_model_simple_class_init (GooCanvasItemModelSimpleClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->dispose  = goo_canvas_item_model_simple_dispose;
+  gobject_class->finalize = goo_canvas_item_model_simple_finalize;
+
+  gobject_class->get_property = goo_canvas_item_model_simple_get_property;
+  gobject_class->set_property = goo_canvas_item_model_simple_set_property;
+
+  goo_canvas_item_simple_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_item_model_simple_init (GooCanvasItemModelSimple *smodel)
+{
+  smodel->simple_data.visibility = GOO_CANVAS_ITEM_VISIBLE;
+  smodel->simple_data.pointer_events = GOO_CANVAS_EVENTS_VISIBLE_PAINTED;
+  smodel->simple_data.clip_fill_rule = CAIRO_FILL_RULE_WINDING;
+}
+
+
+static void
+goo_canvas_item_model_simple_dispose (GObject *object)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) object;
+
+  goo_canvas_item_simple_free_data (&smodel->simple_data);
+
+  G_OBJECT_CLASS (goo_canvas_item_model_simple_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_item_model_simple_finalize (GObject *object)
+{
+  /*GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) object;*/
+
+  G_OBJECT_CLASS (goo_canvas_item_model_simple_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_item_model_simple_get_property (GObject              *object,
+					   guint                 prop_id,
+					   GValue               *value,
+					   GParamSpec           *pspec)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) object;
+  GooCanvasItemSimpleData *simple_data = &smodel->simple_data;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT:
+      g_value_set_object (value, smodel->parent);
+      break;
+    case PROP_TITLE:
+      g_value_set_string (value, smodel->title);
+      break;
+    case PROP_DESCRIPTION:
+      g_value_set_string (value, smodel->description);
+      break;
+    default:
+      goo_canvas_item_simple_get_common_property (object, simple_data, NULL,
+						  prop_id, value, pspec);
+      break;
+    }
+}
+
+
+extern void _goo_canvas_item_model_emit_changed (GooCanvasItemModel *model,
+						 gboolean            recompute_bounds);
+
+static void
+goo_canvas_item_model_simple_set_property (GObject              *object,
+					   guint                 prop_id,
+					   const GValue         *value,
+					   GParamSpec           *pspec)
+{
+  GooCanvasItemModel *model = (GooCanvasItemModel*) object;
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) object;
+  GooCanvasItemSimpleData *simple_data = &smodel->simple_data;
+  GooCanvasItemModel *parent;
+  gboolean recompute_bounds;
+
+  switch (prop_id)
+    {
+    case PROP_PARENT:
+      parent = g_value_get_object (value);
+      goo_canvas_item_model_remove (model);
+      goo_canvas_item_model_add_child (parent, model, -1);
+      break;
+    case PROP_TITLE:
+      g_free (smodel->title);
+      smodel->title = g_value_dup_string (value);
+      break;
+    case PROP_DESCRIPTION:
+      g_free (smodel->description);
+      smodel->description = g_value_dup_string (value);
+      break;
+    default:
+      recompute_bounds = goo_canvas_item_simple_set_common_property (object,
+								     simple_data,
+								     prop_id,
+								     value,
+								     pspec);
+      _goo_canvas_item_model_emit_changed (model, recompute_bounds);
+      break;
+    }
+}
+
+
+static GooCanvasItemModel*
+goo_canvas_item_model_simple_get_parent (GooCanvasItemModel   *model)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+  return smodel->parent;
+}
+
+
+static void
+goo_canvas_item_model_simple_set_parent (GooCanvasItemModel   *model,
+					 GooCanvasItemModel   *parent)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+  smodel->parent = parent;
+}
+
+
+static gboolean
+goo_canvas_item_model_simple_get_transform (GooCanvasItemModel  *model,
+					    cairo_matrix_t      *matrix)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+  GooCanvasItemSimpleData *simple_data = &smodel->simple_data;
+
+  if (!simple_data->transform)
+    return FALSE;
+
+  *matrix = *simple_data->transform;
+  return TRUE;
+}
+
+
+static void
+goo_canvas_item_model_simple_set_transform (GooCanvasItemModel   *model,
+					    const cairo_matrix_t *transform)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+  GooCanvasItemSimpleData *simple_data = &smodel->simple_data;
+
+  if (transform)
+    {
+      if (!simple_data->transform)
+	simple_data->transform = g_slice_new (cairo_matrix_t);
+
+      *simple_data->transform = *transform;
+    }
+  else
+    {
+      g_slice_free (cairo_matrix_t, simple_data->transform);
+      simple_data->transform = NULL;
+    }
+
+  _goo_canvas_item_model_emit_changed (model, TRUE);
+}
+
+
+static GooCanvasStyle*
+goo_canvas_item_model_simple_get_style (GooCanvasItemModel  *model)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+
+  return smodel->simple_data.style;
+}
+
+
+static void
+goo_canvas_item_model_simple_set_style (GooCanvasItemModel *model,
+					GooCanvasStyle     *style)
+{
+  GooCanvasItemModelSimple *smodel = (GooCanvasItemModelSimple*) model;
+  GooCanvasItemSimpleData *simple_data = &smodel->simple_data;
+
+  if (simple_data->style)
+    g_object_unref (simple_data->style);
+
+  if (style)
+    {
+      simple_data->style = goo_canvas_style_copy (style);
+      simple_data->own_style = TRUE;
+    }
+  else
+    {
+      simple_data->style = NULL;
+      simple_data->own_style = FALSE;
+    }
+
+  _goo_canvas_item_model_emit_changed (model, TRUE);
+}
+
+
+static void
+item_model_interface_init  (GooCanvasItemModelIface *iface)
+{
+  iface->get_parent     = goo_canvas_item_model_simple_get_parent;
+  iface->set_parent     = goo_canvas_item_model_simple_set_parent;
+  iface->get_transform  = goo_canvas_item_model_simple_get_transform;
+  iface->set_transform  = goo_canvas_item_model_simple_set_transform;
+  iface->get_style      = goo_canvas_item_model_simple_get_style;
+  iface->set_style      = goo_canvas_item_model_simple_set_style;
+}
diff --git a/libgoocanvas/goocanvasitemsimple.h b/libgoocanvas/goocanvasitemsimple.h
new file mode 100644
index 0000000..39ad00f
--- /dev/null
+++ b/libgoocanvas/goocanvasitemsimple.h
@@ -0,0 +1,247 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasitemsimple.h - abstract base class for simple items.
+ */
+#ifndef __GOO_CANVAS_ITEM_SIMPLE_H__
+#define __GOO_CANVAS_ITEM_SIMPLE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitem.h"
+#include "goocanvasitemmodel.h"
+#include "goocanvasstyle.h"
+#include "goocanvasutils.h"
+
+G_BEGIN_DECLS
+
+
+/**
+ * GooCanvasItemSimpleData
+ * @style: the style to draw with.
+ * @transform: the transformation matrix of the item, or %NULL.
+ * @clip_path_commands: an array of #GooCanvasPathCommand specifying the clip
+ *  path of the item, or %NULL.
+ * @tooltip: the item's tooltip.
+ * @visibility_threshold: the threshold scale setting at which to show the item
+ *  (if the @visibility setting is set to %VISIBLE_ABOVE_THRESHOLD).
+ * @visibility: the #GooCanvasItemVisibility setting specifying whether the
+ *  item is visible, invisible, or visible above a given canvas scale setting.
+ * @pointer_events: the #GooCanvasPointerEvents setting specifying the events
+ *  the item should receive.
+ * @can_focus: if the item can take the keyboard focus.
+ * @own_style: if the item has its own style, rather than using its parent's.
+ * @clip_fill_rule: the #cairo_fill_rule_t setting specifying the fill rule
+ *  used for the clip path.
+ * @is_static: if the item is static.
+ *
+ * This is the data common to both the model and view classes.
+ */
+typedef struct _GooCanvasItemSimpleData   GooCanvasItemSimpleData;
+struct _GooCanvasItemSimpleData
+{
+  GooCanvasStyle *style;
+  cairo_matrix_t *transform;
+  GArray *clip_path_commands;
+  gchar *tooltip;
+
+  /*< public >*/
+  gdouble visibility_threshold;
+  guint visibility			: 2;
+  guint pointer_events			: 4;
+  guint can_focus                       : 1;
+  guint own_style                       : 1;
+  guint clip_fill_rule			: 4;
+  guint is_static			: 1;
+
+  /*< private >*/
+  /* We might use this in future for a cache setting - never/always/visible. */
+  guint cache_setting			: 2;
+  /* We might need this for tooltips in future. */
+  guint has_tooltip			: 1;
+};
+
+
+#define GOO_TYPE_CANVAS_ITEM_SIMPLE            (goo_canvas_item_simple_get_type ())
+#define GOO_CANVAS_ITEM_SIMPLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ITEM_SIMPLE, GooCanvasItemSimple))
+#define GOO_CANVAS_ITEM_SIMPLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_ITEM_SIMPLE, GooCanvasItemSimpleClass))
+#define GOO_IS_CANVAS_ITEM_SIMPLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ITEM_SIMPLE))
+#define GOO_IS_CANVAS_ITEM_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_ITEM_SIMPLE))
+#define GOO_CANVAS_ITEM_SIMPLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_ITEM_SIMPLE, GooCanvasItemSimpleClass))
+
+
+typedef struct _GooCanvasItemSimple       GooCanvasItemSimple;
+typedef struct _GooCanvasItemSimpleClass  GooCanvasItemSimpleClass;
+
+typedef struct _GooCanvasItemModelSimple       GooCanvasItemModelSimple;
+
+/**
+ * GooCanvasItemSimple
+ * @canvas: the canvas.
+ * @parent: the parent item.
+ * @model: the item's model, if it has one.
+ * @simple_data: data that is common to both the model and view classes. If
+ *  the canvas item has a model, this will point to the model's
+ *  #GooCanvasItemSimpleData, otherwise the canvas item will have its own
+ *  #GooCanvasItemSimpleData.
+ * @bounds: the bounds of the item, in device space.
+ * @need_update: if the item needs to recompute its bounds and redraw.
+ * @need_entire_subtree_update: if all descendants need to be updated.
+ *
+ * The #GooCanvasItemSimple-struct struct contains the basic data needed to
+ * implement canvas items.
+ */
+struct _GooCanvasItemSimple
+{
+  /* <private> */
+  GObject parent_object;
+
+  /* <public> */
+  GooCanvas *canvas;
+  GooCanvasItem *parent;
+  GooCanvasItemModelSimple *model;
+  GooCanvasItemSimpleData *simple_data;
+  GooCanvasBounds bounds;
+  guint	need_update			: 1;
+  guint need_entire_subtree_update      : 1;
+
+  /* <private> */
+  /* We might use this in future for things like a cache. */
+  gpointer priv;
+};
+
+/**
+ * GooCanvasItemSimpleClass
+ * @simple_create_path: simple subclasses that draw basic shapes and paths only
+ *  need to override this one method. It creates the path for the item.
+ *  All updating, painting and hit-testing is provided automatically by the
+ *  #GooCanvasItemSimple class. (This method is used by the builtin
+ *  #GooCanvasEllipse, #GooCanvasRect and #GooCanvasPath items.)
+ *  More complicated subclasses must override @simple_update, @simple_paint and
+ *  @simple_is_item_at instead.
+ * @simple_update: subclasses should override this to calculate their new
+ *  bounds, in user space.
+ * @simple_paint: subclasses should override this to paint their item.
+ * @simple_is_item_at: subclasses should override this to do hit-testing.
+ *
+ * The #GooCanvasItemSimpleClass-struct struct contains several methods that
+ * subclasses can override.
+ *
+ * Simple items need only implement the create_path() method. More complex
+ * items must override the update(), paint() and is_item_at() methods instead.
+ */
+struct _GooCanvasItemSimpleClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  void		 (* simple_create_path)	(GooCanvasItemSimple   *simple,
+					 cairo_t               *cr);
+
+  void           (* simple_update)	(GooCanvasItemSimple   *simple,
+					 cairo_t               *cr);
+  void           (* simple_paint)	(GooCanvasItemSimple   *simple,
+					 cairo_t               *cr,
+					 const GooCanvasBounds *bounds);
+  gboolean       (* simple_is_item_at)  (GooCanvasItemSimple   *simple,
+					 gdouble                x,
+					 gdouble                y,
+					 cairo_t               *cr,
+					 gboolean               is_pointer_event);
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType    goo_canvas_item_simple_get_type		(void) G_GNUC_CONST;
+
+
+void     goo_canvas_item_simple_get_path_bounds		(GooCanvasItemSimple	*item,
+							 cairo_t		*cr,
+							 GooCanvasBounds	*bounds);
+void     goo_canvas_item_simple_user_bounds_to_device	(GooCanvasItemSimple	*item,
+							 cairo_t		*cr,
+							 GooCanvasBounds	*bounds);
+void     goo_canvas_item_simple_user_bounds_to_parent	(GooCanvasItemSimple	*item,
+							 cairo_t		*cr,
+							 GooCanvasBounds	*bounds);
+gboolean goo_canvas_item_simple_check_in_path		(GooCanvasItemSimple	*item,
+							 gdouble		 x,
+							 gdouble		 y,
+							 cairo_t		*cr,
+							 GooCanvasPointerEvents  pointer_events);
+void     goo_canvas_item_simple_paint_path		(GooCanvasItemSimple	*item,
+							 cairo_t		*cr);
+
+void     goo_canvas_item_simple_changed			(GooCanvasItemSimple	*item,
+							 gboolean		 recompute_bounds);
+void     goo_canvas_item_simple_check_style		(GooCanvasItemSimple	*item);
+gdouble  goo_canvas_item_simple_get_line_width		(GooCanvasItemSimple   *item);
+void	 goo_canvas_item_simple_set_model		(GooCanvasItemSimple	*item,
+							 GooCanvasItemModel	*model);
+
+
+
+
+
+#define GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE            (goo_canvas_item_model_simple_get_type ())
+#define GOO_CANVAS_ITEM_MODEL_SIMPLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE, GooCanvasItemModelSimple))
+#define GOO_CANVAS_ITEM_MODEL_SIMPLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE, GooCanvasItemModelSimpleClass))
+#define GOO_IS_CANVAS_ITEM_MODEL_SIMPLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE))
+#define GOO_IS_CANVAS_ITEM_MODEL_SIMPLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE))
+#define GOO_CANVAS_ITEM_MODEL_SIMPLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE, GooCanvasItemModelSimpleClass))
+
+
+typedef struct _GooCanvasItemModelSimpleClass  GooCanvasItemModelSimpleClass;
+
+/**
+ * GooCanvasItemModelSimple
+ * @parent: the parent model.
+ * @simple_data: data used by the canvas item for viewing the model.
+ *
+ * The #GooCanvasItemModelSimple-struct struct contains the basic data needed
+ * to implement canvas item models.
+ */
+struct _GooCanvasItemModelSimple
+{
+  GObject parent_object;
+
+  /*< public >*/
+  GooCanvasItemModel *parent;
+  GooCanvasItemSimpleData simple_data;
+
+  /*< private >*/
+
+  /* The title and description of the item for accessibility. */
+  gchar *title;
+  gchar *description;
+};
+
+
+struct _GooCanvasItemModelSimpleClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType    goo_canvas_item_model_simple_get_type  (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_ITEM_SIMPLE_H__ */
diff --git a/libgoocanvas/goocanvasmarshal.list b/libgoocanvas/goocanvasmarshal.list
new file mode 100644
index 0000000..c62de42
--- /dev/null
+++ b/libgoocanvas/goocanvasmarshal.list
@@ -0,0 +1,8 @@
+VOID:VOID
+VOID:INT
+VOID:INT,INT
+VOID:BOOLEAN
+VOID:OBJECT,OBJECT
+BOOLEAN:BOXED
+BOOLEAN:OBJECT,BOXED
+BOOLEAN:DOUBLE,DOUBLE,BOOLEAN,OBJECT
diff --git a/libgoocanvas/goocanvaspath.c b/libgoocanvas/goocanvaspath.c
new file mode 100644
index 0000000..0254a66
--- /dev/null
+++ b/libgoocanvas/goocanvaspath.c
@@ -0,0 +1,806 @@
+/*
+ * 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
+ * <ulink url="http://www.w3.org/Graphics/SVG/";>SVG specification</ulink>.
+ *
+ * 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().
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvaspath.h"
+#include "goocanvas.h"
+
+
+enum {
+  PROP_0,
+
+  PROP_DATA,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT
+};
+
+static void canvas_item_interface_init   (GooCanvasItemIface  *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasPath, goo_canvas_path,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_path_install_common_properties (GObjectClass *gobject_class)
+{
+  /**
+   * GooCanvasPath:data
+   *
+   * The sequence of path commands, specified as a string using the same syntax
+   * as in the <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable Vector
+   * Graphics (SVG)</ulink> 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));
+
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the path"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the path"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the path"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the path"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_path_init (GooCanvasPath *path)
+{
+  path->path_data = g_slice_new0 (GooCanvasPathData);
+}
+
+
+/**
+ * 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 <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable
+ *  Vector Graphics (SVG)</ulink> path element.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new path item.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a red line from (20,20) to (40,40):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *path = goo_canvas_path_new (mygroup,
+ *                                             "M 20 20 L 40 40",
+ *                                             "stroke-color", "red",
+ *                                             NULL);
+ * </programlisting></informalexample>
+ * 
+ * This example creates a cubic bezier curve from (20,100) to (100,100) with
+ * the control points at (20,50) and (100,50):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *path = goo_canvas_path_new (mygroup,
+ *                                             "M20,100 C20,50 100,50 100,100",
+ *                                             "stroke-color", "blue",
+ *                                             NULL);
+ * </programlisting></informalexample>
+ *
+ * This example uses an elliptical arc to create a filled circle with one
+ * quarter missing:
+ * 
+ * <informalexample><programlisting>
+ *  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);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new path item.
+ **/
+GooCanvasItem*
+goo_canvas_path_new               (GooCanvasItem *parent,
+				   const gchar   *path_data,
+				   ...)
+{
+  GooCanvasItem *item;
+  GooCanvasPath *path;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_PATH, NULL);
+  path = (GooCanvasPath*) item;
+
+  path->path_data->path_commands = goo_canvas_parse_path_data (path_data);
+
+  va_start (var_args, path_data);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_path_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasPath *path = (GooCanvasPath*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    {
+      if (path->path_data->path_commands)
+	g_array_free (path->path_data->path_commands, TRUE);
+      g_slice_free (GooCanvasPathData, path->path_data);
+    }
+  path->path_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_path_parent_class)->finalize (object);
+}
+
+static void
+goo_canvas_path_common_get_extent (GooCanvas            *canvas,
+                                   GooCanvasPathData    *path_data,
+                                   GooCanvasBounds      *bounds)
+{
+  cairo_t *cr;
+
+  cr = goo_canvas_create_cairo_context (canvas);
+  goo_canvas_create_path (path_data->path_commands, cr);
+  cairo_fill_extents (cr, &bounds->x1, &bounds->y1, &bounds->x2, &bounds->y2);
+  cairo_destroy (cr);
+}
+
+
+/* Moves all the absolute points in the command by the given amounts.
+   Relative points don't need to be moved. */
+static void
+goo_canvas_path_move_command (GooCanvasPathCommand *cmd,
+			      gdouble               x_offset,
+			      gdouble               y_offset)
+{
+  switch (cmd->simple.type)
+    {
+    case GOO_CANVAS_PATH_MOVE_TO:
+    case GOO_CANVAS_PATH_CLOSE_PATH:
+    case GOO_CANVAS_PATH_LINE_TO:
+    case GOO_CANVAS_PATH_HORIZONTAL_LINE_TO:
+    case GOO_CANVAS_PATH_VERTICAL_LINE_TO:
+      if (!cmd->simple.relative)
+        {
+          cmd->simple.x += x_offset;
+          cmd->simple.y += y_offset;
+        }
+      break;
+    case GOO_CANVAS_PATH_CURVE_TO:
+    case GOO_CANVAS_PATH_SMOOTH_CURVE_TO:
+    case GOO_CANVAS_PATH_QUADRATIC_CURVE_TO:
+    case GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO:
+      if (!cmd->curve.relative)
+        {
+          cmd->curve.x += x_offset;
+          cmd->curve.y += y_offset;
+          cmd->curve.x1 += x_offset;
+          cmd->curve.y1 += y_offset;
+          cmd->curve.x2 += x_offset;
+          cmd->curve.y2 += y_offset;
+        }
+      break;
+    case GOO_CANVAS_PATH_ELLIPTICAL_ARC:
+      if (!cmd->arc.relative)
+        {
+          cmd->arc.x += x_offset;
+          cmd->arc.y += y_offset;
+        }
+      break;
+    default:
+      g_assert_not_reached();
+      break;
+    }
+}
+
+
+/* Scales all the points in the command by the given amounts. Absolute points
+   are scaled about the given origin. */
+static void
+goo_canvas_path_scale_command (GooCanvasPathCommand *cmd,
+			       gdouble               x_origin,
+			       gdouble               y_origin,
+			       gdouble               x_scale,
+			       gdouble               y_scale)
+{
+  switch (cmd->simple.type)
+    {
+    case GOO_CANVAS_PATH_MOVE_TO:
+    case GOO_CANVAS_PATH_CLOSE_PATH:
+    case GOO_CANVAS_PATH_LINE_TO:
+    case GOO_CANVAS_PATH_HORIZONTAL_LINE_TO:
+    case GOO_CANVAS_PATH_VERTICAL_LINE_TO:
+      if (cmd->simple.relative)
+        {
+          cmd->simple.x *= x_scale;
+          cmd->simple.y *= y_scale;
+        }
+      else
+        {
+          cmd->simple.x = x_origin + (cmd->simple.x - x_origin) * x_scale;
+          cmd->simple.y = y_origin + (cmd->simple.y - y_origin) * y_scale;
+        }
+      break;
+    case GOO_CANVAS_PATH_CURVE_TO:
+    case GOO_CANVAS_PATH_SMOOTH_CURVE_TO:
+    case GOO_CANVAS_PATH_QUADRATIC_CURVE_TO:
+    case GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO:
+      if (cmd->curve.relative)
+        {
+          cmd->curve.x *= x_scale;
+          cmd->curve.y *= y_scale;
+          cmd->curve.x1 *= x_scale;
+          cmd->curve.y1 *= y_scale;
+          cmd->curve.x2 *= x_scale;
+          cmd->curve.y2 *= y_scale;
+        }
+      else
+        {
+          cmd->curve.x =  x_origin + (cmd->curve.x -  x_origin) * x_scale;
+          cmd->curve.y =  y_origin + (cmd->curve.y -  y_origin) * y_scale;
+          cmd->curve.x1 = x_origin + (cmd->curve.x1 - x_origin) * x_scale;
+          cmd->curve.y1 = y_origin + (cmd->curve.y1 - y_origin) * y_scale;
+          cmd->curve.x2 = x_origin + (cmd->curve.x2 - x_origin) * x_scale;
+          cmd->curve.y2 = y_origin + (cmd->curve.y2 - y_origin) * y_scale;
+        }
+      break;
+    case GOO_CANVAS_PATH_ELLIPTICAL_ARC:
+      if (cmd->arc.relative)
+        {
+          cmd->arc.x *= x_scale;
+          cmd->arc.y *= y_scale;
+        }
+      else
+        {
+          cmd->arc.x = x_origin + (cmd->arc.x - x_origin) * x_scale;
+          cmd->arc.y = y_origin + (cmd->arc.y - y_origin) * y_scale;
+        }
+      break;
+    default:
+      g_assert_not_reached();
+      break;
+  }
+}
+
+static void
+goo_canvas_path_get_common_property (GObject              *object,
+                                     GooCanvas            *canvas,
+				     GooCanvasPathData    *path_data,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasBounds extent;
+
+  switch (prop_id)
+    {
+    case PROP_X:
+      goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+      g_value_set_double (value, extent.x1);
+      break;
+    case PROP_Y:
+      goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+      g_value_set_double (value, extent.y1);
+      break;
+    case PROP_WIDTH:
+      goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+      g_value_set_double (value, extent.x2 - extent.x1);
+      break;
+    case PROP_HEIGHT:
+      goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+      g_value_set_double (value, extent.y2 - extent.y1);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_path_get_property (GObject              *object,
+			      guint                 prop_id,
+			      GValue               *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasPath *path = (GooCanvasPath*) object;
+
+  goo_canvas_path_get_common_property (object, simple->canvas,
+                                       path->path_data, prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_path_set_common_property (GObject              *object,
+                                     GooCanvas            *canvas,
+				     GooCanvasPathData    *path_data,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasBounds extent;
+  GooCanvasPathCommand *cmd;
+  gdouble x_offset, y_offset, x_scale, y_scale;
+  guint i;
+
+  switch (prop_id)
+    {
+    case PROP_DATA:
+      if (path_data->path_commands)
+	g_array_free (path_data->path_commands, TRUE);
+      path_data->path_commands = goo_canvas_parse_path_data (g_value_get_string (value));
+      g_object_notify (object, "x");
+      g_object_notify (object, "y");
+      g_object_notify (object, "width");
+      g_object_notify (object, "height");
+      break;
+    case PROP_X:
+      if (path_data->path_commands->len > 0)
+        {
+	  /* Calculate the x offset from the current position. */
+          goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+          x_offset = g_value_get_double (value) - extent.x1;
+
+	  /* Add the offset to all the absolute x coordinates. */
+          for (i = 0; i < path_data->path_commands->len; i++)
+            {
+              cmd = &g_array_index (path_data->path_commands,
+				    GooCanvasPathCommand, i);
+              goo_canvas_path_move_command (cmd, x_offset, 0.0);
+            }
+          g_object_notify (object, "data");
+        }
+      break;
+    case PROP_Y:
+      if (path_data->path_commands->len > 0)
+        {
+	  /* Calculate the y offset from the current position. */
+          goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+          y_offset = g_value_get_double (value) - extent.y1;
+
+	  /* Add the offset to all the absolute y coordinates. */
+          for (i = 0; i < path_data->path_commands->len; i++)
+            {
+              cmd = &g_array_index (path_data->path_commands,
+				    GooCanvasPathCommand, i);
+              goo_canvas_path_move_command (cmd, 0.0, y_offset);
+            }
+          g_object_notify (object, "data");
+        }
+      break;
+    case PROP_WIDTH:
+      if (path_data->path_commands->len >= 2)
+        {
+          goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+          if (extent.x2 - extent.x1 != 0.0)
+            {
+	      /* Calculate the amount to scale the path. */
+              x_scale = g_value_get_double (value) / (extent.x2 - extent.x1);
+
+	      /* Scale the x coordinates, relative to the left-most point. */
+              for (i = 0; i < path_data->path_commands->len; i++)
+                {
+                  cmd = &g_array_index (path_data->path_commands,
+					GooCanvasPathCommand, i);
+                  goo_canvas_path_scale_command (cmd, extent.x1, 0.0,
+						 x_scale, 1.0);
+                }
+              g_object_notify (object, "data");
+            }
+        }
+      break;
+    case PROP_HEIGHT:
+      if (path_data->path_commands->len >= 2)
+        {
+          goo_canvas_path_common_get_extent (canvas, path_data, &extent);
+          if (extent.y2 - extent.y1 != 0.0)
+            {
+	      /* Calculate the amount to scale the polyline. */
+              y_scale = g_value_get_double (value) / (extent.y2 - extent.y1);
+
+	      /* Scale the y coordinates, relative to the top-most point. */
+              for (i = 0; i < path_data->path_commands->len; i++)
+                {
+                  cmd = &g_array_index (path_data->path_commands,
+					GooCanvasPathCommand, i);
+                  goo_canvas_path_scale_command (cmd, 0.0, extent.y1,
+						 1.0, y_scale);
+                }
+              g_object_notify (object, "data");
+            }
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_path_set_property (GObject              *object,
+			      guint                 prop_id,
+			      const GValue         *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasPath *path = (GooCanvasPath*) object;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_path_set_common_property (object, simple->canvas, path->path_data,
+                                       prop_id, value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_path_create_path (GooCanvasItemSimple *simple,
+			     cairo_t             *cr)
+{
+  GooCanvasPath *path = (GooCanvasPath*) simple;
+
+  goo_canvas_create_path (path->path_data->path_commands, cr);
+}
+
+
+static gboolean
+goo_canvas_path_is_item_at (GooCanvasItemSimple *simple,
+			    gdouble              x,
+			    gdouble              y,
+			    cairo_t             *cr,
+			    gboolean             is_pointer_event)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasPointerEvents pointer_events = GOO_CANVAS_EVENTS_ALL;
+  gboolean do_fill;
+
+  /* By default only check the fill if a fill color/pattern is specified. */
+  do_fill = goo_canvas_style_set_fill_options (simple_data->style, cr);
+  if (!do_fill)
+    pointer_events &= ~GOO_CANVAS_EVENTS_FILL_MASK;
+
+  /* If is_pointer_event is set use the pointer_events property instead. */
+  if (is_pointer_event)
+    pointer_events = simple_data->pointer_events;
+
+  goo_canvas_path_create_path (simple, cr);
+  if (goo_canvas_item_simple_check_in_path (simple, x, y, cr, pointer_events))
+    return TRUE;
+
+  return FALSE;
+}
+
+
+static void
+goo_canvas_path_set_model    (GooCanvasItem      *item,
+			      GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasPath *path = (GooCanvasPath*) item;
+  GooCanvasPathModel *emodel = (GooCanvasPathModel*) model;
+
+  /* If our data was allocated, free it. */
+  if (!simple->model)
+    {
+      if (path->path_data->path_commands)
+	g_array_free (path->path_data->path_commands, TRUE);
+      g_slice_free (GooCanvasPathData, path->path_data);
+    }
+
+  /* Now use the new model's data instead. */
+  path->path_data = &emodel->path_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model      = goo_canvas_path_set_model;
+}
+
+
+static void
+goo_canvas_path_class_init (GooCanvasPathClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) 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;
+
+  simple_class->simple_create_path = goo_canvas_path_create_path;
+  simple_class->simple_is_item_at  = goo_canvas_path_is_item_at;
+
+  goo_canvas_path_install_common_properties (gobject_class);
+}
+
+
+/**
+ * SECTION:goocanvaspathmodel
+ * @Title: GooCanvasPathModel
+ * @Short_Description: a model for path items (a series of lines and curves).
+ *
+ * GooCanvasPathModel represents a model for path items, which are a series of
+ * one or more lines, bezier curves, or elliptical arcs.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "stroke-color", "fill-color" and "line-width".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * #GooCanvasPathModel uses the same path specification strings as the Scalable
+ * Vector Graphics (SVG) path element. For details see the
+ * <ulink url="http://www.w3.org/Graphics/SVG/";>SVG specification</ulink>.
+ *
+ * To create a #GooCanvasPathModel use goo_canvas_path_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasPathModel, 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 #GooCanvasPath objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_path_model_finalize     (GObject            *object);
+static void goo_canvas_path_model_get_property (GObject            *object,
+						   guint               param_id,
+						   GValue             *value,
+						   GParamSpec         *pspec);
+static void goo_canvas_path_model_set_property (GObject            *object,
+						   guint               param_id,
+						   const GValue       *value,
+						   GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasPathModel, goo_canvas_path_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_path_model_class_init (GooCanvasPathModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = goo_canvas_path_model_finalize;
+
+  gobject_class->get_property = goo_canvas_path_model_get_property;
+  gobject_class->set_property = goo_canvas_path_model_set_property;
+
+  goo_canvas_path_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_path_model_init (GooCanvasPathModel *pmodel)
+{
+
+}
+
+
+/**
+ * goo_canvas_path_model_new:
+ * @parent: the parent model, 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 <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable
+ *  Vector Graphics (SVG)</ulink> path element.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new path model.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a red line from (20,20) to (40,40):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *path = goo_canvas_path_model_new (mygroup,
+ *                                                        "M 20 20 L 40 40",
+ *                                                        "stroke-color", "red",
+ *                                                        NULL);
+ * </programlisting></informalexample>
+ * 
+ * This example creates a cubic bezier curve from (20,100) to (100,100) with
+ * the control points at (20,50) and (100,50):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *path = goo_canvas_path_model_new (mygroup,
+ *                                                        "M20,100 C20,50 100,50 100,100",
+ *                                                        "stroke-color", "blue",
+ *                                                        NULL);
+ * </programlisting></informalexample>
+ *
+ * This example uses an elliptical arc to create a filled circle with one
+ * quarter missing:
+ * 
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *path = goo_canvas_path_model_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);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new path model.
+ **/
+GooCanvasItemModel*
+goo_canvas_path_model_new (GooCanvasItemModel *parent,
+			   const gchar        *path_data,
+			   ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasPathModel *pmodel;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_PATH_MODEL, NULL);
+  pmodel = (GooCanvasPathModel*) model;
+
+  pmodel->path_data.path_commands = goo_canvas_parse_path_data (path_data);
+
+  va_start (var_args, path_data);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_path_model_finalize (GObject *object)
+{
+  GooCanvasPathModel *pmodel = (GooCanvasPathModel*) object;
+
+  if (pmodel->path_data.path_commands)
+    g_array_free (pmodel->path_data.path_commands, TRUE);
+
+  G_OBJECT_CLASS (goo_canvas_path_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_path_model_get_property (GObject              *object,
+				    guint                 prop_id,
+				    GValue               *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasPathModel *pmodel = (GooCanvasPathModel*) object;
+
+  goo_canvas_path_get_common_property (object, NULL, &pmodel->path_data,
+                                       prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_path_model_set_property (GObject              *object,
+				    guint                 prop_id,
+				    const GValue         *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasPathModel *pmodel = (GooCanvasPathModel*) object;
+
+  goo_canvas_path_set_common_property (object, NULL, &pmodel->path_data,
+                                       prop_id, value, pspec);
+  g_signal_emit_by_name (pmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_path_model_create_item (GooCanvasItemModel *model,
+				   GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_PATH, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_path_model_create_item;
+}
diff --git a/libgoocanvas/goocanvaspath.h b/libgoocanvas/goocanvaspath.h
new file mode 100644
index 0000000..5b11e61
--- /dev/null
+++ b/libgoocanvas/goocanvaspath.h
@@ -0,0 +1,116 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvaspath.h - a path item, very similar to the SVG path element.
+ */
+#ifndef __GOO_CANVAS_PATH_H__
+#define __GOO_CANVAS_PATH_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasPathData   GooCanvasPathData;
+struct _GooCanvasPathData
+{
+  /* An array of GooCanvasPathCommand. */
+  GArray *path_commands;
+};
+
+
+#define GOO_TYPE_CANVAS_PATH            (goo_canvas_path_get_type ())
+#define GOO_CANVAS_PATH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_PATH, GooCanvasPath))
+#define GOO_CANVAS_PATH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_PATH, GooCanvasPathClass))
+#define GOO_IS_CANVAS_PATH(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_PATH))
+#define GOO_IS_CANVAS_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_PATH))
+#define GOO_CANVAS_PATH_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_PATH, GooCanvasPathClass))
+
+
+typedef struct _GooCanvasPath       GooCanvasPath;
+typedef struct _GooCanvasPathClass  GooCanvasPathClass;
+
+/**
+ * GooCanvasPath
+ *
+ * The #GooCanvasPath-struct struct contains private data only.
+ */
+struct _GooCanvasPath
+{
+  GooCanvasItemSimple parent;
+
+  GooCanvasPathData *path_data;
+};
+
+struct _GooCanvasPathClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_path_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_path_new       (GooCanvasItem      *parent,
+					       const gchar        *path_data,
+					       ...);
+
+
+
+#define GOO_TYPE_CANVAS_PATH_MODEL            (goo_canvas_path_model_get_type ())
+#define GOO_CANVAS_PATH_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_PATH_MODEL, GooCanvasPathModel))
+#define GOO_CANVAS_PATH_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_PATH_MODEL, GooCanvasPathModelClass))
+#define GOO_IS_CANVAS_PATH_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_PATH_MODEL))
+#define GOO_IS_CANVAS_PATH_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_PATH_MODEL))
+#define GOO_CANVAS_PATH_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_PATH_MODEL, GooCanvasPathModelClass))
+
+
+typedef struct _GooCanvasPathModel       GooCanvasPathModel;
+typedef struct _GooCanvasPathModelClass  GooCanvasPathModelClass;
+
+/**
+ * GooCanvasPathModel
+ *
+ * The #GooCanvasPathModel-struct struct contains private data only.
+ */
+struct _GooCanvasPathModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasPathData path_data;
+};
+
+struct _GooCanvasPathModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_path_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_path_model_new (GooCanvasItemModel *parent,
+					       const gchar        *path_data,
+					       ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_PATH_H__ */
diff --git a/libgoocanvas/goocanvaspolyline.c b/libgoocanvas/goocanvaspolyline.c
new file mode 100644
index 0000000..b34908e
--- /dev/null
+++ b/libgoocanvas/goocanvaspolyline.c
@@ -0,0 +1,1350 @@
+/*
+ * 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(), or
+ * goo_canvas_polyline_new_line() for a simple line between two points.
+ *
+ * To get or set the properties of an existing #GooCanvasPolyline, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <math.h>
+#include <stdarg.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvaspolyline.h"
+#include "goocanvas.h"
+
+
+/**
+ * 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_slice_new (GooCanvasPoints);
+  points->num_points = num_points;
+  points->coords = g_slice_alloc (num_points * 2 * sizeof (double));
+  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_slice_free1 (points->num_points * 2 * sizeof (double), points->coords);
+      g_slice_free (GooCanvasPoints, 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,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT
+};
+
+
+static void canvas_item_interface_init       (GooCanvasItemIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasPolyline, goo_canvas_polyline,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_polyline_install_common_properties (GObjectClass *gobject_class)
+{
+  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));
+
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the left-most point of the polyline"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the top-most point of the polyline"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the polyline"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the polyline"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_polyline_init (GooCanvasPolyline *polyline)
+{
+  polyline->polyline_data = g_slice_new0 (GooCanvasPolylineData);
+}
+
+
+static void
+goo_canvas_polyline_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    {
+      g_slice_free1 (polyline->polyline_data->num_points * 2 * sizeof (gdouble),
+		     polyline->polyline_data->coords);
+      g_slice_free (GooCanvasPolylineArrowData, polyline->polyline_data->arrow_data);
+      g_slice_free (GooCanvasPolylineData, polyline->polyline_data);
+    }
+  polyline->polyline_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_polyline_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_polyline_get_extent (GooCanvasPolylineData *polyline_data,
+                                GooCanvasBounds *bounds)
+{
+  guint i;
+
+  if (polyline_data->num_points == 0)
+    {
+      bounds->x1 = bounds->y1 = bounds->x2 = bounds->y2 = 0.0;
+    }
+  else
+    {
+      bounds->x1 = bounds->x2 = polyline_data->coords[0];
+      bounds->y1 = bounds->y2 = polyline_data->coords[1];
+
+      for (i = 1; i < polyline_data->num_points; i++)
+        {
+	  bounds->x1 = MIN (bounds->x1, polyline_data->coords[2 * i]);
+	  bounds->y1 = MIN (bounds->y1, polyline_data->coords[2 * i + 1]);
+	  bounds->x2 = MAX (bounds->x2, polyline_data->coords[2 * i]);
+	  bounds->y2 = MAX (bounds->y2, polyline_data->coords[2 * i + 1]);
+        }
+    }
+}
+
+
+static void
+goo_canvas_polyline_get_common_property (GObject              *object,
+					 GooCanvasPolylineData *polyline_data,
+					 guint                 prop_id,
+					 GValue               *value,
+					 GParamSpec           *pspec)
+{
+  GooCanvasPoints *points;
+  GooCanvasBounds  extent;
+
+  switch (prop_id)
+    {
+    case PROP_POINTS:
+      if (polyline_data->num_points == 0)
+	{
+	  g_value_set_boxed (value, NULL);
+	}
+      else
+	{
+	  points = goo_canvas_points_new (polyline_data->num_points);
+	  memcpy (points->coords, polyline_data->coords,
+		  polyline_data->num_points * 2 * sizeof (double));
+	  g_value_set_boxed (value, points);
+	  goo_canvas_points_unref (points);
+	}
+      break;
+    case PROP_CLOSE_PATH:
+      g_value_set_boolean (value, polyline_data->close_path);
+      break;
+    case PROP_START_ARROW:
+      g_value_set_boolean (value, polyline_data->start_arrow);
+      break;
+    case PROP_END_ARROW:
+      g_value_set_boolean (value, polyline_data->end_arrow);
+      break;
+    case PROP_ARROW_LENGTH:
+      g_value_set_double (value, polyline_data->arrow_data
+			  ? polyline_data->arrow_data->arrow_length : 5.0);
+      break;
+    case PROP_ARROW_WIDTH:
+      g_value_set_double (value, polyline_data->arrow_data
+			  ? polyline_data->arrow_data->arrow_width : 4.0);
+      break;
+    case PROP_ARROW_TIP_LENGTH:
+      g_value_set_double (value, polyline_data->arrow_data
+			  ? polyline_data->arrow_data->arrow_tip_length : 4.0);
+      break;
+    case PROP_X:
+      goo_canvas_polyline_get_extent (polyline_data, &extent);
+      g_value_set_double (value, extent.x1);
+      break;
+    case PROP_Y:
+      goo_canvas_polyline_get_extent (polyline_data, &extent);
+      g_value_set_double (value, extent.y1);
+      break;
+    case PROP_WIDTH:
+      goo_canvas_polyline_get_extent (polyline_data, &extent);
+      g_value_set_double (value, extent.x2 - extent.x1);
+      break;
+    case PROP_HEIGHT:
+      goo_canvas_polyline_get_extent (polyline_data, &extent);
+      g_value_set_double (value, extent.y2 - extent.y1);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_polyline_get_property (GObject              *object,
+				 guint                 prop_id,
+				 GValue               *value,
+				 GParamSpec           *pspec)
+{
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) object;
+
+  goo_canvas_polyline_get_common_property (object, polyline->polyline_data,
+					   prop_id, value, pspec);
+}
+
+
+static void
+ensure_arrow_data (GooCanvasPolylineData *polyline_data)
+{
+  if (!polyline_data->arrow_data)
+    {
+      polyline_data->arrow_data = g_slice_new (GooCanvasPolylineArrowData);
+
+      /* These seem like reasonable defaults for the arrow dimensions.
+	 They are specified in multiples of the line width so they scale OK. */
+      polyline_data->arrow_data->arrow_width = 4.0;
+      polyline_data->arrow_data->arrow_length = 5.0;
+      polyline_data->arrow_data->arrow_tip_length = 4.0;
+    }
+}
+
+#define GOO_CANVAS_EPSILON 1e-10
+
+static void
+reconfigure_arrow (GooCanvasPolylineData *polyline_data,
+		   gint                   end_point,
+		   gint                   prev_point,
+		   gdouble                line_width,
+		   gdouble               *line_coords,
+		   gdouble               *arrow_coords)
+{
+  GooCanvasPolylineArrowData *arrow = polyline_data->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_data->coords[prev_point] - polyline_data->coords[end_point];
+  dy = polyline_data->coords[prev_point + 1] - polyline_data->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_data->coords[end_point];
+  arrow_coords[1] = polyline_data->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 */
+static void
+goo_canvas_polyline_reconfigure_arrows (GooCanvasPolyline *polyline)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) polyline;
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  double line_width;
+
+  if (polyline_data->num_points < 2
+      || (!polyline_data->start_arrow && !polyline_data->end_arrow))
+    return;
+
+  line_width = goo_canvas_item_simple_get_line_width (simple);
+  ensure_arrow_data (polyline_data);
+
+  if (polyline_data->start_arrow)
+    reconfigure_arrow (polyline_data, 0, 2, line_width,
+		       polyline_data->arrow_data->line_start,
+		       polyline_data->arrow_data->start_arrow_coords);
+
+  if (polyline_data->end_arrow)
+    {
+      int end_point, prev_point;
+
+      if (polyline_data->close_path)
+	{
+	  end_point = 0;
+	  prev_point = polyline_data->num_points - 1;
+	}
+      else
+	{
+	  end_point = polyline_data->num_points - 1;
+	  prev_point = polyline_data->num_points - 2;
+	}
+
+      reconfigure_arrow (polyline_data, end_point * 2, prev_point * 2,
+			 line_width, polyline_data->arrow_data->line_end,
+			 polyline_data->arrow_data->end_arrow_coords);
+    }
+}
+
+
+static void
+goo_canvas_polyline_set_common_property (GObject              *object,
+					 GooCanvasPolylineData *polyline_data,
+					 guint                 prop_id,
+					 const GValue         *value,
+					 GParamSpec           *pspec)
+{
+  GooCanvasPoints *points;
+  GooCanvasBounds  extent;
+  gdouble x_offset, y_offset, x_scale, y_scale;
+  guint i;
+
+  switch (prop_id)
+    {
+    case PROP_POINTS:
+      points = g_value_get_boxed (value);
+
+      if (polyline_data->coords)
+	{
+	  g_slice_free1 (polyline_data->num_points * 2 * sizeof (double), polyline_data->coords);
+	  polyline_data->coords = NULL;
+	}
+
+      if (!points)
+	{
+	  polyline_data->num_points = 0;
+	}
+      else
+	{
+	  polyline_data->num_points = points->num_points;
+	  polyline_data->coords = g_slice_alloc (polyline_data->num_points * 2 * sizeof (double));
+	  memcpy (polyline_data->coords, points->coords,
+		  polyline_data->num_points * 2 * sizeof (double));
+	}
+      g_object_notify (object, "x");
+      g_object_notify (object, "y");
+      g_object_notify (object, "width");
+      g_object_notify (object, "height");
+      break;
+    case PROP_CLOSE_PATH:
+      polyline_data->close_path = g_value_get_boolean (value);
+      break;
+    case PROP_START_ARROW:
+      polyline_data->start_arrow = g_value_get_boolean (value);
+      break;
+    case PROP_END_ARROW:
+      polyline_data->end_arrow = g_value_get_boolean (value);
+      break;
+    case PROP_ARROW_LENGTH:
+      ensure_arrow_data (polyline_data);
+      polyline_data->arrow_data->arrow_length = g_value_get_double (value);
+      break;
+    case PROP_ARROW_WIDTH:
+      ensure_arrow_data (polyline_data);
+      polyline_data->arrow_data->arrow_width = g_value_get_double (value);
+      break;
+    case PROP_ARROW_TIP_LENGTH:
+      ensure_arrow_data (polyline_data);
+      polyline_data->arrow_data->arrow_tip_length = g_value_get_double (value);
+      break;
+    case PROP_X:
+      if (polyline_data->num_points > 0)
+        {
+	  /* Calculate the x offset from the current position. */
+          goo_canvas_polyline_get_extent (polyline_data, &extent);
+          x_offset = g_value_get_double (value) - extent.x1;
+
+	  /* Add the offset to all the x coordinates. */
+          for (i = 0; i < polyline_data->num_points; i++)
+            polyline_data->coords[2 * i] += x_offset;
+
+          g_object_notify (object, "points");
+        }
+      break;
+    case PROP_Y:
+      if (polyline_data->num_points > 0)
+        {
+	  /* Calculate the y offset from the current position. */
+          goo_canvas_polyline_get_extent (polyline_data, &extent);
+          y_offset = g_value_get_double (value) - extent.y1;
+
+	  /* Add the offset to all the y coordinates. */
+          for (i = 0; i < polyline_data->num_points; i++)
+            polyline_data->coords[2 * i + 1] += y_offset;
+
+          g_object_notify (object, "points");
+        }
+      break;
+    case PROP_WIDTH:
+      if (polyline_data->num_points >= 2)
+        {
+          goo_canvas_polyline_get_extent (polyline_data, &extent);
+          if (extent.x2 - extent.x1 != 0.0)
+            {
+	      /* Calculate the amount to scale the polyline. */
+              x_scale = g_value_get_double (value) / (extent.x2 - extent.x1);
+
+	      /* Scale the x coordinates, relative to the left-most point. */
+              for (i = 0; i < polyline_data->num_points; i++)
+                polyline_data->coords[2 * i] = extent.x1 + (polyline_data->coords[2 * i] - extent.x1) * x_scale;
+
+              g_object_notify (object, "points");
+            }
+        }
+      break;
+    case PROP_HEIGHT:
+      if (polyline_data->num_points >= 2)
+        {
+          goo_canvas_polyline_get_extent (polyline_data, &extent);
+          if (extent.y2 - extent.y1 != 0.0)
+            {
+	      /* Calculate the amount to scale the polyline. */
+              y_scale = g_value_get_double (value) / (extent.y2 - extent.y1);
+
+	      /* Scale the y coordinates, relative to the top-most point. */
+              for (i = 0; i < polyline_data->num_points; i++)
+                polyline_data->coords[2 * i + 1] = extent.y1 + (polyline_data->coords[2 * i + 1] - extent.y1) * y_scale;
+
+              g_object_notify (object, "points");
+            }
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_polyline_set_property (GObject              *object,
+				 guint                 prop_id,
+				 const GValue         *value,
+				 GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) object;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_polyline_set_common_property (object, polyline->polyline_data,
+					   prop_id, value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+/**
+ * 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.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a filled triangle with vertices
+ * at (100,100), (300,100), and (200,300).
+ *
+ * <informalexample><programlisting>
+ *  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);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new polyline item.
+ **/
+GooCanvasItem*
+goo_canvas_polyline_new               (GooCanvasItem *parent,
+				       gboolean       close_path,
+				       gint           num_points,
+				       ...)
+{
+  GooCanvasItem *item;
+  GooCanvasPolyline *polyline;
+  GooCanvasPolylineData *polyline_data;
+  const char *first_property;
+  va_list var_args;
+  gint i;
+
+  item = g_object_new (GOO_TYPE_CANVAS_POLYLINE, NULL);
+  polyline = (GooCanvasPolyline*) item;
+
+  polyline_data = polyline->polyline_data;
+  polyline_data->close_path = close_path;
+  polyline_data->num_points = num_points;
+  if (num_points)
+    polyline_data->coords = g_slice_alloc (num_points * 2 * sizeof (gdouble));
+
+  va_start (var_args, num_points);
+  for (i = 0; i < num_points * 2; i++)
+    polyline_data->coords[i] = va_arg (var_args, gdouble);
+
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, 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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new polyline item with a single line.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a line from (100,100) to (300,300).
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *polyline = goo_canvas_polyline_new_line (mygroup,
+ *                                                          100.0, 100.0,
+ *                                                          300.0, 300.0,
+ *                                                          "stroke-color", "red",
+ *                                                          "line-width", 5.0,
+ *                                                          NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new polyline item.
+ **/
+GooCanvasItem*
+goo_canvas_polyline_new_line          (GooCanvasItem *parent,
+				       gdouble        x1,
+				       gdouble        y1,
+				       gdouble        x2,
+				       gdouble        y2,
+				       ...)
+{
+  GooCanvasItem *item;
+  GooCanvasPolyline *polyline;
+  GooCanvasPolylineData *polyline_data;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_POLYLINE, NULL);
+  polyline = (GooCanvasPolyline*) item;
+
+  polyline_data = polyline->polyline_data;
+  polyline_data->close_path = FALSE;
+  polyline_data->num_points = 2;
+  polyline_data->coords = g_slice_alloc (4 * sizeof (gdouble));
+  polyline_data->coords[0] = x1;
+  polyline_data->coords[1] = y1;
+  polyline_data->coords[2] = x2;
+  polyline_data->coords[3] = y2;
+
+  va_start (var_args, y2);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_polyline_create_path (GooCanvasPolyline *polyline,
+				 cairo_t           *cr)
+{
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  GooCanvasPolylineArrowData *arrow = polyline_data->arrow_data;
+  gint i;
+
+  cairo_new_path (cr);
+
+  if (polyline_data->num_points == 0)
+    return;
+
+  /* If there is an arrow at the start of the polyline, we need to move the
+     start of the line slightly to avoid drawing over the arrow tip. */
+  if (polyline_data->start_arrow && polyline_data->num_points >= 2)
+    cairo_move_to (cr, arrow->line_start[0], arrow->line_start[1]);
+  else
+    cairo_move_to (cr, polyline_data->coords[0], polyline_data->coords[1]);
+
+  if (polyline_data->end_arrow && polyline_data->num_points >= 2)
+    {
+      gint last_point = polyline_data->num_points - 1;
+
+      if (!polyline_data->close_path)
+	last_point--;
+
+      for (i = 1; i <= last_point; i++)
+	cairo_line_to (cr, polyline_data->coords[i * 2],
+		       polyline_data->coords[i * 2 + 1]);
+
+      cairo_line_to (cr, arrow->line_end[0], arrow->line_end[1]);
+    }
+  else
+    {
+      for (i = 1; i < polyline_data->num_points; i++)
+	cairo_line_to (cr, polyline_data->coords[i * 2],
+		       polyline_data->coords[i * 2 + 1]);
+
+      if (polyline_data->close_path)
+	cairo_close_path (cr);
+    }
+}
+
+
+static void
+goo_canvas_polyline_create_start_arrow_path (GooCanvasPolyline *polyline,
+					     cairo_t           *cr)
+{
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  GooCanvasPolylineArrowData *arrow = polyline_data->arrow_data;
+  gint i;
+
+  cairo_new_path (cr);
+
+  if (polyline_data->num_points < 2)
+    return;
+
+  cairo_move_to (cr, arrow->start_arrow_coords[0],
+		 arrow->start_arrow_coords[1]);
+  for (i = 1; i < NUM_ARROW_POINTS; i++)
+    {
+      cairo_line_to (cr, arrow->start_arrow_coords[i * 2],
+		     arrow->start_arrow_coords[i * 2 + 1]);
+    }
+  cairo_close_path (cr);
+}
+
+
+static void
+goo_canvas_polyline_create_end_arrow_path (GooCanvasPolyline *polyline,
+					   cairo_t           *cr)
+{
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  GooCanvasPolylineArrowData *arrow = polyline_data->arrow_data;
+  gint i;
+
+  cairo_new_path (cr);
+
+  if (polyline_data->num_points < 2)
+    return;
+
+  cairo_move_to (cr, arrow->end_arrow_coords[0],
+		 arrow->end_arrow_coords[1]);
+  for (i = 1; i < NUM_ARROW_POINTS; i++)
+    {
+      cairo_line_to (cr, arrow->end_arrow_coords[i * 2],
+		     arrow->end_arrow_coords[i * 2 + 1]);
+    }
+  cairo_close_path (cr);
+}
+
+
+static gboolean
+goo_canvas_polyline_is_item_at (GooCanvasItemSimple *simple,
+				gdouble              x,
+				gdouble              y,
+				cairo_t             *cr,
+				gboolean             is_pointer_event)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) simple;
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  GooCanvasPointerEvents pointer_events = GOO_CANVAS_EVENTS_ALL;
+  gboolean do_stroke;
+
+  if (polyline_data->num_points == 0)
+    return FALSE;
+
+  /* Check if the item should receive events. */
+  if (is_pointer_event)
+    pointer_events = simple_data->pointer_events;
+
+  /* If the path isn't closed, we never check the fill. */
+  if (!(polyline_data->close_path && polyline_data->num_points > 2))
+    pointer_events &= ~GOO_CANVAS_EVENTS_FILL_MASK;
+
+  goo_canvas_polyline_create_path (polyline, cr);
+  if (goo_canvas_item_simple_check_in_path (simple, x, y, cr, pointer_events))
+    return TRUE;
+
+  /* Check the arrows, if the polyline has them. */
+  if ((polyline_data->start_arrow || polyline_data->end_arrow)
+      && polyline_data->num_points >= 2
+      && (pointer_events & GOO_CANVAS_EVENTS_STROKE_MASK))
+    {
+      /* We use the stroke pattern to match the style of the line. */
+      do_stroke = goo_canvas_style_set_stroke_options (simple_data->style, cr);
+      if (!(pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK) || do_stroke)
+	{
+	  if (polyline_data->start_arrow)
+	    {
+	      goo_canvas_polyline_create_start_arrow_path (polyline, cr);
+	      if (cairo_in_fill (cr, x, y))
+		return TRUE;
+	    }
+
+	  if (polyline_data->end_arrow)
+	    {
+	      goo_canvas_polyline_create_end_arrow_path (polyline, cr);
+	      if (cairo_in_fill (cr, x, y))
+		return TRUE;
+	    }
+	}
+    }
+
+  return FALSE;
+}
+
+
+static void
+goo_canvas_polyline_compute_bounds (GooCanvasPolyline     *polyline,
+				    cairo_t               *cr,
+				    GooCanvasBounds       *bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) polyline;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+  GooCanvasBounds tmp_bounds;
+  cairo_matrix_t transform;
+
+  if (polyline_data->num_points == 0)
+    {
+      bounds->x1 = bounds->x2 = bounds->y1 = bounds->y2 = 0.0;
+      return;
+    }
+
+  /* Use the identity matrix to get the bounds completely in user space. */
+  cairo_get_matrix (cr, &transform);
+  cairo_identity_matrix (cr);
+
+  goo_canvas_polyline_create_path (polyline, cr);
+  goo_canvas_item_simple_get_path_bounds (simple, cr, bounds);
+
+  /* Add on the arrows, if required. */
+  if ((polyline_data->start_arrow || polyline_data->end_arrow)
+      && polyline_data->num_points >= 2)
+    {
+      /* We use the stroke pattern to match the style of the line. */
+      goo_canvas_style_set_stroke_options (simple_data->style, cr);
+
+      if (polyline_data->start_arrow)
+	{
+	  goo_canvas_polyline_create_start_arrow_path (polyline, cr);
+	  cairo_fill_extents (cr, &tmp_bounds.x1, &tmp_bounds.y1,
+			      &tmp_bounds.x2, &tmp_bounds.y2);
+	  bounds->x1 = MIN (bounds->x1, tmp_bounds.x1);
+	  bounds->y1 = MIN (bounds->y1, tmp_bounds.y1);
+	  bounds->x2 = MAX (bounds->x2, tmp_bounds.x2);
+	  bounds->y2 = MAX (bounds->y2, tmp_bounds.y2);
+	}
+
+      if (polyline_data->end_arrow)
+	{
+	  goo_canvas_polyline_create_end_arrow_path (polyline, cr);
+	  cairo_fill_extents (cr, &tmp_bounds.x1, &tmp_bounds.y1,
+			      &tmp_bounds.x2, &tmp_bounds.y2);
+	  bounds->x1 = MIN (bounds->x1, tmp_bounds.x1);
+	  bounds->y1 = MIN (bounds->y1, tmp_bounds.y1);
+	  bounds->x2 = MAX (bounds->x2, tmp_bounds.x2);
+	  bounds->y2 = MAX (bounds->y2, tmp_bounds.y2);
+	}
+    }
+
+  cairo_set_matrix (cr, &transform);
+}
+
+
+static void
+goo_canvas_polyline_update  (GooCanvasItemSimple *simple,
+			     cairo_t             *cr)
+{
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) simple;
+
+  goo_canvas_polyline_reconfigure_arrows (polyline);
+
+  /* Compute the new bounds. */
+  goo_canvas_polyline_compute_bounds (polyline, cr, &simple->bounds);
+}
+
+
+static void
+goo_canvas_polyline_paint (GooCanvasItemSimple   *simple,
+			   cairo_t               *cr,
+			   const GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) simple;
+  GooCanvasPolylineData *polyline_data = polyline->polyline_data;
+
+  if (polyline_data->num_points == 0)
+    return;
+
+  goo_canvas_polyline_create_path (polyline, cr);
+  goo_canvas_item_simple_paint_path (simple, cr);
+
+  /* Paint the arrows, if required. */
+  if ((polyline_data->start_arrow || polyline_data->end_arrow)
+      && polyline_data->num_points >= 2)
+    {
+      /* We use the stroke pattern to match the style of the line. */
+      goo_canvas_style_set_stroke_options (simple_data->style, cr);
+
+      if (polyline_data->start_arrow)
+	{
+	  goo_canvas_polyline_create_start_arrow_path (polyline, cr);
+	  cairo_fill (cr);
+	}
+
+      if (polyline_data->end_arrow)
+	{
+	  goo_canvas_polyline_create_end_arrow_path (polyline, cr);
+	  cairo_fill (cr);
+	}
+    }
+}
+
+
+static void
+goo_canvas_polyline_set_model    (GooCanvasItem      *item,
+				 GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasPolyline *polyline = (GooCanvasPolyline*) item;
+  GooCanvasPolylineModel *pmodel = (GooCanvasPolylineModel*) model;
+
+  /* If our polyline_data was allocated, free it. */
+  if (!simple->model)
+    g_slice_free (GooCanvasPolylineData, polyline->polyline_data);
+
+  /* Now use the new model's polyline_data instead. */
+  polyline->polyline_data = &pmodel->polyline_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model   = goo_canvas_polyline_set_model;
+}
+
+
+static void
+goo_canvas_polyline_class_init (GooCanvasPolylineClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) 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;
+
+  simple_class->simple_update        = goo_canvas_polyline_update;
+  simple_class->simple_paint         = goo_canvas_polyline_paint;
+  simple_class->simple_is_item_at    = goo_canvas_polyline_is_item_at;
+
+  goo_canvas_polyline_install_common_properties (gobject_class);
+}
+
+
+/**
+ * SECTION:goocanvaspolylinemodel
+ * @Title: GooCanvasPolylineModel
+ * @Short_Description: a model for polyline items (a series of lines with
+ *  optional arrows).
+ *
+ * GooCanvasPolylineModel represents a model for polyline items, which are a
+ * series of one or more lines, with optional arrows at either end.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "stroke-color", "fill-color" and "line-width".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasPolylineModel use goo_canvas_polyline_model_new(), or
+ * goo_canvas_polyline_model_new_line() for a simple line between two points.
+ *
+ * To get or set the properties of an existing #GooCanvasPolylineModel, 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 #GooCanvasPolyline objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_polyline_model_finalize     (GObject          *object);
+static void goo_canvas_polyline_model_get_property (GObject          *object,
+						    guint             param_id,
+						    GValue           *value,
+						    GParamSpec       *pspec);
+static void goo_canvas_polyline_model_set_property (GObject          *object,
+						    guint             param_id,
+						    const GValue     *value,
+						    GParamSpec       *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasPolylineModel, goo_canvas_polyline_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_polyline_model_class_init (GooCanvasPolylineModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = goo_canvas_polyline_model_finalize;
+
+  gobject_class->get_property = goo_canvas_polyline_model_get_property;
+  gobject_class->set_property = goo_canvas_polyline_model_set_property;
+
+  goo_canvas_polyline_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_polyline_model_init (GooCanvasPolylineModel *pmodel)
+{
+
+}
+
+
+/**
+ * goo_canvas_polyline_model_new:
+ * @parent: the parent model, 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 model.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a filled triangle with vertices
+ * at (100,100), (300,100), and (200,300).
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *polyline = goo_canvas_polyline_model_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);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new polyline model.
+ **/
+GooCanvasItemModel*
+goo_canvas_polyline_model_new (GooCanvasItemModel *parent,
+			       gboolean            close_path,
+			       gint                num_points,
+			       ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasPolylineModel *pmodel;
+  GooCanvasPolylineData *polyline_data;
+  const char *first_property;
+  va_list var_args;
+  gint i;
+
+  model = g_object_new (GOO_TYPE_CANVAS_POLYLINE_MODEL, NULL);
+  pmodel = (GooCanvasPolylineModel*) model;
+
+  polyline_data = &pmodel->polyline_data;
+  polyline_data->close_path = close_path;
+  polyline_data->num_points = num_points;
+  if (num_points)
+    polyline_data->coords = g_slice_alloc (num_points * 2 * sizeof (gdouble));
+
+  va_start (var_args, num_points);
+  for (i = 0; i < num_points * 2; i++)
+    polyline_data->coords[i] = va_arg (var_args, gdouble);
+
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+/**
+ * goo_canvas_polyline_model_new_line:
+ * @parent: the parent model, 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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new polyline model with a single line.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a line from (100,100) to (300,300).
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *polyline = goo_canvas_polyline_model_new_line (mygroup,
+ *                                                                     100.0, 100.0,
+ *                                                                     300.0, 300.0,
+ *                                                                     "stroke-color", "red",
+ *                                                                     "line-width", 5.0,
+ *                                                                     NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new polyline model.
+ **/
+GooCanvasItemModel*
+goo_canvas_polyline_model_new_line (GooCanvasItemModel *parent,
+				    gdouble             x1,
+				    gdouble             y1,
+				    gdouble             x2,
+				    gdouble             y2,
+				    ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasPolylineModel *pmodel;
+  GooCanvasPolylineData *polyline_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_POLYLINE_MODEL, NULL);
+  pmodel = (GooCanvasPolylineModel*) model;
+
+  polyline_data = &pmodel->polyline_data;
+  polyline_data->close_path = FALSE;
+  polyline_data->num_points = 2;
+  polyline_data->coords = g_slice_alloc (4 * sizeof (gdouble));
+  polyline_data->coords[0] = x1;
+  polyline_data->coords[1] = y1;
+  polyline_data->coords[2] = x2;
+  polyline_data->coords[3] = y2;
+
+  va_start (var_args, y2);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_polyline_model_finalize (GObject *object)
+{
+  GooCanvasPolylineModel *pmodel = (GooCanvasPolylineModel*) object;
+
+  g_slice_free1 (pmodel->polyline_data.num_points * 2 * sizeof (gdouble),
+		 pmodel->polyline_data.coords);
+  g_slice_free (GooCanvasPolylineArrowData, pmodel->polyline_data.arrow_data);
+
+  G_OBJECT_CLASS (goo_canvas_polyline_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_polyline_model_get_property (GObject              *object,
+					guint                 prop_id,
+					GValue               *value,
+					GParamSpec           *pspec)
+{
+  GooCanvasPolylineModel *pmodel = (GooCanvasPolylineModel*) object;
+
+  goo_canvas_polyline_get_common_property (object, &pmodel->polyline_data,
+					   prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_polyline_model_set_property (GObject              *object,
+					guint                 prop_id,
+					const GValue         *value,
+					GParamSpec           *pspec)
+{
+  GooCanvasPolylineModel *pmodel = (GooCanvasPolylineModel*) object;
+
+  goo_canvas_polyline_set_common_property (object, &pmodel->polyline_data,
+					   prop_id, value, pspec);
+  g_signal_emit_by_name (pmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_polyline_model_create_item (GooCanvasItemModel *model,
+				       GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_POLYLINE, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_polyline_model_create_item;
+}
diff --git a/libgoocanvas/goocanvaspolyline.h b/libgoocanvas/goocanvaspolyline.h
new file mode 100644
index 0000000..96212bd
--- /dev/null
+++ b/libgoocanvas/goocanvaspolyline.h
@@ -0,0 +1,176 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvaspolyline.h - polyline item, with optional arrows.
+ */
+#ifndef __GOO_CANVAS_POLYLINE_H__
+#define __GOO_CANVAS_POLYLINE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/**
+ * GooCanvasPoints
+ * @coords: the coordinates of the points, in pairs.
+ * @num_points: the number of points.
+ * @ref_count: the reference count of the struct.
+ *
+ * #GooCairoPoints represents an array of points.
+ */
+typedef struct _GooCanvasPoints GooCanvasPoints;
+struct _GooCanvasPoints
+{
+  double *coords;
+  int num_points;
+  int ref_count;
+};
+
+#define GOO_TYPE_CANVAS_POINTS goo_canvas_points_get_type()
+GType            goo_canvas_points_get_type (void);
+GooCanvasPoints* goo_canvas_points_new      (int              num_points);
+GooCanvasPoints* goo_canvas_points_ref      (GooCanvasPoints *points);
+void             goo_canvas_points_unref    (GooCanvasPoints *points);
+
+
+#define NUM_ARROW_POINTS     5		/* number of points in an arrowhead */
+
+typedef struct _GooCanvasPolylineArrowData GooCanvasPolylineArrowData;
+struct _GooCanvasPolylineArrowData
+{
+  /* These are specified in multiples of the line width, e.g. if arrow_width
+     is 2 the width of the arrow will be twice the width of the line. */
+  gdouble arrow_width, arrow_length, arrow_tip_length;
+
+  gdouble line_start[2], line_end[2];
+  gdouble start_arrow_coords[NUM_ARROW_POINTS * 2];
+  gdouble end_arrow_coords[NUM_ARROW_POINTS * 2];
+};
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasPolylineData   GooCanvasPolylineData;
+struct _GooCanvasPolylineData
+{
+  gdouble *coords;
+
+  GooCanvasPolylineArrowData *arrow_data;
+
+  guint num_points	   : 16;
+  guint close_path	   : 1;
+  guint start_arrow	   : 1;
+  guint end_arrow          : 1;
+  guint reconfigure_arrows : 1;		/* Not used any more. */
+};
+
+
+#define GOO_TYPE_CANVAS_POLYLINE            (goo_canvas_polyline_get_type ())
+#define GOO_CANVAS_POLYLINE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_POLYLINE, GooCanvasPolyline))
+#define GOO_CANVAS_POLYLINE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_POLYLINE, GooCanvasPolylineClass))
+#define GOO_IS_CANVAS_POLYLINE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_POLYLINE))
+#define GOO_IS_CANVAS_POLYLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_POLYLINE))
+#define GOO_CANVAS_POLYLINE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_POLYLINE, GooCanvasPolylineClass))
+
+
+typedef struct _GooCanvasPolyline       GooCanvasPolyline;
+typedef struct _GooCanvasPolylineClass  GooCanvasPolylineClass;
+
+/**
+ * GooCanvasPolyline
+ *
+ * The #GooCanvasPolyline-struct struct contains private data only.
+ */
+struct _GooCanvasPolyline
+{
+  GooCanvasItemSimple parent;
+
+  GooCanvasPolylineData *polyline_data;
+};
+
+struct _GooCanvasPolylineClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_polyline_get_type       (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_polyline_new            (GooCanvasItem      *parent,
+							gboolean            close_path,
+							gint                num_points,
+							...);
+
+GooCanvasItem*      goo_canvas_polyline_new_line       (GooCanvasItem      *parent,
+							gdouble             x1,
+							gdouble             y1,
+							gdouble             x2,
+							gdouble             y2,
+							...);
+
+
+
+#define GOO_TYPE_CANVAS_POLYLINE_MODEL            (goo_canvas_polyline_model_get_type ())
+#define GOO_CANVAS_POLYLINE_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_POLYLINE_MODEL, GooCanvasPolylineModel))
+#define GOO_CANVAS_POLYLINE_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_POLYLINE_MODEL, GooCanvasPolylineModelClass))
+#define GOO_IS_CANVAS_POLYLINE_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_POLYLINE_MODEL))
+#define GOO_IS_CANVAS_POLYLINE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_POLYLINE_MODEL))
+#define GOO_CANVAS_POLYLINE_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_POLYLINE_MODEL, GooCanvasPolylineModelClass))
+
+
+typedef struct _GooCanvasPolylineModel       GooCanvasPolylineModel;
+typedef struct _GooCanvasPolylineModelClass  GooCanvasPolylineModelClass;
+
+/**
+ * GooCanvasPolylineModel
+ *
+ * The #GooCanvasPolylineModel-struct struct contains private data only.
+ */
+struct _GooCanvasPolylineModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasPolylineData polyline_data;
+};
+
+struct _GooCanvasPolylineModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_polyline_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_polyline_model_new      (GooCanvasItemModel *parent,
+							gboolean            close_path,
+							gint                num_points,
+							...);
+
+GooCanvasItemModel* goo_canvas_polyline_model_new_line (GooCanvasItemModel *parent,
+							gdouble             x1,
+							gdouble             y1,
+							gdouble             x2,
+							gdouble             y2,
+							...);
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_POLYLINE_H__ */
diff --git a/libgoocanvas/goocanvasprivate.h b/libgoocanvas/goocanvasprivate.h
new file mode 100644
index 0000000..423f37e
--- /dev/null
+++ b/libgoocanvas/goocanvasprivate.h
@@ -0,0 +1,59 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasprivate.h - private types & utility functions.
+ */
+#ifndef __GOO_CANVAS_PRIVATE_H__
+#define __GOO_CANVAS_PRIVATE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasstyle.h"
+
+G_BEGIN_DECLS
+
+
+/*
+ * GPtrArray extensions.
+ */
+void goo_canvas_util_ptr_array_insert     (GPtrArray *ptr_array,
+					   gpointer   data,
+					   gint       index);
+
+void goo_canvas_util_ptr_array_move       (GPtrArray *ptr_array,
+					   gint       old_index,
+					   gint       new_index);
+
+gint goo_canvas_util_ptr_array_find_index (GPtrArray *ptr_array,
+					   gpointer   data);
+
+
+cairo_pattern_t* goo_canvas_cairo_pattern_from_pixbuf (GdkPixbuf *pixbuf);
+cairo_surface_t* goo_canvas_cairo_surface_from_pixbuf (GdkPixbuf *pixbuf);
+
+guint goo_canvas_convert_colors_to_rgba (double red,
+					 double green,
+					 double blue,
+					 double alpha);
+
+void goo_canvas_get_rgba_value_from_pattern (cairo_pattern_t *pattern,
+					     GValue          *value);
+
+void goo_canvas_set_style_property_from_pattern (GooCanvasStyle  *style,
+						 GQuark           property_id,
+						 cairo_pattern_t *pattern);
+
+cairo_pattern_t* goo_canvas_create_pattern_from_color_value  (const GValue *value);
+cairo_pattern_t* goo_canvas_create_pattern_from_rgba_value   (const GValue *value);
+cairo_pattern_t* goo_canvas_create_pattern_from_pixbuf_value (const GValue *value);
+
+
+gboolean goo_canvas_boolean_handled_accumulator (GSignalInvocationHint *ihint,
+						 GValue                *return_accu,
+						 const GValue          *handler_return,
+						 gpointer               dummy);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_PRIVATE_H__ */
diff --git a/libgoocanvas/goocanvasrect.c b/libgoocanvas/goocanvasrect.c
new file mode 100644
index 0000000..0af1021
--- /dev/null
+++ b/libgoocanvas/goocanvasrect.c
@@ -0,0 +1,603 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasrect.c - rectangle item.
+ */
+
+/**
+ * SECTION:goocanvasrect
+ * @Title: GooCanvasRect
+ * @Short_Description: a rectangle item.
+ *
+ * GooCanvasRect represents a rectangle item.
+ *
+ * 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 #GooCanvasRect use goo_canvas_rect_new().
+ *
+ * To get or set the properties of an existing #GooCanvasRect, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvasrect.h"
+#include "goocanvas.h"
+
+
+enum {
+  PROP_0,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_RADIUS_X,
+  PROP_RADIUS_Y
+};
+
+
+static void canvas_item_interface_init   (GooCanvasItemIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasRect, goo_canvas_rect,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_rect_install_common_properties (GObjectClass *gobject_class)
+{
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the rectangle"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the rectangle"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the rectangle"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the rectangle"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RADIUS_X,
+				   g_param_spec_double ("radius_x",
+							_("Radius X"),
+							_("The horizontal radius to use for rounded corners"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_RADIUS_Y,
+				   g_param_spec_double ("radius_y",
+							_("Radius Y"),
+							_("The vertical radius to use for rounded corners"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_rect_init (GooCanvasRect *rect)
+{
+  rect->rect_data = g_slice_new0 (GooCanvasRectData);
+}
+
+
+/**
+ * goo_canvas_rect_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.
+ * @x: the x coordinate of the left of the rectangle.
+ * @y: the y coordinate of the top of the rectangle.
+ * @width: the width of the rectangle.
+ * @height: the height of the rectangle.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new rectangle item.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a rectangle at (100,100) with a
+ * width of 200 and a height of 100.
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *rect = goo_canvas_rect_new (mygroup, 100.0, 100.0, 200.0, 100.0,
+ *                                             "stroke-color", "red",
+ *                                             "line-width", 5.0,
+ *                                             "fill-color", "blue",
+ *                                             NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new rectangle item.
+ **/
+GooCanvasItem*
+goo_canvas_rect_new (GooCanvasItem *parent,
+		     gdouble        x,
+		     gdouble        y,
+		     gdouble        width,
+		     gdouble        height,
+		     ...)
+{
+  GooCanvasItem *item;
+  GooCanvasRect *rect;
+  GooCanvasRectData *rect_data;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_RECT, NULL);
+  rect = (GooCanvasRect*) item;
+
+  rect_data = rect->rect_data;
+  rect_data->x = x;
+  rect_data->y = y;
+  rect_data->width = width;
+  rect_data->height = height;
+  rect_data->radius_x = 0;
+  rect_data->radius_y = 0;
+
+  va_start (var_args, height);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_rect_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasRect *rect = (GooCanvasRect*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    g_slice_free (GooCanvasRectData, rect->rect_data);
+  rect->rect_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_rect_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_rect_get_common_property (GObject              *object,
+				     GooCanvasRectData    *rect_data,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      g_value_set_double (value, rect_data->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, rect_data->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, rect_data->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, rect_data->height);
+      break;
+    case PROP_RADIUS_X:
+      g_value_set_double (value, rect_data->radius_x);
+      break;
+    case PROP_RADIUS_Y:
+      g_value_set_double (value, rect_data->radius_y);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_rect_get_property (GObject              *object,
+			      guint                 prop_id,
+			      GValue               *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasRect *rect = (GooCanvasRect*) object;
+
+  goo_canvas_rect_get_common_property (object, rect->rect_data, prop_id,
+				       value, pspec);
+}
+
+
+static void
+goo_canvas_rect_set_common_property (GObject              *object,
+				     GooCanvasRectData    *rect_data,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      rect_data->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      rect_data->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      rect_data->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      rect_data->height = g_value_get_double (value);
+      break;
+    case PROP_RADIUS_X:
+      rect_data->radius_x = g_value_get_double (value);
+      break;
+    case PROP_RADIUS_Y:
+      rect_data->radius_y = g_value_get_double (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_rect_set_property (GObject              *object,
+			      guint                 prop_id,
+			      const GValue         *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasRect *rect = (GooCanvasRect*) object;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_rect_set_common_property (object, rect->rect_data, prop_id,
+				       value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_rect_create_path (GooCanvasItemSimple *simple,
+			     cairo_t             *cr)
+{
+  GooCanvasRect *rect = (GooCanvasRect*) simple;
+  GooCanvasRectData *rect_data = rect->rect_data;
+
+  cairo_new_path (cr);
+
+  /* Check if we need to draw rounded corners. */
+  if (rect_data->radius_x > 0 && rect_data->radius_y > 0)
+    {
+      /* The radii can't be more than half the size of the rect. */
+      double rx = MIN (rect_data->radius_x, rect_data->width / 2);
+      double ry = MIN (rect_data->radius_y, rect_data->height / 2);
+
+      /* Draw the top-right arc. */
+      cairo_save (cr);
+      cairo_translate (cr, rect_data->x + rect_data->width - rx,
+		       rect_data->y + ry);
+      cairo_scale (cr, rx, ry);
+      cairo_arc (cr, 0.0, 0.0, 1.0, 1.5 * M_PI, 2.0 * M_PI);
+      cairo_restore (cr);
+
+      /* Draw the line down the right side. */
+      cairo_line_to (cr, rect_data->x + rect_data->width,
+		     rect_data->y + rect_data->height - ry);
+
+      /* Draw the bottom-right arc. */
+      cairo_save (cr);
+      cairo_translate (cr, rect_data->x + rect_data->width - rx,
+		       rect_data->y + rect_data->height - ry);
+      cairo_scale (cr, rx, ry);
+      cairo_arc (cr, 0.0, 0.0, 1.0, 0.0, 0.5 * M_PI);
+      cairo_restore (cr);
+
+      /* Draw the line left across the bottom. */
+      cairo_line_to (cr, rect_data->x + rx, rect_data->y + rect_data->height);
+
+      /* Draw the bottom-left arc. */
+      cairo_save (cr);
+      cairo_translate (cr, rect_data->x + rx,
+		       rect_data->y + rect_data->height - ry);
+      cairo_scale (cr, rx, ry);
+      cairo_arc (cr, 0.0, 0.0, 1.0, 0.5 * M_PI, M_PI);
+      cairo_restore (cr);
+
+      /* Draw the line up the left side. */
+      cairo_line_to (cr, rect_data->x, rect_data->y + ry);
+
+      /* Draw the top-left arc. */
+      cairo_save (cr);
+      cairo_translate (cr, rect_data->x + rx, rect_data->y + ry);
+      cairo_scale (cr, rx, ry);
+      cairo_arc (cr, 0.0, 0.0, 1.0, M_PI, 1.5 * M_PI);
+      cairo_restore (cr);
+
+      /* Close the path across the top. */
+      cairo_close_path (cr);
+    }
+  else
+    {
+      /* Draw the plain rectangle. */
+      cairo_rectangle (cr, rect_data->x, rect_data->y,
+		       rect_data->width, rect_data->height);
+    }
+}
+
+
+static void
+goo_canvas_rect_update  (GooCanvasItemSimple *simple,
+			 cairo_t             *cr)
+{
+  GooCanvasRect *rect = (GooCanvasRect*) simple;
+  GooCanvasRectData *rect_data = rect->rect_data;
+  gdouble half_line_width;
+
+  /* We can quickly compute the bounds as being just the rectangle's size
+     plus half the line width around each edge. */
+  half_line_width = goo_canvas_item_simple_get_line_width (simple) / 2;
+
+  simple->bounds.x1 = rect_data->x - half_line_width;
+  simple->bounds.y1 = rect_data->y - half_line_width;
+  simple->bounds.x2 = rect_data->x + rect_data->width + half_line_width;
+  simple->bounds.y2 = rect_data->y + rect_data->height + half_line_width;
+}
+
+
+static void
+goo_canvas_rect_set_model    (GooCanvasItem      *item,
+			      GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasRect *rect = (GooCanvasRect*) item;
+  GooCanvasRectModel *rmodel = (GooCanvasRectModel*) model;
+
+  /* If our rect_data was allocated, free it. */
+  if (!simple->model)
+    g_slice_free (GooCanvasRectData, rect->rect_data);
+
+  /* Now use the new model's rect_data instead. */
+  rect->rect_data = &rmodel->rect_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_model      = goo_canvas_rect_set_model;
+}
+
+
+static void
+goo_canvas_rect_class_init (GooCanvasRectClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  gobject_class->finalize     = goo_canvas_rect_finalize;
+
+  gobject_class->get_property = goo_canvas_rect_get_property;
+  gobject_class->set_property = goo_canvas_rect_set_property;
+
+  simple_class->simple_create_path = goo_canvas_rect_create_path;
+  simple_class->simple_update      = goo_canvas_rect_update;
+
+  goo_canvas_rect_install_common_properties (gobject_class);
+}
+
+
+/**
+ * SECTION:goocanvasrectmodel
+ * @Title: GooCanvasRectModel
+ * @Short_Description: a model for rectangle items.
+ *
+ * GooCanvasRectModel represents a model for rectangle items.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "stroke-color", "fill-color" and "line-width".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasRectModel use goo_canvas_rect_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasRectModel, use
+ * g_object_get() and g_object_set().
+ *
+ * To respond to events such as mouse clicks on the rectangle you must connect
+ * to the signal handlers of the corresponding #GooCanvasRect objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_rect_model_get_property (GObject            *object,
+						guint               param_id,
+						GValue             *value,
+						GParamSpec         *pspec);
+static void goo_canvas_rect_model_set_property (GObject            *object,
+						guint               param_id,
+						const GValue       *value,
+						GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasRectModel, goo_canvas_rect_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_rect_model_class_init (GooCanvasRectModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->get_property = goo_canvas_rect_model_get_property;
+  gobject_class->set_property = goo_canvas_rect_model_set_property;
+
+  goo_canvas_rect_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_rect_model_init (GooCanvasRectModel *rmodel)
+{
+
+}
+
+
+/**
+ * goo_canvas_rect_model_new:
+ * @parent: the parent model, 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.
+ * @x: the x coordinate of the left of the rectangle.
+ * @y: the y coordinate of the top of the rectangle.
+ * @width: the width of the rectangle.
+ * @height: the height of the rectangle.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new rectangle item.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a rectangle at (100,100) with a
+ * width of 200 and a height of 100.
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *rect = goo_canvas_rect_model_new (mygroup, 100.0, 100.0, 200.0, 100.0,
+ *                                                        "stroke-color", "red",
+ *                                                        "line-width", 5.0,
+ *                                                        "fill-color", "blue",
+ *                                                        NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new rectangle model.
+ **/
+GooCanvasItemModel*
+goo_canvas_rect_model_new (GooCanvasItemModel *parent,
+			   gdouble             x,
+			   gdouble             y,
+			   gdouble             width,
+			   gdouble             height,
+			   ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasRectModel *rmodel;
+  GooCanvasRectData *rect_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_RECT_MODEL, NULL);
+  rmodel = (GooCanvasRectModel*) model;
+
+  rect_data = &rmodel->rect_data;
+  rect_data->x = x;
+  rect_data->y = y;
+  rect_data->width = width;
+  rect_data->height = height;
+  rect_data->radius_x = 0;
+  rect_data->radius_y = 0;
+
+  va_start (var_args, height);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_rect_model_get_property (GObject              *object,
+				    guint                 prop_id,
+				    GValue               *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasRectModel *rmodel = (GooCanvasRectModel*) object;
+
+  goo_canvas_rect_get_common_property (object, &rmodel->rect_data, prop_id,
+				       value, pspec);
+}
+
+
+static void
+goo_canvas_rect_model_set_property (GObject              *object,
+				    guint                 prop_id,
+				    const GValue         *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasRectModel *rmodel = (GooCanvasRectModel*) object;
+
+  goo_canvas_rect_set_common_property (object, &rmodel->rect_data, prop_id,
+				       value, pspec);
+  g_signal_emit_by_name (rmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_rect_model_create_item (GooCanvasItemModel *model,
+				   GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_RECT, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_rect_model_create_item;
+}
diff --git a/libgoocanvas/goocanvasrect.h b/libgoocanvas/goocanvasrect.h
new file mode 100644
index 0000000..4d92928
--- /dev/null
+++ b/libgoocanvas/goocanvasrect.h
@@ -0,0 +1,121 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasrect.h - rectangle item.
+ */
+#ifndef __GOO_CANVAS_RECT_H__
+#define __GOO_CANVAS_RECT_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasRectData   GooCanvasRectData;
+struct _GooCanvasRectData
+{
+  gdouble x, y, width, height, radius_x, radius_y;
+};
+
+
+#define GOO_TYPE_CANVAS_RECT            (goo_canvas_rect_get_type ())
+#define GOO_CANVAS_RECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_RECT, GooCanvasRect))
+#define GOO_CANVAS_RECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_RECT, GooCanvasRectClass))
+#define GOO_IS_CANVAS_RECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_RECT))
+#define GOO_IS_CANVAS_RECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_RECT))
+#define GOO_CANVAS_RECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_RECT, GooCanvasRectClass))
+
+
+typedef struct _GooCanvasRect       GooCanvasRect;
+typedef struct _GooCanvasRectClass  GooCanvasRectClass;
+
+/**
+ * GooCanvasRect
+ *
+ * The #GooCanvasRect-struct struct contains private data only.
+ */
+struct _GooCanvasRect
+{
+  GooCanvasItemSimple parent;
+
+  GooCanvasRectData *rect_data;
+};
+
+struct _GooCanvasRectClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_rect_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_rect_new       (GooCanvasItem      *parent,
+					       gdouble             x,
+					       gdouble             y,
+					       gdouble             width,
+					       gdouble             height,
+					       ...);
+
+
+
+#define GOO_TYPE_CANVAS_RECT_MODEL            (goo_canvas_rect_model_get_type ())
+#define GOO_CANVAS_RECT_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_RECT_MODEL, GooCanvasRectModel))
+#define GOO_CANVAS_RECT_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_RECT_MODEL, GooCanvasRectModelClass))
+#define GOO_IS_CANVAS_RECT_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_RECT_MODEL))
+#define GOO_IS_CANVAS_RECT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_RECT_MODEL))
+#define GOO_CANVAS_RECT_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_RECT_MODEL, GooCanvasRectModelClass))
+
+
+typedef struct _GooCanvasRectModel       GooCanvasRectModel;
+typedef struct _GooCanvasRectModelClass  GooCanvasRectModelClass;
+
+/**
+ * GooCanvasRectModel
+ *
+ * The #GooCanvasRectModel-struct struct contains private data only.
+ */
+struct _GooCanvasRectModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasRectData rect_data;
+};
+
+struct _GooCanvasRectModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_rect_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_rect_model_new (GooCanvasItemModel *parent,
+					       gdouble             x,
+					       gdouble             y,
+					       gdouble             width,
+					       gdouble             height,
+					       ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_RECT_H__ */
diff --git a/libgoocanvas/goocanvasstyle.c b/libgoocanvas/goocanvasstyle.c
new file mode 100644
index 0000000..378fba6
--- /dev/null
+++ b/libgoocanvas/goocanvasstyle.c
@@ -0,0 +1,547 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasstyle.c - cascading styles.
+ */
+
+/**
+ * SECTION:goocanvasstyle
+ * @Title: GooCanvasStyle
+ * @Short_Description: support for cascading style properties for canvas items.
+ *
+ * #GooCanvasStyle provides support for cascading style properties for canvas
+ * items. It is intended to be used when implementing new canvas items.
+ *
+ * <note><para>
+ * 	The cascading styles canvas feature may be removed in a future version
+ *      of GooCanvas.
+ * </para></note>
+ *
+ * Style properties are identified by a unique #GQuark, and contain
+ * arbitrary data stored in a #GValue.
+ *
+ * #GooCanvasStyle also provides a few convenience functions such as
+ * goo_canvas_style_set_stroke_options() and
+ * goo_canvas_style_set_fill_options() which efficiently apply an item's
+ * standard style properties to the given cairo_t.
+ */
+#include <config.h>
+#include <gtk/gtk.h>
+#include "goocanvasstyle.h"
+#include "goocanvasutils.h"
+
+/* GQuarks for the basic properties. */
+
+/**
+ * goo_canvas_style_stroke_pattern_id:
+ * 
+ * Unique #GQuark identifier used for the standard stroke pattern property.
+ **/
+GQuark goo_canvas_style_stroke_pattern_id;
+
+/**
+ * goo_canvas_style_fill_pattern_id:
+ * 
+ * Unique #GQuark identifier used for the standard fill pattern property.
+ **/
+GQuark goo_canvas_style_fill_pattern_id;
+
+/**
+ * goo_canvas_style_fill_rule_id:
+ * 
+ * Unique #GQuark identifier used for the standard fill rule property.
+ **/
+GQuark goo_canvas_style_fill_rule_id;
+
+/**
+ * goo_canvas_style_operator_id:
+ * 
+ * Unique #GQuark identifier used for the standard operator property.
+ **/
+GQuark goo_canvas_style_operator_id;
+
+/**
+ * goo_canvas_style_antialias_id:
+ * 
+ * Unique #GQuark identifier used for the standard antialias property.
+ **/
+GQuark goo_canvas_style_antialias_id;
+
+/**
+ * goo_canvas_style_line_width_id:
+ * 
+ * Unique #GQuark identifier used for the standard line width property.
+ **/
+GQuark goo_canvas_style_line_width_id;
+
+/**
+ * goo_canvas_style_line_cap_id:
+ * 
+ * Unique #GQuark identifier used for the standard line cap property.
+ **/
+GQuark goo_canvas_style_line_cap_id;
+
+/**
+ * goo_canvas_style_line_join_id:
+ * 
+ * Unique #GQuark identifier used for the standard line join property.
+ **/
+GQuark goo_canvas_style_line_join_id;
+
+/**
+ * goo_canvas_style_line_join_miter_limit_id:
+ * 
+ * Unique #GQuark identifier used for the standard miter limit property.
+ **/
+GQuark goo_canvas_style_line_join_miter_limit_id;
+
+/**
+ * goo_canvas_style_line_dash_id:
+ * 
+ * Unique #GQuark identifier used for the standard line dash property.
+ **/
+GQuark goo_canvas_style_line_dash_id;
+
+/**
+ * goo_canvas_style_font_desc_id:
+ * 
+ * Unique #GQuark identifier used for the standard font description property.
+ **/
+GQuark goo_canvas_style_font_desc_id;
+
+/**
+ * goo_canvas_style_hint_metrics_id:
+ * 
+ * Unique #GQuark identifier used for the standard hint metrics property.
+ **/
+GQuark goo_canvas_style_hint_metrics_id;
+
+static void goo_canvas_style_dispose  (GObject *object);
+static void goo_canvas_style_finalize (GObject *object);
+
+G_DEFINE_TYPE (GooCanvasStyle, goo_canvas_style, G_TYPE_OBJECT)
+
+
+/* Create GQuarks for the basic properties. This is called by
+   goo_canvas_style_class_init(), goo_canvas_item_base_init() and
+   goo_canvas_item_model_base_init() to try to ensure the GQuarks are
+   initialized before they are needed. */
+void
+_goo_canvas_style_init (void)
+{
+  static gboolean initialized = FALSE;
+  
+  if (!initialized)
+    {
+      goo_canvas_style_stroke_pattern_id = g_quark_from_static_string ("GooCanvasStyle:stroke_pattern");
+      goo_canvas_style_fill_pattern_id = g_quark_from_static_string ("GooCanvasStyle:fill_pattern");
+      goo_canvas_style_fill_rule_id = g_quark_from_static_string ("GooCanvasStyle:fill_rule");
+      goo_canvas_style_operator_id = g_quark_from_static_string ("GooCanvasStyle:operator");
+      goo_canvas_style_antialias_id = g_quark_from_static_string ("GooCanvasStyle:antialias");
+      goo_canvas_style_line_width_id = g_quark_from_static_string ("GooCanvasStyle:line_width");
+      goo_canvas_style_line_cap_id = g_quark_from_static_string ("GooCanvasStyle:line_cap");
+      goo_canvas_style_line_join_id = g_quark_from_static_string ("GooCanvasStyle:line_join");
+      goo_canvas_style_line_join_miter_limit_id = g_quark_from_static_string ("GooCanvasStyle:line_join_miter_limit");
+      goo_canvas_style_line_dash_id = g_quark_from_static_string ("GooCanvasStyle:line_dash");
+      goo_canvas_style_font_desc_id = g_quark_from_static_string ("GooCanvasStyle:font_desc");
+      goo_canvas_style_hint_metrics_id = g_quark_from_static_string ("GooCanvasStyle:hint_metrics");
+
+      initialized = TRUE;
+    }
+}
+
+
+static void
+goo_canvas_style_class_init (GooCanvasStyleClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->dispose  = goo_canvas_style_dispose;
+  gobject_class->finalize = goo_canvas_style_finalize;
+
+  _goo_canvas_style_init ();
+}
+
+
+static void
+goo_canvas_style_init (GooCanvasStyle *style)
+{
+  style->properties = g_array_new (0, 0, sizeof (GooCanvasStyleProperty));
+}
+
+
+/**
+ * goo_canvas_style_new:
+ * 
+ * Creates a new #GooCanvasStyle.
+ * 
+ * Returns: a new #GooCanvasStyle.
+ **/
+GooCanvasStyle*
+goo_canvas_style_new (void)
+{
+  return GOO_CANVAS_STYLE (g_object_new (GOO_TYPE_CANVAS_STYLE, NULL));
+}
+
+
+static void
+goo_canvas_style_dispose (GObject *object)
+{
+  GooCanvasStyle *style = (GooCanvasStyle*) object;
+  GooCanvasStyleProperty *property;
+  gint i;
+
+  if (style->parent)
+    {
+      g_object_unref (style->parent);
+      style->parent = NULL;
+    }
+
+  /* Free the property GValues. */
+  for (i = 0; i < style->properties->len; i++)
+    {
+      property = &g_array_index (style->properties, GooCanvasStyleProperty, i);
+      g_value_unset (&property->value);
+    }
+  g_array_set_size (style->properties, 0);
+
+  G_OBJECT_CLASS (goo_canvas_style_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_style_finalize (GObject *object)
+{
+  GooCanvasStyle *style = (GooCanvasStyle*) object;
+
+  g_array_free (style->properties, TRUE);
+
+  G_OBJECT_CLASS (goo_canvas_style_parent_class)->finalize (object);
+}
+
+
+/**
+ * goo_canvas_style_copy:
+ * @style: a #GooCanvasStyle.
+ * 
+ * Copies the given #GooCanvasStyle, by copying all of its properties.
+ * Though the parent of the new style is left unset.
+ * 
+ * Returns: a copy of the given #GooCanvasStyle.
+ **/
+GooCanvasStyle*
+goo_canvas_style_copy               (GooCanvasStyle *style)
+{
+  GooCanvasStyle *copy;
+  GooCanvasStyleProperty *property;
+  gint i;
+
+  copy = goo_canvas_style_new ();
+
+  for (i = 0; i < style->properties->len; i++)
+    {
+      property = &g_array_index (style->properties, GooCanvasStyleProperty, i);
+      goo_canvas_style_set_property (copy, property->id, &property->value);
+    }
+
+  return copy;
+}
+
+
+/**
+ * goo_canvas_style_get_parent:
+ * @style: a style.
+ * 
+ * Gets the parent of the style.
+ * 
+ * Returns: the parent of the given style, or %NULL.
+ **/
+GooCanvasStyle*
+goo_canvas_style_get_parent         (GooCanvasStyle *style)
+{
+  return style->parent;
+}
+
+
+/**
+ * goo_canvas_style_set_parent:
+ * @style: a style.
+ * @parent: the new parent.
+ * 
+ * Sets the parent of the style.
+ **/
+void
+goo_canvas_style_set_parent         (GooCanvasStyle *style,
+				     GooCanvasStyle *parent)
+{
+  if (style->parent == parent)
+    return;
+
+  if (style->parent)
+    g_object_unref (style->parent);
+
+  style->parent = parent;
+
+  if (style->parent)
+    g_object_ref (style->parent);
+}
+
+
+/**
+ * goo_canvas_style_get_property:
+ * @style: a style.
+ * @property_id: the property identifier.
+ * 
+ * Gets the value of a property.
+ *
+ * This searches though all the #GooCanvasStyle's own list of property settings
+ * and also all ancestor #GooCanvasStyle objects.
+ *
+ * Note that it returns a pointer to the internal #GValue setting, which should
+ * not be changed.
+ * 
+ * Returns: the property value, or %NULL if it isn't set.
+ **/
+GValue*
+goo_canvas_style_get_property       (GooCanvasStyle *style,
+				     GQuark          property_id)
+{
+  GooCanvasStyleProperty *property;
+  gint i;
+
+  /* Step up the hierarchy of styles until we find the property. Note that
+     if style is passed in as NULL we simply return NULL. */
+  while (style)
+    {
+      for (i = 0; i < style->properties->len; i++)
+	{
+	  property = &g_array_index (style->properties, GooCanvasStyleProperty,
+				     i);
+	  if (property->id == property_id)
+	    return &property->value;
+	}
+
+      style = style->parent;
+    }
+
+  return NULL;
+}
+
+
+/**
+ * goo_canvas_style_set_property:
+ * @style: a style.
+ * @property_id: the property identifier.
+ * @value: the value of the property.
+ * 
+ * Sets a property in the style, replacing any current setting.
+ *
+ * Note that this will override the property setting in ancestor
+ * #GooCanvasStyle objects.
+ **/
+void
+goo_canvas_style_set_property	    (GooCanvasStyle *style,
+				     GQuark          property_id,
+				     const GValue   *value)
+{
+  GooCanvasStyleProperty *property, new_property = { 0 };
+  gint i;
+
+  /* See if the property is already set. */
+  for (i = 0; i < style->properties->len; i++)
+    {
+      property = &g_array_index (style->properties, GooCanvasStyleProperty, i);
+      if (property->id == property_id)
+	{
+	  /* If the new value is NULL, remove the property setting, otherwise
+	     update the property value. */
+	  if (value)
+	    {
+	      g_value_copy (value, &property->value);
+	    }
+	  else
+	    {
+	      g_value_unset (&property->value);
+	      g_array_remove_index_fast (style->properties, i);
+	    }
+
+	  return;
+	}
+    }
+
+  /* The property isn't set, so append a new property. */
+  if (value)
+    {
+      new_property.id = property_id;
+      g_value_init (&new_property.value, G_VALUE_TYPE (value));
+      g_value_copy (value, &new_property.value);
+      g_array_append_val (style->properties, new_property);
+    }
+}
+
+
+/**
+ * goo_canvas_style_set_stroke_options:
+ * @style: a style.
+ * @cr: a cairo context.
+ * 
+ * Sets the standard cairo stroke options using the given style.
+ * 
+ * Returns: %TRUE if a paint source is set, or %FALSE if the stroke should
+ * be skipped.
+ **/
+gboolean
+goo_canvas_style_set_stroke_options (GooCanvasStyle *style,
+				     cairo_t        *cr)
+{
+  GooCanvasStyleProperty *property;
+  gboolean operator_set = FALSE, antialias_set = FALSE;
+  gboolean stroke_pattern_set = FALSE, line_width_set = FALSE;
+  gboolean line_cap_set = FALSE, line_join_set = FALSE;
+  gboolean miter_limit_set = FALSE, line_dash_set = FALSE;
+  gboolean source_set = FALSE, need_stroke = TRUE;
+  gint i;
+
+  if (!style)
+    return TRUE;
+
+  /* Step up the hierarchy of styles looking for the properties. */
+  while (style)
+    {
+      for (i = 0; i < style->properties->len; i++)
+	{
+	  property = &g_array_index (style->properties, GooCanvasStyleProperty,
+				     i);
+
+	  if (property->id == goo_canvas_style_operator_id && !operator_set)
+	    {
+	      cairo_set_operator (cr, property->value.data[0].v_long);
+	      operator_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_antialias_id && !antialias_set)
+	    {
+	      cairo_set_antialias (cr, property->value.data[0].v_long);
+	      antialias_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_stroke_pattern_id && !stroke_pattern_set)
+	    {
+	      if (property->value.data[0].v_pointer)
+		{
+		  cairo_set_source (cr, property->value.data[0].v_pointer);
+		  source_set = TRUE;
+		}
+	      else
+		{
+		  /* If the stroke pattern has been explicitly set to NULL,
+		     then we don't need to do the stroke. */
+		  need_stroke = FALSE;
+		}
+	      stroke_pattern_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_line_width_id && !line_width_set)
+	    {
+	      cairo_set_line_width (cr, property->value.data[0].v_double);
+	      line_width_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_line_cap_id && !line_cap_set)
+	    {
+	      cairo_set_line_cap (cr, property->value.data[0].v_long);
+	      line_cap_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_line_join_id && !line_join_set)
+	    {
+	      cairo_set_line_join (cr, property->value.data[0].v_long);
+	      line_join_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_line_join_miter_limit_id && !miter_limit_set)
+	    {
+	      cairo_set_miter_limit (cr, property->value.data[0].v_double);
+	      miter_limit_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_line_dash_id && !line_dash_set)
+	    {
+	      GooCanvasLineDash *dash = property->value.data[0].v_pointer;
+	      if (dash)
+		cairo_set_dash (cr, dash->dashes, dash->num_dashes,
+				dash->dash_offset);
+	      else
+		cairo_set_dash (cr, NULL, 0, 0);
+	      line_dash_set = TRUE;
+	    }
+	}
+
+      style = style->parent;
+    }
+
+  /* If a stroke pattern hasn't been set in the style we reset the source to
+     black, just in case a fill pattern was used for the item. */
+  if (!source_set)
+    cairo_set_source_rgb (cr, 0, 0, 0);
+
+  return need_stroke;
+}
+
+
+/**
+ * goo_canvas_style_set_fill_options:
+ * @style: a style.
+ * @cr: a cairo context.
+ * 
+ * Sets the standard cairo fill options using the given style.
+ * 
+ * Returns: %TRUE if a paint source is set, or %FALSE if the fill should
+ * be skipped.
+ **/
+gboolean
+goo_canvas_style_set_fill_options   (GooCanvasStyle *style,
+				     cairo_t        *cr)
+{
+  GooCanvasStyleProperty *property;
+  gboolean operator_set = FALSE, antialias_set = FALSE;
+  gboolean fill_rule_set = FALSE, fill_pattern_set = FALSE;
+  gboolean need_fill = FALSE;
+  gint i;
+
+  if (!style)
+    return FALSE;
+
+  /* Step up the hierarchy of styles looking for the properties. */
+  while (style)
+    {
+      for (i = 0; i < style->properties->len; i++)
+	{
+	  property = &g_array_index (style->properties, GooCanvasStyleProperty,
+				     i);
+
+	  if (property->id == goo_canvas_style_operator_id && !operator_set)
+	    {
+	      cairo_set_operator (cr, property->value.data[0].v_long);
+	      operator_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_antialias_id && !antialias_set)
+	    {
+	      cairo_set_antialias (cr, property->value.data[0].v_long);
+	      antialias_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_fill_rule_id && !fill_rule_set)
+	    {
+	      cairo_set_fill_rule (cr, property->value.data[0].v_long);
+	      fill_rule_set = TRUE;
+	    }
+	  else if (property->id == goo_canvas_style_fill_pattern_id && !fill_pattern_set)
+	    {
+	      if (property->value.data[0].v_pointer)
+		{
+		  cairo_set_source (cr, property->value.data[0].v_pointer);
+		  need_fill = TRUE;
+		}
+	      fill_pattern_set = TRUE;
+	    }
+	}
+
+      style = style->parent;
+    }
+
+  return need_fill;
+}
diff --git a/libgoocanvas/goocanvasstyle.h b/libgoocanvas/goocanvasstyle.h
new file mode 100644
index 0000000..ec76640
--- /dev/null
+++ b/libgoocanvas/goocanvasstyle.h
@@ -0,0 +1,110 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasstyle.h - cascading styles.
+ */
+#ifndef __GOO_CANVAS_STYLE_H__
+#define __GOO_CANVAS_STYLE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+
+/* GQuarks for the basic properties. */
+extern GQuark goo_canvas_style_stroke_pattern_id;
+extern GQuark goo_canvas_style_fill_pattern_id;
+extern GQuark goo_canvas_style_fill_rule_id;
+extern GQuark goo_canvas_style_operator_id;
+extern GQuark goo_canvas_style_antialias_id;
+extern GQuark goo_canvas_style_line_width_id;
+extern GQuark goo_canvas_style_line_cap_id;
+extern GQuark goo_canvas_style_line_join_id;
+extern GQuark goo_canvas_style_line_join_miter_limit_id;
+extern GQuark goo_canvas_style_line_dash_id;
+extern GQuark goo_canvas_style_font_desc_id;
+extern GQuark goo_canvas_style_hint_metrics_id;
+
+
+/**
+ * GooCanvasStyleProperty
+ * @id: the unique property identifier.
+ * @value: the value of the property.
+ *
+ * #GooCanvasStyleProperty represents a property setting.
+ */
+typedef struct _GooCanvasStyleProperty GooCanvasStyleProperty;
+struct _GooCanvasStyleProperty
+{
+  GQuark id;
+  GValue value;
+};
+
+
+#define GOO_TYPE_CANVAS_STYLE            (goo_canvas_style_get_type ())
+#define GOO_CANVAS_STYLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_STYLE, GooCanvasStyle))
+#define GOO_CANVAS_STYLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_STYLE, GooCanvasStyleClass))
+#define GOO_IS_CANVAS_STYLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_STYLE))
+#define GOO_IS_CANVAS_STYLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_STYLE))
+#define GOO_CANVAS_STYLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_STYLE, GooCanvasStyleClass))
+
+
+typedef struct _GooCanvasStyle       GooCanvasStyle;
+typedef struct _GooCanvasStyleClass  GooCanvasStyleClass;
+
+/**
+ * GooCanvasStyle
+ * @parent: the parent style.
+ * @properties: an array of #GooCanvasStyleProperty property settings.
+ *
+ * #GooCanvasStyle holds the style properties of a canvas item, as well as a
+ * pointer to the parent style.
+ */
+struct _GooCanvasStyle
+{
+  /* <private> */
+  GObject parent_object;
+
+  /* <public> */
+  GooCanvasStyle *parent;
+  GArray *properties;
+};
+
+struct _GooCanvasStyleClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType           goo_canvas_style_get_type           (void) G_GNUC_CONST;
+GooCanvasStyle* goo_canvas_style_new                (void);
+GooCanvasStyle* goo_canvas_style_copy               (GooCanvasStyle *style);
+
+GooCanvasStyle* goo_canvas_style_get_parent         (GooCanvasStyle *style);
+void            goo_canvas_style_set_parent         (GooCanvasStyle *style,
+						     GooCanvasStyle *parent);
+
+GValue*         goo_canvas_style_get_property       (GooCanvasStyle *style,
+						     GQuark          property_id);
+void            goo_canvas_style_set_property	    (GooCanvasStyle *style,
+						     GQuark          property_id,
+						     const GValue   *value);
+
+/* Convenience functions to set the standard cairo stroke and fill options. */
+gboolean        goo_canvas_style_set_stroke_options (GooCanvasStyle *style,
+						     cairo_t        *cr);
+gboolean        goo_canvas_style_set_fill_options   (GooCanvasStyle *style,
+						     cairo_t        *cr);
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_STYLE_H__ */
diff --git a/libgoocanvas/goocanvastable.c b/libgoocanvas/goocanvastable.c
new file mode 100644
index 0000000..911869e
--- /dev/null
+++ b/libgoocanvas/goocanvastable.c
@@ -0,0 +1,3023 @@
+/*
+ * GooCanvas. Copyright (C) 2005-7 Damon Chaplin.
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 1997-2000 the GTK+ team.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvastable.c - table item. A lot of this code has been ported from
+ * the GtkTable widget.
+ */
+
+/**
+ * SECTION:goocanvastable
+ * @Title: GooCanvasTable
+ * @Short_Description: a table container to layout items.
+ *
+ * #GooCanvasTable is a table container used to lay out other canvas items.
+ * It is used in a similar way to how the GtkTable widget is used to lay out
+ * GTK+ widgets.
+ *
+ * Items are added to the table using the normal methods, then
+ * goo_canvas_item_set_child_properties() is used to specify how each child
+ * item is to be positioned within the table (i.e. which row and column it is
+ * in, how much padding it should have and whether it should expand or
+ * shrink).
+ *
+ * #GooCanvasTable is a subclass of #GooCanvasItemSimple and so
+ * inherits all of the style properties such as "stroke-color", "fill-color"
+ * and "line-width". Setting a style property on a #GooCanvasTable will affect
+ * all children of the #GooCanvasTable (unless the children override the
+ * property setting).
+ *
+ * #GooCanvasTable implements the #GooCanvasItem interface, so you can use
+ * the #GooCanvasItem functions such as goo_canvas_item_raise() and
+ * goo_canvas_item_rotate(), and the properties such as "visibility" and
+ * "pointer-events".
+ *
+ * To create a #GooCanvasTable use goo_canvas_table_new().
+ *
+ * To get or set the properties of an existing #GooCanvasTable, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <math.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvastable.h"
+#include "goocanvas.h"
+
+
+enum
+{
+  PROP_0,
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_ROW_SPACING,
+  PROP_COLUMN_SPACING,
+  PROP_HOMOGENEOUS_ROWS,
+  PROP_HOMOGENEOUS_COLUMNS,
+  PROP_X_BORDER_SPACING,
+  PROP_Y_BORDER_SPACING,
+  PROP_VERT_GRID_LINE_WIDTH,
+  PROP_HORZ_GRID_LINE_WIDTH
+};
+
+enum
+{
+  CHILD_PROP_0,
+  CHILD_PROP_LEFT_PADDING,
+  CHILD_PROP_RIGHT_PADDING,
+  CHILD_PROP_TOP_PADDING,
+  CHILD_PROP_BOTTOM_PADDING,
+  CHILD_PROP_X_ALIGN,
+  CHILD_PROP_Y_ALIGN,
+  CHILD_PROP_ROW,
+  CHILD_PROP_COLUMN,
+  CHILD_PROP_ROWS,
+  CHILD_PROP_COLUMNS,
+  CHILD_PROP_X_EXPAND,
+  CHILD_PROP_X_FILL,
+  CHILD_PROP_X_SHRINK,
+  CHILD_PROP_Y_EXPAND,
+  CHILD_PROP_Y_FILL,
+  CHILD_PROP_Y_SHRINK
+};
+
+#define HORZ 0
+#define VERT 1
+
+typedef enum
+{
+  GOO_CANVAS_TABLE_CHILD_EXPAND	= 1 << 0,
+  GOO_CANVAS_TABLE_CHILD_FILL 	= 1 << 1,
+  GOO_CANVAS_TABLE_CHILD_SHRINK	= 1 << 2
+} GooCanvasTableChildFlags;
+
+
+/* This is used in the GooCanvasTableData children GArray to keep the child
+   properties for each of the children. */
+typedef struct _GooCanvasTableChild  GooCanvasTableChild;
+struct _GooCanvasTableChild
+{
+  gdouble position[2];			/* Translation offset in table. */
+  gdouble start_pad[2], end_pad[2];
+  gdouble align[2];
+  guint16 start[2], size[2];		/* Start row/col & num rows/cols. */
+  guint8 flags[2];			/* GooCanvasTableChildFlags. */
+};
+
+typedef struct _GooCanvasTableDimensionLayoutData GooCanvasTableDimensionLayoutData;
+struct _GooCanvasTableDimensionLayoutData
+{
+  /* This is the actual spacing for after the row or column, including
+     the grid line width. It is set in goo_canvas_table_init_layout_data(). */
+  gdouble spacing;
+
+  /* Stores the grid line visibilty for the grid lines to the right or bottom
+     of this row/column, respectivel, for each cell in the other dimension. */
+  guint32 *grid_line_visibility;
+
+  /* The requisition is calculated in goo_canvas_table_size_request_pass[123]*/
+  gdouble requisition;
+
+  /* The allocation is initialized in goo_canvas_table_size_allocate_init()
+     and calculated in goo_canvas_table_size_allocate_pass1(). */
+  gdouble allocation;
+
+  /* The row or column start and end positions are calculated in
+     goo_canvas_table_size_allocate_pass2(). */
+  gdouble start;
+  gdouble end;
+
+  /* These flags are initialized in goo_canvas_table_init_layout_data() and
+     set in goo_canvas_table_size_request_init(). */
+  guint need_expand : 1;
+  guint need_shrink : 1;
+  guint expand : 1;
+  guint shrink : 1;
+  guint empty : 1;
+};
+
+typedef struct _GooCanvasTableChildLayoutData GooCanvasTableChildLayoutData;
+struct _GooCanvasTableChildLayoutData
+{
+  gdouble requested_position[2];
+  gdouble requested_size[2];
+  gdouble start_pad[2], end_pad[2];
+};
+
+/* Convenience macros to set/unset/check bit-flags. */
+#define GOO_CANVAS_TABLE_SET_GRID_LINE_VISIBILITY(dim, x, y) \
+  ((dim)[(x)].grid_line_visibility[(y)/32] |= (1 << ((y) % 32)))
+
+#define GOO_CANVAS_TABLE_UNSET_GRID_LINE_VISIBILITY(dim, x, y) \
+  ((dim)[(x)].grid_line_visibility[(y)/32] &= ~(1 << ((y) % 32)))
+
+#define GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(dim, x, y) \
+  ((dim)[(x)].grid_line_visibility[(y)/32] & (1 << ((y) % 32)))
+
+/* The children array is only kept around while doing the layout.
+   It gets freed in goo_canvas_table_allocate_area(). */
+struct _GooCanvasTableLayoutData
+{
+  GooCanvasTableDimensionLayoutData *dldata[2];
+  GooCanvasTableChildLayoutData *children;
+
+  /* Position of the table */
+  gdouble x;
+  gdouble y;
+
+  /* This is TRUE if we are rounding everything to the nearest integer. */
+  gboolean integer_layout;
+
+  /* This is the border width used, possibly rounded to an integer. */
+  gdouble border_width;
+
+  /* The actual property value */
+  gdouble prop_grid_line_width[2];
+
+  /* The grid line width in both directions (rounded for integer layout). */
+  gdouble grid_line_width[2];
+
+  /* This is the actual spacing between the uppermost, bottommost, leftmost
+     and rightmost cells from the widget border. This is the border spacing
+     for that dimension. */
+  gdouble border_spacing[2];
+
+  /* These are in the table's coordinate space. */
+  gdouble natural_size[2];
+  gdouble requested_size[2];
+  gdouble allocated_size[2];
+
+  /* This is the last width for which we have calculated the requested heights.
+     It is initialized to -1 in goo_canvas_table_init_layout_data() and
+     checked/set in goo_canvas_table_update_requested_heights(). */
+  gdouble last_width;
+};
+
+static GooCanvasItemIface *goo_canvas_table_parent_iface;
+
+static void item_interface_init           (GooCanvasItemIface *iface);
+static void goo_canvas_table_finalize     (GObject            *object);
+static void goo_canvas_table_get_property (GObject            *object,
+					   guint               param_id,
+					   GValue             *value,
+					   GParamSpec         *pspec);
+static void goo_canvas_table_set_property (GObject            *object,
+					   guint               param_id,
+					   const GValue       *value,
+					   GParamSpec         *pspec);
+
+static void goo_canvas_table_init_layout_data (GooCanvasTable *table);
+static void goo_canvas_table_free_layout_data (GooCanvasTableData *table_data);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasTable, goo_canvas_table,
+			 GOO_TYPE_CANVAS_GROUP,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						item_interface_init))
+
+typedef void (*InstallChildPropertyFunc) (GObjectClass*, guint, GParamSpec*);
+
+static void
+goo_canvas_table_install_common_properties (GObjectClass *gobject_class,
+					    InstallChildPropertyFunc install_child_property)
+{
+  /* Override from GooCanvasGroup */
+  g_object_class_override_property (gobject_class, PROP_X, "x");
+  g_object_class_override_property (gobject_class, PROP_Y, "y");
+  g_object_class_override_property (gobject_class, PROP_WIDTH, "width");
+  g_object_class_override_property (gobject_class, PROP_HEIGHT, "height");
+
+  /* FIXME: Support setting individual row/col spacing. */
+  g_object_class_install_property (gobject_class, PROP_ROW_SPACING,
+                                   g_param_spec_double ("row-spacing",
+							_("Row Spacing"),
+							_("The default space between rows"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_COLUMN_SPACING,
+                                   g_param_spec_double ("column-spacing",
+							_("Column Spacing"),
+							_("The default space between columns"),
+							0.0, G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HOMOGENEOUS_ROWS,
+                                   g_param_spec_boolean ("homogeneous-rows",
+							 _("Homogenous Rows"),
+							 _("If all rows are the same height"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HOMOGENEOUS_COLUMNS,
+                                   g_param_spec_boolean ("homogeneous-columns",
+							 _("Homogenous Columns"),
+							 _("If all columns are the same width"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_X_BORDER_SPACING,
+                                   g_param_spec_double("x-border-spacing",
+                                                       _("X Border Spacing"),
+                                                       _("The amount of spacing between the lefmost and rightmost cells and the border grid line"),
+                                                       0.0, G_MAXDOUBLE, 0.0,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_Y_BORDER_SPACING,
+                                   g_param_spec_double("y-border-spacing",
+                                                       _("Y Border Spacing"),
+                                                       _("The amount of spacing between the topmost and bottommost cells and the border grid line"),
+                                                       0.0, G_MAXDOUBLE, 0.0,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_HORZ_GRID_LINE_WIDTH,
+                                   g_param_spec_double("horz-grid-line-width",
+                                                        _("Horizontal Grid Line Width"),
+                                                        _("The width of the grid line to draw between rows"),
+                                                        0.0, G_MAXDOUBLE, 0.0,
+                                                        G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_VERT_GRID_LINE_WIDTH,
+                                   g_param_spec_double("vert-grid-line-width",
+                                                       _("Vertical Grid Line Width"),
+                                                       _("The width of the grid line to draw between columns"),
+                                                       0.0, G_MAXDOUBLE, 0.0,
+                                                       G_PARAM_READWRITE));
+
+  /*
+   * Child properties.
+   */
+  install_child_property (gobject_class, CHILD_PROP_LEFT_PADDING,
+			  g_param_spec_double ("left-padding", 
+					       _("Left Padding"), 
+					       _("Extra space to add to the left of the item"),
+					       0.0, G_MAXDOUBLE, 0.0,
+					       G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_RIGHT_PADDING,
+			  g_param_spec_double ("right-padding", 
+					       _("Right Padding"), 
+					       _("Extra space to add to the right of the item"),
+					       0.0, G_MAXDOUBLE, 0.0,
+					       G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_TOP_PADDING,
+			  g_param_spec_double ("top-padding", 
+					       _("Top Padding"), 
+					       _("Extra space to add above the item"),
+					       0.0, G_MAXDOUBLE, 0.0,
+					       G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_BOTTOM_PADDING,
+			  g_param_spec_double ("bottom-padding", 
+					       _("Bottom Padding"), 
+					       _("Extra space to add below the item"),
+					       0.0, G_MAXDOUBLE, 0.0,
+					       G_PARAM_READWRITE));
+
+  install_child_property (gobject_class, CHILD_PROP_X_ALIGN,
+			  g_param_spec_double ("x-align", 
+					       _("X Align"), 
+					       _("The horizontal position of the item within its allocated space. 0.0 is left-aligned, 1.0 is right-aligned"),
+					       0.0, 1.0, 0.5,
+					       G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_Y_ALIGN,
+			  g_param_spec_double ("y-align", 
+					       _("Y Align"), 
+					       _("The vertical position of the item within its allocated space. 0.0 is top-aligned, 1.0 is bottom-aligned"),
+					       0.0, 1.0, 0.5,
+					       G_PARAM_READWRITE));
+
+  install_child_property (gobject_class, CHILD_PROP_ROW,
+			  g_param_spec_uint ("row", 
+					     _("Row"), 
+					     _("The row to place the item in"),
+					     0, 65535, 0,
+					     G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_COLUMN,
+			  g_param_spec_uint ("column", 
+					     _("Column"), 
+					     _("The column to place the item in"),
+					     0, 65535, 0,
+					     G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_ROWS,
+			  g_param_spec_uint ("rows", 
+					     _("Rows"), 
+					     _("The number of rows that the item spans"),
+					     0, 65535, 1,
+					     G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_COLUMNS,
+			  g_param_spec_uint ("columns", 
+					     _("Columns"), 
+					     _("The number of columns that the item spans"),
+					     0, 65535, 1,
+					     G_PARAM_READWRITE));
+
+  install_child_property (gobject_class, CHILD_PROP_X_EXPAND,
+			  g_param_spec_boolean ("x-expand", 
+						_("X Expand"), 
+						_("If the item expands horizontally as the table expands"),
+						FALSE,
+						G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_X_FILL,
+			  g_param_spec_boolean ("x-fill", 
+						_("X Fill"), 
+						_("If the item fills all horizontal allocated space"),
+						FALSE,
+						G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_X_SHRINK,
+			  g_param_spec_boolean ("x-shrink", 
+						_("X Shrink"), 
+						_("If the item can shrink smaller than its requested size horizontally"),
+						FALSE,
+						G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_Y_EXPAND,
+			  g_param_spec_boolean ("y-expand", 
+						_("Y Expand"), 
+						_("If the item expands vertically as the table expands"),
+						FALSE,
+						G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_Y_FILL,
+			  g_param_spec_boolean ("y-fill", 
+						_("Y Fill"), 
+						_("If the item fills all vertical allocated space"),
+						FALSE,
+						G_PARAM_READWRITE));
+  install_child_property (gobject_class, CHILD_PROP_Y_SHRINK,
+			  g_param_spec_boolean ("y-shrink", 
+						_("Y Shrink"), 
+						_("If the item can shrink smaller than its requested size vertically"),
+						FALSE,
+						G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_table_class_init (GooCanvasTableClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  goo_canvas_table_parent_iface = g_type_interface_peek (goo_canvas_table_parent_class, GOO_TYPE_CANVAS_ITEM);
+
+  gobject_class->finalize = goo_canvas_table_finalize;
+  gobject_class->get_property = goo_canvas_table_get_property;
+  gobject_class->set_property = goo_canvas_table_set_property;
+
+  goo_canvas_table_install_common_properties (gobject_class, goo_canvas_item_class_install_child_property);
+}
+
+
+/* This initializes the common table data. */
+static void
+goo_canvas_table_init_data (GooCanvasTableData *table_data)
+{
+  gint d;
+
+  table_data->width = -1.0;
+  table_data->height = -1.0;
+
+  for (d = 0; d < 2; d++)
+    {
+      table_data->dimensions[d].size = 0;
+      table_data->dimensions[d].default_spacing = 0.0;
+      table_data->dimensions[d].spacings = NULL;
+      table_data->dimensions[d].homogeneous = FALSE;
+    }
+
+  table_data->border_width = 0.0;
+
+  table_data->children = g_array_new (0, 0, sizeof (GooCanvasTableChild));
+
+  table_data->layout_data = g_slice_new (GooCanvasTableLayoutData);
+  table_data->layout_data->x = 0.0;
+  table_data->layout_data->y = 0.0;
+
+  table_data->layout_data->children = NULL;
+  for (d = 0; d < 2; d++)
+    {
+      table_data->layout_data->dldata[d] = NULL;
+      table_data->layout_data->prop_grid_line_width[d] = 0.0;
+      table_data->layout_data->grid_line_width[d] = 0.0;
+      table_data->layout_data->border_spacing[d] = 0.0;
+    }
+}
+
+
+/* This frees the contents of the table data, but not the struct itself. */
+static void
+goo_canvas_table_free_data (GooCanvasTableData *table_data)
+{
+  gint d;
+
+  g_array_free (table_data->children, TRUE);
+
+  for (d = 0; d < 2; d++)
+    {
+      g_free (table_data->dimensions[d].spacings);
+      table_data->dimensions[d].spacings = NULL;
+    }
+
+  goo_canvas_table_free_layout_data (table_data);
+}
+
+
+static void
+goo_canvas_table_init (GooCanvasTable *table)
+{
+  table->table_data = g_slice_new0 (GooCanvasTableData);
+  goo_canvas_table_init_data (table->table_data);
+}
+
+
+/**
+ * goo_canvas_table_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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new table item.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a table with a square, a circle and
+ * a triangle in it:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *table, *square, *circle, *triangle;
+ *  
+ *  table = goo_canvas_table_new (root,
+ *                                "row-spacing", 4.0,
+ *                                "column-spacing", 4.0,
+ *                                NULL);
+ *  goo_canvas_item_translate (table, 400, 200);
+ *  
+ *  square = goo_canvas_rect_new (table, 0.0, 0.0, 50.0, 50.0,
+ *                                "fill-color", "red",
+ *                                NULL);
+ *  goo_canvas_item_set_child_properties (table, square,
+ *                                        "row", 0,
+ *                                        "column", 0,
+ *                                        NULL);
+ *  
+ *  circle = goo_canvas_ellipse_new (table, 0.0, 0.0, 25.0, 25.0,
+ *                                   "fill-color", "blue",
+ *                                   NULL);
+ *  goo_canvas_item_set_child_properties (table, circle,
+ *                                        "row", 0,
+ *                                        "column", 1,
+ *                                        NULL);
+ *  
+ *  triangle = goo_canvas_polyline_new (table, TRUE, 3,
+ *                                      25.0, 0.0, 0.0, 50.0, 50.0, 50.0,
+ *                                      "fill-color", "yellow",
+ *                                      NULL);
+ *  goo_canvas_item_set_child_properties (table, triangle,
+ *                                        "row", 0,
+ *                                        "column", 2,
+ *                                        NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new table item.
+ **/
+GooCanvasItem*
+goo_canvas_table_new (GooCanvasItem  *parent,
+		      ...)
+{
+  GooCanvasItem *item;
+  va_list var_args;
+  const char *first_property;
+
+  item = g_object_new (GOO_TYPE_CANVAS_TABLE, NULL);
+
+  va_start (var_args, parent);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist (G_OBJECT (item), first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_table_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasTable *table = (GooCanvasTable*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    {
+      goo_canvas_table_free_data (table->table_data);
+      g_slice_free (GooCanvasTableData, table->table_data);
+    }
+  table->table_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_table_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_table_get_common_property (GObject              *object,
+				      GooCanvasTableData   *table_data,
+				      guint                 prop_id,
+				      GValue               *value,
+				      GParamSpec           *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      g_value_set_double (value, table_data->layout_data->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, table_data->layout_data->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, table_data->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, table_data->height);
+      break;
+    case PROP_ROW_SPACING:
+      g_value_set_double (value, table_data->dimensions[VERT].default_spacing);
+      break;
+    case PROP_COLUMN_SPACING:
+      g_value_set_double (value, table_data->dimensions[HORZ].default_spacing);
+      break;
+    case PROP_HOMOGENEOUS_ROWS:
+      g_value_set_boolean (value, table_data->dimensions[VERT].homogeneous);
+      break;
+    case PROP_HOMOGENEOUS_COLUMNS:
+      g_value_set_boolean (value, table_data->dimensions[HORZ].homogeneous);
+      break;
+    case PROP_X_BORDER_SPACING:
+      g_value_set_double (value, table_data->layout_data->border_spacing[HORZ]);
+      break;
+    case PROP_Y_BORDER_SPACING:
+      g_value_set_double (value, table_data->layout_data->border_spacing[VERT]);
+      break;
+    case PROP_HORZ_GRID_LINE_WIDTH:
+      g_value_set_double (value, table_data->layout_data->prop_grid_line_width[HORZ]);
+      break;
+    case PROP_VERT_GRID_LINE_WIDTH:
+      g_value_set_double (value, table_data->layout_data->prop_grid_line_width[VERT]);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_table_get_property (GObject              *object,
+			       guint                 prop_id,
+			       GValue               *value,
+			       GParamSpec           *pspec)
+{
+  GooCanvasTable *table = (GooCanvasTable*) object;
+
+  goo_canvas_table_get_common_property (object, table->table_data,
+                                        prop_id, value, pspec);
+}
+
+
+static gboolean
+goo_canvas_table_set_common_property (GObject              *object,
+				      GooCanvasTableData   *table_data,
+				      guint                 prop_id,
+				      const GValue         *value,
+				      GParamSpec           *pspec)
+{
+  gboolean recompute_bounds = TRUE;
+
+  switch (prop_id)
+    {
+    case PROP_X:
+      table_data->layout_data->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      table_data->layout_data->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      table_data->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      table_data->height = g_value_get_double (value);
+      break;
+    case PROP_ROW_SPACING:
+      table_data->dimensions[VERT].default_spacing = g_value_get_double (value);
+      break;
+    case PROP_COLUMN_SPACING:
+      table_data->dimensions[HORZ].default_spacing = g_value_get_double (value);
+      break;
+    case PROP_HOMOGENEOUS_ROWS:
+      table_data->dimensions[VERT].homogeneous = g_value_get_boolean (value);
+      break;
+    case PROP_HOMOGENEOUS_COLUMNS:
+      table_data->dimensions[HORZ].homogeneous = g_value_get_boolean (value);
+      break;
+    case PROP_X_BORDER_SPACING:
+      table_data->layout_data->border_spacing[HORZ] = g_value_get_double (value);
+      break;
+    case PROP_Y_BORDER_SPACING:
+      table_data->layout_data->border_spacing[VERT] = g_value_get_double (value);
+      break;
+    case PROP_HORZ_GRID_LINE_WIDTH:
+      table_data->layout_data->prop_grid_line_width[HORZ] = g_value_get_double (value);
+      break;
+    case PROP_VERT_GRID_LINE_WIDTH:
+      table_data->layout_data->prop_grid_line_width[VERT] = g_value_get_double (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+
+  return recompute_bounds;
+}
+
+
+static void
+goo_canvas_table_set_property (GObject              *object,
+			       guint                 prop_id,
+			       const GValue         *value,
+			       GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasTable *table = (GooCanvasTable*) object;
+  gboolean recompute_bounds;
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  recompute_bounds = goo_canvas_table_set_common_property (object,
+							   table->table_data,
+							   prop_id, value,
+							   pspec);
+  goo_canvas_item_simple_changed (simple, recompute_bounds);
+}
+
+
+static void
+goo_canvas_table_update_dimensions (GooCanvasTableData    *table_data,
+				    GooCanvasTableChild   *table_child)
+{
+  GooCanvasTableLayoutData *layout_data;
+  gint d, size[2], i;
+  layout_data = table_data->layout_data;
+  size[0] = table_child->start[0] + table_child->size[0];
+  size[1] = table_child->start[1] + table_child->size[1];
+
+  for (d = 0; d < 2; d++)
+    {
+      if (size[d] > table_data->dimensions[d].size)
+        {
+	  /* Resize the spacings array and the layout data array. */
+          table_data->dimensions[d].spacings = g_realloc (table_data->dimensions[d].spacings, size[d] * sizeof (gdouble));
+          layout_data->dldata[d] = g_renew (GooCanvasTableDimensionLayoutData, layout_data->dldata[d], size[d]);
+
+          /* Initialize new spacings to -1.0 so the default is used, and
+	     set the new grid_line_visibility arrays to NULL. */
+          for (i = table_data->dimensions[d].size; i < size[d]; i++)
+	    {
+	      table_data->dimensions[d].spacings[i] = -1.0;
+	      layout_data->dldata[d][i].grid_line_visibility = NULL;
+	    }
+        }
+    }
+
+  table_data->dimensions[0].size = MAX (size[0], table_data->dimensions[0].size);
+  table_data->dimensions[1].size = MAX (size[1], table_data->dimensions[1].size);
+}
+
+/* Sets or unsets grid line visibility for a given child */
+static void
+goo_canvas_update_grid_line_visibility (GooCanvasTableData *table_data)
+{
+  GooCanvasTableLayoutData *layout_data;
+  GooCanvasTableChild *table_child;
+  guint32 grid_line_size;
+  gint d, d2, child_num, i, j;
+
+  layout_data = table_data->layout_data;
+  for (d = 0; d < 2; d++)
+    {
+      /* This is the opposite dimension. */
+      d2 = 1 - d;
+
+      /* The amount of guint32s we need to store grid line visibility
+         for a particular row or column. In case of vertical grid lines
+         we store the grid lines (right to) a particular column for each row.
+         Therefore we need one bit for each row. This is also why this depends
+         on dimensions[1-d] instead of dimensions[d]. */
+      grid_line_size = ((table_data->dimensions[d2].size + 31) / 32);
+
+      /* Allocate or reallocate the grid_line_visibility arrays and initialize
+	 all grid lines to visible (by setting to the arrays to all 0xFF). */
+      for (i = 0; i + 1 < table_data->dimensions[d].size; i++)
+	{
+	  layout_data->dldata[d][i].grid_line_visibility
+	    = g_realloc (layout_data->dldata[d][i].grid_line_visibility,
+			 grid_line_size * sizeof (guint32));
+	  memset (layout_data->dldata[d][i].grid_line_visibility, 0xff,
+		  grid_line_size * sizeof (guint32));
+	}
+
+      /* Remove lines for each child spanning multiple rows/colmuns */
+      for (child_num = 0; child_num < table_data->children->len; child_num++)
+        {
+          table_child = &g_array_index (table_data->children,
+					GooCanvasTableChild, child_num);
+
+          /* Foreach cell the child is spanning */
+          for (i = table_child->start[d];
+	       i < table_child->start[d] + table_child->size[d] - 1;
+	       i++)
+            {
+              /* Iterate through occupied cells in the other dimension */
+              for (j = table_child->start[d2];
+		   j < table_child->start[d2] + table_child->size[d2];
+		   j++)
+                {
+                  GOO_CANVAS_TABLE_UNSET_GRID_LINE_VISIBILITY (layout_data->dldata[d], i, j);
+                }
+            }
+        }
+    }
+}
+
+static void
+goo_canvas_table_add_child_internal (GooCanvasTableData *table_data,
+				     gint                position)
+{
+  GooCanvasTableChild table_child;
+  gint d;
+
+  /* Initialize a table child struct. */
+  for (d = 0; d < 2; d++)
+    {
+      table_child.start_pad[d] = 0.0;
+      table_child.end_pad[d] = 0.0;
+      table_child.align[d] = 0.5;
+      table_child.start[d] = 0;
+      table_child.size[d] = 1;
+
+      /* Unlike GtkTable our children do not expand & fill by default,
+	 as that makes more sense with the builtin simple items. */
+      table_child.flags[d] = 0;
+    }
+
+  if (position < 0)
+    position = table_data->children->len;
+  g_array_insert_val (table_data->children, position, table_child);
+
+  goo_canvas_table_update_dimensions (table_data, &table_child);
+}
+
+
+static void
+goo_canvas_table_add_child     (GooCanvasItem  *item,
+				GooCanvasItem  *child,
+				gint            position)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+
+  if (!simple->model)
+    goo_canvas_table_add_child_internal (table->table_data, position);
+
+  /* Let the parent GooCanvasGroup code do the rest. */
+  goo_canvas_table_parent_iface->add_child (item, child, position);
+}
+
+
+static void
+goo_canvas_table_move_child_internal    (GooCanvasTableData *table_data,
+					 gint	             old_position,
+					 gint                new_position)
+{
+  GooCanvasTableChild *table_child, tmp_child;
+
+  /* Copy the child temporarily. */
+  table_child = &g_array_index (table_data->children, GooCanvasTableChild,
+				old_position);
+  tmp_child = *table_child;
+
+  /* Move the array data up or down. */
+  if (new_position > old_position)
+    {
+      /* Move the items down one place. */
+      g_memmove (table_child,
+		 &g_array_index (table_data->children, GooCanvasTableChild,
+				 old_position + 1),
+		 sizeof (GooCanvasTableChild) * (new_position - old_position));
+    }
+  else
+    {
+      /* Move the items up one place. */
+      g_memmove (&g_array_index (table_data->children, GooCanvasTableChild,
+				 new_position + 1),
+		 &g_array_index (table_data->children, GooCanvasTableChild,
+				 new_position),
+		 sizeof (GooCanvasTableChild) * (old_position - new_position));
+    }
+
+  /* Copy the child into its new position. */
+  table_child = &g_array_index (table_data->children, GooCanvasTableChild,
+				new_position);
+  *table_child = tmp_child;
+}
+
+
+static void
+goo_canvas_table_move_child    (GooCanvasItem  *item,
+				gint	        old_position,
+				gint            new_position)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+
+  if (!simple->model)
+    goo_canvas_table_move_child_internal (table->table_data, old_position,
+					  new_position);
+
+  /* Let the parent GooCanvasGroup code do the rest. */
+  goo_canvas_table_parent_iface->move_child (item, old_position, new_position);
+}
+
+
+static void
+goo_canvas_table_remove_child  (GooCanvasItem  *item,
+				gint            child_num)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+
+  g_return_if_fail (child_num < group->items->len);
+
+  if (!simple->model)
+    g_array_remove_index (table->table_data->children, child_num);
+
+  /* Let the parent GooCanvasGroup code do the rest. */
+  goo_canvas_table_parent_iface->remove_child (item, child_num);
+}
+
+
+static void
+goo_canvas_table_get_common_child_property (GObject             *object,
+					    GooCanvasTableChild *table_child,
+					    guint                property_id,
+					    GValue              *value,
+					    GParamSpec          *pspec)
+{
+  switch (property_id)
+    {
+    case CHILD_PROP_LEFT_PADDING:
+      g_value_set_double (value, table_child->start_pad[HORZ]);
+      break;
+    case CHILD_PROP_RIGHT_PADDING:
+      g_value_set_double (value, table_child->end_pad[HORZ]);
+      break;
+    case CHILD_PROP_TOP_PADDING:
+      g_value_set_double (value, table_child->start_pad[VERT]);
+      break;
+    case CHILD_PROP_BOTTOM_PADDING:
+      g_value_set_double (value, table_child->end_pad[VERT]);
+      break;
+    case CHILD_PROP_X_ALIGN:
+      g_value_set_double (value, table_child->align[HORZ]);
+      break;
+    case CHILD_PROP_Y_ALIGN:
+      g_value_set_double (value, table_child->align[VERT]);
+      break;
+    case CHILD_PROP_ROW:
+      g_value_set_uint (value, table_child->start[VERT]);
+      break;
+    case CHILD_PROP_COLUMN:
+      g_value_set_uint (value, table_child->start[HORZ]);
+      break;
+    case CHILD_PROP_ROWS:
+      g_value_set_uint (value, table_child->size[VERT]);
+      break;
+    case CHILD_PROP_COLUMNS:
+      g_value_set_uint (value, table_child->size[HORZ]);
+      break;
+    case CHILD_PROP_X_EXPAND:
+      g_value_set_boolean (value, table_child->flags[HORZ] & GOO_CANVAS_TABLE_CHILD_EXPAND);
+      break;
+    case CHILD_PROP_X_FILL:
+      g_value_set_boolean (value, table_child->flags[HORZ] & GOO_CANVAS_TABLE_CHILD_FILL);
+      break;
+    case CHILD_PROP_X_SHRINK:
+      g_value_set_boolean (value, table_child->flags[HORZ] & GOO_CANVAS_TABLE_CHILD_SHRINK);
+      break;
+    case CHILD_PROP_Y_EXPAND:
+      g_value_set_boolean (value, table_child->flags[VERT] & GOO_CANVAS_TABLE_CHILD_EXPAND);
+      break;
+    case CHILD_PROP_Y_FILL:
+      g_value_set_boolean (value, table_child->flags[VERT] & GOO_CANVAS_TABLE_CHILD_FILL);
+      break;
+    case CHILD_PROP_Y_SHRINK:
+      g_value_set_boolean (value, table_child->flags[VERT] & GOO_CANVAS_TABLE_CHILD_SHRINK);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PSPEC ((object), "child property id",
+				   (property_id), (pspec));
+      break;
+    }
+}
+
+
+static void
+goo_canvas_table_get_child_property (GooCanvasItem     *item,
+				     GooCanvasItem     *child,
+				     guint              property_id,
+				     GValue            *value,
+				     GParamSpec        *pspec)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableChild *table_child;
+  gint child_num;
+
+  for (child_num = 0; child_num < group->items->len; child_num++)
+    {
+      if (group->items->pdata[child_num] == child)
+	{
+	  table_child = &g_array_index (table->table_data->children,
+					GooCanvasTableChild, child_num);
+	  goo_canvas_table_get_common_child_property ((GObject*) table,
+						      table_child,
+						      property_id, value,
+						      pspec);
+	  break;
+	}
+    }
+}
+
+
+static void
+goo_canvas_table_set_common_child_property (GObject             *object,
+					    GooCanvasTableData  *table_data,
+					    GooCanvasTableChild *table_child,
+					    guint                property_id,
+					    const GValue        *value,
+					    GParamSpec          *pspec)
+{
+  switch (property_id)
+    {
+    case CHILD_PROP_LEFT_PADDING:
+      table_child->start_pad[HORZ] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_RIGHT_PADDING:
+      table_child->end_pad[HORZ] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_TOP_PADDING:
+      table_child->start_pad[VERT] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_BOTTOM_PADDING:
+      table_child->end_pad[VERT] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_X_ALIGN:
+      table_child->align[HORZ] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_Y_ALIGN:
+      table_child->align[VERT] = g_value_get_double (value);
+      break;
+    case CHILD_PROP_ROW:
+      table_child->start[VERT] = g_value_get_uint (value);
+      break;
+    case CHILD_PROP_COLUMN:
+      table_child->start[HORZ] = g_value_get_uint (value);
+      break;
+    case CHILD_PROP_ROWS:
+      table_child->size[VERT] = g_value_get_uint (value);
+      break;
+    case CHILD_PROP_COLUMNS:
+      table_child->size[HORZ] = g_value_get_uint (value);
+      break;
+
+    case CHILD_PROP_X_EXPAND:
+      if (g_value_get_boolean (value))
+	table_child->flags[HORZ] |= GOO_CANVAS_TABLE_CHILD_EXPAND;
+      else
+	table_child->flags[HORZ] &= ~GOO_CANVAS_TABLE_CHILD_EXPAND;
+      break;
+    case CHILD_PROP_X_FILL:
+      if (g_value_get_boolean (value))
+	table_child->flags[HORZ] |= GOO_CANVAS_TABLE_CHILD_FILL;
+      else
+	table_child->flags[HORZ] &= ~GOO_CANVAS_TABLE_CHILD_FILL;
+      break;
+    case CHILD_PROP_X_SHRINK:
+      if (g_value_get_boolean (value))
+	table_child->flags[HORZ] |= GOO_CANVAS_TABLE_CHILD_SHRINK;
+      else
+	table_child->flags[HORZ] &= ~GOO_CANVAS_TABLE_CHILD_SHRINK;
+      break;
+
+    case CHILD_PROP_Y_EXPAND:
+      if (g_value_get_boolean (value))
+	table_child->flags[VERT] |= GOO_CANVAS_TABLE_CHILD_EXPAND;
+      else
+	table_child->flags[VERT] &= ~GOO_CANVAS_TABLE_CHILD_EXPAND;
+      break;
+    case CHILD_PROP_Y_FILL:
+      if (g_value_get_boolean (value))
+	table_child->flags[VERT] |= GOO_CANVAS_TABLE_CHILD_FILL;
+      else
+	table_child->flags[VERT] &= ~GOO_CANVAS_TABLE_CHILD_FILL;
+      break;
+    case CHILD_PROP_Y_SHRINK:
+      if (g_value_get_boolean (value))
+	table_child->flags[VERT] |= GOO_CANVAS_TABLE_CHILD_SHRINK;
+      else
+	table_child->flags[VERT] &= ~GOO_CANVAS_TABLE_CHILD_SHRINK;
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PSPEC ((object), "child property id",
+				   (property_id), (pspec));
+      break;
+    }
+
+  goo_canvas_table_update_dimensions (table_data, table_child);
+}
+
+
+static void
+goo_canvas_table_set_child_property (GooCanvasItem     *item,
+				     GooCanvasItem     *child,
+				     guint              property_id,
+				     const GValue      *value,
+				     GParamSpec        *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableChild *table_child;
+  gint child_num;
+
+  for (child_num = 0; child_num < group->items->len; child_num++)
+    {
+      if (group->items->pdata[child_num] == child)
+	{
+	  table_child = &g_array_index (table->table_data->children,
+					GooCanvasTableChild, child_num);
+	  goo_canvas_table_set_common_child_property ((GObject*) table,
+						      table->table_data,
+						      table_child,
+						      property_id, value,
+						      pspec);
+	  break;
+	}
+    }
+
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_table_set_model    (GooCanvasItem      *item,
+			       GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+
+  /* If our table_data was allocated, free it. */
+  if (!simple->model)
+    {
+      goo_canvas_table_free_data (table->table_data);
+      g_slice_free (GooCanvasTableData, table->table_data);
+    }
+
+  /* Now use the new model's table_data instead. */
+  table->table_data = &tmodel->table_data;
+
+  /* Let the parent GooCanvasGroup code do the rest. */
+  goo_canvas_table_parent_iface->set_model (item, model);
+}
+
+
+/*
+ * Size requisition/allocation code.
+ */
+
+static void
+goo_canvas_table_free_layout_data (GooCanvasTableData *table_data)
+{
+  gint i;
+
+  if (table_data->layout_data)
+    {
+      for (i = 0; i < table_data->dimensions[VERT].size; i++)
+        g_free (table_data->layout_data->dldata[VERT][i].grid_line_visibility);
+      for (i = 0; i < table_data->dimensions[HORZ].size; i++)
+        g_free (table_data->layout_data->dldata[HORZ][i].grid_line_visibility);
+      g_free (table_data->layout_data->dldata[HORZ]);
+      g_free (table_data->layout_data->dldata[VERT]);
+      g_free (table_data->layout_data->children);
+      g_slice_free (GooCanvasTableLayoutData, table_data->layout_data);
+      table_data->layout_data = NULL;
+    }
+}
+
+
+/* This allocates the layout data, and initializes the row and column data. */
+static void
+goo_canvas_table_init_layout_data (GooCanvasTable *table)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) table;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableDimension *dimension;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *dldata;
+  gint d, i;
+
+  layout_data->children = g_new (GooCanvasTableChildLayoutData,
+				 table_data->children->len);
+  layout_data->last_width = -1;
+
+  /* If we are not yet added to a canvas, integer layout is irrelevant anyway.
+     We still need the layout_data for private fields, such as border spacing
+     and grid line visibility. */
+  if (simple->canvas)
+    layout_data->integer_layout = simple->canvas->integer_layout;
+  else
+    layout_data->integer_layout = FALSE;
+  layout_data->border_width = table_data->border_width;
+  if (layout_data->integer_layout)
+    layout_data->border_width = floor (layout_data->border_width + 0.5);
+
+  layout_data->grid_line_width[0] = layout_data->prop_grid_line_width[0];
+  layout_data->grid_line_width[1] = layout_data->prop_grid_line_width[1];
+  if (layout_data->integer_layout)
+    {
+      layout_data->grid_line_width[0] = floor (layout_data->grid_line_width[0] + 0.5);
+      layout_data->grid_line_width[1] = floor (layout_data->grid_line_width[1] + 0.5);
+    }
+
+  for (d = 0; d < 2; d++)
+    {
+      dimension = &table_data->dimensions[d];
+
+      /* Already allocated in goo_canvas_table_update_dimensions() */
+      /*layout_data->dldata[d] = g_renew (GooCanvasTableDimensionLayoutData,
+                                        layout_data->dldata[d],
+				        dimension->size);*/
+      dldata = layout_data->dldata[d];
+
+      for (i = 0; i < dimension->size; i++)
+	{
+	  /* Calculate the actual spacings. If -ve use the default. */
+	  if (dimension->spacings[i] > 0)
+	    dldata[i].spacing = dimension->spacings[i];
+	  else
+	    dldata[i].spacing = dimension->default_spacing;
+
+          /* Add grid line widths to spacing */
+          dldata[i].spacing += layout_data->prop_grid_line_width[1-d];
+
+	  /* In integer layout mode, round spacings to the nearest integer. */
+	  if (layout_data->integer_layout)
+	    dldata[i].spacing = floor (dldata[i].spacing + 0.5);
+
+	  dldata[i].need_expand = FALSE;
+	  dldata[i].need_shrink = TRUE;
+	  dldata[i].expand = FALSE;
+	  dldata[i].shrink = TRUE;
+	  dldata[i].empty = TRUE;
+	}
+    }
+
+  /* Update grid line visibility */
+  goo_canvas_update_grid_line_visibility (table_data);
+}
+
+
+/* This gets the requested size of all child items, and sets the expand,
+   shrink and empty flags for each row and column.
+   It should only be called once in the entire size_request/allocate procedure
+   and nothing should change the values it sets (except the requested height
+   of children may change after calling get_requested_height() on them). */
+static void
+goo_canvas_table_size_request_init (GooCanvasTable *table,
+				    cairo_t        *cr)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) table;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimension *dimension;
+  GooCanvasTableDimensionLayoutData *dldata;
+  GooCanvasTableChild *child;
+  GooCanvasItem *child_item;
+  GooCanvasBounds bounds;
+  gboolean allocate, has_expand, has_shrink;
+  gint i, j, d, start, end;
+  guint8 flags;
+
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+      child_item = group->items->pdata[i];
+
+      /* Children will return FALSE if they don't need space allocated. */
+      allocate = goo_canvas_item_get_requested_area (child_item, cr, &bounds);
+
+      /* Remember the requested position and size of the child. */
+      layout_data->children[i].requested_position[HORZ] = bounds.x1;
+      layout_data->children[i].requested_position[VERT] = bounds.y1;
+
+      if (!allocate)
+	{
+	  layout_data->children[i].requested_size[HORZ] = -1.0;
+	  layout_data->children[i].requested_size[VERT] = -1.0;
+	  continue;
+	}
+
+      layout_data->children[i].requested_size[HORZ] = bounds.x2 - bounds.x1;
+      layout_data->children[i].requested_size[VERT] = bounds.y2 - bounds.y1;
+
+      layout_data->children[i].start_pad[HORZ] = child->start_pad[HORZ];
+      layout_data->children[i].end_pad[HORZ] = child->end_pad[HORZ];
+      layout_data->children[i].start_pad[VERT] = child->start_pad[VERT];
+      layout_data->children[i].end_pad[VERT] = child->end_pad[VERT];
+
+      if (layout_data->integer_layout)
+	{
+	  layout_data->children[i].requested_size[HORZ] = ceil (layout_data->children[i].requested_size[HORZ]);
+	  layout_data->children[i].requested_size[VERT] = ceil (layout_data->children[i].requested_size[VERT]);
+
+	  layout_data->children[i].start_pad[HORZ] = floor (layout_data->children[i].start_pad[HORZ] + 0.5);
+	  layout_data->children[i].end_pad[HORZ] = floor (layout_data->children[i].end_pad[HORZ] + 0.5);
+	  layout_data->children[i].start_pad[VERT] = floor (layout_data->children[i].start_pad[VERT] + 0.5);
+	  layout_data->children[i].end_pad[VERT] = floor (layout_data->children[i].end_pad[VERT] + 0.5);
+	}
+
+      /* Set the expand, shrink & empty flags in the
+	 GooCanvasTableDimensionLayoutData for the row/column, if the item
+	 only spans 1 row/column. */
+      for (d = 0; d < 2; d++)
+	{
+	  dldata = layout_data->dldata[d];
+	  start = child->start[d];
+	  flags = child->flags[d];
+
+	  if (child->size[d] == 1)
+	    {
+	      if (flags & GOO_CANVAS_TABLE_CHILD_EXPAND)
+		dldata[start].expand = TRUE;
+	      if (!(flags & GOO_CANVAS_TABLE_CHILD_SHRINK))
+		dldata[start].shrink = FALSE;
+	      dldata[start].empty = FALSE;
+	    }
+	}
+    }
+
+  /* Now handle children that span more than one row or column. */
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+
+      if (layout_data->children[i].requested_size[HORZ] < 0.0)
+	continue;
+
+      for (d = 0; d < 2; d++)
+	{
+	  dldata = layout_data->dldata[d];
+	  start = child->start[d];
+	  end = start + child->size[d] - 1;
+	  flags = child->flags[d];
+
+	  if (child->size[d] != 1)
+	    {
+	      has_expand = FALSE;
+	      has_shrink = TRUE;
+
+	      for (j = start; j <= end; j++)
+		{
+		  dldata[j].empty = FALSE;
+		  if (dldata[j].expand)
+		    has_expand = TRUE;
+		  if (!dldata[j].shrink)
+		    has_shrink = FALSE;
+		}
+
+	      /* If the child expands, but none of the rows/columns already
+		 has expand set, we set need_expand for all of them. */
+	      if (!has_expand && (flags & GOO_CANVAS_TABLE_CHILD_EXPAND))
+		{
+		  for (j = start; j <= end; j++)
+		    dldata[j].need_expand = TRUE;
+		}
+
+	      /* If the child doesn't shrink, but all of the rows/columns have
+		 shrink set, we set need_shrink to FALSE for all of them. */
+	      if (has_shrink && !(flags & GOO_CANVAS_TABLE_CHILD_SHRINK))
+		{
+		  for (j = start; j <= end; j++)
+		    dldata[j].need_shrink = FALSE;
+		}
+	    }
+	}
+    }
+
+  /* Now set the final expand, shrink & empty flags. They shouldn't be
+     changed after this point. */
+  for (d = 0; d < 2; d++)
+    {
+      dimension = &table_data->dimensions[d];
+      dldata = layout_data->dldata[d];
+
+      for (i = 0; i < dimension->size; i++)
+	{
+	  if (dldata[i].empty)
+	    {
+	      dldata[i].expand = FALSE;
+	      dldata[i].shrink = FALSE;
+	    }
+	  else
+	    {
+	      if (dldata[i].need_expand)
+		dldata[i].expand = TRUE;
+	      if (!dldata[i].need_shrink)
+		dldata[i].shrink = FALSE;
+	    }
+	}
+    }
+}
+
+
+/* This calculates the initial size request for each row & column, which is
+   the maximum size of all items that are in that row or column. (It skips
+   items that span more than one row or column.) */
+static void
+goo_canvas_table_size_request_pass1 (GooCanvasTable *table,
+				     gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableDimension *dimension = &table_data->dimensions[d];
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *dldata = layout_data->dldata[d];
+  GooCanvasTableChild *child;
+  gdouble requested_size;
+  gint i, start;
+
+  for (i = 0; i < dimension->size; i++)
+    dldata[i].requisition = 0.0;
+
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+
+      requested_size = layout_data->children[i].requested_size[d];
+
+      /* Child needs allocation & spans a single row or column. */
+      if (requested_size >= 0.0 && child->size[d] == 1)
+	{
+	  requested_size += layout_data->children[i].start_pad[d]
+	    + layout_data->children[i].end_pad[d];
+	  start = child->start[d];
+	  dldata[start].requisition = MAX (dldata[start].requisition,
+					   requested_size);
+	}
+    }
+}
+
+
+/* This ensures that homogeneous tables have all rows/columns the same size.
+   Note that it is called once after items in a single row/column have been
+   calculated, and then again after items spanning several rows/columns have
+   been calculated. */
+static void
+goo_canvas_table_size_request_pass2 (GooCanvasTable *table,
+				     gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *dldata = layout_data->dldata[d];
+  gdouble max_size = 0.0;
+  gint i;
+  
+  if (table_data->dimensions[d].homogeneous)
+    {
+      /* Calculate the maximum row or column size. */
+      for (i = 0; i < table_data->dimensions[d].size; i++)
+	max_size = MAX (max_size, dldata[i].requisition);
+
+      /* Use the maximum size for all rows or columns. */
+      for (i = 0; i < table_data->dimensions[d].size; i++)
+	dldata[i].requisition = max_size;
+    }
+}
+
+
+/* This handles items that span more than one row or column, by making sure
+   there is enough space for them and expanding the row/column size request
+   if needed. */
+static void
+goo_canvas_table_size_request_pass3 (GooCanvasTable *table,
+				     gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *dldata;
+  GooCanvasTableChild *child;
+  gint i, j;
+  
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+      
+      if (layout_data->children[i].requested_size[HORZ] <= 0.0)
+	continue;
+
+      /* Child spans multiple rows/columns. */
+      if (child->size[d] != 1)
+	{
+	  gint start = child->start[d];
+	  gint end = start + child->size[d] - 1;
+	  gdouble total_space = 0.0, space_needed;
+
+	  dldata = layout_data->dldata[d];
+
+	  /* Check if there is already enough space for the child. */
+	  for (j = start; j <= end; j++)
+	    {
+	      total_space += dldata[j].requisition;
+	      if (j < end)
+		total_space += dldata[j].spacing;
+	    }
+	      
+	  /* If we need to request more space for this child to fill
+	     its requisition, then divide up the needed space amongst the
+	     columns it spans, favoring expandable columns if any. */
+	  space_needed = layout_data->children[i].requested_size[d]
+	    + layout_data->children[i].start_pad[d]
+	    + layout_data->children[i].end_pad[d];
+
+	  if (total_space < space_needed)
+	    {
+	      gint n_expand = 0;
+	      gboolean force_expand = FALSE;
+	      gdouble expand = space_needed - total_space;
+	      gdouble extra;
+
+	      for (j = start; j <= end; j++)
+		{
+		  if (dldata[j].expand)
+		    n_expand++;
+		}
+
+	      if (n_expand == 0)
+		{
+		  n_expand = child->size[d];
+		  force_expand = TRUE;
+		}
+		    
+	      if (layout_data->integer_layout)
+		{
+		  for (j = start; j <= end; j++)
+		    {
+		      if (force_expand || dldata[j].expand)
+			{
+			  extra = floor (expand / n_expand + 0.5);
+			  dldata[j].requisition += extra;
+			  expand -= extra;
+			  n_expand--;
+			}
+		    }
+		}
+	      else
+		{
+		  extra = expand / n_expand;
+		  for (j = start; j <= end; j++)
+		    {
+		      if (force_expand || dldata[j].expand)
+			dldata[j].requisition += extra;
+		    }
+		}
+	    }
+	}
+    }
+}
+
+
+static void
+goo_canvas_table_size_allocate_init (GooCanvasTable *table,
+				     gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimension *dimension = &table_data->dimensions[d];
+  GooCanvasTableDimensionLayoutData *dldata = layout_data->dldata[d];
+  gint i;
+  
+  /* Set the initial allocation, by copying over the requisition.
+     Also set the final expand & shrink flags. */
+  for (i = 0; i < dimension->size; i++)
+    dldata[i].allocation = dldata[i].requisition;
+}
+
+
+static void
+goo_canvas_table_size_allocate_pass1 (GooCanvasTable *table,
+				      gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimension *dimension;
+  GooCanvasTableDimensionLayoutData *dldata;
+  gdouble total_size, size_to_allocate, natural_size, extra, old_extra;
+  gint i, nexpand, nshrink;
+  
+  /* If we were allocated more space than we requested
+   *  then we have to expand any expandable rows and columns
+   *  to fill in the extra space.
+   */
+  dimension = &table_data->dimensions[d];
+  dldata = layout_data->dldata[d];
+
+  natural_size = 0;
+  nexpand = 0;
+  nshrink = 0;
+
+  for (i = 0; i < dimension->size; i++)
+    {
+      natural_size += dldata[i].requisition;
+      if (dldata[i].expand)
+	nexpand += 1;
+      if (dldata[i].shrink && dldata[i].allocation > 0.0)
+	nshrink += 1;
+    }
+  for (i = 0; i + 1 < dimension->size; i++)
+    natural_size += dldata[i].spacing;
+
+  /* Note: natural_size does not contain without border width and
+     border spacing here. */
+
+  /* total_size is the size available for allocating widgets. We always
+     allocate space for border_width, but for right/bottom border_spacing only
+     if all children can be allocated without being shrunk. */
+  if (layout_data->allocated_size[d] < layout_data->border_width * 2.0 + layout_data->border_spacing[d] + layout_data->grid_line_width[1-d])
+    total_size = 0;
+  else if (layout_data->allocated_size[d] < layout_data->border_width * 2.0 + layout_data->border_spacing[d] + layout_data->grid_line_width[1-d] + natural_size)
+    total_size = layout_data->allocated_size[d] - layout_data->border_width * 2.0 - layout_data->border_spacing[d] - layout_data->grid_line_width[1-d];
+  else if (layout_data->allocated_size[d] < layout_data->border_width * 2.0 + (layout_data->border_spacing[d] + layout_data->grid_line_width[1-d]) * 2.0 + natural_size)
+    total_size = natural_size;
+  else
+    total_size = layout_data->allocated_size[d] - layout_data->border_width * 2.0 - (layout_data->border_spacing[d] + layout_data->grid_line_width[1-d])* 2.0;
+
+  if (dimension->homogeneous)
+    {
+      /* If the table is homogeneous in this dimension we check if any of
+	 the children expand, or if there are no children, or if we were
+	 allocated less space than we requested and any children shrink.
+	 If so, we divide up all the allocated space. */
+      if (nexpand || table_data->children->len == 0
+	  || (natural_size > total_size && nshrink))
+	{
+	  size_to_allocate = total_size;
+	  for (i = 0; i + 1 < dimension->size; i++)
+	    size_to_allocate -= dldata[i].spacing;
+	  
+	  if (layout_data->integer_layout)
+	    {
+	      gint n_elements = dimension->size;
+	      for (i = 0; i < dimension->size; i++)
+		{
+		  dldata[i].allocation = floor (size_to_allocate / n_elements + 0.5);
+		  size_to_allocate -= dldata[i].allocation;
+		  n_elements--;
+		}
+	    }
+	  else
+	    {
+	      size_to_allocate /= dimension->size;
+	      for (i = 0; i < dimension->size; i++)
+		dldata[i].allocation = size_to_allocate;
+	    }
+	}
+    }
+  else
+    {
+      /* Check to see if we were allocated more width than we requested.
+       */
+      if ((natural_size < total_size) && (nexpand >= 1))
+	{
+	  if (layout_data->integer_layout)
+	    {
+	      gdouble expand = total_size - natural_size;
+	      for (i = 0; i < dimension->size; i++)
+		{
+		  if (dldata[i].expand)
+		    {
+		      extra = floor (expand / nexpand + 0.5);
+		      dldata[i].allocation += extra;
+		      expand -= extra;
+		      nexpand--;
+		    }
+		}
+	    }
+	  else
+	    {
+	      extra = (total_size - natural_size) / nexpand;
+	      for (i = 0; i < dimension->size; i++)
+		{
+		  if (dldata[i].expand)
+		    dldata[i].allocation += extra;
+		}
+	    }
+	}
+	  
+      /* Check to see if we were allocated less width than we requested,
+       * then shrink until we fit the size give.
+       */
+      if (natural_size > total_size)
+	{
+	  gint total_nshrink = nshrink;
+	      
+	  extra = natural_size - total_size;
+	  while (total_nshrink > 0 && extra > 0)
+	    {
+	      nshrink = total_nshrink;
+	      old_extra = extra;
+	      for (i = 0; i < dimension->size; i++)
+		{
+		  if (dldata[i].shrink && dldata[i].allocation > 0.0)
+		    {
+		      gdouble old_allocation = dldata[i].allocation;
+		      gdouble shrink_amount = extra / nshrink;
+		      gdouble new_allocation;
+
+		      if (layout_data->integer_layout)
+			shrink_amount = floor (shrink_amount + 0.5);
+
+		      new_allocation = old_allocation - shrink_amount;
+		      dldata[i].allocation = MAX (0.0, new_allocation);
+		      extra -= old_allocation - dldata[i].allocation;
+		      nshrink -= 1;
+		      if (dldata[i].allocation <= 0.0)
+			total_nshrink -= 1;
+		    }
+		}
+	      if (extra >= old_extra)
+		break;
+	    }
+	}
+    }
+}
+
+
+/* Calculates the start and end position of each row/column. */
+static void
+goo_canvas_table_size_allocate_pass2 (GooCanvasTable *table,
+				      gint            d)
+{
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimension *dimension;
+  GooCanvasTableDimensionLayoutData *dldata;
+  gdouble pos;
+  gint i;
+
+  dimension = &table_data->dimensions[d];
+  dldata = layout_data->dldata[d];
+
+  pos = layout_data->border_width + layout_data->border_spacing[d] + layout_data->grid_line_width[1-d];
+  for (i = 0; i < dimension->size; i++)
+    {
+      dldata[i].start = pos;
+      pos += dldata[i].allocation;
+      dldata[i].end = pos;
+      pos += dldata[i].spacing;
+
+#if 0
+      g_print ("%s %i: %g - %g\n", d ? "Row" : "Column", i,
+	       dldata[i].start, dldata[i].end);
+#endif
+    }
+}
+
+
+/* This does the actual allocation of each child, calculating its size and
+   position according to the rows & columns it spans. */
+static void
+goo_canvas_table_size_allocate_pass3 (GooCanvasTable *table,
+				      cairo_t        *cr,
+				      gdouble         table_x_offset,
+				      gdouble         table_y_offset)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) table;
+  GooCanvasGroup *group = (GooCanvasGroup*) table;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *rows = layout_data->dldata[VERT];
+  GooCanvasTableDimensionLayoutData *columns = layout_data->dldata[HORZ];
+  GooCanvasTableChild *child;
+  GooCanvasItem *child_item;
+  GooCanvasTableChildLayoutData *child_data;
+  GooCanvasBounds requested_area, allocated_area;
+  GtkTextDirection direction = GTK_TEXT_DIR_NONE;
+  gint start_column, end_column, start_row, end_row, i;
+  gdouble x, y, max_width, max_height, width, height;
+  gdouble requested_width, requested_height;
+  gdouble x_offset, y_offset;
+
+  if (simple->canvas)
+    direction = gtk_widget_get_direction (GTK_WIDGET (simple->canvas));
+
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+      child_item = group->items->pdata[i];
+      child_data = &layout_data->children[i];
+
+      requested_width = child_data->requested_size[HORZ];
+      requested_height = child_data->requested_size[VERT];
+
+      if (requested_width <= 0.0)
+	continue;
+      
+      start_column = child->start[HORZ];
+      end_column = child->start[HORZ] + child->size[HORZ] - 1;
+      x = columns[start_column].start + layout_data->children[i].start_pad[HORZ];
+      max_width = columns[end_column].end - layout_data->children[i].end_pad[HORZ] - x;
+
+      start_row = child->start[VERT];
+      end_row = child->start[VERT] + child->size[VERT] - 1;
+      y = rows[start_row].start + layout_data->children[i].start_pad[VERT];
+      max_height = rows[end_row].end - layout_data->children[i].end_pad[VERT] - y;
+
+      width = max_width = MAX (0.0, max_width);
+      height = max_height = MAX (0.0, max_height);
+
+      if (!(child->flags[HORZ] & GOO_CANVAS_TABLE_CHILD_FILL))
+	{
+	  width = MIN (max_width, requested_width);
+	  x += (max_width - width) * child->align[HORZ];
+
+	  if (layout_data->integer_layout)
+	    x = floor (x + 0.5);
+	}
+	  
+      if (!(child->flags[VERT] & GOO_CANVAS_TABLE_CHILD_FILL))
+	{
+	  height = MIN (max_height, requested_height);
+	  y += (max_height - height) * child->align[VERT];
+
+	  if (layout_data->integer_layout)
+	    y = floor (y + 0.5);
+	}
+
+      if (direction == GTK_TEXT_DIR_RTL)
+	x = layout_data->allocated_size[HORZ] - width - x;
+
+      requested_area.x1 = layout_data->children[i].requested_position[HORZ];
+      requested_area.y1 = layout_data->children[i].requested_position[VERT];
+      requested_area.x2 = requested_area.x1 + requested_width;
+      requested_area.y2 = requested_area.y1 + requested_height;
+
+      allocated_area.x1 = x;
+      allocated_area.y1 = y;
+      allocated_area.x2 = allocated_area.x1 + width;
+      allocated_area.y2 = allocated_area.y1 + height;
+
+      /* Calculate how much we will have to translate the child by. */
+      child->position[HORZ] = allocated_area.x1 - requested_area.x1;
+      child->position[VERT] = allocated_area.y1 - requested_area.y1;
+
+      /* Translate the cairo context so the item's user space is correct. */
+      cairo_translate (cr, child->position[HORZ], child->position[VERT]);
+
+      x_offset = allocated_area.x1 - requested_area.x1;
+      y_offset = allocated_area.y1 - requested_area.y1;
+      cairo_user_to_device_distance (cr, &x_offset, &y_offset);
+      x_offset += table_x_offset;
+      y_offset += table_y_offset;
+
+      goo_canvas_item_allocate_area (child_item, cr, &requested_area,
+				     &allocated_area, x_offset, y_offset);
+
+      cairo_translate (cr, -child->position[HORZ], -child->position[VERT]);
+    }
+}
+
+
+static void
+goo_canvas_table_update_requested_heights (GooCanvasItem       *item,
+					   cairo_t	       *cr)
+{
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *rows = layout_data->dldata[VERT];
+  GooCanvasTableDimensionLayoutData *columns = layout_data->dldata[HORZ];
+  GooCanvasTableChild *child;
+  GooCanvasItem *child_item;
+  GooCanvasTableChildLayoutData *child_data;
+  gint start_column, end_column, i, row, end;
+  gdouble x, max_width, width, requested_width, requested_height, height = 0.0;
+
+  /* Just return if we've already calculated requested heights for this exact
+     width. */
+  if (layout_data->last_width == layout_data->allocated_size[HORZ])
+    return;
+  layout_data->last_width = layout_data->allocated_size[HORZ];
+
+  /* First calculate the allocations for each column. */
+  goo_canvas_table_size_allocate_init (table, HORZ);
+  goo_canvas_table_size_allocate_pass1 (table, HORZ);
+  goo_canvas_table_size_allocate_pass2 (table, HORZ);
+
+  /* Now check if any child changes height based on their allocated width. */
+  for (i = 0; i < table_data->children->len; i++)
+    {
+      child = &g_array_index (table_data->children, GooCanvasTableChild, i);
+      child_item = group->items->pdata[i];
+      child_data = &layout_data->children[i];
+
+      requested_width = child_data->requested_size[HORZ];
+
+      /* Skip children that won't be allocated. */
+      if (requested_width <= 0.0)
+	continue;
+
+      start_column = child->start[HORZ];
+      end_column = child->start[HORZ] + child->size[HORZ] - 1;
+      x = columns[start_column].start + layout_data->children[i].start_pad[HORZ];
+      max_width = columns[end_column].end - layout_data->children[i].end_pad[HORZ] - x;
+
+      width = max_width = MAX (0.0, max_width);
+
+      if (!(child->flags[HORZ] & GOO_CANVAS_TABLE_CHILD_FILL))
+	width = MIN (max_width, requested_width);
+
+      requested_height = goo_canvas_item_get_requested_height (child_item, cr,
+							       width);
+      if (requested_height >= 0.0)
+	child_data->requested_size[VERT] = requested_height;
+    }
+
+  /* Now recalculate the requested heights of each row. */
+  goo_canvas_table_size_request_pass1 (table, VERT);
+  goo_canvas_table_size_request_pass2 (table, VERT);
+  goo_canvas_table_size_request_pass3 (table, VERT);
+  goo_canvas_table_size_request_pass2 (table, VERT);
+
+  end = table_data->dimensions[VERT].size - 1;
+  for (row = 0; row <= end; row++)
+    {
+      height += rows[row].requisition;
+      if (row < end)
+	height += rows[row].spacing;
+    }
+  height += (layout_data->border_width + layout_data->border_spacing[VERT] + layout_data->grid_line_width[HORZ]) * 2.0;
+
+  layout_data->natural_size[VERT] = height;
+}
+
+
+/* Returns FALSE if item shouldn't be allocated. */
+static gboolean
+goo_canvas_table_get_requested_area (GooCanvasItem        *item,
+				     cairo_t              *cr,
+				     GooCanvasBounds      *requested_area)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *rows, *columns;
+  gdouble width = 0.0, height = 0.0;
+  gint row, column, end;
+
+  /* Request a redraw of the existing bounds */
+  goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+  
+  /* We reset the bounds to 0, just in case we are hidden or aren't allocated
+     any area. */
+  simple->bounds.x1 = simple->bounds.x2 = 0.0;
+  simple->bounds.y1 = simple->bounds.y2 = 0.0;
+
+  simple->need_update = FALSE;
+
+  goo_canvas_item_simple_check_style (simple);
+
+  if (simple_data->visibility == GOO_CANVAS_ITEM_HIDDEN)
+    return FALSE;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  cairo_translate (cr, layout_data->x, layout_data->y);
+
+  /* Initialize the layout data, get the requested sizes of all children, and
+     set the expand, shrink and empty flags. */
+  goo_canvas_table_init_layout_data (table);
+  goo_canvas_table_size_request_init (table, cr);
+
+  /* Calculate the requested width of the table. */
+  goo_canvas_table_size_request_pass1 (table, HORZ);
+  goo_canvas_table_size_request_pass2 (table, HORZ);
+  goo_canvas_table_size_request_pass3 (table, HORZ);
+  goo_canvas_table_size_request_pass2 (table, HORZ);
+
+  rows = layout_data->dldata[VERT];
+  columns = layout_data->dldata[HORZ];
+
+  end = table_data->dimensions[HORZ].size - 1;
+  for (column = 0; column <= end; column++)
+    {
+      width += columns[column].requisition;
+      if (column < end)
+	width += columns[column].spacing;
+    }
+  width += (layout_data->border_width + layout_data->border_spacing[HORZ] + layout_data->grid_line_width[VERT]) * 2.0;
+  
+  /* Save the natural size, so we know if we have to clip children. */
+  layout_data->natural_size[HORZ] = width;
+
+  /* If the width has been set, that overrides the calculations. */
+  if (table_data->width > 0.0)
+    width = table_data->width;
+
+  layout_data->requested_size[HORZ] = width;
+  layout_data->allocated_size[HORZ] = width;
+
+  /* Update the requested heights, based on the requested column widths. */
+  goo_canvas_table_update_requested_heights (item, cr);
+
+  goo_canvas_table_size_request_pass1 (table, VERT);
+  goo_canvas_table_size_request_pass2 (table, VERT);
+  goo_canvas_table_size_request_pass3 (table, VERT);
+  goo_canvas_table_size_request_pass2 (table, VERT);
+
+  end = table_data->dimensions[VERT].size - 1;
+  for (row = 0; row <= end; row++)
+    {
+      height += rows[row].requisition;
+      if (row < end)
+	height += rows[row].spacing;
+    }
+  height += (layout_data->border_width + layout_data->border_spacing[VERT] + layout_data->grid_line_width[HORZ]) * 2.0;
+
+  /* Save the natural size, so we know if we have to clip children. */
+  layout_data->natural_size[VERT] = height;
+
+  /* If the height has been set, that overrides the calculations. */
+  if (table_data->height > 0.0)
+    height = table_data->height;
+
+  layout_data->requested_size[VERT] = height;
+
+  /* Copy the user bounds to the requested area. */
+  requested_area->x1 = requested_area->y1 = 0.0;
+  requested_area->x2 = width;
+  requested_area->y2 = height;
+
+  /* Convert to the parent's coordinate space. */
+  goo_canvas_item_simple_user_bounds_to_parent (simple, cr, requested_area);
+
+  cairo_restore (cr);
+
+  return TRUE;
+}
+
+
+static gdouble
+goo_canvas_table_get_requested_height (GooCanvasItem    *item,
+				       cairo_t		*cr,
+				       gdouble           width)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  gdouble allocated_width = width, height;
+
+  /* If we have a transformation besides a simple scale & translation, just
+     return -1 as we can't adjust the height in that case. */
+  if (simple_data->transform && (simple_data->transform->xy != 0.0
+				 || simple_data->transform->yx != 0.0))
+    return -1;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  cairo_translate (cr, layout_data->x, layout_data->y);
+
+  /* Convert the width from the parent's coordinate space. Note that we only
+     need to support a simple scale operation here. */
+  if (simple_data->transform)
+    allocated_width /= simple_data->transform->xx;
+  layout_data->allocated_size[HORZ] = allocated_width;
+
+  if (layout_data->integer_layout)
+    layout_data->allocated_size[HORZ] = floor (layout_data->allocated_size[HORZ]);
+
+  goo_canvas_table_update_requested_heights (item, cr);
+
+  cairo_restore (cr);
+
+  /* Convert to the parent's coordinate space. As above,  we only need to
+     support a simple scale operation here. */
+  height = layout_data->natural_size[VERT];
+  if (simple_data->transform)
+    height *= simple_data->transform->yy;
+
+  /* Return the new requested height of the table. */
+  return height;
+}
+
+
+static void
+goo_canvas_table_allocate_area (GooCanvasItem         *item,
+				cairo_t               *cr,
+				const GooCanvasBounds *requested_area,
+				const GooCanvasBounds *allocated_area,
+				gdouble                x_offset,
+				gdouble                y_offset)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  gdouble requested_width, requested_height, allocated_width, allocated_height;
+  gdouble width_proportion, height_proportion, min_proportion;
+
+  requested_width = requested_area->x2 - requested_area->x1;
+  requested_height = requested_area->y2 - requested_area->y1;
+  allocated_width = allocated_area->x2 - allocated_area->x1;
+  allocated_height = allocated_area->y2 - allocated_area->y1;
+
+  /* Calculate what proportion of our requested size we were allocated. */
+  width_proportion = allocated_width / requested_width;
+  height_proportion = allocated_height / requested_height;
+
+  /* If the table is rotated we have to scale it to make sure it fits in the
+     allocated area. */
+  if (simple_data->transform && (simple_data->transform->xy != 0.0
+				 || simple_data->transform->yx != 0.0))
+    {
+      /* Calculate the minimum proportion, which we'll use to scale our
+	 original width & height requests. */
+      min_proportion = MIN (width_proportion, height_proportion);
+
+      layout_data->allocated_size[HORZ] = layout_data->requested_size[HORZ] * min_proportion;
+      layout_data->allocated_size[VERT] = layout_data->requested_size[VERT] * min_proportion;
+    }
+  else
+    {
+      /* If the table isn't rotated we can just scale according to the
+	 allocated proportions. */
+      layout_data->allocated_size[HORZ] = layout_data->requested_size[HORZ] * width_proportion;
+      layout_data->allocated_size[VERT] = layout_data->requested_size[VERT] * height_proportion;
+    }
+
+  if (layout_data->integer_layout)
+    {
+      layout_data->allocated_size[HORZ] = floor (layout_data->requested_size[HORZ]);
+      layout_data->allocated_size[VERT] = floor (layout_data->requested_size[VERT]);
+    }
+
+  /* We have to remove the translation that our parent is giving us while we
+     update the size requests, otherwise child items' bounds will include the
+     translation which we result in incorrect bounds after allocation. */
+  cairo_save (cr);
+  cairo_translate (cr, -(allocated_area->x1 - requested_area->x1),
+		   -(allocated_area->y1 - requested_area->y1));
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+  cairo_translate (cr, layout_data->x, layout_data->y);
+  goo_canvas_table_update_requested_heights (item, cr);
+  cairo_restore (cr);
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+  cairo_translate (cr, layout_data->x, layout_data->y);
+
+  /* Calculate the table's bounds. */
+  simple->bounds.x1 = simple->bounds.y1 = 0.0;
+  simple->bounds.x2 = layout_data->allocated_size[HORZ];
+  simple->bounds.y2 = layout_data->allocated_size[VERT];
+  goo_canvas_item_simple_user_bounds_to_device (simple, cr, &simple->bounds);
+
+  goo_canvas_table_size_allocate_init (table, VERT);
+  goo_canvas_table_size_allocate_pass1 (table, VERT);
+  goo_canvas_table_size_allocate_pass2 (table, VERT);
+
+  goo_canvas_table_size_allocate_pass3 (table, cr, x_offset, y_offset);
+
+  /* We free the children array but keep the dimension layout data, since
+     we may need that for clipping children. */
+  g_free (layout_data->children);
+  layout_data->children = NULL;
+
+  cairo_restore (cr);
+
+  goo_canvas_request_item_redraw (simple->canvas, &simple->bounds, simple_data->is_static);
+}
+
+
+static void
+goo_canvas_table_update  (GooCanvasItem   *item,
+			  gboolean         entire_tree,
+			  cairo_t         *cr,
+			  GooCanvasBounds *bounds)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasBounds tmp_bounds;
+
+  if (entire_tree || simple->need_update)
+    {
+      simple->need_update = FALSE;
+      simple->need_entire_subtree_update = FALSE;
+
+      goo_canvas_item_simple_check_style (simple);
+
+      /* We just allocate exactly what is requested. */
+      if (goo_canvas_table_get_requested_area (item, cr, &tmp_bounds))
+	{
+	  goo_canvas_table_allocate_area (item, cr, &tmp_bounds, &tmp_bounds,
+					  0, 0);
+	}
+    }
+
+  *bounds = simple->bounds;
+}
+
+
+static void
+goo_canvas_table_paint (GooCanvasItem         *item,
+			cairo_t               *cr,
+			const GooCanvasBounds *bounds,
+			gdouble                scale)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasStyle *style = simple_data->style;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *rows = layout_data->dldata[VERT];
+  GooCanvasTableDimensionLayoutData *columns = layout_data->dldata[HORZ];
+  gdouble vert_grid_line_width = layout_data->grid_line_width[VERT];
+  gdouble horz_grid_line_width = layout_data->grid_line_width[HORZ];
+  GArray *children = table_data->children;
+  GooCanvasTableChild *table_child;
+  GooCanvasItem *child;
+  gboolean check_clip = FALSE, clip;
+  gint start_column, end_column, start_row, end_row, i, j;
+  gdouble x, y, end_x, end_y, clip_width, clip_height;
+  gdouble frame_width, frame_height;
+  gdouble line_start, line_end;
+  gdouble spacing, half_spacing_before, half_spacing_after;
+  gboolean old_grid_line_visibility = FALSE, cur_grid_line_visibility;
+  GtkTextDirection direction = GTK_TEXT_DIR_NONE;
+
+  /* Skip the item if the bounds don't intersect the expose rectangle. */
+  if (simple->bounds.x1 > bounds->x2 || simple->bounds.x2 < bounds->x1
+      || simple->bounds.y1 > bounds->y2 || simple->bounds.y2 < bounds->y1)
+    return;
+
+  /* Check if the item should be visible. */
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && simple->canvas->scale < simple_data->visibility_threshold))
+    return;
+
+  if (simple->canvas)
+    direction = gtk_widget_get_direction (GTK_WIDGET (simple->canvas));
+
+  /* Paint all the items in the group. */
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+  cairo_translate (cr, layout_data->x, layout_data->y);
+
+  /* Clip with the table's clip path, if it is set. */
+  if (simple_data->clip_path_commands)
+    {
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      cairo_clip (cr);
+    }
+
+  /* Check if the table was allocated less space than it requested, in which
+     case we may need to clip children. */
+  if (layout_data->allocated_size[HORZ] < layout_data->natural_size[HORZ]
+      || layout_data->allocated_size[VERT] < layout_data->natural_size[VERT])
+    check_clip = TRUE;
+
+  /* frame_width/frame_height is the size of the table we draw the grid lines
+     around. This normally is the allocated size, except when we are shrunk
+     in which case we use the natural size. The grid will be clipped in that
+     case. */
+  frame_width = MAX (layout_data->allocated_size[HORZ], layout_data->natural_size[HORZ]);
+  frame_height = MAX (layout_data->allocated_size[VERT], layout_data->natural_size[VERT]);
+
+  /* Draw border and grid lines */
+  if (check_clip)
+    {
+      cairo_rectangle (cr,
+                       layout_data->border_width,
+                       layout_data->border_width,
+                       layout_data->allocated_size[HORZ] - 2*layout_data->border_width,
+                       layout_data->allocated_size[VERT] - 2*layout_data->border_width);
+      cairo_clip (cr);
+    }
+
+  /* Save current line width, line cap etc. for drawing items after having
+     drawn grid lines */
+  cairo_save (cr);
+
+  /* Fill the table, if desired. */
+  if (goo_canvas_style_set_fill_options (style, cr))
+    {
+      cairo_rectangle (cr,
+                       layout_data->border_width + vert_grid_line_width,
+                       layout_data->border_width + horz_grid_line_width,
+                       layout_data->allocated_size[HORZ] - 2 * (layout_data->border_width + vert_grid_line_width),
+                       layout_data->allocated_size[VERT] - 2 * (layout_data->border_width + horz_grid_line_width));
+      cairo_fill (cr);
+    }
+
+  /* We use the style for the stroke color, but the line cap style and line
+     width are overridden here. */
+  goo_canvas_style_set_stroke_options (style, cr);
+
+  cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+
+  /* Horizontal grid lines */
+  if (horz_grid_line_width > 0.0)
+    {
+      cairo_set_line_width (cr, horz_grid_line_width);
+
+      /* Outer lines */
+      line_start = layout_data->border_width;
+      line_end = frame_width - layout_data->border_width;
+
+      cairo_move_to (cr, line_start, layout_data->border_width + horz_grid_line_width/2);
+      cairo_rel_line_to (cr, line_end - line_start, 0);
+
+      cairo_move_to (cr, line_start, frame_height - layout_data->border_width - horz_grid_line_width/2);
+      cairo_rel_line_to (cr, line_end - line_start, 0);
+
+      /* Inner lines. Make sure we don't do overlapping drawing operations,
+         so we could easily draw alpha transparent borders */
+      for (i = 0; i + 1 < table_data->dimensions[VERT].size; i++)
+        {
+          for (j = 0; j < table_data->dimensions[HORZ].size; j++)
+            {
+              cur_grid_line_visibility = GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[VERT], i, j);
+              if (cur_grid_line_visibility)
+                {
+                  spacing = (columns[j].spacing - vert_grid_line_width);
+		  half_spacing_before = spacing / 2.0;
+                  if (simple->canvas->integer_layout)
+		    half_spacing_before = floor (half_spacing_before);
+		  half_spacing_after = spacing - half_spacing_before;
+
+                  if (j == 0)
+                    line_start = layout_data->border_width + vert_grid_line_width;
+                  else
+                    if (old_grid_line_visibility)
+                      line_start = columns[j].start - half_spacing_after;
+                    else
+                      line_start = columns[j-1].end + half_spacing_before;
+
+                  half_spacing_after = (columns[j].spacing - vert_grid_line_width)/2.0;
+                  if (simple->canvas->integer_layout)
+                    half_spacing_after = ceil (half_spacing_after);
+
+                  if (j == table_data->dimensions[HORZ].size - 1)
+                    line_end = frame_width - layout_data->border_width - vert_grid_line_width;
+                  else
+                    line_end = columns[j + 1].start - half_spacing_after;
+
+                  cairo_move_to (cr, line_start, rows[i].end + rows[i].spacing/2.0);
+                  cairo_rel_line_to (cr, line_end - line_start, 0);
+                }
+
+              old_grid_line_visibility = cur_grid_line_visibility;
+            }
+        }
+      
+      cairo_stroke (cr);
+    }
+
+  /* Vertical grid lines */
+  if (vert_grid_line_width > 0.0)
+    {
+      cairo_set_line_width (cr, vert_grid_line_width);
+
+      /* Outer lines */
+      line_start = layout_data->border_width + horz_grid_line_width;
+      line_end = frame_height - layout_data->border_width - horz_grid_line_width;
+
+      cairo_move_to (cr, layout_data->border_width + vert_grid_line_width/2, line_start);
+      cairo_rel_line_to (cr, 0, line_end - line_start);
+
+      cairo_move_to (cr, frame_width - layout_data->border_width - vert_grid_line_width/2, line_start);
+      cairo_rel_line_to (cr, 0, line_end - line_start);
+
+      /* Inner lines. Make sure we don't do overlapping drawing operations,
+         so we could easily draw alpha transparent borders. We need to
+         take additionally care that we don't cross already drawn
+         horizontal lines. */
+      for (i = 0; i + 1 < table_data->dimensions[HORZ].size; i++)
+        {
+          for (j = 0; j < table_data->dimensions[VERT].size; j++)
+            {
+              cur_grid_line_visibility = GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[HORZ], i, j);
+              if (cur_grid_line_visibility)
+                {
+                  spacing = (rows[j].spacing - horz_grid_line_width);
+		  half_spacing_before = spacing / 2.0;
+                  if (simple->canvas->integer_layout)
+		    half_spacing_before = floor (half_spacing_before);
+		  half_spacing_after = spacing - half_spacing_before;
+
+                  if (j == 0)
+                    line_start = layout_data->border_width + horz_grid_line_width;
+                  else
+                    if (old_grid_line_visibility)
+                      line_start = rows[j].start - half_spacing_after;
+                    else
+                      /* Don't draw top part if already drawn by horizontal grid line */
+                      if (GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[VERT], j-1, i)
+                          || GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[VERT], j-1, i+1))
+                        line_start = rows[j].start - half_spacing_after;
+                      else
+                        line_start = rows[j-1].end + half_spacing_before;
+
+                  half_spacing_before = half_spacing_after = (rows[j].spacing - horz_grid_line_width)/2.0;
+                  if (simple->canvas->integer_layout)
+                    {
+                      half_spacing_before = floor (half_spacing_before);
+                      half_spacing_after = ceil (half_spacing_after);
+                    }
+
+                  if (j == table_data->dimensions[VERT].size - 1)
+                    line_end = frame_height - layout_data->border_width - horz_grid_line_width;
+                  else
+                    /* Don't draw bottom part if already drawn by horizontal grid line */
+                    if (GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[VERT], j, i)
+                        || GOO_CANVAS_TABLE_IS_GRID_LINE_VISIBLE(layout_data->dldata[VERT], j, i+1))
+                      line_end = rows[j].end + half_spacing_before;
+                    else
+                      line_end = rows[j + 1].start - half_spacing_after;
+
+                  cairo_move_to (cr, columns[i].end + columns[i].spacing/2.0, line_start);
+                  cairo_rel_line_to (cr, 0, line_end - line_start);
+                }
+
+              old_grid_line_visibility = cur_grid_line_visibility;
+            }
+        }
+
+      cairo_stroke (cr);
+    }
+
+  cairo_restore (cr);
+
+  for (i = 0; i < group->items->len; i++)
+    {
+      child = group->items->pdata[i];
+
+      table_child = &g_array_index (children, GooCanvasTableChild, i);
+      clip = FALSE;
+
+      /* Clip the child to make sure it doesn't go outside its area. */
+      if (check_clip)
+	{
+	  /* Calculate the position of the child and how much space it has. */
+	  start_column = table_child->start[HORZ];
+	  end_column = table_child->start[HORZ] + table_child->size[HORZ] - 1;
+	  x = columns[start_column].start + table_child->start_pad[HORZ];
+	  end_x = columns[end_column].end - table_child->end_pad[HORZ];
+
+	  start_row = table_child->start[VERT];
+	  end_row = table_child->start[VERT] + table_child->size[VERT] - 1;
+	  y = rows[start_row].start + table_child->start_pad[VERT];
+	  end_y = rows[end_row].end - table_child->end_pad[VERT];
+
+	  if (simple->canvas->integer_layout)
+	    {
+	      x = floor (x + 0.5);
+	      end_x = floor (end_x + 0.5);
+	      y = floor (y + 0.5);
+	      end_y = floor (end_y + 0.5);
+	    }
+
+	  /* Make sure it doesn't go outside the table's allocated size. */
+	  end_x = MIN (end_x, layout_data->allocated_size[HORZ]);
+	  end_y = MIN (end_y, layout_data->allocated_size[VERT]);
+
+	  /* Only try to paint the child if it has some allocated space. */
+	  if (end_x <= x || end_y <= y)
+	    continue;
+
+	  /* Check if any of the rows/columns the child is in can shrink. */
+	  for (j = start_column; j <= end_column; j++)
+	    if (columns[j].shrink)
+	      clip = TRUE;
+
+	  for (j = start_row; j <= end_row; j++)
+	    if (rows[j].shrink)
+	      clip = TRUE;
+			 
+	  /* Only clip the child if it may have been shrunk. */
+	  if (clip)
+	    {
+	      clip_width = end_x - x;
+	      clip_height = end_y - y;
+
+	      if (direction == GTK_TEXT_DIR_RTL)
+		x = layout_data->allocated_size[HORZ] - clip_width - x;
+
+	      cairo_save (cr);
+	      cairo_rectangle (cr, x, y, clip_width, clip_height);
+	      cairo_clip (cr);
+	    }
+	}
+
+      cairo_translate (cr, table_child->position[HORZ],
+		       table_child->position[VERT]);
+      goo_canvas_item_paint (child, cr, bounds, scale);
+      cairo_translate (cr, -table_child->position[HORZ],
+		       -table_child->position[VERT]);
+
+      if (clip)
+	cairo_restore (cr);
+    }
+  cairo_restore (cr);
+}
+
+
+static GList*
+goo_canvas_table_get_items_at (GooCanvasItem  *item,
+			       gdouble         x,
+			       gdouble         y,
+			       cairo_t        *cr,
+			       gboolean        is_pointer_event,
+			       gboolean        parent_visible,
+			       GList          *found_items)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableData *table_data = table->table_data;
+  GooCanvasTableLayoutData *layout_data = table_data->layout_data;
+  GooCanvasTableDimensionLayoutData *rows = layout_data->dldata[VERT];
+  GooCanvasTableDimensionLayoutData *columns = layout_data->dldata[HORZ];
+  GArray *children = table_data->children;
+  GooCanvasTableChild *table_child;
+  GooCanvasItem *child;
+  gboolean visible = parent_visible, check_clip = FALSE;
+  double user_x = x, user_y = y;
+  gint start_column, end_column, start_row, end_row, i;
+  gdouble start_x, end_x, start_y, end_y;
+
+  if (simple->need_update)
+    goo_canvas_item_ensure_updated (item);
+
+  /* Skip the item if the point isn't in the item's bounds. */
+  if (simple->bounds.x1 > x || simple->bounds.x2 < x
+      || simple->bounds.y1 > y || simple->bounds.y2 < y)
+    return found_items;
+
+  if (simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE
+      || (simple_data->visibility == GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD
+	  && simple->canvas->scale < simple_data->visibility_threshold))
+    visible = FALSE;
+
+  /* Check if the group should receive events. */
+  if (is_pointer_event
+      && (simple_data->pointer_events == GOO_CANVAS_EVENTS_NONE
+	  || ((simple_data->pointer_events & GOO_CANVAS_EVENTS_VISIBLE_MASK)
+	      && !visible)))
+    return found_items;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+  cairo_translate (cr, layout_data->x, layout_data->y);
+
+  cairo_device_to_user (cr, &user_x, &user_y);
+
+  /* If the table has a clip path, check if the point is inside it. */
+  if (simple_data->clip_path_commands)
+    {
+      goo_canvas_create_path (simple_data->clip_path_commands, cr);
+      cairo_set_fill_rule (cr, simple_data->clip_fill_rule);
+      if (!cairo_in_fill (cr, user_x, user_y))
+	{
+	  cairo_restore (cr);
+	  return found_items;
+	}
+    }
+
+  /* Check if the table was allocated less space than it requested, in which
+     case we may need to clip children. */
+  if (layout_data->allocated_size[HORZ] < layout_data->natural_size[HORZ]
+      || layout_data->allocated_size[VERT] < layout_data->natural_size[VERT])
+    check_clip = TRUE;
+
+  /* Step up from the bottom of the children to the top, adding any items
+     found to the start of the list. */
+  for (i = 0; i < group->items->len; i++)
+    {
+      child = group->items->pdata[i];
+
+      table_child = &g_array_index (children, GooCanvasTableChild, i);
+
+      /* If the child may have been clipped, check the point is in the child's
+	 allocated area. */
+      if (check_clip)
+	{
+	  /* Calculate the position of the child and how much space it has. */
+	  start_column = table_child->start[HORZ];
+	  end_column = table_child->start[HORZ] + table_child->size[HORZ] - 1;
+	  start_x = columns[start_column].start + table_child->start_pad[HORZ];
+	  end_x = columns[end_column].end - table_child->end_pad[HORZ];
+
+	  start_row = table_child->start[VERT];
+	  end_row = table_child->start[VERT] + table_child->size[VERT] - 1;
+	  start_y = rows[start_row].start + table_child->start_pad[VERT];
+	  end_y = rows[end_row].end - table_child->end_pad[VERT];
+
+	  if (simple->canvas->integer_layout)
+	    {
+	      start_x = floor (start_x + 0.5);
+	      end_x = floor (end_x + 0.5);
+	      start_y = floor (start_y + 0.5);
+	      end_y = floor (end_y + 0.5);
+	    }
+
+	  if (user_x < start_x || user_x > end_x
+	      || user_y < start_y || user_y > end_y)
+	    continue;
+	}
+
+      cairo_translate (cr, table_child->position[HORZ],
+		       table_child->position[VERT]);
+
+      found_items = goo_canvas_item_get_items_at (child, x, y, cr,
+						  is_pointer_event, visible,
+						  found_items);
+
+      cairo_translate (cr, -table_child->position[HORZ],
+		       -table_child->position[VERT]);
+    }
+  cairo_restore (cr);
+
+  return found_items;
+}
+
+
+gboolean
+goo_canvas_table_get_transform_for_child  (GooCanvasItem  *item,
+					   GooCanvasItem  *child,
+					   cairo_matrix_t *transform)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasGroup *group = (GooCanvasGroup*) item;
+  GooCanvasTable *table = (GooCanvasTable*) item;
+  GooCanvasTableChild *table_child;
+  gboolean has_transform = FALSE;
+  gint child_num;
+
+
+  if (simple->simple_data->transform)
+    {
+      *transform = *simple->simple_data->transform;
+      has_transform = TRUE;
+    }
+  else
+    {
+      cairo_matrix_init_identity (transform);
+    }
+
+  for (child_num = 0; child_num < group->items->len; child_num++)
+    {
+      if (group->items->pdata[child_num] == child)
+	{
+	  table_child = &g_array_index (table->table_data->children,
+					GooCanvasTableChild, child_num);
+	  cairo_matrix_translate (transform, table_child->position[HORZ],
+				  table_child->position[VERT]);
+	  has_transform = TRUE;
+	  break;
+	}
+    }
+
+  return has_transform;
+}
+
+
+static void
+item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->add_child               = goo_canvas_table_add_child;
+  iface->move_child              = goo_canvas_table_move_child;
+  iface->remove_child            = goo_canvas_table_remove_child;
+  iface->get_child_property      = goo_canvas_table_get_child_property;
+  iface->set_child_property      = goo_canvas_table_set_child_property;
+  iface->get_transform_for_child = goo_canvas_table_get_transform_for_child;
+
+  iface->update                  = goo_canvas_table_update;
+  iface->get_requested_area      = goo_canvas_table_get_requested_area;
+  iface->get_requested_height    = goo_canvas_table_get_requested_height;
+  iface->allocate_area           = goo_canvas_table_allocate_area;
+  iface->paint                   = goo_canvas_table_paint;
+  iface->get_items_at	         = goo_canvas_table_get_items_at;
+
+  iface->set_model               = goo_canvas_table_set_model;
+}
+
+
+
+/**
+ * SECTION:goocanvastablemodel
+ * @Title: GooCanvasTableModel
+ * @Short_Description: a model for a table container to layout items.
+ *
+ * #GooCanvasTableModel is a model for a table container used to lay out other
+ * canvas items. It is used in a similar way to how the GtkTable widget is used
+ * to lay out GTK+ widgets.
+ *
+ * Item models are added to the table using the normal methods, then
+ * goo_canvas_item_model_set_child_properties() is used to specify how each
+ * child item is to be positioned within the table (i.e. which row and column
+ * it is in, how much padding it should have and whether it should expand or
+ * shrink).
+ *
+ * #GooCanvasTableModel is a subclass of #GooCanvasItemModelSimple and so
+ * inherits all of the style properties such as "stroke-color", "fill-color"
+ * and "line-width". Setting a style property on a #GooCanvasTableModel will
+ * affect all children of the #GooCanvasTableModel (unless the children
+ * override the property setting).
+ *
+ * #GooCanvasTableModel implements the #GooCanvasItemModel interface, so you
+ * can use the #GooCanvasItemModel functions such as
+ * goo_canvas_item_model_raise() and goo_canvas_item_rotate(), and the
+ * properties such as "visibility" and "pointer-events".
+ *
+ * To create a #GooCanvasTableModel use goo_canvas_table_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasTableModel, use
+ * g_object_get() and g_object_set().
+ */
+
+static GooCanvasItemModelIface *goo_canvas_table_model_parent_iface;
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_table_model_finalize (GObject *object);
+static void goo_canvas_table_model_get_property (GObject            *object,
+						 guint               param_id,
+						 GValue             *value,
+						 GParamSpec         *pspec);
+static void goo_canvas_table_model_set_property (GObject            *object,
+						 guint               param_id,
+						 const GValue       *value,
+						 GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasTableModel, goo_canvas_table_model,
+			 GOO_TYPE_CANVAS_GROUP_MODEL,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_table_model_class_init (GooCanvasTableModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  goo_canvas_table_model_parent_iface = g_type_interface_peek (goo_canvas_table_model_parent_class, GOO_TYPE_CANVAS_ITEM_MODEL);
+
+  gobject_class->finalize = goo_canvas_table_model_finalize;
+
+  gobject_class->get_property = goo_canvas_table_model_get_property;
+  gobject_class->set_property = goo_canvas_table_model_set_property;
+
+  goo_canvas_table_install_common_properties (gobject_class, goo_canvas_item_model_class_install_child_property);
+}
+
+
+static void
+goo_canvas_table_model_init (GooCanvasTableModel *tmodel)
+{
+  goo_canvas_table_init_data (&tmodel->table_data);
+}
+
+
+/**
+ * goo_canvas_table_model_new:
+ * @parent: the parent model, 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.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new table model.
+ *
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a table with a square, a circle and
+ * a triangle in it:
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *table, *square, *circle, *triangle;
+ *
+ *  table = goo_canvas_table_model_new (root,
+ *                                      "row-spacing", 4.0,
+ *                                      "column-spacing", 4.0,
+ *                                      NULL);
+ *  goo_canvas_item_model_translate (table, 400, 200);
+ *
+ *  square = goo_canvas_rect_model_new (table, 0.0, 0.0, 50.0, 50.0,
+ *                                      "fill-color", "red",
+ *                                      NULL);
+ *  goo_canvas_item_model_set_child_properties (table, square,
+ *                                              "row", 0,
+ *                                              "column", 0,
+ *                                              NULL);
+ *
+ *  circle = goo_canvas_ellipse_model_new (table, 0.0, 0.0, 25.0, 25.0,
+ *                                         "fill-color", "blue",
+ *                                         NULL);
+ *  goo_canvas_item_model_set_child_properties (table, circle,
+ *                                              "row", 0,
+ *                                              "column", 1,
+ *                                              NULL);
+ *
+ *  triangle = goo_canvas_polyline_model_new (table, TRUE, 3,
+ *                                            25.0, 0.0, 0.0, 50.0, 50.0, 50.0,
+ *                                            "fill-color", "yellow",
+ *                                            NULL);
+ *  goo_canvas_item_model_set_child_properties (table, triangle,
+ *                                              "row", 0,
+ *                                              "column", 2,
+ *                                              NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new table model.
+ **/
+GooCanvasItemModel*
+goo_canvas_table_model_new (GooCanvasItemModel *parent,
+			    ...)
+{
+  GooCanvasItemModel *model;
+  va_list var_args;
+  const char *first_property;
+
+  model = g_object_new (GOO_TYPE_CANVAS_TABLE_MODEL, NULL);
+
+  va_start (var_args, parent);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist (G_OBJECT (model), first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_table_model_finalize (GObject *object)
+{
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) object;
+
+  goo_canvas_table_free_data (&tmodel->table_data);
+
+  G_OBJECT_CLASS (goo_canvas_table_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_table_model_get_property (GObject              *object,
+				     guint                 prop_id,
+				     GValue               *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasTableModel *emodel = (GooCanvasTableModel*) object;
+
+  goo_canvas_table_get_common_property (object, &emodel->table_data,
+					prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_table_model_set_property (GObject              *object,
+				     guint                 prop_id,
+				     const GValue         *value,
+				     GParamSpec           *pspec)
+{
+  GooCanvasTableModel *emodel = (GooCanvasTableModel*) object;
+  gboolean recompute_bounds;
+
+  recompute_bounds = goo_canvas_table_set_common_property (object,
+							   &emodel->table_data,
+							   prop_id, value,
+							   pspec);
+  g_signal_emit_by_name (emodel, "changed", recompute_bounds);
+}
+
+
+static void
+goo_canvas_table_model_add_child     (GooCanvasItemModel *model,
+				      GooCanvasItemModel *child,
+				      gint                position)
+{
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+
+  goo_canvas_table_add_child_internal (&tmodel->table_data, position);
+
+  /* Let the parent GooCanvasGroupModel code do the rest. */
+  goo_canvas_table_model_parent_iface->add_child (model, child, position);
+}
+
+
+static void
+goo_canvas_table_model_move_child    (GooCanvasItemModel *model,
+				      gint	          old_position,
+				      gint                new_position)
+{
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+
+  goo_canvas_table_move_child_internal (&tmodel->table_data, old_position,
+					new_position);
+
+  /* Let the parent GooCanvasGroupModel code do the rest. */
+  goo_canvas_table_model_parent_iface->move_child (model, old_position,
+						   new_position);
+}
+
+
+static void
+goo_canvas_table_model_remove_child  (GooCanvasItemModel *model,
+				      gint                child_num)
+{
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+
+  g_array_remove_index (tmodel->table_data.children, child_num);
+
+  /* Let the parent GooCanvasGroupModel code do the rest. */
+  goo_canvas_table_model_parent_iface->remove_child (model, child_num);
+}
+
+
+static void
+goo_canvas_table_model_get_child_property (GooCanvasItemModel *model,
+					   GooCanvasItemModel *child,
+					   guint               property_id,
+					   GValue             *value,
+					   GParamSpec         *pspec)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+  GooCanvasTableChild *table_child;
+  gint child_num;
+
+  for (child_num = 0; child_num < gmodel->children->len; child_num++)
+    {
+      if (gmodel->children->pdata[child_num] == child)
+	{
+	  table_child = &g_array_index (tmodel->table_data.children,
+					GooCanvasTableChild, child_num);
+	  goo_canvas_table_get_common_child_property ((GObject*) tmodel,
+						      table_child,
+						      property_id, value,
+						      pspec);
+	  break;
+	}
+    }
+}
+
+
+static void
+goo_canvas_table_model_set_child_property (GooCanvasItemModel *model,
+					   GooCanvasItemModel *child,
+					   guint               property_id,
+					   const GValue       *value,
+					   GParamSpec         *pspec)
+{
+  GooCanvasGroupModel *gmodel = (GooCanvasGroupModel*) model;
+  GooCanvasTableModel *tmodel = (GooCanvasTableModel*) model;
+  GooCanvasTableChild *table_child;
+  gint child_num;
+
+  for (child_num = 0; child_num < gmodel->children->len; child_num++)
+    {
+      if (gmodel->children->pdata[child_num] == child)
+	{
+	  table_child = &g_array_index (tmodel->table_data.children,
+					GooCanvasTableChild, child_num);
+	  goo_canvas_table_set_common_child_property ((GObject*) tmodel,
+						      &tmodel->table_data,
+						      table_child,
+						      property_id, value,
+						      pspec);
+	  break;
+	}
+    }
+
+  g_signal_emit_by_name (tmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_table_model_create_item (GooCanvasItemModel *model,
+				    GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = goo_canvas_table_new (NULL, NULL);
+  /* Note that we set the canvas before the model, since we may need the
+     canvas to create any child items. */
+  goo_canvas_item_set_canvas (item, canvas);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->add_child          = goo_canvas_table_model_add_child;
+  iface->move_child         = goo_canvas_table_model_move_child;
+  iface->remove_child       = goo_canvas_table_model_remove_child;
+  iface->get_child_property = goo_canvas_table_model_get_child_property;
+  iface->set_child_property = goo_canvas_table_model_set_child_property;
+
+  iface->create_item        = goo_canvas_table_model_create_item;
+}
diff --git a/libgoocanvas/goocanvastable.h b/libgoocanvas/goocanvastable.h
new file mode 100644
index 0000000..e204657
--- /dev/null
+++ b/libgoocanvas/goocanvastable.h
@@ -0,0 +1,139 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvastable.h - table item.
+ */
+#ifndef __GOO_CANVAS_TABLE_H__
+#define __GOO_CANVAS_TABLE_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasgroup.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct _GooCanvasTableDimension   GooCanvasTableDimension;
+struct _GooCanvasTableDimension
+{
+  gint size;
+
+  gdouble default_spacing;
+
+  /* These are specific spacings for particular rows or columns. A negative
+     value indicates that the default should be used. */
+  gdouble *spacings;
+
+  guint homogeneous : 1;
+};
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasTableData   GooCanvasTableData;
+typedef struct _GooCanvasTableLayoutData GooCanvasTableLayoutData;
+struct _GooCanvasTableData
+{
+  /* The explicitly set width or height, or -1. */
+  gdouble width, height;
+
+  /* One for rows & one for columns. */
+  GooCanvasTableDimension dimensions[2];
+
+  gdouble border_width;
+
+  /* An array of GooCanvasTableChild. */
+  GArray *children;
+
+  GooCanvasTableLayoutData *layout_data;
+};
+
+
+#define GOO_TYPE_CANVAS_TABLE            (goo_canvas_table_get_type ())
+#define GOO_CANVAS_TABLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_TABLE, GooCanvasTable))
+#define GOO_CANVAS_TABLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_TABLE, GooCanvasTableClass))
+#define GOO_IS_CANVAS_TABLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_TABLE))
+#define GOO_IS_CANVAS_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_TABLE))
+#define GOO_CANVAS_TABLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_TABLE, GooCanvasTableClass))
+
+
+typedef struct _GooCanvasTable       GooCanvasTable;
+typedef struct _GooCanvasTableClass  GooCanvasTableClass;
+
+/**
+ * GooCanvasTable
+ *
+ * The #GooCanvasTable-struct struct contains private data only.
+ */
+struct _GooCanvasTable
+{
+  GooCanvasGroup parent;
+
+  GooCanvasTableData *table_data;
+};
+
+struct _GooCanvasTableClass
+{
+  GooCanvasGroupClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType          goo_canvas_table_get_type    (void) G_GNUC_CONST;
+GooCanvasItem* goo_canvas_table_new         (GooCanvasItem  *parent,
+					     ...);
+
+
+
+
+#define GOO_TYPE_CANVAS_TABLE_MODEL            (goo_canvas_table_model_get_type ())
+#define GOO_CANVAS_TABLE_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_TABLE_MODEL, GooCanvasTableModel))
+#define GOO_CANVAS_TABLE_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_TABLE_MODEL, GooCanvasTableModelClass))
+#define GOO_IS_CANVAS_TABLE_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_TABLE_MODEL))
+#define GOO_IS_CANVAS_TABLE_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_TABLE_MODEL))
+#define GOO_CANVAS_TABLE_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_TABLE_MODEL, GooCanvasTableModelClass))
+
+
+typedef struct _GooCanvasTableModel       GooCanvasTableModel;
+typedef struct _GooCanvasTableModelClass  GooCanvasTableModelClass;
+
+/**
+ * GooCanvasTableModel
+ *
+ * The #GooCanvasTableModel-struct struct contains private data only.
+ */
+struct _GooCanvasTableModel
+{
+  GooCanvasGroupModel parent_object;
+
+  GooCanvasTableData table_data;
+};
+
+struct _GooCanvasTableModelClass
+{
+  GooCanvasGroupModelClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_table_model_get_type (void) G_GNUC_CONST;
+GooCanvasItemModel* goo_canvas_table_model_new      (GooCanvasItemModel *parent,
+						     ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_TABLE_H__ */
diff --git a/libgoocanvas/goocanvastext.c b/libgoocanvas/goocanvastext.c
new file mode 100644
index 0000000..7cb3c3b
--- /dev/null
+++ b/libgoocanvas/goocanvastext.c
@@ -0,0 +1,1090 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvastext.c - text item.
+ */
+
+/**
+ * SECTION:goocanvastext
+ * @Title: GooCanvasText
+ * @Short_Description: a text item.
+ *
+ * GooCanvasText represents a text item.
+ *
+ * It is a subclass of #GooCanvasItemSimple and so inherits all of the style
+ * properties such as "fill-color".
+ *
+ * It also implements the #GooCanvasItem interface, so you can use the
+ * #GooCanvasItem functions such as goo_canvas_item_raise() and
+ * goo_canvas_item_rotate().
+ *
+ * The #GooCanvasText:width and #GooCanvasText:height properties specify the
+ * area of the item. If it exceeds that area because there is too much text,
+ * it is clipped. The properties can be set to -1 to disable clipping.
+ *
+ * To create a #GooCanvasText use goo_canvas_text_new().
+ *
+ * To get or set the properties of an existing #GooCanvasText, use
+ * g_object_get() and g_object_set().
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvastext.h"
+#include "goocanvas.h"
+
+typedef struct _GooCanvasTextPrivate GooCanvasTextPrivate;
+struct _GooCanvasTextPrivate {
+  gdouble height;
+};
+
+#define GOO_CANVAS_TEXT_GET_PRIVATE(text) \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((text), GOO_TYPE_CANVAS_TEXT, GooCanvasTextPrivate))
+#define GOO_CANVAS_TEXT_MODEL_GET_PRIVATE(text) \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((text), GOO_TYPE_CANVAS_TEXT_MODEL, GooCanvasTextPrivate))
+
+enum {
+  PROP_0,
+
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_TEXT,
+  PROP_USE_MARKUP,
+  PROP_ANCHOR,
+  PROP_ALIGN,
+  PROP_ELLIPSIZE,
+  PROP_WRAP
+};
+
+static PangoLayout*
+goo_canvas_text_create_layout (GooCanvasItemSimpleData *simple_data,
+			       GooCanvasTextData       *text_data,
+			       gdouble                  layout_width,
+			       cairo_t                 *cr,
+			       GooCanvasBounds         *bounds,
+			       gdouble	               *origin_x_return,
+			       gdouble	               *origin_y_return);
+
+static void goo_canvas_text_finalize     (GObject            *object);
+static void canvas_item_interface_init   (GooCanvasItemIface *iface);
+static void goo_canvas_text_get_property (GObject            *object,
+					  guint               param_id,
+					  GValue             *value,
+					  GParamSpec         *pspec);
+static void goo_canvas_text_set_property (GObject            *object,
+					  guint               param_id,
+					  const GValue       *value,
+					  GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasText, goo_canvas_text,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_text_install_common_properties (GObjectClass *gobject_class)
+{
+  /* Text */
+  g_object_class_install_property (gobject_class, PROP_TEXT,
+				   g_param_spec_string ("text",
+							_("Text"),
+							_("The text to display"),
+							NULL,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_USE_MARKUP,
+				   g_param_spec_boolean ("use-markup",
+							 _("Use Markup"),
+							 _("Whether to parse PangoMarkup in the text, to support different styles"),
+							 FALSE,
+							 G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_ELLIPSIZE,
+                                   g_param_spec_enum ("ellipsize",
+                                                      _("Ellipsize"),
+                                                      _("The preferred place to ellipsize the string, if the label does not have enough room to display the entire string"),
+						      PANGO_TYPE_ELLIPSIZE_MODE,
+						      PANGO_ELLIPSIZE_NONE,
+                                                      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WRAP,
+                                   g_param_spec_enum ("wrap",
+                                                      _("Wrap"),
+                                                      _("The preferred method of wrapping the string if a width has been set"),
+						      PANGO_TYPE_WRAP_MODE,
+						      PANGO_WRAP_WORD,
+                                                      G_PARAM_READWRITE));
+
+  /* Position */
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the text"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the text"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width to use to layout the text, or -1 to let the text use as much horizontal space as needed"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height to use to layout the text, or -1 to let the text use as much vertical space as needed"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+
+  g_object_class_install_property (gobject_class, PROP_ANCHOR,
+				   g_param_spec_enum ("anchor",
+						      _("Anchor"),
+						      _("How to position the text relative to the given x and y coordinates"),
+						      GOO_TYPE_CANVAS_ANCHOR_TYPE,
+						      GOO_CANVAS_ANCHOR_NW,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_ALIGN,
+				   g_param_spec_enum ("alignment",
+						      _("Alignment"),
+						      _("How to align the text"),
+						      PANGO_TYPE_ALIGNMENT,
+						      PANGO_ALIGN_LEFT,
+						      G_PARAM_READWRITE));
+}
+
+
+static void
+goo_canvas_text_init (GooCanvasText *text)
+{
+  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_GET_PRIVATE (text);
+
+  text->text_data = g_slice_new0 (GooCanvasTextData);
+  text->text_data->width = -1.0;
+  text->text_data->anchor = GOO_CANVAS_ANCHOR_NW;
+  text->text_data->ellipsize = PANGO_ELLIPSIZE_NONE;
+  text->text_data->wrap = PANGO_WRAP_WORD;
+
+  text->layout_width = -1.0;
+
+  priv->height = -1.0;
+}
+
+
+/**
+ * goo_canvas_text_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.
+ * @string: the text to display.
+ * @x: the x coordinate of the text.
+ * @y: the y coordinate of the text.
+ * @width: the width of the text item, or -1 for unlimited width.
+ * @anchor: the position of the text relative to the given @x and @y
+ *  coordinates. For example an anchor of %GDK_ANCHOR_NW will result in the
+ *  top-left of the text being placed at the given @x and @y coordinates.
+ *  An anchor of %GDK_ANCHOR_CENTER will result in the center of the text being
+ *  placed at the @x and @y coordinates.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new text item.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a text item with the bottom right
+ * of the text box placed at (500,500):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItem *text = goo_canvas_text_new (mygroup, "Hello World", 500.0, 500.0, 200.0, GOO_CANVAS_ANCHOR_SE,
+ *                                             "fill-color", "blue",
+ *                                             NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new text item.
+ **/
+GooCanvasItem*
+goo_canvas_text_new (GooCanvasItem *parent,
+		     const char    *string,
+		     gdouble        x,
+		     gdouble        y,
+		     gdouble        width,
+		     GooCanvasAnchorType  anchor,
+		     ...)
+{
+  GooCanvasItem *item;
+  GooCanvasText *text;
+  GooCanvasTextData *text_data;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_TEXT, NULL);
+  text = (GooCanvasText*) item;
+
+  text_data = text->text_data;
+  text_data->text = g_strdup (string);
+  text_data->x = x;
+  text_data->y = y;
+  text_data->width = width;
+  text_data->anchor = anchor;
+
+  va_start (var_args, anchor);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+static void
+goo_canvas_text_finalize (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasText *text = (GooCanvasText*) object;
+
+  /* Free our data if we didn't have a model. (If we had a model it would
+     have been reset in dispose() and simple_data will be NULL.) */
+  if (simple->simple_data)
+    {
+      g_free (text->text_data->text);
+      g_slice_free (GooCanvasTextData, text->text_data);
+    }
+  text->text_data = NULL;
+
+  G_OBJECT_CLASS (goo_canvas_text_parent_class)->finalize (object);
+}
+
+
+/* Gets the private data to use, from the model or from the item itself. */
+static GooCanvasTextPrivate*
+goo_canvas_text_get_private (GooCanvasText *text)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) text;
+
+  if (simple->model)
+    return GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (simple->model);
+  else
+    return GOO_CANVAS_TEXT_GET_PRIVATE (text);
+}
+
+
+static void
+goo_canvas_text_get_common_property (GObject                 *object,
+				     GooCanvasTextData       *text_data,
+				     GooCanvasTextPrivate    *priv,
+				     guint                    prop_id,
+				     GValue                  *value,
+				     GParamSpec              *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      g_value_set_double (value, text_data->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, text_data->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, text_data->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, priv->height);
+      break;
+    case PROP_TEXT:
+      g_value_set_string (value, text_data->text);
+      break;
+    case PROP_USE_MARKUP:
+      g_value_set_boolean (value, text_data->use_markup);
+      break;
+    case PROP_ELLIPSIZE:
+      g_value_set_enum (value, text_data->ellipsize);
+      break;
+    case PROP_WRAP:
+      g_value_set_enum (value, text_data->wrap);
+      break;
+    case PROP_ANCHOR:
+      g_value_set_enum (value, text_data->anchor);
+      break;
+    case PROP_ALIGN:
+      g_value_set_enum (value, text_data->alignment);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_text_get_property (GObject              *object,
+			      guint                 prop_id,
+			      GValue               *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasText *text = (GooCanvasText*) object;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+
+  goo_canvas_text_get_common_property (object, text->text_data, priv,
+                                       prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_text_set_common_property (GObject                 *object,
+				     GooCanvasTextData       *text_data,
+				     GooCanvasTextPrivate    *priv,
+				     guint                    prop_id,
+				     const GValue            *value,
+				     GParamSpec              *pspec)
+{
+  switch (prop_id)
+    {
+    case PROP_X:
+      text_data->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      text_data->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      text_data->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      priv->height = g_value_get_double (value);
+      break;
+    case PROP_TEXT:
+      g_free (text_data->text);
+      text_data->text = g_value_dup_string (value);
+      break;
+    case PROP_USE_MARKUP:
+      text_data->use_markup = g_value_get_boolean (value);
+      break;
+    case PROP_ELLIPSIZE:
+      text_data->ellipsize = g_value_get_enum (value);
+      break;
+    case PROP_WRAP:
+      text_data->wrap = g_value_get_enum (value);
+      break;
+    case PROP_ANCHOR:
+      text_data->anchor = g_value_get_enum (value);
+      break;
+    case PROP_ALIGN:
+      text_data->alignment = g_value_get_enum (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+goo_canvas_text_set_property (GObject              *object,
+			      guint                 prop_id,
+			      const GValue         *value,
+			      GParamSpec           *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasText *text = (GooCanvasText*) object;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+
+  if (simple->model)
+    {
+      g_warning ("Can't set property of a canvas item with a model - set the model property instead");
+      return;
+    }
+
+  goo_canvas_text_set_common_property (object, text->text_data, priv, prop_id,
+				       value, pspec);
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static PangoLayout*
+goo_canvas_text_create_layout (GooCanvasItemSimpleData *simple_data,
+			       GooCanvasTextData       *text_data,
+			       gdouble                  layout_width,
+			       cairo_t                 *cr,
+			       GooCanvasBounds         *bounds,
+			       gdouble	               *origin_x_return,
+			       gdouble	               *origin_y_return)
+{
+  GooCanvasStyle *style = simple_data->style;
+  GValue *svalue;
+  PangoLayout *layout;
+  PangoContext *context;
+  PangoRectangle ink_rect, logical_rect;
+  double logical_width, logical_height, align_width, origin_x, origin_y;
+  gchar *string;
+  double x1_extension, x2_extension, y1_extension, y2_extension;
+  cairo_font_options_t *font_options;
+  cairo_hint_metrics_t hint_metrics = CAIRO_HINT_METRICS_OFF;
+
+  string = text_data->text ? text_data->text : "";
+
+  layout = pango_cairo_create_layout (cr);
+  context = pango_layout_get_context (layout);
+
+  if (layout_width > 0)
+    pango_layout_set_width (layout, (double) layout_width * PANGO_SCALE);
+
+  if (text_data->use_markup)
+    pango_layout_set_markup (layout, string, -1);
+  else
+    pango_layout_set_text (layout, string, -1);
+
+  svalue = goo_canvas_style_get_property (style,
+					  goo_canvas_style_font_desc_id);
+  if (svalue)
+    pango_layout_set_font_description (layout, svalue->data[0].v_pointer);
+
+  svalue = goo_canvas_style_get_property (style,
+					  goo_canvas_style_hint_metrics_id);
+  if (svalue)
+    hint_metrics = svalue->data[0].v_long;
+
+  font_options = cairo_font_options_create ();
+  cairo_font_options_set_hint_metrics (font_options, hint_metrics);
+  pango_cairo_context_set_font_options (context, font_options);
+  cairo_font_options_destroy (font_options);
+
+  if (text_data->alignment != PANGO_ALIGN_LEFT)
+    pango_layout_set_alignment (layout, text_data->alignment);
+
+  pango_layout_set_ellipsize (layout, text_data->ellipsize);
+
+  pango_layout_set_wrap (layout, text_data->wrap);
+
+  if (bounds)
+    {
+      /* Get size of the text, so we can position it according to anchor. */
+      pango_layout_get_extents (layout, &ink_rect, &logical_rect);
+
+      logical_width = (double) logical_rect.width / PANGO_SCALE;
+      logical_height = (double) logical_rect.height / PANGO_SCALE;
+
+      /* If the text width has been set, that width is used to do the alignment
+	 positioning. Otherwise the actual width is used. */
+      if (text_data->width > 0)
+	align_width = text_data->width;
+      else
+	align_width = logical_width;
+
+      /* Now calculate the origin of the text, i.e. where we will tell Pango
+	 to draw it. */
+      origin_x = text_data->x;
+      origin_y = text_data->y;
+
+      switch (text_data->anchor)
+	{
+	case GOO_CANVAS_ANCHOR_N:
+	case GOO_CANVAS_ANCHOR_CENTER:
+	case GOO_CANVAS_ANCHOR_S:
+	  origin_x -= align_width / 2.0;
+	break;
+	case GOO_CANVAS_ANCHOR_NE:
+	case GOO_CANVAS_ANCHOR_E:
+	case GOO_CANVAS_ANCHOR_SE:
+	  origin_x -= align_width;
+	  break;
+	default:
+	  break;
+	}
+
+      switch (text_data->anchor)
+	{
+	case GOO_CANVAS_ANCHOR_W:
+	case GOO_CANVAS_ANCHOR_CENTER:
+	case GOO_CANVAS_ANCHOR_E:
+	  origin_y -= logical_height / 2.0;
+	  break;
+	case GOO_CANVAS_ANCHOR_SW:
+	case GOO_CANVAS_ANCHOR_S:
+	case GOO_CANVAS_ANCHOR_SE:
+	  origin_y -= logical_height;
+	  break;
+	default:
+	  break;
+	}
+
+      /* Return the origin of the text if required. */
+      if (origin_x_return)
+	*origin_x_return = origin_x;
+      if (origin_y_return)
+	*origin_y_return = origin_y;
+
+      /* Now calculate the logical bounds. */
+      bounds->x1 = origin_x;
+      bounds->y1 = origin_y;
+
+      if (text_data->width > 0)
+	{
+	  /* If the text width has been set, and the alignment isn't
+	     PANGO_ALIGN_LEFT, we need to adjust for the difference between
+	     the actual width of the text and the width that was used for
+	     alignment. */
+	  switch (text_data->alignment)
+	    {
+	    case PANGO_ALIGN_CENTER:
+	      bounds->x1 += (align_width - logical_width) / 2.0;
+	      break;
+	    case PANGO_ALIGN_RIGHT:
+	      bounds->x1 += align_width - logical_width;
+	      break;
+	    default:
+	      break;
+	    }
+	}
+
+      bounds->x2 = bounds->x1 + logical_width;
+      bounds->y2 = bounds->y1 + logical_height;
+
+      /* Now adjust it to take into account the ink bounds. Calculate how far
+	 the ink rect extends outside each edge of the logical rect and adjust
+	 the bounds as necessary. */
+      x1_extension = logical_rect.x - ink_rect.x;
+      if (x1_extension > 0)
+	bounds->x1 -= x1_extension / PANGO_SCALE;
+
+      x2_extension = (ink_rect.x + ink_rect.width)
+	- (logical_rect.x + logical_rect.width);
+      if (x2_extension > 0)
+	bounds->x2 += x2_extension / PANGO_SCALE;
+
+      y1_extension = logical_rect.y - ink_rect.y;
+      if (y1_extension > 0)
+	bounds->y1 -= y1_extension / PANGO_SCALE;
+
+      y2_extension = (ink_rect.y + ink_rect.height)
+	- (logical_rect.y + logical_rect.height);
+      if (y2_extension > 0)
+	bounds->y2 += y2_extension / PANGO_SCALE;
+    }
+
+  return layout;
+}
+
+
+static void
+goo_canvas_text_update  (GooCanvasItemSimple *simple,
+			 cairo_t             *cr)
+{
+  GooCanvasText *text = (GooCanvasText*) simple;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+  PangoLayout *layout;
+
+  /* Initialize the layout width to the text item's specified width property.
+     It may get changed later in get_requested_height() according to the
+     layout container and settings. */
+  text->layout_width = text->text_data->width;
+
+  /* Compute the new bounds. */
+  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
+					  text->layout_width, cr,
+					  &simple->bounds, NULL, NULL);
+  g_object_unref (layout);
+
+  /* If the height is set, use that. */
+  if (priv->height > 0.0)
+    simple->bounds.y2 = simple->bounds.y1 + priv->height;
+}
+
+
+static gboolean
+goo_canvas_text_is_unpainted (GooCanvasStyle *style)
+{
+  GValue *value;
+
+  value = goo_canvas_style_get_property (style,
+					 goo_canvas_style_fill_pattern_id);
+
+  /* We only return TRUE if the value is explicitly set to NULL. */
+  if (value && !value->data[0].v_pointer)
+    return TRUE;
+  return FALSE;
+}
+
+
+static gboolean
+goo_canvas_text_is_item_at (GooCanvasItemSimple *simple,
+			    gdouble              x,
+			    gdouble              y,
+			    cairo_t             *cr,
+			    gboolean             is_pointer_event)
+{
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasText *text = (GooCanvasText*) simple;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+  PangoLayout *layout;
+  GooCanvasBounds bounds;
+  PangoLayoutIter *iter;
+  PangoRectangle ink_rect, log_rect;
+  int px, py, x1, y1, x2, y2;
+  int log_x2, ink_x2, log_y2, ink_y2;
+  gdouble origin_x, origin_y;
+  gboolean in_item = FALSE;
+
+  /* If there is no text just return. */
+  if (!text->text_data->text || !text->text_data->text[0])
+    return FALSE;
+
+  if (is_pointer_event
+      && simple_data->pointer_events & GOO_CANVAS_EVENTS_PAINTED_MASK
+      && goo_canvas_text_is_unpainted (simple_data->style))
+    return FALSE;
+
+  /* Check if the point is outside the clipped height. */
+  if (priv->height > 0.0 && y > priv->height)
+    return FALSE;
+
+  layout = goo_canvas_text_create_layout (simple_data, text->text_data,
+					  text->layout_width, cr, &bounds,
+					  &origin_x, &origin_y);
+
+  /* Convert the coordinates into Pango units. */
+  px = (x - origin_x) * PANGO_SCALE;
+  py = (y - origin_y) * PANGO_SCALE;
+
+  /* We use line extents here. Note that SVG uses character cells to determine
+     hits so we have slightly different behavior. */
+  iter = pango_layout_get_iter (layout);
+  do
+    {
+      pango_layout_iter_get_line_extents (iter, &ink_rect, &log_rect);
+
+      /* We use a union of the ink rect and the logical rect, as we want to
+	 let the user click on any part of the ink, even if it extends outside
+	 the character cell (i.e. the ink rect), or click on the space in the
+	 character cell (i.e. the logical rect). */
+      x1 = MIN (log_rect.x, ink_rect.x);
+      y1 = MIN (log_rect.y, ink_rect.y);
+
+      log_x2 = log_rect.x + log_rect.width;
+      ink_x2 = ink_rect.x + ink_rect.width;
+      x2 = MAX (log_x2, ink_x2);
+
+      log_y2 = log_rect.y + log_rect.height;
+      ink_y2 = ink_rect.y + ink_rect.height;
+      y2 = MAX (log_y2, ink_y2);
+
+      if (px >= x1 && px < x2 && py >= y1 && py < y2)
+	{
+	  in_item = TRUE;
+	  break;
+	}
+
+    } while (pango_layout_iter_next_line (iter));
+
+  pango_layout_iter_free (iter);
+
+  g_object_unref (layout);
+
+  return in_item;
+}
+
+
+static void
+goo_canvas_text_paint (GooCanvasItemSimple   *simple,
+		       cairo_t               *cr,
+		       const GooCanvasBounds *bounds)
+{
+  GooCanvasText *text = (GooCanvasText*) simple;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+  PangoLayout *layout;
+  GooCanvasBounds layout_bounds;
+  gdouble origin_x, origin_y;
+
+  /* If there is no text just return. */
+  if (!text->text_data->text || !text->text_data->text[0])
+    return;
+
+  goo_canvas_style_set_fill_options (simple->simple_data->style, cr);
+
+  cairo_new_path (cr);
+  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
+					  text->layout_width, cr,
+					  &layout_bounds,
+					  &origin_x, &origin_y);
+  cairo_save (cr);
+
+  if (priv->height > 0.0)
+    {
+      cairo_rectangle (cr, origin_x, origin_y,
+		       text->layout_width, priv->height);
+      cairo_clip (cr);
+    }
+  cairo_move_to (cr, origin_x, origin_y);
+  pango_cairo_show_layout (cr, layout);
+
+  cairo_restore (cr);
+  g_object_unref (layout);
+}
+
+
+static gdouble
+goo_canvas_text_get_requested_height (GooCanvasItem	*item,
+				      cairo_t		*cr,
+				      gdouble            width)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasItemSimpleData *simple_data = simple->simple_data;
+  GooCanvasText *text = (GooCanvasText*) item;
+  GooCanvasTextPrivate *priv = goo_canvas_text_get_private (text);
+  PangoLayout *layout;
+  gdouble height;
+
+  /* If we have a transformation besides a simple scale & translation, just
+     return -1 as we can't adjust the height in that case. */
+  if (simple_data->clip_path_commands
+      || (simple_data->transform && (simple_data->transform->xy != 0.0
+				     || simple_data->transform->yx != 0.0)))
+    return -1;
+
+  cairo_save (cr);
+  if (simple_data->transform)
+    cairo_transform (cr, simple_data->transform);
+
+  /* Convert the width from the parent's coordinate space. Note that we only
+     need to support a simple scale operation here. */
+  text->layout_width = width;
+  if (simple_data->transform)
+    text->layout_width /= simple_data->transform->xx;
+
+  if (priv->height < 0.0)
+    {
+     /* Create layout with given width. */
+      layout = goo_canvas_text_create_layout (simple_data, text->text_data,
+					      text->layout_width, cr,
+					      &simple->bounds, NULL, NULL);
+      g_object_unref (layout);
+
+      height = simple->bounds.y2 - simple->bounds.y1;
+    }
+  else
+    {
+      height = priv->height;
+    }
+
+  /* Convert to the parent's coordinate space. As above, we only need to
+     support a simple scale operation here. */
+  if (simple_data->transform)
+    height *= simple_data->transform->yy;
+
+  /* Convert the item's bounds to device space. */
+  goo_canvas_item_simple_user_bounds_to_device (simple, cr, &simple->bounds);
+
+  cairo_restore (cr);
+
+  /* Return the new requested height of the text. */
+  return height;
+}
+
+
+/**
+ * goo_canvas_text_get_natural_extents:
+ * @text: a #GooCanvasText.
+ * @ink_rect: the location to return the ink rect, or %NULL.
+ * @logical_rect: the location to return the logical rect, or %NULL.
+ * 
+ * Gets the natural extents of the text, in the text item's coordinate space.
+ *
+ * The final extents of the text may be different, if the text item is placed
+ * in a layout container such as #GooCanvasTable.
+ **/
+void
+goo_canvas_text_get_natural_extents (GooCanvasText  *text,
+				     PangoRectangle *ink_rect,
+				     PangoRectangle *logical_rect)
+{
+  GooCanvasItem *item = (GooCanvasItem*) text;
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) text;
+  PangoLayout *layout;
+  cairo_t *cr;
+
+  if (simple->need_update)
+    goo_canvas_item_ensure_updated (item);
+
+  cr = goo_canvas_create_cairo_context (simple->canvas);
+  layout = goo_canvas_text_create_layout (simple->simple_data, text->text_data,
+					  text->text_data->width, cr, NULL,
+					  NULL, NULL);
+  pango_layout_get_extents (layout, ink_rect, logical_rect);
+  g_object_unref (layout);
+  cairo_destroy (cr);
+}
+
+
+static void
+goo_canvas_text_set_model    (GooCanvasItem      *item,
+			      GooCanvasItemModel *model)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasText *text = (GooCanvasText*) item;
+  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) model;
+
+  /* If our text_data was allocated, free it. */
+  if (!simple->model)
+    g_slice_free (GooCanvasTextData, text->text_data);
+
+  /* Now use the new model's text_data instead. */
+  text->text_data = &tmodel->text_data;
+
+  /* Let the parent GooCanvasItemSimple code do the rest. */
+  goo_canvas_item_simple_set_model (simple, model);
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->get_requested_height = goo_canvas_text_get_requested_height;
+  iface->set_model            = goo_canvas_text_set_model;
+}
+
+
+static void
+goo_canvas_text_class_init (GooCanvasTextClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasTextPrivate));
+
+  gobject_class->finalize = goo_canvas_text_finalize;
+
+  gobject_class->get_property = goo_canvas_text_get_property;
+  gobject_class->set_property = goo_canvas_text_set_property;
+
+  simple_class->simple_update        = goo_canvas_text_update;
+  simple_class->simple_paint         = goo_canvas_text_paint;
+  simple_class->simple_is_item_at    = goo_canvas_text_is_item_at;
+
+  goo_canvas_text_install_common_properties (gobject_class);
+}
+
+
+
+/**
+ * SECTION:goocanvastextmodel
+ * @Title: GooCanvasTextModel
+ * @Short_Description: a model for text items.
+ *
+ * GooCanvasTextModel represents a model for text items.
+ *
+ * It is a subclass of #GooCanvasItemModelSimple and so inherits all of the
+ * style properties such as "fill-color".
+ *
+ * It also implements the #GooCanvasItemModel interface, so you can use the
+ * #GooCanvasItemModel functions such as goo_canvas_item_model_raise() and
+ * goo_canvas_item_model_rotate().
+ *
+ * To create a #GooCanvasTextModel use goo_canvas_text_model_new().
+ *
+ * To get or set the properties of an existing #GooCanvasTextModel, use
+ * g_object_get() and g_object_set().
+ *
+ * To respond to events such as mouse clicks on the text item you must connect
+ * to the signal handlers of the corresponding #GooCanvasText objects.
+ * (See goo_canvas_get_item() and #GooCanvas::item-created.)
+ */
+
+static void item_model_interface_init (GooCanvasItemModelIface *iface);
+static void goo_canvas_text_model_finalize     (GObject            *object);
+static void goo_canvas_text_model_get_property (GObject            *object,
+						guint               param_id,
+						GValue             *value,
+						GParamSpec         *pspec);
+static void goo_canvas_text_model_set_property (GObject            *object,
+						guint               param_id,
+						const GValue       *value,
+						GParamSpec         *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasTextModel, goo_canvas_text_model,
+			 GOO_TYPE_CANVAS_ITEM_MODEL_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_MODEL,
+						item_model_interface_init))
+
+
+static void
+goo_canvas_text_model_class_init (GooCanvasTextModelClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  g_type_class_add_private (gobject_class, sizeof (GooCanvasTextPrivate));
+
+  gobject_class->finalize     = goo_canvas_text_model_finalize;
+
+  gobject_class->get_property = goo_canvas_text_model_get_property;
+  gobject_class->set_property = goo_canvas_text_model_set_property;
+
+  goo_canvas_text_install_common_properties (gobject_class);
+}
+
+
+static void
+goo_canvas_text_model_init (GooCanvasTextModel *tmodel)
+{
+  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);
+
+  tmodel->text_data.width = -1.0;
+  tmodel->text_data.anchor = GOO_CANVAS_ANCHOR_NW;
+  tmodel->text_data.ellipsize = PANGO_ELLIPSIZE_NONE;
+  tmodel->text_data.wrap = PANGO_WRAP_WORD;
+
+  priv->height = -1.0;
+}
+
+
+/**
+ * goo_canvas_text_model_new:
+ * @parent: the parent model, 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.
+ * @string: the text to display.
+ * @x: the x coordinate of the text.
+ * @y: the y coordinate of the text.
+ * @width: the width of the text item, or -1 for unlimited width.
+ * @anchor: the position of the text relative to the given @x and @y
+ *  coordinates. For example an anchor of %GDK_ANCHOR_NW will result in the
+ *  top-left of the text being placed at the given @x and @y coordinates.
+ *  An anchor of %GDK_ANCHOR_CENTER will result in the center of the text being
+ *  placed at the @x and @y coordinates.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ * 
+ * Creates a new text model.
+ * 
+ * <!--PARAMETERS-->
+ *
+ * Here's an example showing how to create a text item with the bottom right
+ * of the text box placed at (500,500):
+ *
+ * <informalexample><programlisting>
+ *  GooCanvasItemModel *text = goo_canvas_text_model_new (mygroup, "Hello World", 500.0, 500.0, 200.0, GOO_CANVAS_ANCHOR_SE,
+ *                                                        "fill-color", "blue",
+ *                                                        NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new text model.
+ **/
+GooCanvasItemModel*
+goo_canvas_text_model_new (GooCanvasItemModel *parent,
+			   const char         *string,
+			   gdouble             x,
+			   gdouble             y,
+			   gdouble             width,
+			   GooCanvasAnchorType       anchor,
+			   ...)
+{
+  GooCanvasItemModel *model;
+  GooCanvasTextModel *tmodel;
+  GooCanvasTextData *text_data;
+  const char *first_property;
+  va_list var_args;
+
+  model = g_object_new (GOO_TYPE_CANVAS_TEXT_MODEL, NULL);
+  tmodel = (GooCanvasTextModel*) model;
+
+  text_data = &tmodel->text_data;
+  text_data->text = g_strdup (string);
+  text_data->x = x;
+  text_data->y = y;
+  text_data->width = width;
+  text_data->anchor = anchor;
+
+  va_start (var_args, anchor);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) model, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_model_add_child (parent, model, -1);
+      g_object_unref (model);
+    }
+
+  return model;
+}
+
+
+static void
+goo_canvas_text_model_finalize (GObject *object)
+{
+  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;
+
+  g_free (tmodel->text_data.text);
+
+  G_OBJECT_CLASS (goo_canvas_text_model_parent_class)->finalize (object);
+}
+
+
+static void
+goo_canvas_text_model_get_property (GObject              *object,
+				    guint                 prop_id,
+				    GValue               *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;
+  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);
+
+  goo_canvas_text_get_common_property (object, &tmodel->text_data, priv,
+				       prop_id, value, pspec);
+}
+
+
+static void
+goo_canvas_text_model_set_property (GObject              *object,
+				    guint                 prop_id,
+				    const GValue         *value,
+				    GParamSpec           *pspec)
+{
+  GooCanvasTextModel *tmodel = (GooCanvasTextModel*) object;
+  GooCanvasTextPrivate *priv = GOO_CANVAS_TEXT_MODEL_GET_PRIVATE (tmodel);
+
+  goo_canvas_text_set_common_property (object, &tmodel->text_data, priv,
+				       prop_id, value, pspec);
+  g_signal_emit_by_name (tmodel, "changed", TRUE);
+}
+
+
+static GooCanvasItem*
+goo_canvas_text_model_create_item (GooCanvasItemModel *model,
+				   GooCanvas          *canvas)
+{
+  GooCanvasItem *item;
+
+  item = g_object_new (GOO_TYPE_CANVAS_TEXT, NULL);
+  goo_canvas_item_set_model (item, model);
+
+  return item;
+}
+
+
+static void
+item_model_interface_init (GooCanvasItemModelIface *iface)
+{
+  iface->create_item    = goo_canvas_text_model_create_item;
+}
diff --git a/libgoocanvas/goocanvastext.h b/libgoocanvas/goocanvastext.h
new file mode 100644
index 0000000..8569a48
--- /dev/null
+++ b/libgoocanvas/goocanvastext.h
@@ -0,0 +1,133 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvastext.h - text item.
+ */
+#ifndef __GOO_CANVAS_TEXT_H__
+#define __GOO_CANVAS_TEXT_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+/* This is the data used by both model and view classes. */
+typedef struct _GooCanvasTextData   GooCanvasTextData;
+struct _GooCanvasTextData
+{
+  gchar *text;
+  gdouble x, y, width;
+  guint use_markup		: 1;
+  guint anchor			: 5;	/* GooCanvasAnchorType */
+  guint alignment		: 3;	/* PangoAlignment */
+  guint ellipsize		: 3;	/* PangoEllipsizeMode */
+  guint wrap			: 3;	/* PangoWrapMode */
+};
+
+
+#define GOO_TYPE_CANVAS_TEXT            (goo_canvas_text_get_type ())
+#define GOO_CANVAS_TEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_TEXT, GooCanvasText))
+#define GOO_CANVAS_TEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_TEXT, GooCanvasTextClass))
+#define GOO_IS_CANVAS_TEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_TEXT))
+#define GOO_IS_CANVAS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_TEXT))
+#define GOO_CANVAS_TEXT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_TEXT, GooCanvasTextClass))
+
+
+typedef struct _GooCanvasText       GooCanvasText;
+typedef struct _GooCanvasTextClass  GooCanvasTextClass;
+
+/**
+ * GooCanvasText
+ *
+ * The #GooCanvasText-struct struct contains private data only.
+ */
+struct _GooCanvasText
+{
+  GooCanvasItemSimple parent;
+
+  GooCanvasTextData *text_data;
+  gdouble layout_width;
+};
+
+struct _GooCanvasTextClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_text_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItem*      goo_canvas_text_new       (GooCanvasItem      *parent,
+					       const char         *string,
+					       gdouble             x,
+					       gdouble             y,
+					       gdouble             width,
+					       GooCanvasAnchorType       anchor,
+					       ...);
+
+void	goo_canvas_text_get_natural_extents   (GooCanvasText  *text,
+					       PangoRectangle *ink_rect,
+					       PangoRectangle *logical_rect);
+
+
+#define GOO_TYPE_CANVAS_TEXT_MODEL            (goo_canvas_text_model_get_type ())
+#define GOO_CANVAS_TEXT_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_TEXT_MODEL, GooCanvasTextModel))
+#define GOO_CANVAS_TEXT_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_TEXT_MODEL, GooCanvasTextModelClass))
+#define GOO_IS_CANVAS_TEXT_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_TEXT_MODEL))
+#define GOO_IS_CANVAS_TEXT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_TEXT_MODEL))
+#define GOO_CANVAS_TEXT_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_TEXT_MODEL, GooCanvasTextModelClass))
+
+
+typedef struct _GooCanvasTextModel       GooCanvasTextModel;
+typedef struct _GooCanvasTextModelClass  GooCanvasTextModelClass;
+
+/**
+ * GooCanvasTextModel
+ *
+ * The #GooCanvasTextModel-struct struct contains private data only.
+ */
+struct _GooCanvasTextModel
+{
+  GooCanvasItemModelSimple parent_object;
+
+  GooCanvasTextData text_data;
+};
+
+struct _GooCanvasTextModelClass
+{
+  GooCanvasItemModelSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType               goo_canvas_text_model_get_type  (void) G_GNUC_CONST;
+
+GooCanvasItemModel* goo_canvas_text_model_new (GooCanvasItemModel *parent,
+					       const char         *string,
+					       gdouble             x,
+					       gdouble             y,
+					       gdouble             width,
+					       GooCanvasAnchorType       anchor,
+					       ...);
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_TEXT_H__ */
diff --git a/libgoocanvas/goocanvasutils.c b/libgoocanvas/goocanvasutils.c
new file mode 100644
index 0000000..ca07559
--- /dev/null
+++ b/libgoocanvas/goocanvasutils.c
@@ -0,0 +1,1294 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasutils.c - utility functions.
+ */
+
+/**
+ * SECTION:goocanvasutils
+ * @Title: GooCanvas Types
+ * @Short_Description: types used in GooCanvas.
+ *
+ * This section describes the types used throughout GooCanvas.
+ */
+#include <config.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include "goocanvas.h"
+
+
+/* Glib doesn't provide a g_ptr_array_index() so we need our own one. */
+void
+goo_canvas_util_ptr_array_insert (GPtrArray *ptr_array,
+				  gpointer   data,
+				  gint       index)
+{
+  gint i;
+
+  /* Add the pointer at the end so there is enough room. */
+  g_ptr_array_add (ptr_array, data);
+
+  /* If index is -1, we are done. */
+  if (index == -1)
+    return;
+
+  /* Move the other pointers to make room for the new one. */
+  for (i = ptr_array->len - 1; i > index; i--)
+    ptr_array->pdata[i] = ptr_array->pdata[i - 1];
+
+  /* Put the new element in its proper place. */
+  ptr_array->pdata[index] = data;
+}
+
+
+/* Glib doesn't provide a g_ptr_array_move() so we need our own one. */
+void
+goo_canvas_util_ptr_array_move (GPtrArray *ptr_array,
+				gint       old_index,
+				gint       new_index)
+{
+  gpointer data;
+  gint i;
+
+  data = ptr_array->pdata[old_index];
+
+  if (new_index > old_index)
+    {
+      /* Move the pointers down one place. */
+      for (i = old_index; i < new_index; i++)
+	ptr_array->pdata[i] = ptr_array->pdata[i + 1];
+    }
+  else
+    {
+      /* Move the pointers up one place. */
+      for (i = old_index; i > new_index; i--)
+	ptr_array->pdata[i] = ptr_array->pdata[i - 1];
+    }
+
+  ptr_array->pdata[new_index] = data;
+}
+
+
+/* Glib doesn't provide a g_ptr_array_move() so we need our own one. */
+gint
+goo_canvas_util_ptr_array_find_index (GPtrArray *ptr_array,
+				      gpointer   data)
+{
+  gint i;
+
+  for (i = 0; i < ptr_array->len; i++)
+    {
+      if (ptr_array->pdata[i] == data)
+	return i;
+    }
+
+  return -1;
+}
+
+
+/*
+ * Cairo utilities.
+ */
+
+cairo_surface_t*
+goo_canvas_cairo_surface_from_pixbuf (GdkPixbuf *pixbuf)
+{
+  gint width = gdk_pixbuf_get_width (pixbuf);
+  gint height = gdk_pixbuf_get_height (pixbuf);
+  guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
+  int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+  int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+  guchar *cairo_pixels;
+  cairo_format_t format;
+  cairo_surface_t *surface;
+  static const cairo_user_data_key_t key;
+  int j;
+
+  if (n_channels == 3)
+    format = CAIRO_FORMAT_RGB24;
+  else
+    format = CAIRO_FORMAT_ARGB32;
+
+  cairo_pixels = g_malloc (4 * width * height);
+  surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels,
+						 format,
+						 width, height, 4 * width);
+  cairo_surface_set_user_data (surface, &key,
+			       cairo_pixels, (cairo_destroy_func_t)g_free);
+
+  for (j = height; j; j--)
+    {
+      guchar *p = gdk_pixels;
+      guchar *q = cairo_pixels;
+
+      if (n_channels == 3)
+	{
+	  guchar *end = p + 3 * width;
+	  
+	  while (p < end)
+	    {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+	      q[0] = p[2];
+	      q[1] = p[1];
+	      q[2] = p[0];
+#else	  
+	      q[1] = p[0];
+	      q[2] = p[1];
+	      q[3] = p[2];
+#endif
+	      p += 3;
+	      q += 4;
+	    }
+	}
+      else
+	{
+	  guchar *end = p + 4 * width;
+	  guint t1,t2,t3;
+	    
+#define MULT(d,c,a,t) G_STMT_START { t = c * a; d = ((t >> 8) + t) >> 8; } G_STMT_END
+
+	  while (p < end)
+	    {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+	      MULT(q[0], p[2], p[3], t1);
+	      MULT(q[1], p[1], p[3], t2);
+	      MULT(q[2], p[0], p[3], t3);
+	      q[3] = p[3];
+#else	  
+	      q[0] = p[3];
+	      MULT(q[1], p[0], p[3], t1);
+	      MULT(q[2], p[1], p[3], t2);
+	      MULT(q[3], p[2], p[3], t3);
+#endif
+	      
+	      p += 4;
+	      q += 4;
+	    }
+	  
+#undef MULT
+	}
+
+      gdk_pixels += gdk_rowstride;
+      cairo_pixels += 4 * width;
+    }
+
+  return surface;
+}
+
+
+cairo_pattern_t*
+goo_canvas_cairo_pattern_from_pixbuf (GdkPixbuf *pixbuf)
+{
+  cairo_surface_t *surface;
+  cairo_pattern_t *pattern;
+
+  surface = goo_canvas_cairo_surface_from_pixbuf (pixbuf);
+  pattern = cairo_pattern_create_for_surface (surface);
+  cairo_surface_destroy (surface);
+
+  return pattern;
+}
+
+
+/*
+ * Cairo types.
+ */
+GType
+goo_cairo_pattern_get_type (void)
+{
+  static GType cairo_pattern_type = 0;
+  
+  if (cairo_pattern_type == 0)
+    cairo_pattern_type = g_boxed_type_register_static
+      ("GooCairoPattern",
+       (GBoxedCopyFunc) cairo_pattern_reference,
+       (GBoxedFreeFunc) cairo_pattern_destroy);
+
+  return cairo_pattern_type;
+}
+
+
+GType
+goo_cairo_fill_rule_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_FILL_RULE_WINDING, "CAIRO_FILL_RULE_WINDING", "winding" },
+      { CAIRO_FILL_RULE_EVEN_ODD, "CAIRO_FILL_RULE_EVEN_ODD", "even-odd" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoFillRule", values);
+  }
+  return etype;
+}
+
+
+GType
+goo_cairo_operator_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_OPERATOR_CLEAR, "CAIRO_OPERATOR_CLEAR", "clear" },
+
+      { CAIRO_OPERATOR_SOURCE, "CAIRO_OPERATOR_SOURCE", "source" },
+      { CAIRO_OPERATOR_OVER, "CAIRO_OPERATOR_OVER", "over" },
+      { CAIRO_OPERATOR_IN, "CAIRO_OPERATOR_IN", "in" },
+      { CAIRO_OPERATOR_OUT, "CAIRO_OPERATOR_OUT", "out" },
+      { CAIRO_OPERATOR_ATOP, "CAIRO_OPERATOR_ATOP", "atop" },
+
+      { CAIRO_OPERATOR_DEST, "CAIRO_OPERATOR_DEST", "dest" },
+      { CAIRO_OPERATOR_DEST_OVER, "CAIRO_OPERATOR_DEST_OVER", "dest-over" },
+      { CAIRO_OPERATOR_DEST_IN, "CAIRO_OPERATOR_DEST_IN", "dest-in" },
+      { CAIRO_OPERATOR_DEST_OUT, "CAIRO_OPERATOR_DEST_OUT", "dest-out" },
+      { CAIRO_OPERATOR_DEST_ATOP, "CAIRO_OPERATOR_DEST_ATOP", "dest-atop" },
+
+      { CAIRO_OPERATOR_XOR, "CAIRO_OPERATOR_XOR", "xor" },
+      { CAIRO_OPERATOR_ADD, "CAIRO_OPERATOR_ADD", "add" },
+      { CAIRO_OPERATOR_SATURATE, "CAIRO_OPERATOR_SATURATE", "saturate" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoOperator", values);
+  }
+  return etype;
+}
+
+
+GType
+goo_cairo_antialias_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_ANTIALIAS_DEFAULT, "CAIRO_ANTIALIAS_DEFAULT", "default" },
+      { CAIRO_ANTIALIAS_NONE, "CAIRO_ANTIALIAS_NONE", "none" },
+      { CAIRO_ANTIALIAS_GRAY, "CAIRO_ANTIALIAS_GRAY", "gray" },
+      { CAIRO_ANTIALIAS_SUBPIXEL, "CAIRO_ANTIALIAS_SUBPIXEL", "subpixel" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoAntialias", values);
+  }
+  return etype;
+}
+
+
+GType
+goo_cairo_line_cap_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_LINE_CAP_BUTT, "CAIRO_LINE_CAP_BUTT", "butt" },
+      { CAIRO_LINE_CAP_ROUND, "CAIRO_LINE_CAP_ROUND", "round" },
+      { CAIRO_LINE_CAP_SQUARE, "CAIRO_LINE_CAP_SQUARE", "square" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoLineCap", values);
+  }
+  return etype;
+}
+
+
+GType
+goo_cairo_line_join_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_LINE_JOIN_MITER, "CAIRO_LINE_JOIN_MITER", "miter" },
+      { CAIRO_LINE_JOIN_ROUND, "CAIRO_LINE_JOIN_ROUND", "round" },
+      { CAIRO_LINE_JOIN_BEVEL, "CAIRO_LINE_JOIN_BEVEL", "bevel" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoLineJoin", values);
+  }
+  return etype;
+}
+
+
+GType
+goo_cairo_hint_metrics_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      { CAIRO_HINT_METRICS_DEFAULT, "CAIRO_HINT_METRICS_DEFAULT", "default" },
+      { CAIRO_HINT_METRICS_OFF, "CAIRO_HINT_METRICS_OFF", "off" },
+      { CAIRO_HINT_METRICS_ON, "CAIRO_HINT_METRICS_ON", "on" },
+      { 0, NULL, NULL }
+    };
+    etype = g_enum_register_static ("GooCairoHintMetrics", values);
+  }
+  return etype;
+}
+
+
+/**
+ * goo_canvas_line_dash_ref:
+ * @dash: a #GooCanvasLineDash.
+ * 
+ * Increments the reference count of the dash pattern.
+ * 
+ * Returns: the dash pattern.
+ **/
+GooCanvasLineDash*
+goo_canvas_line_dash_ref   (GooCanvasLineDash *dash)
+{
+  if (dash)
+    dash->ref_count++;
+  return dash;
+}
+
+
+/**
+ * goo_canvas_line_dash_unref:
+ * @dash: a #GooCanvasLineDash.
+ * 
+ * Decrements the reference count of the dash pattern. If it falls to 0
+ * it is freed.
+ **/
+void
+goo_canvas_line_dash_unref (GooCanvasLineDash *dash)
+{
+  if (dash && --dash->ref_count == 0) {
+    g_free (dash->dashes);
+    g_free (dash);
+  }
+}
+
+
+GType
+goo_canvas_line_dash_get_type (void)
+{
+  static GType cairo_line_dash_type = 0;
+  
+  if (cairo_line_dash_type == 0)
+    cairo_line_dash_type = g_boxed_type_register_static
+      ("GooCanvasLineDash",
+       (GBoxedCopyFunc) goo_canvas_line_dash_ref,
+       (GBoxedFreeFunc) goo_canvas_line_dash_unref);
+
+  return cairo_line_dash_type;
+}
+
+
+/**
+ * goo_canvas_line_dash_new:
+ * @num_dashes: the number of dashes and gaps in the pattern.
+ * @...: the length of each dash and gap.
+ * 
+ * Creates a new dash pattern.
+ * 
+ * Returns: a new dash pattern.
+ **/
+GooCanvasLineDash*
+goo_canvas_line_dash_new (gint num_dashes,
+			  ...)
+{
+  GooCanvasLineDash *dash;
+  va_list var_args;
+  gint i;
+
+  dash = g_new (GooCanvasLineDash, 1);
+  dash->ref_count = 1;
+  dash->num_dashes = num_dashes;
+  dash->dashes = g_new (double, num_dashes);
+  dash->dash_offset = 0.0;
+
+  va_start (var_args, num_dashes);
+
+  for (i = 0; i < num_dashes; i++)
+    {
+      dash->dashes[i] = va_arg (var_args, double);
+    }
+
+  va_end (var_args);
+
+  return dash;
+}
+
+/**
+ * goo_canvas_line_dash_newv:
+ * @num_dashes: the number of dashes and gaps in the pattern.
+ * @dashes: a g_new-allocated vector of doubles, the length of each
+ * dash and gap.
+ * 
+ * Creates a new dash pattern.  Takes ownership of the @dashes vector.
+ * 
+ * Returns: a new dash pattern.
+ **/
+GooCanvasLineDash*
+goo_canvas_line_dash_newv (gint    num_dashes,
+                           double *dashes)
+{
+  GooCanvasLineDash *dash;
+
+  dash = g_new (GooCanvasLineDash, 1);
+  dash->ref_count = 1;
+  dash->num_dashes = num_dashes;
+  dash->dashes = dashes;
+  dash->dash_offset = 0.0;
+
+  return dash;
+}
+
+cairo_matrix_t*
+goo_cairo_matrix_copy   (const cairo_matrix_t *matrix)
+{
+  cairo_matrix_t *matrix_copy;
+
+  if (!matrix)
+    return NULL;
+
+  matrix_copy = g_slice_new (cairo_matrix_t);
+  *matrix_copy = *matrix;
+
+  return matrix_copy;
+}
+
+
+void
+goo_cairo_matrix_free   (cairo_matrix_t *matrix)
+{
+  g_slice_free (cairo_matrix_t, matrix);
+}
+
+
+GType
+goo_cairo_matrix_get_type (void)
+{
+  static GType type_cairo_matrix = 0;
+
+  if (!type_cairo_matrix)
+    type_cairo_matrix = g_boxed_type_register_static
+      ("GooCairoMatrix", 
+       (GBoxedCopyFunc) goo_cairo_matrix_copy,
+       (GBoxedFreeFunc) goo_cairo_matrix_free);
+
+  return type_cairo_matrix;
+}
+
+
+/* This is a semi-private function to help gtk-doc find child properties. */
+GParamSpec**
+goo_canvas_query_child_properties (gpointer  class,
+				   guint     *n_properties)
+{
+  if (!G_TYPE_IS_CLASSED (G_TYPE_FROM_CLASS (class)))
+    return NULL;
+
+  if (g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM))
+    return goo_canvas_item_class_list_child_properties (class,
+							n_properties);
+
+  if (g_type_interface_peek (class, GOO_TYPE_CANVAS_ITEM_MODEL))
+    return goo_canvas_item_model_class_list_child_properties (class,
+							      n_properties);
+
+  return NULL;
+}
+
+
+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;
+}
+
+
+/**
+ * goo_canvas_parse_path_data:
+ * @path_data: the sequence of path commands, specified as a string using the
+ *  same syntax as in the <ulink url="http://www.w3.org/Graphics/SVG/";>Scalable
+ *  Vector Graphics (SVG)</ulink> path element.
+ * 
+ * Parses the given SVG path specification string.
+ * 
+ * Returns: a #GArray of #GooCanvasPathCommand elements.
+ **/
+GArray*
+goo_canvas_parse_path_data (const gchar       *path_data)
+{
+  GArray *commands;
+  GooCanvasPathCommand cmd;
+  gchar *pos, command = 0, next_command;
+  gboolean error = FALSE;
+
+  commands = g_array_new (0, 0, sizeof (GooCanvasPathCommand));
+
+  if (!path_data)
+    return commands;
+
+  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 commands;
+
+      g_array_append_val (commands, cmd);
+    }
+
+  return commands;
+}
+
+
+static void
+do_curve_to (GooCanvasPathCommand *cmd,
+	     cairo_t              *cr,
+	     gdouble              *x,
+	     gdouble              *y,
+	     gdouble              *last_control_point_x,
+	     gdouble              *last_control_point_y)
+{
+  if (cmd->curve.relative)
+    {
+      cairo_curve_to (cr, *x + cmd->curve.x1, *y + cmd->curve.y1,
+		      *x + cmd->curve.x2, *y + cmd->curve.y2,
+		      *x + cmd->curve.x, *y + cmd->curve.y);
+      *last_control_point_x = *x + cmd->curve.x2;
+      *last_control_point_y = *y + cmd->curve.y2;
+      *x += cmd->curve.x;
+      *y += cmd->curve.y;
+    }
+  else
+    {
+      cairo_curve_to (cr, cmd->curve.x1, cmd->curve.y1,
+		      cmd->curve.x2, cmd->curve.y2,
+		      cmd->curve.x, cmd->curve.y);
+      *last_control_point_x = cmd->curve.x2;
+      *last_control_point_y = cmd->curve.y2;
+      *x = cmd->curve.x;
+      *y = cmd->curve.y;
+    }
+}
+
+
+static void
+do_smooth_curve_to (GooCanvasPathCommand    *cmd,
+		    GooCanvasPathCommandType prev_cmd_type,
+		    cairo_t                 *cr,
+		    gdouble                 *x,
+		    gdouble                 *y,
+		    gdouble                 *last_control_point_x,
+		    gdouble                 *last_control_point_y)
+{
+  gdouble x1, y1;
+
+  /* If the last command was a curveto or smooth curveto, we use the
+     reflection of the last control point about the current point as
+     the first control point of this curve. Otherwise we use the
+     current point as the first control point. */
+  if (prev_cmd_type == GOO_CANVAS_PATH_CURVE_TO
+      || prev_cmd_type == GOO_CANVAS_PATH_SMOOTH_CURVE_TO)
+    {
+      x1 = *x + (*x - *last_control_point_x);
+      y1 = *y + (*y - *last_control_point_y);
+    }
+  else
+    {
+      x1 = *x;
+      y1 = *y;
+    }
+
+  if (cmd->curve.relative)
+    {
+      cairo_curve_to (cr, x1, y1, *x + cmd->curve.x2, *y + cmd->curve.y2,
+		      *x + cmd->curve.x, *y + cmd->curve.y);
+      *last_control_point_x = *x + cmd->curve.x2;
+      *last_control_point_y = *y + cmd->curve.y2;
+      *x += cmd->curve.x;
+      *y += cmd->curve.y;
+    }
+  else
+    {
+      cairo_curve_to (cr, x1, y1, cmd->curve.x2, cmd->curve.y2,
+		      cmd->curve.x, cmd->curve.y);
+      *last_control_point_x = cmd->curve.x2;
+      *last_control_point_y = cmd->curve.y2;
+      *x = cmd->curve.x;
+      *y = cmd->curve.y;
+    }
+}
+
+
+static void
+do_quadratic_curve_to (GooCanvasPathCommand *cmd,
+		       cairo_t              *cr,
+		       gdouble              *x,
+		       gdouble              *y,
+		       gdouble              *last_control_point_x,
+		       gdouble              *last_control_point_y)
+{
+  gdouble qx1, qy1, qx2, qy2, x1, y1, x2, y2;
+
+  if (cmd->curve.relative)
+    {
+      qx1 = *x + cmd->curve.x1;
+      qy1 = *y + cmd->curve.y1;
+      qx2 = *x + cmd->curve.x;
+      qy2 = *y + cmd->curve.y;
+    }
+  else
+    {
+      qx1 = cmd->curve.x1;
+      qy1 = cmd->curve.y1;
+      qx2 = cmd->curve.x;
+      qy2 = cmd->curve.y;
+    }
+
+  /* We need to convert the quadratic into a cubic bezier. */
+  x1 = *x + (qx1 - *x) * 2.0 / 3.0;
+  y1 = *y + (qy1 - *y) * 2.0 / 3.0;
+
+  x2 = x1 + (qx2 - *x) / 3.0;
+  y2 = y1 + (qy2 - *y) / 3.0;
+
+  cairo_curve_to (cr, x1, y1, x2, y2, qx2, qy2);
+
+  *x = qx2;
+  *y = qy2;
+  *last_control_point_x = qx1;
+  *last_control_point_y = qy1;
+}
+
+
+static void
+do_smooth_quadratic_curve_to (GooCanvasPathCommand    *cmd,
+			      GooCanvasPathCommandType prev_cmd_type,
+			      cairo_t                 *cr,
+			      gdouble                 *x,
+			      gdouble                 *y,
+			      gdouble                 *last_control_point_x,
+			      gdouble                 *last_control_point_y)
+{
+  gdouble qx1, qy1, qx2, qy2, x1, y1, x2, y2;
+
+  /* If the last command was a quadratic or smooth quadratic, we use
+     the reflection of the last control point about the current point
+     as the first control point of this curve. Otherwise we use the
+     current point as the first control point. */
+  if (prev_cmd_type == GOO_CANVAS_PATH_QUADRATIC_CURVE_TO
+      || prev_cmd_type == GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO)
+    {
+      qx1 = *x + (*x - *last_control_point_x);
+      qy1 = *y + (*y - *last_control_point_y);
+    }
+  else
+    {
+      qx1 = *x;
+      qy1 = *y;
+    }
+
+  if (cmd->curve.relative)
+    {
+      qx2 = *x + cmd->curve.x;
+      qy2 = *y + cmd->curve.y;
+    }
+  else
+    {
+      qx2 = cmd->curve.x;
+      qy2 = cmd->curve.y;
+    }
+
+  /* We need to convert the quadratic into a cubic bezier. */
+  x1 = *x + (qx1 - *x) * 2.0 / 3.0;
+  y1 = *y + (qy1 - *y) * 2.0 / 3.0;
+
+  x2 = x1 + (qx2 - *x) / 3.0;
+  y2 = y1 + (qy2 - *y) / 3.0;
+
+  cairo_curve_to (cr, x1, y1, x2, y2, qx2, qy2);
+
+  *x = qx2;
+  *y = qy2;
+  *last_control_point_x = qx1;
+  *last_control_point_y = qy1;
+}
+
+
+static gdouble
+calc_angle (gdouble ux, gdouble uy, gdouble vx, gdouble vy)
+{
+  gdouble top, u_magnitude, v_magnitude, angle_cos, angle;
+
+  top = ux * vx + uy * vy;
+  u_magnitude = sqrt (ux * ux + uy * uy);
+  v_magnitude = sqrt (vx * vx + vy * vy);
+  angle_cos = top / (u_magnitude * v_magnitude);
+
+  /* We check if the cosine is slightly out-of-bounds. */
+  if (angle_cos >= 1.0)
+    angle = 0.0;
+  if (angle_cos <= -1.0)
+    angle = M_PI;
+  else
+    angle = acos (angle_cos);
+
+  if (ux * vy - uy * vx < 0)
+    angle = - angle;
+
+  return angle;
+}
+
+
+/* FIXME: Maybe we should do these calculations once when the path data is
+   parsed, and keep the cairo parameters we need in the command instead. */
+static void
+do_elliptical_arc (GooCanvasPathCommand    *cmd,
+		   cairo_t                 *cr,
+		   gdouble                 *x,
+		   gdouble                 *y)
+{
+  gdouble x1 = *x, y1 = *y, x2, y2, rx, ry, lambda;
+  gdouble v1, v2, angle, angle_sin, angle_cos, x11, y11;
+  gdouble rx_squared, ry_squared, x11_squared, y11_squared, top, bottom;
+  gdouble c, cx1, cy1, cx, cy, start_angle, angle_delta;
+
+  /* Calculate the end point of the arc - x2,y2. */
+  if (cmd->arc.relative)
+    {
+      x2 = x1 + cmd->arc.x;
+      y2 = y1 + cmd->arc.y;
+    }
+  else
+    {
+      x2 = cmd->arc.x;
+      y2 = cmd->arc.y;
+    }
+
+  *x = x2;
+  *y = y2;
+
+  /* If the endpoints are exactly the same, just return (see SVG spec). */
+  if (x1 == x2 && y1 == y2)
+    return;
+
+  /* If either rx or ry is 0, do a simple lineto (see SVG spec). */
+  if (cmd->arc.rx == 0.0 || cmd->arc.ry == 0.0)
+    {
+      cairo_line_to (cr, x2, y2);
+      return;
+    }
+
+  /* Calculate x1' and y1' (as per SVG implementation notes). */
+  v1 = (x1 - x2) / 2.0;
+  v2 = (y1 - y2) / 2.0;
+
+  angle = cmd->arc.x_axis_rotation * (M_PI / 180.0);
+  angle_sin = sin (angle);
+  angle_cos = cos (angle);
+
+  x11 = (angle_cos * v1) + (angle_sin * v2);
+  y11 = - (angle_sin * v1) + (angle_cos * v2);
+
+  /* Ensure rx and ry are positive and large enough. */
+  rx = cmd->arc.rx > 0.0 ? cmd->arc.rx : - cmd->arc.rx;
+  ry = cmd->arc.ry > 0.0 ? cmd->arc.ry : - cmd->arc.ry;
+  lambda = (x11 * x11) / (rx * rx) + (y11 * y11) / (ry * ry);
+  if (lambda > 1.0)
+    {
+      gdouble square_root = sqrt (lambda);
+      rx *= square_root;
+      ry *= square_root;
+    }
+
+  /* Calculate cx' and cy'. */
+  rx_squared = rx * rx;
+  ry_squared = ry * ry;
+  x11_squared = x11 * x11;
+  y11_squared = y11 * y11;
+
+  top = (rx_squared * ry_squared) - (rx_squared * y11_squared)
+    - (ry_squared * x11_squared);
+  if (top < 0.0)
+    {
+      c = 0.0;
+    }
+  else
+    {
+      bottom = (rx_squared * y11_squared) + (ry_squared * x11_squared);
+      c = sqrt (top / bottom);
+    }
+
+  if (cmd->arc.large_arc_flag == cmd->arc.sweep_flag)
+    c = - c;
+
+  cx1 = c * ((rx * y11) / ry);
+  cy1 = c * (- (ry * x11) / rx);
+
+  /* Calculate cx and cy. */
+  cx = (angle_cos * cx1) - (angle_sin * cy1) + (x1 + x2) / 2;
+  cy = (angle_sin * cx1) + (angle_cos * cy1) + (y1 + y2) / 2;
+
+  /* Calculate the start and end angles. */
+  v1 = (x11 - cx1) / rx;
+  v2 = (y11 - cy1) / ry;
+
+  start_angle = calc_angle (1, 0, v1, v2);
+  angle_delta = calc_angle (v1, v2, (-x11 - cx1) / rx, (-y11 - cy1) / ry);
+
+  if (cmd->arc.sweep_flag == 0 && angle_delta > 0.0)
+    angle_delta -= 2 * M_PI;
+  else if (cmd->arc.sweep_flag == 1 && angle_delta < 0.0)
+    angle_delta += 2 * M_PI;
+
+  /* Now draw the arc. */
+  cairo_save (cr);
+  cairo_translate (cr, cx, cy);
+  cairo_rotate (cr, angle);
+  cairo_scale (cr, rx, ry);
+
+  if (angle_delta > 0.0)
+    cairo_arc (cr, 0.0, 0.0, 1.0,
+	       start_angle, start_angle + angle_delta);
+  else
+    cairo_arc_negative (cr, 0.0, 0.0, 1.0,
+			start_angle, start_angle + angle_delta);
+
+  cairo_restore (cr);
+}
+
+
+/**
+ * goo_canvas_create_path:
+ * @commands: an array of #GooCanvasPathCommand.
+ * @cr: a cairo context.
+ * 
+ * Creates the path specified by the given #GooCanvasPathCommand array.
+ **/
+void
+goo_canvas_create_path (GArray              *commands,
+			cairo_t             *cr)
+{
+  GooCanvasPathCommand *cmd;
+  GooCanvasPathCommandType prev_cmd_type = GOO_CANVAS_PATH_CLOSE_PATH;
+  gdouble x = 0, y = 0, path_start_x = 0, path_start_y = 0;
+  gdouble last_control_point_x = 0.0, last_control_point_y = 0.0;
+  gint i;
+
+  cairo_new_path (cr);
+
+  if (!commands || commands->len == 0)
+    return;
+
+  for (i = 0; i < commands->len; i++)
+    {
+      cmd = &g_array_index (commands, GooCanvasPathCommand, i);
+      switch (cmd->simple.type)
+	{
+	  /* Simple commands like moveto and lineto: MmZzLlHhVv. */
+	case GOO_CANVAS_PATH_MOVE_TO:
+	  if (cmd->simple.relative)
+	    {
+	      x += cmd->simple.x;
+	      y += cmd->simple.y;
+	    }
+	  else
+	    {
+	      x = cmd->simple.x;
+	      y = cmd->simple.y;
+	    }
+	  path_start_x = x;
+	  path_start_y = y;
+	  cairo_move_to (cr, x, y);
+	  break;
+
+	case GOO_CANVAS_PATH_CLOSE_PATH:
+	  x = path_start_x;
+	  y = path_start_y;
+	  cairo_close_path (cr);
+	  break;
+
+	case GOO_CANVAS_PATH_LINE_TO:
+	  if (cmd->simple.relative)
+	    {
+	      x += cmd->simple.x;
+	      y += cmd->simple.y;
+	    }
+	  else
+	    {
+	      x = cmd->simple.x;
+	      y = cmd->simple.y;
+	    }
+	  cairo_line_to (cr, x, y);
+	  break;
+
+	case GOO_CANVAS_PATH_HORIZONTAL_LINE_TO:
+	  if (cmd->simple.relative)
+	    x += cmd->simple.x;
+	  else
+	    x = cmd->simple.x;
+	  cairo_line_to (cr, x, y);
+	  break;
+
+	case GOO_CANVAS_PATH_VERTICAL_LINE_TO:
+	  if (cmd->simple.relative)
+	    y += cmd->simple.y;
+	  else
+	    y = cmd->simple.y;
+	  cairo_line_to (cr, x, y);
+	  break;
+
+	  /* Bezier curve commands: CcSsQqTt. */
+	case GOO_CANVAS_PATH_CURVE_TO:
+	  do_curve_to (cmd, cr, &x, &y,
+		       &last_control_point_x, &last_control_point_y);
+	  break;
+
+	case GOO_CANVAS_PATH_SMOOTH_CURVE_TO:
+	  do_smooth_curve_to (cmd, prev_cmd_type, cr, &x, &y,
+			      &last_control_point_x, &last_control_point_y);
+	  break;
+
+	case GOO_CANVAS_PATH_QUADRATIC_CURVE_TO:
+	  do_quadratic_curve_to (cmd, cr, &x, &y,
+				 &last_control_point_x, &last_control_point_y);
+	  break;
+
+	case GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO:
+	  do_smooth_quadratic_curve_to (cmd, prev_cmd_type, cr, &x, &y,
+					&last_control_point_x,
+					&last_control_point_y);
+	  break;
+
+	  /* The elliptical arc commands: Aa. */
+	case GOO_CANVAS_PATH_ELLIPTICAL_ARC:
+	  do_elliptical_arc (cmd, cr, &x, &y);
+	  break;
+	}
+
+      prev_cmd_type = cmd->simple.type;
+    }
+}
+
+
+/* This is a copy of _gtk_boolean_handled_accumulator. */
+gboolean
+goo_canvas_boolean_handled_accumulator (GSignalInvocationHint *ihint,
+					GValue                *return_accu,
+					const GValue          *handler_return,
+					gpointer               dummy)
+{
+  gboolean continue_emission;
+  gboolean signal_handled;
+  
+  signal_handled = g_value_get_boolean (handler_return);
+  g_value_set_boolean (return_accu, signal_handled);
+  continue_emission = !signal_handled;
+  
+  return continue_emission;
+}
+
+
+static GooCanvasBounds *
+goo_canvas_bounds_copy (const GooCanvasBounds *bounds)
+{
+  GooCanvasBounds *result = g_new (GooCanvasBounds, 1);
+  *result = *bounds;
+
+  return result;
+}
+
+GType
+goo_canvas_bounds_get_type (void)
+{
+  static GType our_type = 0;
+  
+  if (our_type == 0)
+    our_type = g_boxed_type_register_static
+      ("GooCanvasBounds",
+       (GBoxedCopyFunc) goo_canvas_bounds_copy,
+       (GBoxedFreeFunc) g_free);
+
+  return our_type;
+}
+
+
+/* Converts red, green, blue and alpha doubles to an RGBA guint. */
+guint
+goo_canvas_convert_colors_to_rgba (double red,
+				   double green,
+				   double blue,
+				   double alpha)
+{
+  guint red_byte, green_byte, blue_byte, alpha_byte;
+
+  red_byte = red * 256;
+  red_byte -= red_byte >> 8;
+
+  green_byte = green * 256;
+  green_byte -= green_byte >> 8;
+
+  blue_byte = blue * 256;
+  blue_byte -= blue_byte >> 8;
+
+  alpha_byte = alpha * 256;
+  alpha_byte -= alpha_byte >> 8;
+
+  return (red_byte << 24) + (green_byte << 16) + (blue_byte << 8) + alpha_byte;
+}
+
+
+void
+goo_canvas_get_rgba_value_from_pattern (cairo_pattern_t *pattern,
+					GValue          *value)
+{
+  double red, green, blue, alpha;
+  guint rgba = 0;
+
+  if (pattern && cairo_pattern_get_type (pattern) == CAIRO_PATTERN_TYPE_SOLID)
+    {
+      cairo_pattern_get_rgba (pattern, &red, &green, &blue, &alpha);
+      rgba = goo_canvas_convert_colors_to_rgba (red, green, blue, alpha);
+    }
+  g_value_set_uint (value, rgba);
+}
+
+
+/* Sets a style property to the given pattern, taking ownership of it. */
+void
+goo_canvas_set_style_property_from_pattern (GooCanvasStyle  *style,
+					    GQuark           property_id,
+					    cairo_pattern_t *pattern)
+{
+  GValue tmpval = { 0 };
+
+  g_value_init (&tmpval, GOO_TYPE_CAIRO_PATTERN);
+  g_value_take_boxed (&tmpval, pattern);
+  goo_canvas_style_set_property (style, property_id, &tmpval);
+  g_value_unset (&tmpval);
+}
+
+
+cairo_pattern_t*
+goo_canvas_create_pattern_from_color_value (const GValue *value)
+{
+  GdkColor color = { 0, 0, 0, 0, };
+
+  if (g_value_get_string (value))
+    gdk_color_parse (g_value_get_string (value), &color);
+
+  return cairo_pattern_create_rgb (color.red / 65535.0,
+				   color.green / 65535.0,
+				   color.blue / 65535.0);
+}
+  
+
+cairo_pattern_t*
+goo_canvas_create_pattern_from_rgba_value (const GValue *value)
+{
+  guint rgba, red, green, blue, alpha;
+
+  rgba = g_value_get_uint (value);
+  red   = (rgba >> 24) & 0xFF;
+  green = (rgba >> 16) & 0xFF;
+  blue  = (rgba >> 8)  & 0xFF;
+  alpha = (rgba)       & 0xFF;
+
+  return cairo_pattern_create_rgba (red / 255.0, green / 255.0,
+				    blue / 255.0, alpha / 255.0);
+}
+
+
+cairo_pattern_t*
+goo_canvas_create_pattern_from_pixbuf_value (const GValue *value)
+{
+  GdkPixbuf *pixbuf;
+  cairo_surface_t *surface;
+  cairo_pattern_t *pattern;
+
+  pixbuf = g_value_get_object (value);
+  surface = goo_canvas_cairo_surface_from_pixbuf (pixbuf);
+  pattern = cairo_pattern_create_for_surface (surface);
+  cairo_surface_destroy (surface);
+  cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+  return pattern;
+}
diff --git a/libgoocanvas/goocanvasutils.h b/libgoocanvas/goocanvasutils.h
new file mode 100644
index 0000000..23bbda0
--- /dev/null
+++ b/libgoocanvas/goocanvasutils.h
@@ -0,0 +1,378 @@
+/*
+ * GooCanvas. Copyright (C) 2005 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvasutils.h - utility functions.
+ */
+#ifndef __GOO_CANVAS_UTILS_H__
+#define __GOO_CANVAS_UTILS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+
+/*
+ * Enum types.
+ */
+
+/**
+ * GooCanvasPointerEvents
+ * @GOO_CANVAS_EVENTS_VISIBLE_MASK: a mask indicating that the item only
+ *  receives events when it is visible.
+ * @GOO_CANVAS_EVENTS_PAINTED_MASK: a mask indicating that the item only
+ *  receives events when the specified parts of it are painted.
+ * @GOO_CANVAS_EVENTS_FILL_MASK: a mask indicating that the filled part of
+ *  the item receives events.
+ * @GOO_CANVAS_EVENTS_STROKE_MASK: a mask indicating that the stroked part
+ *  of the item receives events.
+ * @GOO_CANVAS_EVENTS_NONE: the item doesn't receive events at all.
+ * @GOO_CANVAS_EVENTS_VISIBLE_PAINTED: the item receives events in its
+ *  painted areas when it is visible (the default).
+ * @GOO_CANVAS_EVENTS_VISIBLE_FILL: the item's interior receives events
+ *  when it is visible.
+ * @GOO_CANVAS_EVENTS_VISIBLE_STROKE: the item's perimeter receives
+ *  events when it is visible.
+ * @GOO_CANVAS_EVENTS_VISIBLE: the item receives events when it is visible,
+ *  whether it is painted or not.
+ * @GOO_CANVAS_EVENTS_PAINTED: the item receives events in its painted areas,
+ *  whether it is visible or not.
+ * @GOO_CANVAS_EVENTS_FILL: the item's interior receives events, whether it
+ *  is visible or painted or not.
+ * @GOO_CANVAS_EVENTS_STROKE: the item's perimeter receives events, whether
+ *  it is visible or painted or not.
+ * @GOO_CANVAS_EVENTS_ALL: the item's perimeter and interior receive events,
+ *  whether it is visible or painted or not.
+ *
+ * Specifies when an item receives pointer events such as mouse clicks.
+ */
+typedef enum
+{
+  GOO_CANVAS_EVENTS_VISIBLE_MASK	= 1 << 0,
+  GOO_CANVAS_EVENTS_PAINTED_MASK	= 1 << 1,
+  GOO_CANVAS_EVENTS_FILL_MASK		= 1 << 2,
+  GOO_CANVAS_EVENTS_STROKE_MASK		= 1 << 3,
+
+  GOO_CANVAS_EVENTS_NONE		= 0,
+  GOO_CANVAS_EVENTS_VISIBLE_PAINTED	= GOO_CANVAS_EVENTS_VISIBLE_MASK | GOO_CANVAS_EVENTS_PAINTED_MASK | GOO_CANVAS_EVENTS_FILL_MASK | GOO_CANVAS_EVENTS_STROKE_MASK,
+  GOO_CANVAS_EVENTS_VISIBLE_FILL	= GOO_CANVAS_EVENTS_VISIBLE_MASK | GOO_CANVAS_EVENTS_FILL_MASK,
+  GOO_CANVAS_EVENTS_VISIBLE_STROKE	= GOO_CANVAS_EVENTS_VISIBLE_MASK | GOO_CANVAS_EVENTS_STROKE_MASK,
+  GOO_CANVAS_EVENTS_VISIBLE		= GOO_CANVAS_EVENTS_VISIBLE_MASK | GOO_CANVAS_EVENTS_FILL_MASK | GOO_CANVAS_EVENTS_STROKE_MASK,
+  GOO_CANVAS_EVENTS_PAINTED		= GOO_CANVAS_EVENTS_PAINTED_MASK | GOO_CANVAS_EVENTS_FILL_MASK | GOO_CANVAS_EVENTS_STROKE_MASK,
+  GOO_CANVAS_EVENTS_FILL		= GOO_CANVAS_EVENTS_FILL_MASK,
+  GOO_CANVAS_EVENTS_STROKE		= GOO_CANVAS_EVENTS_STROKE_MASK,
+  GOO_CANVAS_EVENTS_ALL			= GOO_CANVAS_EVENTS_FILL_MASK | GOO_CANVAS_EVENTS_STROKE_MASK
+} GooCanvasPointerEvents;
+
+
+/**
+ * GooCanvasItemVisibility
+ * @GOO_CANVAS_ITEM_HIDDEN: the item is invisible, and is not allocated any
+ *  space in layout container items such as #GooCanvasTable.
+ * @GOO_CANVAS_ITEM_INVISIBLE: the item is invisible, but it is still allocated
+ *  space in layout container items.
+ * @GOO_CANVAS_ITEM_VISIBLE: the item is visible.
+ * @GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD: the item is visible when the
+ *  canvas scale setting is greater than or equal to the item's visibility
+ *  threshold setting.
+ *
+ * The #GooCanvasItemVisibility enumeration is used to specify when a canvas
+ * item is visible.
+ */
+/* NOTE: Values are important here, as we test for <= GOO_CANVAS_ITEM_INVISIBLE
+   to check if an item is invisible or hidden. */
+typedef enum
+{
+  GOO_CANVAS_ITEM_HIDDEN			= 0,
+  GOO_CANVAS_ITEM_INVISIBLE			= 1,
+  GOO_CANVAS_ITEM_VISIBLE			= 2,
+  GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD	= 3
+} GooCanvasItemVisibility;
+
+
+/**
+ * GooCanvasPathCommandType
+ * @GOO_CANVAS_PATH_MOVE_TO: move to the given point.
+ * @GOO_CANVAS_PATH_CLOSE_PATH: close the current path, drawing a line from the
+ *  current position to the start of the path.
+ * @GOO_CANVAS_PATH_LINE_TO: draw a line to the given point.
+ * @GOO_CANVAS_PATH_HORIZONTAL_LINE_TO: draw a horizontal line to the given
+ *  x coordinate.
+ * @GOO_CANVAS_PATH_VERTICAL_LINE_TO: draw a vertical line to the given y
+ *  coordinate.
+ * @GOO_CANVAS_PATH_CURVE_TO: draw a bezier curve using two control
+ *  points to the given point.
+ * @GOO_CANVAS_PATH_SMOOTH_CURVE_TO: draw a bezier curve using a reflection
+ *  of the last control point of the last curve as the first control point,
+ *  and one new control point, to the given point.
+ * @GOO_CANVAS_PATH_QUADRATIC_CURVE_TO: draw a quadratic bezier curve using
+ *  a single control point to the given point.
+ * @GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO: draw a quadratic bezier curve
+ *  using a reflection of the control point from the previous curve as the
+ *  control point, to the given point.
+ * @GOO_CANVAS_PATH_ELLIPTICAL_ARC: draw an elliptical arc, using the given
+ *  2 radii, the x axis rotation, and the 2 flags to disambiguate the arc,
+ *  to the given point.
+ *
+ * GooCanvasPathCommandType specifies the type of each command in the path.
+ * See the path element in the <ulink url="http://www.w3.org/Graphics/SVG/";>
+ * Scalable Vector Graphics (SVG) specification</ulink> for more details.
+ */
+typedef enum
+{
+  /* Simple commands like moveto and lineto: MmZzLlHhVv. */
+  GOO_CANVAS_PATH_MOVE_TO,
+  GOO_CANVAS_PATH_CLOSE_PATH,
+  GOO_CANVAS_PATH_LINE_TO,
+  GOO_CANVAS_PATH_HORIZONTAL_LINE_TO,
+  GOO_CANVAS_PATH_VERTICAL_LINE_TO,
+
+  /* Bezier curve commands: CcSsQqTt. */
+  GOO_CANVAS_PATH_CURVE_TO,
+  GOO_CANVAS_PATH_SMOOTH_CURVE_TO,
+  GOO_CANVAS_PATH_QUADRATIC_CURVE_TO,
+  GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO,
+
+  /* The elliptical arc commands: Aa. */
+  GOO_CANVAS_PATH_ELLIPTICAL_ARC
+} GooCanvasPathCommandType;
+
+
+
+/**
+ * GooCanvasAnchorType
+ * @GOO_CANVAS_ANCHOR_CENTER: the anchor is in the center of the object.
+ * @GOO_CANVAS_ANCHOR_NORTH: the anchor is at the top of the object, centered horizontally.
+ * @GOO_CANVAS_ANCHOR_NORTH_WEST: the anchor is at the top-left of the object.
+ * @GOO_CANVAS_ANCHOR_NORTH_EAST: the anchor is at the top-right of the object.
+ * @GOO_CANVAS_ANCHOR_SOUTH: the anchor is at the bottom of the object, centered horizontally.
+ * @GOO_CANVAS_ANCHOR_SOUTH_WEST: the anchor is at the bottom-left of the object.
+ * @GOO_CANVAS_ANCHOR_SOUTH_EAST: the anchor is at the bottom-right of the object.
+ * @GOO_CANVAS_ANCHOR_WEST: the anchor is on the left of the object, centered vertically.
+ * @GOO_CANVAS_ANCHOR_EAST: the anchor is on the right of the object, centered vertically.
+ * @GOO_CANVAS_ANCHOR_N: see GOO_CANVAS_ANCHOR_NORTH.
+ * @GOO_CANVAS_ANCHOR_NW: see GOO_CANVAS_ANCHOR_NORTH_WEST.
+ * @GOO_CANVAS_ANCHOR_NE: see GOO_CANVAS_ANCHOR_NORTH_EAST.
+ * @GOO_CANVAS_ANCHOR_S: see GOO_CANVAS_ANCHOR_SOUTH.
+ * @GOO_CANVAS_ANCHOR_SW: see GOO_CANVAS_ANCHOR_SOUTH_WEST.
+ * @GOO_CANVAS_ANCHOR_SE: see GOO_CANVAS_ANCHOR_SOUTH_EAST.
+ * @GOO_CANVAS_ANCHOR_W: see GOO_CANVAS_ANCHOR_WEST.
+ * @GOO_CANVAS_ANCHOR_E: see GOO_CANVAS_ANCHOR_EAST.
+ *
+ * GooCanvasAnchorType is used to specify the positions of objects relative to
+ * a particular anchor point.
+ */
+typedef enum
+{
+  GOO_CANVAS_ANCHOR_CENTER,
+  GOO_CANVAS_ANCHOR_NORTH,
+  GOO_CANVAS_ANCHOR_NORTH_WEST,
+  GOO_CANVAS_ANCHOR_NORTH_EAST,
+  GOO_CANVAS_ANCHOR_SOUTH,
+  GOO_CANVAS_ANCHOR_SOUTH_WEST,
+  GOO_CANVAS_ANCHOR_SOUTH_EAST,
+  GOO_CANVAS_ANCHOR_WEST,
+  GOO_CANVAS_ANCHOR_EAST,
+  GOO_CANVAS_ANCHOR_N		= GOO_CANVAS_ANCHOR_NORTH,
+  GOO_CANVAS_ANCHOR_NW		= GOO_CANVAS_ANCHOR_NORTH_WEST,
+  GOO_CANVAS_ANCHOR_NE		= GOO_CANVAS_ANCHOR_NORTH_EAST,
+  GOO_CANVAS_ANCHOR_S		= GOO_CANVAS_ANCHOR_SOUTH,
+  GOO_CANVAS_ANCHOR_SW		= GOO_CANVAS_ANCHOR_SOUTH_WEST,
+  GOO_CANVAS_ANCHOR_SE		= GOO_CANVAS_ANCHOR_SOUTH_EAST,
+  GOO_CANVAS_ANCHOR_W		= GOO_CANVAS_ANCHOR_WEST,
+  GOO_CANVAS_ANCHOR_E		= GOO_CANVAS_ANCHOR_EAST
+} GooCanvasAnchorType;
+
+typedef union _GooCanvasPathCommand  GooCanvasPathCommand;
+
+/* Note that the command type is always the first element in each struct, so
+   we can always use it whatever type of command it is. */
+
+/**
+ * GooCanvasPathCommand
+ *
+ * GooCanvasPathCommand holds the data for each command in the path.
+ *
+ * The @relative flag specifies that the coordinates for the command are
+ * relative to the current point. Otherwise they are assumed to be absolute
+ * coordinates.
+ */
+union _GooCanvasPathCommand
+{
+  /* Simple commands like moveto and lineto: MmZzLlHhVv. */
+  struct {
+    guint type : 5; /* GooCanvasPathCommandType */
+    guint relative : 1;
+    gdouble x, y;
+  } simple;
+
+  /* Bezier curve commands: CcSsQqTt. */
+  struct {
+    guint type : 5; /* GooCanvasPathCommandType */
+    guint relative : 1;
+    gdouble x, y, x1, y1, x2, y2;
+  } curve;
+
+  /* The elliptical arc commands: Aa. */
+  struct {
+    guint type : 5; /* GooCanvasPathCommandType */
+    guint relative : 1;
+    guint large_arc_flag : 1;
+    guint sweep_flag : 1;
+    gdouble rx, ry, x_axis_rotation, x, y;
+  } arc;
+};
+
+
+GArray*	goo_canvas_parse_path_data	(const gchar       *path_data);
+void	goo_canvas_create_path		(GArray		   *commands,
+					 cairo_t           *cr);
+
+
+/*
+ * Cairo utilities.
+ */
+typedef struct _GooCanvasLineDash GooCanvasLineDash;
+
+/**
+ * GooCanvasLineDash
+ * @ref_count: the reference count of the struct.
+ * @num_dashes: the number of dashes and gaps between them.
+ * @dashes: the sizes of each dash and gap.
+ * @dash_offset: the start offset into the dash pattern.
+ *
+ * #GooCanvasLineDash specifies a dash pattern to be used when drawing items.
+ */
+struct _GooCanvasLineDash
+{
+  int ref_count;
+  int num_dashes;
+  double *dashes;
+  double dash_offset;
+};
+
+
+/* These are here so we can document the cairo type wrappers - don't use. */
+#if 0
+typedef cairo_antialias_t	GooCairoAntialias;
+typedef cairo_fill_rule_t	GooCairoFillRule;
+typedef cairo_hint_metrics_t	GooCairoHintMetrics;
+typedef cairo_line_cap_t	GooCairoLineCap;
+typedef cairo_line_join_t	GooCairoLineJoin;
+typedef cairo_operator_t	GooCairoOperator;
+typedef cairo_matrix_t		GooCairoMatrix;
+typedef cairo_pattern_t		GooCairoPattern;
+#endif
+
+/**
+ * GooCairoAntialias
+ *
+ * #GooCairoAntialias is simply a wrapper for the #cairo_antialias_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_antialias_t documentation.
+ */
+
+/**
+ * GooCairoFillRule
+ *
+ * #GooCairoFillRule is simply a wrapper for the #cairo_fill_rule_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_fill_rule_t documentation.
+ */
+
+/**
+ * GooCairoHintMetrics
+ *
+ * #GooCairoHintMetrics is simply a wrapper for the #cairo_hint_metrics_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_hint_metrics_t documentation.
+ */
+
+/**
+ * GooCairoLineCap
+ *
+ * #GooCairoLineCap is simply a wrapper for the #cairo_line_cap_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_line_cap_t documentation.
+ */
+
+/**
+ * GooCairoLineJoin
+ *
+ * #GooCairoLineJoin is simply a wrapper for the #cairo_line_join_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_line_join_t documentation.
+ */
+
+/**
+ * GooCairoOperator
+ *
+ * #GooCairoOperator is simply a wrapper for the #cairo_operator_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_operator_t documentation.
+ */
+
+/**
+ * GooCairoMatrix
+ *
+ * #GooCairoMatrix is simply a wrapper for the #cairo_matrix_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_matrix_t documentation.
+ */
+
+/**
+ * GooCairoPattern
+ *
+ * #GooCairoPattern is simply a wrapper for the #cairo_pattern_t type,
+ * allowing it to be used for #GObject properties.
+ *
+ * See the #cairo_pattern_t documentation.
+ */
+
+
+#define GOO_TYPE_CANVAS_LINE_DASH  (goo_canvas_line_dash_get_type ())
+GType              goo_canvas_line_dash_get_type (void) G_GNUC_CONST;
+GooCanvasLineDash* goo_canvas_line_dash_new   (gint               num_dashes,
+					       ...);
+GooCanvasLineDash* goo_canvas_line_dash_newv  (gint               num_dashes,
+                                               double            *dashes);
+GooCanvasLineDash* goo_canvas_line_dash_ref   (GooCanvasLineDash *dash);
+void               goo_canvas_line_dash_unref (GooCanvasLineDash *dash);
+
+#define GOO_TYPE_CAIRO_MATRIX	   (goo_cairo_matrix_get_type())
+GType              goo_cairo_matrix_get_type  (void) G_GNUC_CONST;
+cairo_matrix_t*    goo_cairo_matrix_copy      (const cairo_matrix_t *matrix);
+void               goo_cairo_matrix_free      (cairo_matrix_t       *matrix);
+
+#define GOO_TYPE_CAIRO_PATTERN	   (goo_cairo_pattern_get_type ())
+GType              goo_cairo_pattern_get_type (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_FILL_RULE   (goo_cairo_fill_rule_get_type ())
+GType		   goo_cairo_fill_rule_get_type (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_OPERATOR    (goo_cairo_operator_get_type())
+GType		   goo_cairo_operator_get_type  (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_ANTIALIAS   (goo_cairo_antialias_get_type())
+GType		   goo_cairo_antialias_get_type (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_LINE_CAP    (goo_cairo_line_cap_get_type ())
+GType		   goo_cairo_line_cap_get_type  (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_LINE_JOIN   (goo_cairo_line_join_get_type ())
+GType		   goo_cairo_line_join_get_type (void) G_GNUC_CONST;
+
+#define GOO_TYPE_CAIRO_HINT_METRICS (goo_cairo_hint_metrics_get_type ())
+GType		   goo_cairo_hint_metrics_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_UTILS_H__ */
diff --git a/libgoocanvas/goocanvaswidget.c b/libgoocanvas/goocanvaswidget.c
new file mode 100644
index 0000000..fdf8927
--- /dev/null
+++ b/libgoocanvas/goocanvaswidget.c
@@ -0,0 +1,597 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvaswidget.c - wrapper item for an embedded GtkWidget.
+ */
+
+/**
+ * SECTION:goocanvaswidget
+ * @Title: GooCanvasWidget
+ * @Short_Description: an embedded widget item.
+ *
+ * GooCanvasWidget provides support for placing any GtkWidget in the canvas.
+ *
+ * The #GooCanvasWidget:width and #GooCanvasWidget:height properties specify
+ * the widget's size. If either of them is -1, then the requested size of the
+ * widget is used instead, which is the default for both width and height.
+ *
+ * Note that there are a number of limitations in the use of #GooCanvasWidget:
+ *
+ * <itemizedlist><listitem><para>
+ * It doesn't support any transformation besides simple translation.
+ * This means you can't scale a canvas with a #GooCanvasWidget in it.
+ * </para></listitem><listitem><para>
+ * It doesn't support layering, so you can't place other items beneath
+ * or above the #GooCanvasWidget.
+ * </para></listitem><listitem><para>
+ * It doesn't support rendering of widgets to a given cairo_t, which
+ * means you can't output the widget to a pdf or postscript file.
+ * </para></listitem><listitem><para>
+ * It doesn't have a model/view variant like the other standard items,
+ * so it can only be used in a simple canvas without a model.
+ * </para></listitem><listitem><para>
+ * It can't be made a static item.
+ * </para></listitem></itemizedlist>
+ */
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "goocanvas.h"
+#include "goocanvasatk.h"
+
+enum {
+  PROP_0,
+
+  PROP_WIDGET,
+  PROP_X,
+  PROP_Y,
+  PROP_WIDTH,
+  PROP_HEIGHT,
+  PROP_ANCHOR,
+  PROP_VISIBILITY
+};
+
+
+static void canvas_item_interface_init      (GooCanvasItemIface  *iface);
+static void goo_canvas_widget_dispose       (GObject             *object);
+static void goo_canvas_widget_get_property  (GObject             *object,
+					     guint                param_id,
+					     GValue              *value,
+					     GParamSpec          *pspec);
+static void goo_canvas_widget_set_property  (GObject             *object,
+					     guint                param_id,
+					     const GValue        *value,
+					     GParamSpec          *pspec);
+
+G_DEFINE_TYPE_WITH_CODE (GooCanvasWidget, goo_canvas_widget,
+			 GOO_TYPE_CANVAS_ITEM_SIMPLE,
+			 G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM,
+						canvas_item_interface_init))
+
+
+static void
+goo_canvas_widget_init (GooCanvasWidget *witem)
+{
+  /* By default we place the widget at the top-left of the canvas at its
+     requested size. */
+  witem->x = 0.0;
+  witem->y = 0.0;
+  witem->width = -1.0;
+  witem->height = -1.0;
+  witem->anchor = GOO_CANVAS_ANCHOR_NW;
+}
+
+
+/**
+ * goo_canvas_widget_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.
+ * @widget: the widget.
+ * @x: the x coordinate of the item.
+ * @y: the y coordinate of the item.
+ * @width: the width of the item, or -1 to use the widget's requested width.
+ * @height: the height of the item, or -1 to use the widget's requested height.
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ *
+ * Creates a new widget item.
+ *
+ * <!--PARAMETERS-->
+ * 
+ * Here's an example showing how to create an entry widget centered at (100.0,
+ * 100.0):
+ *
+ * <informalexample><programlisting>
+ *  GtkWidget *entry = gtk_entry_new ();
+ *  GooCanvasItem *witem = goo_canvas_widget_new (mygroup, entry,
+ *                                                100, 100, -1, -1,
+ *                                                "anchor", GOO_CANVAS_ANCHOR_CENTER,
+ *                                                NULL);
+ * </programlisting></informalexample>
+ * 
+ * Returns: a new widget item.
+ **/
+GooCanvasItem*
+goo_canvas_widget_new               (GooCanvasItem    *parent,
+				     GtkWidget        *widget,
+				     gdouble           x,
+				     gdouble           y,
+				     gdouble           width,
+				     gdouble           height,
+				     ...)
+{
+  GooCanvasItem *item;
+  GooCanvasWidget *witem;
+  const char *first_property;
+  va_list var_args;
+
+  item = g_object_new (GOO_TYPE_CANVAS_WIDGET, NULL);
+  witem = (GooCanvasWidget*) item;
+
+  witem->widget = widget;
+  g_object_ref (witem->widget);
+  g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", witem);
+
+  witem->x = x;
+  witem->y = y;
+  witem->width = width;
+  witem->height = height;
+
+  /* The widget defaults to being visible, like the canvas item, but this
+     can be overridden by the object property below. */
+  if (widget)
+    gtk_widget_show (widget);
+
+  va_start (var_args, height);
+  first_property = va_arg (var_args, char*);
+  if (first_property)
+    g_object_set_valist ((GObject*) item, first_property, var_args);
+  va_end (var_args);
+
+  if (parent)
+    {
+      goo_canvas_item_add_child (parent, item, -1);
+      g_object_unref (item);
+    }
+
+  return item;
+}
+
+
+/* Returns the anchor position, within the given width. */
+static gdouble
+goo_canvas_widget_anchor_horizontal_pos (GooCanvasAnchorType anchor,
+					 gdouble       width)
+{
+  switch(anchor)
+    {
+    case GOO_CANVAS_ANCHOR_N:
+    case GOO_CANVAS_ANCHOR_CENTER:
+    case GOO_CANVAS_ANCHOR_S:
+      return width / 2.0;
+    case GOO_CANVAS_ANCHOR_NE:
+    case GOO_CANVAS_ANCHOR_E:
+    case GOO_CANVAS_ANCHOR_SE:
+      return width;
+    default:
+      return 0.0;
+    }
+}
+
+
+/* Returns the anchor position, within the given height. */
+static gdouble
+goo_canvas_widget_anchor_vertical_pos (GooCanvasAnchorType anchor,
+				       gdouble       height)
+{
+  switch (anchor)
+    {
+    case GOO_CANVAS_ANCHOR_W:
+    case GOO_CANVAS_ANCHOR_CENTER:
+    case GOO_CANVAS_ANCHOR_E:
+      return height / 2.0;
+    case GOO_CANVAS_ANCHOR_SW:
+    case GOO_CANVAS_ANCHOR_S:
+    case GOO_CANVAS_ANCHOR_SE:
+      return height;
+    default:
+      return 0.0;
+    }
+}
+
+
+/* Returns the size to use for the widget, either the item's width & height
+   properties or the widget's own requested width & height. */
+static void
+goo_canvas_widget_get_widget_size (GooCanvasWidget *witem,
+				   gdouble         *width,
+				   gdouble         *height)
+{
+  GtkRequisition requisition;
+
+  if (witem->widget)
+    {
+      /* Get the widget's requested size, if we need it. */
+      if (witem->width < 0 || witem->height < 0)
+	gtk_widget_size_request (witem->widget, &requisition);
+
+      *width = witem->width < 0 ? requisition.width : witem->width;
+      *height = witem->height < 0 ? requisition.height : witem->height;
+    }
+  else
+    {
+      *width = *height = 0.0;
+    }
+}
+
+
+static void
+goo_canvas_widget_set_widget (GooCanvasWidget *witem,
+			      GtkWidget       *widget)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) witem;
+
+  if (witem->widget)
+    {
+      g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", NULL);
+      gtk_widget_unparent (witem->widget);
+      g_object_unref (witem->widget);
+      witem->widget = NULL;
+    }
+
+  if (widget)
+    {
+      witem->widget = widget;
+      g_object_ref (witem->widget);
+      g_object_set_data (G_OBJECT (witem->widget), "goo-canvas-item", witem);
+
+      if (simple->simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE)
+	gtk_widget_hide (widget);
+      else
+	gtk_widget_show (widget);
+
+      if (simple->canvas)
+	{
+	  if (gtk_widget_get_realized (GTK_WIDGET (simple->canvas)))
+	    gtk_widget_set_parent_window (widget,
+					  simple->canvas->canvas_window);
+
+	  gtk_widget_set_parent (widget, GTK_WIDGET (simple->canvas));
+	}
+    }
+}
+
+
+static void
+goo_canvas_widget_dispose (GObject *object)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasWidget *witem = (GooCanvasWidget*) object;
+
+  if (simple->canvas)
+    goo_canvas_unregister_widget_item (simple->canvas, witem);
+
+  goo_canvas_widget_set_widget (witem, NULL);
+
+  G_OBJECT_CLASS (goo_canvas_widget_parent_class)->dispose (object);
+}
+
+
+static void
+goo_canvas_widget_get_property  (GObject             *object,
+				 guint                prop_id,
+				 GValue              *value,
+				 GParamSpec          *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasWidget *witem = (GooCanvasWidget*) object;
+
+  switch (prop_id)
+    {
+    case PROP_WIDGET:
+      g_value_set_object (value, witem->widget);
+      break;
+    case PROP_X:
+      g_value_set_double (value, witem->x);
+      break;
+    case PROP_Y:
+      g_value_set_double (value, witem->y);
+      break;
+    case PROP_WIDTH:
+      g_value_set_double (value, witem->width);
+      break;
+    case PROP_HEIGHT:
+      g_value_set_double (value, witem->height);
+      break;
+    case PROP_ANCHOR:
+      g_value_set_enum (value, witem->anchor);
+      break;
+    case PROP_VISIBILITY:
+      g_value_set_enum (value, simple->simple_data->visibility);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+goo_canvas_widget_set_property  (GObject             *object,
+				 guint                prop_id,
+				 const GValue        *value,
+				 GParamSpec          *pspec)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) object;
+  GooCanvasWidget *witem = (GooCanvasWidget*) object;
+
+  switch (prop_id)
+    {
+    case PROP_WIDGET:
+      goo_canvas_widget_set_widget (witem, g_value_get_object (value));
+      break;
+    case PROP_X:
+      witem->x = g_value_get_double (value);
+      break;
+    case PROP_Y:
+      witem->y = g_value_get_double (value);
+      break;
+    case PROP_WIDTH:
+      witem->width = g_value_get_double (value);
+      break;
+    case PROP_HEIGHT:
+      witem->height = g_value_get_double (value);
+      break;
+    case PROP_ANCHOR:
+      witem->anchor = g_value_get_enum (value);
+      break;
+    case PROP_VISIBILITY:
+      simple->simple_data->visibility = g_value_get_enum (value);
+      if (simple->simple_data->visibility <= GOO_CANVAS_ITEM_INVISIBLE)
+	gtk_widget_hide (witem->widget);
+      else
+	gtk_widget_show (witem->widget);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+
+  goo_canvas_item_simple_changed (simple, TRUE);
+}
+
+
+static void
+goo_canvas_widget_set_canvas  (GooCanvasItem *item,
+			       GooCanvas     *canvas)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasWidget *witem = (GooCanvasWidget*) item;
+
+  if (simple->canvas != canvas)
+    {
+      if (simple->canvas)
+	goo_canvas_unregister_widget_item (simple->canvas, witem);
+
+      simple->canvas = canvas;
+
+      if (simple->canvas)
+	{
+	  goo_canvas_register_widget_item (simple->canvas, witem);
+
+	  if (witem->widget)
+	    {
+	      if (gtk_widget_get_realized (GTK_WIDGET (simple->canvas)))
+		gtk_widget_set_parent_window (witem->widget,
+					      simple->canvas->canvas_window);
+
+	      gtk_widget_set_parent (witem->widget,
+				     GTK_WIDGET (simple->canvas));
+	    }
+	}
+      else
+	{
+	  if (witem->widget)
+	    gtk_widget_unparent (witem->widget);
+	}
+    }
+}
+
+
+static void
+goo_canvas_widget_set_parent (GooCanvasItem  *item,
+			      GooCanvasItem  *parent)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvas *canvas;
+
+  simple->parent = parent;
+  simple->need_update = TRUE;
+  simple->need_entire_subtree_update = TRUE;
+
+  canvas = parent ? goo_canvas_item_get_canvas (parent) : NULL;
+  goo_canvas_widget_set_canvas (item, canvas);
+}
+
+
+static void
+goo_canvas_widget_update  (GooCanvasItemSimple *simple,
+			   cairo_t             *cr)
+{
+  GooCanvasWidget *witem = (GooCanvasWidget*) simple;
+  gdouble width, height;
+
+  if (witem->widget)
+    {
+      goo_canvas_widget_get_widget_size (witem, &width, &height);
+
+      simple->bounds.x1 = witem->x;
+      simple->bounds.y1 = witem->y;
+
+      simple->bounds.x1 -=
+        goo_canvas_widget_anchor_horizontal_pos (witem->anchor, width);
+      simple->bounds.y1 -=
+        goo_canvas_widget_anchor_vertical_pos (witem->anchor, height);
+
+      simple->bounds.x2 = simple->bounds.x1 + width;
+      simple->bounds.y2 = simple->bounds.y1 + height;
+
+      /* Queue a resize of the widget so it gets moved. Note that the widget
+	 is moved by goo_canvas_size_allocate(). */
+      gtk_widget_queue_resize (witem->widget);
+    }
+  else
+    {
+      simple->bounds.x1 = simple->bounds.y1 = 0.0;
+      simple->bounds.x2 = simple->bounds.y2 = 0.0;
+    }
+}
+
+
+static void
+goo_canvas_widget_allocate_area      (GooCanvasItem         *item,
+				      cairo_t               *cr,
+				      const GooCanvasBounds *requested_area,
+				      const GooCanvasBounds *allocated_area,
+				      gdouble                x_offset,
+				      gdouble                y_offset)
+{
+  GooCanvasItemSimple *simple = (GooCanvasItemSimple*) item;
+  GooCanvasWidget *witem = (GooCanvasWidget*) item;
+  gdouble requested_width, requested_height, allocated_width, allocated_height;
+  gdouble width_proportion, height_proportion;
+  gdouble width, height;
+
+  width = simple->bounds.x2 - simple->bounds.x1;
+  height = simple->bounds.y2 - simple->bounds.y1;
+
+  simple->bounds.x1 += x_offset;
+  simple->bounds.y1 += y_offset;
+
+  requested_width = requested_area->x2 - requested_area->x1;
+  requested_height = requested_area->y2 - requested_area->y1;
+  allocated_width = allocated_area->x2 - allocated_area->x1;
+  allocated_height = allocated_area->y2 - allocated_area->y1;
+
+  width_proportion = allocated_width / requested_width;
+  height_proportion = allocated_height / requested_height;
+
+  width *= width_proportion;
+  height *= height_proportion;
+
+  simple->bounds.x2 = simple->bounds.x1 + width;
+  simple->bounds.y2 = simple->bounds.y1 + height;
+
+  /* Queue a resize of the widget so it gets moved. Note that the widget
+     is moved by goo_canvas_size_allocate(). */
+  gtk_widget_queue_resize (witem->widget);
+}
+
+
+static void
+goo_canvas_widget_paint (GooCanvasItemSimple   *simple,
+			 cairo_t               *cr,
+			 const GooCanvasBounds *bounds)
+{
+  /* Do nothing for now. Maybe render for printing in future. */
+}
+
+
+static gboolean
+goo_canvas_widget_is_item_at (GooCanvasItemSimple *simple,
+			      gdouble              x,
+			      gdouble              y,
+			      cairo_t             *cr,
+			      gboolean             is_pointer_event)
+{
+  /* For now we just assume that the widget covers its entire bounds so we just
+     return TRUE. In future if widget items support transforms we'll need to
+     modify this. */
+  return TRUE;
+}
+
+
+static void
+canvas_item_interface_init (GooCanvasItemIface *iface)
+{
+  iface->set_canvas     = goo_canvas_widget_set_canvas;
+  iface->set_parent	= goo_canvas_widget_set_parent;
+  iface->allocate_area  = goo_canvas_widget_allocate_area;
+}
+
+
+static void
+goo_canvas_widget_class_init (GooCanvasWidgetClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+  GooCanvasItemSimpleClass *simple_class = (GooCanvasItemSimpleClass*) klass;
+
+  gobject_class->dispose = goo_canvas_widget_dispose;
+
+  gobject_class->get_property = goo_canvas_widget_get_property;
+  gobject_class->set_property = goo_canvas_widget_set_property;
+
+  simple_class->simple_update        = goo_canvas_widget_update;
+  simple_class->simple_paint         = goo_canvas_widget_paint;
+  simple_class->simple_is_item_at    = goo_canvas_widget_is_item_at;
+
+  /* Register our accessible factory, but only if accessibility is enabled. */
+  if (!ATK_IS_NO_OP_OBJECT_FACTORY (atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET)))
+    {
+      atk_registry_set_factory_type (atk_get_default_registry (),
+				     GOO_TYPE_CANVAS_WIDGET,
+				     goo_canvas_widget_accessible_factory_get_type ());
+    }
+
+  g_object_class_install_property (gobject_class, PROP_WIDGET,
+				   g_param_spec_object ("widget",
+							_("Widget"),
+							_("The widget to place in the canvas"),
+							GTK_TYPE_WIDGET,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_X,
+				   g_param_spec_double ("x",
+							"X",
+							_("The x coordinate of the widget"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_Y,
+				   g_param_spec_double ("y",
+							"Y",
+							_("The y coordinate of the widget"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, 0.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_WIDTH,
+				   g_param_spec_double ("width",
+							_("Width"),
+							_("The width of the widget, or -1 to use its requested width"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class, PROP_HEIGHT,
+				   g_param_spec_double ("height",
+							_("Height"),
+							_("The height of the widget, or -1 to use its requested height"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE, -1.0,
+							G_PARAM_READWRITE));
+
+
+  g_object_class_install_property (gobject_class, PROP_ANCHOR,
+				   g_param_spec_enum ("anchor",
+						      _("Anchor"),
+						      _("How to position the widget relative to the item's x and y coordinate settings"),
+						      GOO_TYPE_CANVAS_ANCHOR_TYPE,
+						      GOO_CANVAS_ANCHOR_NW,
+						      G_PARAM_READWRITE));
+
+  g_object_class_override_property (gobject_class, PROP_VISIBILITY,
+				    "visibility");
+}
diff --git a/libgoocanvas/goocanvaswidget.h b/libgoocanvas/goocanvaswidget.h
new file mode 100644
index 0000000..48b7998
--- /dev/null
+++ b/libgoocanvas/goocanvaswidget.h
@@ -0,0 +1,66 @@
+/*
+ * GooCanvas. Copyright (C) 2005-6 Damon Chaplin.
+ * Released under the GNU LGPL license. See COPYING for details.
+ *
+ * goocanvaswidget.h - wrapper item for an embedded GtkWidget.
+ */
+#ifndef __GOO_CANVAS_WIDGET_H__
+#define __GOO_CANVAS_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include "goocanvasitemsimple.h"
+
+G_BEGIN_DECLS
+
+
+#define GOO_TYPE_CANVAS_WIDGET            (goo_canvas_widget_get_type ())
+#define GOO_CANVAS_WIDGET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOO_TYPE_CANVAS_WIDGET, GooCanvasWidget))
+#define GOO_CANVAS_WIDGET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GOO_TYPE_CANVAS_WIDGET, GooCanvasWidgetClass))
+#define GOO_IS_CANVAS_WIDGET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOO_TYPE_CANVAS_WIDGET))
+#define GOO_IS_CANVAS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOO_TYPE_CANVAS_WIDGET))
+#define GOO_CANVAS_WIDGET_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GOO_TYPE_CANVAS_WIDGET, GooCanvasWidgetClass))
+
+
+typedef struct _GooCanvasWidget       GooCanvasWidget;
+typedef struct _GooCanvasWidgetClass  GooCanvasWidgetClass;
+
+/**
+ * GooCanvasWidget
+ *
+ * The #GooCanvasWidget-struct struct contains private data only.
+ */
+struct _GooCanvasWidget
+{
+  GooCanvasItemSimple parent_object;
+
+  GtkWidget *widget;
+  gdouble x, y, width, height;
+  GooCanvasAnchorType anchor;
+};
+
+struct _GooCanvasWidgetClass
+{
+  GooCanvasItemSimpleClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_goo_canvas_reserved1) (void);
+  void (*_goo_canvas_reserved2) (void);
+  void (*_goo_canvas_reserved3) (void);
+  void (*_goo_canvas_reserved4) (void);
+};
+
+
+GType          goo_canvas_widget_get_type          (void) G_GNUC_CONST;
+GooCanvasItem* goo_canvas_widget_new               (GooCanvasItem    *parent,
+						    GtkWidget        *widget,
+						    gdouble           x,
+						    gdouble           y,
+						    gdouble           width,
+						    gdouble           height,
+						    ...);
+
+G_END_DECLS
+
+#endif /* __GOO_CANVAS_WIDGET_H__ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]