[anjuta/class-inheritance-yap: 1/3] libgoocanvas: Introduced goocanvas library to replace foocanvas for canvas graphics
- From: Naba Kumar <naba src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [anjuta/class-inheritance-yap: 1/3] libgoocanvas: Introduced goocanvas library to replace foocanvas for canvas graphics
- Date: Mon, 13 Jun 2011 19:37:04 +0000 (UTC)
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>
+ * #include <goocanvas.h>
+ *
+ * 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;
+ *
+ * /* Initialize GTK+. */
+ * gtk_init (&argc, &argv);
+ *
+ * /* Create the window and widgets. */
+ * 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 ();
+ * 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));
+ *
+ * /* Add a few simple items. */
+ * 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);
+ *
+ * /* Connect a signal handler for the rectangle item. */
+ * g_signal_connect (rect_item, "button_press_event",
+ * (GtkSignalFunc) on_rect_button_press, NULL);
+ *
+ * /* Pass control to the GTK+ main event loop. */
+ * gtk_main ();
+ *
+ * return 0;
+ * }
+ *
+ *
+ * /* This handles button presses in item views. We simply output a message to
+ * the console. */
+ * 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]