/* pydiacanvasitem.c
 * Copyright (C) 2002  Arjan Molenaar
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#define NO_IMPORT_PYGOBJECT 1
#define NO_IMPORT_PYGTK 1
#include <pygobject.h>

#include <diacanvas/dia-canvas.h>
#include <diacanvas/dia-canvas-groupable.h>
#include <diacanvas/dia-canvas-editable.h>
#include <diacanvas/diatypebuiltins.h>

//#define ENABLE_DEBUG 1
//#define ENABLE_DEBUG_GROUP 1
//#define ENABLE_DEBUG_EDIT 1

/* For the DiaCanvasItem callbacks, the prefix is 'on_', for 
 * DiaCanvasGroupable the prefix is 'on_groupable_'.
 */
#define METHOD_PREFIX "on_"
#define DATA_IS_PYOBJECT ((int*) 1)
#define DATA_IS_NOT_PYOBJECT ((int*) 2)

extern PyTypeObject PyDiaHandle_Type;
extern PyTypeObject PyDiaCanvasItem_Type;

/*
 * DiaCanvasItem
 */
void	pydia_canvas_item_class_init	(DiaCanvasItemClass *klass);

static void	pydia_canvas_item_update		(DiaCanvasItem *item,
							 gdouble affine[6]);
static gboolean pydia_canvas_item_get_shape_iter	(DiaCanvasItem *item,
							 DiaCanvasIter *iter);
static gboolean pydia_canvas_item_shape_next		(DiaCanvasItem *item,
							 DiaCanvasIter *iter);
static DiaShape* pydia_canvas_item_shape_value		(DiaCanvasItem *item,
							 DiaCanvasIter *iter);
static gdouble	pydia_canvas_item_point			(DiaCanvasItem *item,
							 gdouble x, gdouble y);
static void	pydia_canvas_item_handle_motion		(DiaCanvasItem *item,
							 DiaHandle *handle,
							 gdouble *wx, gdouble *wy,
							 DiaEventMask mask);
static gdouble	pydia_canvas_item_glue			(DiaCanvasItem *item,
							 DiaHandle *handle,
							 gdouble *wx,
							 gdouble *wy);
static gboolean pydia_canvas_item_event			(DiaCanvasItem *item,
							 DiaEvent *event);
static void	pydia_canvas_item_move			(DiaCanvasItem *item,
							 gdouble dx, gdouble dy,
							 gboolean interactive);
static gboolean pydia_canvas_item_connect		(DiaCanvasItem *item,
							 DiaHandle *handle);
static gboolean pydia_canvas_item_disconnect		(DiaCanvasItem *item,
							 DiaHandle *handle);

/*
 * DiaCanvasItem
 */
void
pydia_canvas_item_class_init (DiaCanvasItemClass *klass)
{
	/* Callbacks */
	klass->update = pydia_canvas_item_update;
	klass->point = pydia_canvas_item_point;
	klass->handle_motion = pydia_canvas_item_handle_motion;
	klass->glue = pydia_canvas_item_glue;
	klass->get_shape_iter = pydia_canvas_item_get_shape_iter;
	klass->shape_next = pydia_canvas_item_shape_next;
	klass->shape_value = pydia_canvas_item_shape_value;

	/* Signals. */
	klass->event = pydia_canvas_item_event;
	klass->move = pydia_canvas_item_move;
	klass->connect = pydia_canvas_item_connect;
	klass->disconnect = pydia_canvas_item_disconnect;
}

static void
pydia_canvas_item_update (DiaCanvasItem *item, gdouble affine[6])
{
	PyObject *self;
	PyObject *py_affine, *py_ret;
	GValue v = { 0, };

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif

	pyg_block_threads();

	g_value_init (&v, DIA_TYPE_AFFINE);
	g_value_set_static_boxed (&v, affine);
	//py_affine = pyg_boxed_new (DIA_TYPE_AFFINE, affine, FALSE, FALSE);
	py_affine = pyg_value_as_pyobject (&v, TRUE);
	g_value_unset (&v);

	g_assert (py_affine != NULL);

	self = pygobject_new ((GObject*) item);

	py_ret = PyObject_CallMethod (self, METHOD_PREFIX "update",
				      "(O)", py_affine);
	Py_DECREF (self);
	Py_DECREF (py_affine);
	if (!py_ret) {
		PyErr_Print();
		PyErr_Clear();
	} else
		Py_DECREF (py_ret);
	pyg_unblock_threads();
}

static void
destroy_iter (gpointer data)
{
	DiaCanvasIter *iter = data;
	Py_XDECREF ((PyObject*) iter->data[0]);
	Py_XDECREF ((PyObject*) iter->data[1]);
}


/*
 * Shape handling is a little different:
 * - in get_shape_iter() a PyIter object is requested from shape_iter()
 * - in shape_next() the next item is fetched from the iterator.
 * - in shape_value() the DiaShape object is extracted from the PyObject.
 * The PyIter object is stored in iter.data[0]. The item that is to be fetched
 * by shape_value() is in data[1].
 */

static gboolean
pydia_canvas_item_get_shape_iter (DiaCanvasItem *item, DiaCanvasIter *iter)
{
	PyObject *self;
	PyObject *py_iter;

#ifdef ENABLE_DEBUG2
	g_message (__FUNCTION__);
#endif

	pyg_block_threads();

	self = pygobject_new ((GObject*) item);

	/* Implement generators.
	 * 1. call on_shape_iter(). This will return an Python iterator.
	 * 2. Call PyIter_Next() and store the return value in iter->data[1].
	 * 3. set the destroy notifier (unrefs data[0] and data[1]).
	 */ 
	py_iter = PyObject_CallMethod (self,
				       METHOD_PREFIX "shape_iter",
				       "");
	Py_DECREF (self);

	pyg_unblock_threads();

	if (!py_iter || !PyIter_Check(py_iter)) {
		g_warning("No iterator returned by self.shape_iter()");
		return FALSE;
	}
	iter->destroy_func = destroy_iter;
	iter->data[0] = py_iter;
	iter->data[1] = NULL;

	/* Make sure the iterator is pointing to the first shape: */
	return pydia_canvas_item_shape_next(item, iter);
}

static gboolean
pydia_canvas_item_shape_next (DiaCanvasItem *item, DiaCanvasIter *iter)
{
#ifdef ENABLE_DEBUG2
	g_message (__FUNCTION__);
#endif

	/* remove reference to previously iterated shape */
	Py_XDECREF ((PyObject*) iter->data[1]);

	/* let data[1] point to the next shape */
	iter->data[1] = PyIter_Next (iter->data[0]);

	/* if PyIter_Next() returned NULL there are no more items to fetch */
	return iter->data[1] != NULL;
}

static DiaShape*
pydia_canvas_item_shape_value (DiaCanvasItem *item, DiaCanvasIter *iter)
{
	PyObject *py_shape;
	DiaShape *shape = NULL;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__": data=%p/%p", iter->data[0], iter->data[1]);
#endif
	py_shape = iter->data[1];

	if (py_shape && (pyg_boxed_check (py_shape, DIA_TYPE_SHAPE_PATH)
	     		 || pyg_boxed_check (py_shape, DIA_TYPE_SHAPE_BEZIER)
	     		 || pyg_boxed_check (py_shape, DIA_TYPE_SHAPE_ELLIPSE)
	     		 || pyg_boxed_check (py_shape, DIA_TYPE_SHAPE_TEXT)
	     		 || pyg_boxed_check (py_shape, DIA_TYPE_SHAPE_IMAGE)))
		shape = pyg_boxed_get (py_shape, DiaShape);
	else
		PyErr_SetString (PyExc_TypeError,
				 "iter_shape() should only return DiaShape's.");

	return shape;
}

static gdouble
pydia_canvas_item_point (DiaCanvasItem *item, gdouble x, gdouble y)
{
	PyObject *self;
	PyObject *py_ret;
	gdouble retval = G_MAXDOUBLE;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif
		
	pyg_block_threads();

	self = pygobject_new ((GObject*) item);

	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "point",
				      "(dd)", x, y);
	Py_DECREF (self);
	if (!py_ret) {
		PyErr_Clear ();
		PyErr_SetString (PyExc_TypeError,
				 "method " METHOD_PREFIX "point not found.");
	} else {
		PyObject *py_float = PyNumber_Float(py_ret);
		if (py_float)
			retval = PyFloat_AsDouble (py_float);
		else {
			PyErr_Clear ();
			PyErr_SetString (PyExc_TypeError,
					 "return value should be a float.");
		}
		Py_DECREF (py_float);
	}
	Py_XDECREF (py_ret);

	pyg_unblock_threads();

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__": returning %f", retval);
#endif
	return retval;
}

static void
pydia_canvas_item_move (DiaCanvasItem *item, gdouble dx, gdouble dy, gboolean interactive)
{
	PyObject *self;
	PyObject *py_ret;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif
		
	pyg_block_threads();

	self = pygobject_new ((GObject*) item);

	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "move",
				      "(ddi)", dx, dy, interactive);
	Py_DECREF (self);


	if (!py_ret) {
		PyErr_Print();
		PyErr_Clear();
	} else
		Py_DECREF (py_ret);
	pyg_unblock_threads();
}

static void
pydia_canvas_item_handle_motion (DiaCanvasItem *item, DiaHandle *handle,
				 gdouble *wx, gdouble *wy, DiaEventMask mask)
{
	PyObject *self;
	PyObject *py_handle;
	PyObject *py_ret, *sitem;
	gdouble val[2];
	int i;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif

	pyg_block_threads();

	self = pygobject_new ((GObject*) item);
	py_handle = pygobject_new ((GObject*) handle);
	
	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "handle_motion",
				      "(Oddi)", py_handle, *wx, *wy, mask);

	Py_DECREF (self);
	Py_DECREF (py_handle);

	if (!py_ret) {
		PyErr_Print();
		PyErr_Clear();
		pyg_unblock_threads();
		return;
	}
	if (PySequence_Length(py_ret) != 2) {
		PyErr_SetString(PyExc_TypeError,
				"return value must be a 2 tuple of floats.");
		Py_DECREF (py_ret);
		pyg_unblock_threads();
		return;
	}

	for (i = 0; i < 2; i++) {
		sitem = PySequence_GetItem(py_ret, i);
		Py_DECREF(sitem);
		sitem = PyNumber_Float(sitem);
		if (sitem)
			val[i] = PyFloat_AsDouble(sitem);
		else {
			PyErr_Clear();
			PyErr_SetString(PyExc_TypeError,
					"sequence item not a float");
			Py_DECREF (py_ret);
			pyg_unblock_threads();
			return;
		}
		Py_DECREF(sitem);
	}
	*wx = val[0];
	*wy = val[1];
	Py_DECREF (py_ret);
	pyg_unblock_threads();
}

static gdouble
pydia_canvas_item_glue (DiaCanvasItem *item, DiaHandle *handle,
			gdouble *wx, gdouble *wy)
{
	PyObject *self;
	PyObject *py_handle, *py_ret, *sitem;
	GValue v_wxy = { 0, };
	DiaPoint *wxy;
	gdouble retval = G_MAXDOUBLE;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif

	pyg_block_threads();

	self = pygobject_new ((GObject*) item);
	py_handle = pygobject_new ((GObject*)handle);
	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "glue",
				      "(Odd)", py_handle, *wx, *wy);
	Py_DECREF (self);
	Py_DECREF (py_handle);
	if (!py_ret || py_ret == Py_None) {
		Py_XDECREF (py_ret);
		pyg_unblock_threads();
		return G_MAXDOUBLE;
	}

	if (PySequence_Length(py_ret) != 2) {
		PyErr_SetString(PyExc_TypeError,
				"return value must be a tuple of a float and a point.");
		Py_DECREF(py_ret);
		pyg_unblock_threads();
		return G_MAXDOUBLE;
	}

	/* Get distance */
	sitem = PySequence_GetItem(py_ret, 0);
	Py_DECREF(sitem);
	sitem = PyNumber_Float(sitem);
	if (sitem)
		retval = PyFloat_AsDouble(sitem);
	else {
		PyErr_Clear();
		PyErr_SetString(PyExc_TypeError,
				"first item of sequence item not a float");
		//Py_DECREF(sitem);
		Py_DECREF(py_ret);
		pyg_unblock_threads();
		return G_MAXDOUBLE;
	}
	Py_DECREF(sitem);

	/* Get glue point */
	g_value_init (&v_wxy, DIA_TYPE_POINT);

	sitem = PySequence_GetItem(py_ret, 1);

	if (pyg_value_from_pyobject(&v_wxy, sitem) != 0) {
		PyErr_SetString(PyExc_TypeError, "second item of sequence is not a tuple of 2 floats");
		Py_XDECREF(sitem);
		Py_DECREF(py_ret);
		pyg_unblock_threads();
		return G_MAXDOUBLE;
	}
	wxy = g_value_get_boxed (&v_wxy);

	*wx = wxy->x;
	*wy = wxy->y;
	g_value_unset (&v_wxy);
	Py_DECREF(sitem);
	Py_DECREF(py_ret);

	pyg_unblock_threads();

	return retval;
}

static gboolean
pydia_canvas_item_event (DiaCanvasItem *item, DiaEvent *event)
{
	PyObject *self;
	PyObject *py_event;
	PyObject *py_ret;
	gboolean ret = FALSE;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif

	pyg_block_threads();

	self = pygobject_new ((GObject*) item);
	py_event = pyg_boxed_new (DIA_TYPE_EVENT, event, FALSE, FALSE);
	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "event",
				      "(O)", py_event);
	Py_DECREF (self);
	Py_DECREF (py_event);
	if (py_ret) {
		if (PyObject_IsTrue (py_ret))
			ret = TRUE;
		Py_DECREF (py_ret);
	} else {
		PyErr_Print ();
		PyErr_Clear ();
	}
	pyg_unblock_threads();
	return ret;
}

static gboolean
pydia_canvas_item_connect (DiaCanvasItem *item, DiaHandle *handle)
{
	PyObject *self;
	PyObject *py_handle;
	PyObject *py_ret;
	gboolean ret = FALSE;

	self = pygobject_new ((GObject*) item);

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif
		
	pyg_block_threads();

	py_handle = pygobject_new ((GObject*)handle);
	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "connect_handle",
				      "(O)", py_handle);
	Py_DECREF (self);
	Py_DECREF (py_handle);
	if (py_ret) {
		if (PyObject_IsTrue (py_ret))
			ret = TRUE;
		Py_DECREF (py_ret);
	} else {
		PyErr_Print ();
		PyErr_Clear ();
	}
	pyg_unblock_threads();
	return ret;
}

static gboolean
pydia_canvas_item_disconnect (DiaCanvasItem *item, DiaHandle *handle)
{
	PyObject *self;
	PyObject *py_handle;
	PyObject *py_ret;
	gboolean ret = FALSE;

#ifdef ENABLE_DEBUG
	g_message (__FUNCTION__);
#endif
		
	pyg_block_threads();

	self = pygobject_new ((GObject*) item);
	py_handle = pygobject_new ((GObject*)handle);
	py_ret = PyObject_CallMethod (self,
				      METHOD_PREFIX "disconnect_handle",
				      "(O)", py_handle);
	Py_DECREF (self);
	Py_DECREF (py_handle);
	if (py_ret) {
		if (PyObject_IsTrue (py_ret))
			ret = TRUE;
		Py_DECREF (py_ret);
	} else {
		PyErr_Print ();
		PyErr_Clear ();
	}
	pyg_unblock_threads();
	return ret;
}

/*
 * Methods called by the python code to call their superclass.
 *
 * In order to get access to those methods, the user should have called
 * diacanvas.register_callbacks() on the class. 
 */

#define GET_NON_PYTHON_PARENT_CLASS(klass, callback) \
	klass = DIA_CANVAS_ITEM_GET_CLASS (self->obj); \
	while (klass && klass->callback == pydia_canvas_item_ ## callback) \
		klass = g_type_class_peek_parent (klass);
	
PyObject *
_wrap_dia_canvas_item_on_update(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "affine", NULL };
	PyObject *py_affine;
	GValue v = { 0, };
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:DiaCanvasItem.on_update", kwlist, &py_affine))
		return NULL;
	g_value_init (&v, DIA_TYPE_AFFINE);

	if (pyg_value_from_pyobject (&v, py_affine) != 0)
		return NULL;
    
	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, update);

	if (klass->update)
		klass->update (DIA_CANVAS_ITEM(self->obj),
			       g_value_get_boxed (&v));

	g_value_unset (&v);

	Py_INCREF(Py_None);
	return Py_None;
}

/* Return a Python iterator for all shapes in the canvas item.
 * This function is used to wrap the shapes found in the C classes to
 * Python.
 */
PyObject *
_wrap_dia_canvas_item_on_shape_iter (PyGObject *self)
{
	DiaCanvasItem *item = DIA_CANVAS_ITEM(self->obj);
	DiaCanvasItemClass *klass;
	DiaCanvasIter iter;
	DiaShape *shape;
	PyObject *list, *obj, *listiter;

	list = PyList_New (0);

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, get_shape_iter);

	dia_canvas_iter_init (&iter);

        if (klass && klass->get_shape_iter && klass->shape_value &&
	    klass->get_shape_iter (item, &iter)) do {
                shape = klass->shape_value (item, &iter);
		if (shape) {
			/* do not copy the shape, nor free it when the
			 * box is freed: */
			obj =  pyg_boxed_new (dia_shape_get_type(shape), shape,
					      FALSE, FALSE);
			PyList_Append (list, obj);
			Py_DECREF (obj);
		}
        } while (klass->shape_next && klass->shape_next (item, &iter));

	dia_canvas_iter_destroy (&iter);

	listiter = PyObject_GetIter (list);
	Py_DECREF(list);
	return listiter;
}

PyObject *
_wrap_dia_canvas_item_on_point(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "x", "y", NULL };
	double x, y, ret = G_MAXDOUBLE;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "dd:DiaCanvasItem.on_point",
					 kwlist, &x, &y))
		return NULL;

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, point);

	if (klass->point)
		ret = klass->point (DIA_CANVAS_ITEM(self->obj), x, y);

	return PyFloat_FromDouble(ret);
}

PyObject *
_wrap_dia_canvas_item_on_move(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "x", "y", "interactive", NULL };
	double x, y;
	int interactive;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "ddi:DiaCanvasItem.on_move",
					 kwlist, &x, &y, &interactive))
		return NULL;

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, move);

	if (klass->move)
		klass->move (DIA_CANVAS_ITEM(self->obj), x, y,
			     (gboolean) interactive);

	Py_INCREF(Py_None);
	return Py_None;
}

PyObject *
_wrap_dia_canvas_item_on_handle_motion(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "handle", "x", "y", "mask", NULL };
	PyGObject *handle;
	double x, y;
	DiaEventMask mask;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "Oddi:DiaCanvasItem.on_handle_motion",
					 kwlist, &handle, &x, &y, &mask))
		return NULL;
	if (!pygobject_check(handle, &PyDiaHandle_Type)) {
		PyErr_SetString(PyExc_TypeError, "handle should be a DiaHandle");
		return NULL;
	}

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, handle_motion);

	if (klass->handle_motion)
		klass->handle_motion (DIA_CANVAS_ITEM(self->obj), DIA_HANDLE (handle->obj), &x, &y, mask);

	/* We should somehow forward x and y. */
	return Py_BuildValue ("(dd)", x, y);
}

PyObject *
_wrap_dia_canvas_item_on_glue(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "handle", "x", "y", NULL };
	PyGObject *handle;
	double x = 0.0, y = 0.0, ret = G_MAXDOUBLE;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "Odd:DiaCanvasItem.on_glue",
					 kwlist, &handle, &x, &y))
		return NULL;
    
	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, glue);

	if (klass->glue)
		ret = klass->glue (DIA_CANVAS_ITEM(self->obj), DIA_HANDLE (handle->obj), &x, &y);

	return Py_BuildValue ("(d(dd))", ret, x, y);
}

PyObject *
_wrap_dia_canvas_item_on_event(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "event", NULL };
	PyObject *py_event, *py_ret;
	int ret = FALSE;
	DiaEvent *event = NULL;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "O:DiaCanvasItem.on_event",
					 kwlist, &py_event))
		return NULL;
	if (pyg_boxed_check(py_event, DIA_TYPE_EVENT))
		event = pyg_boxed_get(py_event, DiaEvent);
	else {
		PyErr_SetString(PyExc_TypeError, "event should be a DiaEvent");
		return NULL;
	}

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, event);

	if (klass->event)
		ret = klass->event (DIA_CANVAS_ITEM(self->obj), event);

	py_ret = ret ? Py_True : Py_False;
	Py_INCREF(py_ret);
	return py_ret;
}

PyObject *
_wrap_dia_canvas_item_on_connect_handle(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "handle", NULL };
	PyGObject *handle;
	PyObject *py_ret;
	int ret = FALSE;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "O!:DiaCanvasItem.on_connect_handle",
					 kwlist, &PyDiaHandle_Type, &handle))
		return NULL;

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, connect);

	if (klass->connect)
		ret = klass->connect (DIA_CANVAS_ITEM(self->obj), DIA_HANDLE(handle->obj));

	py_ret = ret ? Py_True : Py_False;
	Py_INCREF(py_ret);
	return py_ret;
}

PyObject *
_wrap_dia_canvas_item_on_disconnect_handle(PyGObject *self, PyObject *args, PyObject *kwargs)
{
	static char *kwlist[] = { "handle", NULL };
	PyGObject *handle;
	PyObject *py_ret;
	int ret = FALSE;
	DiaCanvasItemClass *klass;

	if (!PyArg_ParseTupleAndKeywords(args, kwargs,
					 "O!:DiaCanvasItem.on_disconnect_handle",
					 kwlist, &PyDiaHandle_Type, &handle))
		return NULL;

	/* Find the first class that doesn't have the python callback. */
	GET_NON_PYTHON_PARENT_CLASS(klass, disconnect);

	if (klass->disconnect)
		ret = klass->disconnect (DIA_CANVAS_ITEM(self->obj), DIA_HANDLE(handle->obj));

	py_ret = ret ? Py_True : Py_False;
	Py_INCREF(py_ret);
	return py_ret;
}

/*
 * DiaCanvasGroupable
 *
 * The only canvas item that is groupable by default is DiaCanvasGroup.
 *
 * For now only C->Python groupability will be coded.
 */

#undef METHOD_PREFIX
#define METHOD_PREFIX "on_groupable_"

static void	pydia_canvas_groupable_add		(DiaCanvasGroupable *group,
							 DiaCanvasItem *item);
static void	pydia_canvas_groupable_remove		(DiaCanvasGroupable *group,
							 DiaCanvasItem *item);
static gboolean pydia_canvas_groupable_get_iter		(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);
static gboolean pydia_canvas_groupable_next		(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);
static DiaCanvasItem* pydia_canvas_groupable_value	(DiaCanvasGroupable *group,
							 DiaCanvasIter *iter);

void
pydia_canvas_groupable_init (DiaCanvasGroupableIface *iface)
{
	iface->add = pydia_canvas_groupable_add;
	iface->remove = pydia_canvas_groupable_remove;
	iface->get_iter = pydia_canvas_groupable_get_iter;
	iface->next = pydia_canvas_groupable_next;
	iface->value = pydia_canvas_groupable_value;
}

static void
pydia_canvas_groupable_add (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	PyObject *self;

	self = pygobject_new ((GObject*) group);

#ifdef ENABLE_DEBUG_GROUP
	g_message (__FUNCTION__": self = %p, obj = %p", self, group);
#endif

	if (PyObject_HasAttrString (self, METHOD_PREFIX "add")) {
		PyObject *py_ret, *py_item;

		pyg_block_threads();

		py_item = pygobject_new ((GObject*) item);
		py_ret = PyObject_CallMethod (self,
					      METHOD_PREFIX "add",
					      "O", py_item);
		Py_DECREF(py_item);
		if (py_ret) {
			Py_DECREF (py_ret);
		} else {
			PyErr_Print ();
			PyErr_Clear ();
		}
		pyg_unblock_threads();
	} else {
		g_signal_stop_emission_by_name (group, "add");
	}
	Py_DECREF (self);
}

static void
pydia_canvas_groupable_remove (DiaCanvasGroupable *group, DiaCanvasItem *item)
{
	PyObject *self;

	self = pygobject_new ((GObject*) group);

#ifdef ENABLE_DEBUG_GROUP
	g_message (__FUNCTION__": self = %p, obj = %p", self, group);
#endif
	if (PyObject_HasAttrString (self, METHOD_PREFIX "remove")) {
		PyObject *py_item, *py_ret;

		pyg_block_threads();

		py_item = pygobject_new ((GObject*) item);
		py_ret = PyObject_CallMethod (self,
					      METHOD_PREFIX "remove",
					      "O", py_item);
		Py_DECREF (py_item);
		if (py_ret) {
			Py_DECREF (py_ret);
		} else {
			PyErr_Print ();
			PyErr_Clear ();
		}
		pyg_unblock_threads();
	} else {
		g_signal_stop_emission_by_name (group, "add");
	}
	Py_DECREF (self);
}

static gboolean
pydia_canvas_groupable_get_iter (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	PyObject *self;
	gboolean ret = FALSE;

	self = pygobject_new ((GObject*) group);

#ifdef ENABLE_DEBUG_GROUP
	g_message (__FUNCTION__": self = %p, obj = %p", self, group);
#endif
	if (PyObject_HasAttrString (self, METHOD_PREFIX "iter")) {
		PyObject *py_iter;

		pyg_block_threads();

		py_iter = PyObject_CallMethod (self,
					       METHOD_PREFIX "iter",
					       NULL);
		pyg_unblock_threads();

		if (py_iter && PyIter_Check(py_iter)) {
			iter->destroy_func = destroy_iter;
			iter->data[0] = py_iter;
			iter->data[1] = NULL;

			/* Make sure the iterator is pointing to the first shape: */
			ret = pydia_canvas_groupable_next (group, iter);
		} else {
			g_warning("No iterator returned by self.groupable_iter()");
		}
	}
	Py_DECREF (self);

	return ret;
}

static gboolean
pydia_canvas_groupable_next (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
#ifdef ENABLE_DEBUG_GROUP
	g_message (__FUNCTION__": self = %p, obj = %p", self, group);
#endif

	/* remove reference to previously iterated shape */
	Py_XDECREF ((PyObject*) iter->data[1]);

	/* let data[1] point to the next shape */
	iter->data[1] = PyIter_Next (iter->data[0]);

	/* if PyIter_Next() returned NULL there are no more items to fetch */
	return iter->data[1] != NULL;
}

static DiaCanvasItem*
pydia_canvas_groupable_value (DiaCanvasGroupable *group, DiaCanvasIter *iter)
{
	PyObject *py_item;

#ifdef ENABLE_DEBUG_GROUP
	g_message (__FUNCTION__": self = %p, obj = %p", self, group);
#endif

	py_item = iter->data[1];
	
	if (!PyObject_TypeCheck(py_item, &PyDiaCanvasItem_Type)) {
            PyErr_SetString(PyExc_TypeError,"argument must be a DiaCanvasItem instance");
                return NULL;
        }
	return (DiaCanvasItem*) ((PyGObject*) py_item)->obj;
}


/*
 * DiaCanvasEditable
 *
 * Only C->Python is needed.
 */
#undef METHOD_PREFIX
#define METHOD_PREFIX "on_editable_"

static void	pydia_canvas_editable_start_editing	(DiaCanvasEditable *editable,
							 DiaShapeText *text_shape);
static void	pydia_canvas_editable_editing_done	(DiaCanvasEditable *editable,
							 DiaShapeText *text_shape,
							 const gchar *new_text);
static void	pydia_canvas_editable_text_changed	(DiaCanvasEditable *editable,
							 DiaShapeText *text_shape,
							 const gchar *new_text);

void
pydia_canvas_editable_init (DiaCanvasEditableIface *iface)
{
	iface->start_editing = pydia_canvas_editable_start_editing;
	iface->editing_done = pydia_canvas_editable_editing_done;
	iface->text_changed = pydia_canvas_editable_text_changed;
}


static void
pydia_canvas_editable_start_editing (DiaCanvasEditable *editable,
				     DiaShapeText *text_shape)
{
	PyObject *self;

	self = pygobject_new ((GObject*) editable);

#ifdef ENABLE_DEBUG_EDIT
	g_message (__FUNCTION__": self = %p, obj = %p", self, text_shape);
#endif

	if (PyObject_HasAttrString (self, METHOD_PREFIX "start_editing")) {
		PyObject *py_ret, *py_text_shape;

		pyg_block_threads ();

		py_text_shape = pyg_boxed_new (dia_shape_get_type ((DiaShape*) text_shape),
					       text_shape, FALSE, FALSE);

		py_ret = PyObject_CallMethod (self,
					      METHOD_PREFIX "start_editing",
					      "O", py_text_shape);
		Py_DECREF (py_text_shape);
		pyg_unblock_threads ();

		if (!py_ret) {
			PyErr_Print ();
			PyErr_Clear ();
		} else {
			Py_DECREF (py_ret);
		}
	}
	Py_DECREF (self);
}

static void
pydia_canvas_editable_editing_done (DiaCanvasEditable *editable,
				    DiaShapeText *text_shape,
				    const gchar *new_text)
{
	PyObject *self;

	self = pygobject_new ((GObject*) editable);

#ifdef ENABLE_DEBUG_EDIT
	g_message (__FUNCTION__": self = %p, obj = %p", self, text_shape);
#endif

	if (PyObject_HasAttrString (self, METHOD_PREFIX "editing_done")) {
		PyObject *py_ret, *py_text_shape;

		pyg_block_threads ();

		py_text_shape = pyg_boxed_new (dia_shape_get_type ((DiaShape*) text_shape),
					       text_shape, FALSE, FALSE);

		py_ret = PyObject_CallMethod (self,
					      METHOD_PREFIX "editing_done",
					      "Os", py_text_shape, new_text);
		Py_DECREF (py_text_shape);
		pyg_unblock_threads ();

		if (!py_ret) {
			PyErr_Print ();
			PyErr_Clear ();
		} else {
			Py_DECREF (py_ret);
		}
	}
	Py_DECREF (self);
}

static void
pydia_canvas_editable_text_changed (DiaCanvasEditable *editable,
				    DiaShapeText *text_shape,
				    const gchar *new_text)
{
	PyObject *self;

	self = pygobject_new ((GObject*) editable);

#ifdef ENABLE_DEBUG_EDIT
	g_message (__FUNCTION__": self = %p, obj = %p", self, text_shape);
#endif

	if (PyObject_HasAttrString (self, METHOD_PREFIX "text_changed")) {
		PyObject *py_ret, *py_text_shape;

		pyg_block_threads ();

		py_text_shape = pyg_boxed_new (dia_shape_get_type ((DiaShape*) text_shape),
					       text_shape, FALSE, FALSE);

		py_ret = PyObject_CallMethod (self,
					      METHOD_PREFIX "text_changed",
					      "Os", py_text_shape, new_text);
		Py_DECREF (py_text_shape);
		pyg_unblock_threads ();

		if (!py_ret) {
			PyErr_Print ();
			PyErr_Clear ();
		} else {
			Py_DECREF (py_ret);
		}
	}
	Py_DECREF (self);
}
