/* dia-canvas-view-item.c
 * Copyright (C) 2000  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.
 */

#include <gtk/gtk.h>
#include "dia-canvas-view.h"
#include "dia-canvas-groupable.h"
#include "dia-canvas-editable.h"
#include "dia-handle-layer.h"
#include "dia-shape.h"
#include "dia-shape-art.h"
#include "dia-shape-x.h"
#include <libart_lgpl/art_uta_svp.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_svp_ops.h>
#include <libart_lgpl/art_uta_ops.h>
#include <libgnomecanvas/gnome-canvas-util.h>
#include "dia-canvas-i18n.h"

//#define D(msg) G_STMT_START { g_print (G_STRLOC": "); g_print msg; g_print ("\n"); } G_STMT_END
#define D(msg)

static ArtUta*
uta_dup (ArtUta *uta)
{
	ArtUta *new_uta = art_new (ArtUta, 1);
	*new_uta = *uta;
	new_uta->utiles = art_new (ArtUtaBbox, uta->width * uta->height);
	memcpy (new_uta->utiles, uta->utiles,
		uta->width * uta->height * sizeof(ArtUtaBbox));
	return new_uta;
}

/* Memory allocation:
 * - In every DiaShape (DiaShapeViewInfo)
 * - handles
 */
/*#define DIA_CANVAS_VIEW_EVENTS	GDK_ALL_EVENTS_MASK
#define DIA_CANVAS_VIEW_EVENTS	GDK_POINTER_MOTION_MASK \
				| GDK_POINTER_MOTION_HINT_MASK \
				| GDK_BUTTON_MOTION_MASK \
				| GDK_BUTTON1_MOTION_MASK \
				| GDK_BUTTON2_MOTION_MASK \
				| GDK_BUTTON3_MOTION_MASK \
				| GDK_BUTTON_PRESS_MASK \
				| GDK_BUTTON_RELEASE_MASK \
				| GDK_KEY_PRESS_MASK \
				| GDK_KEY_RELEASE_MASK */
#define DIA_CANVAS_VIEW_EVENTS (GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK)

#define DIA_CANVAS_VIEW_ITEM_USE_GRAB

//#define BB_DEBUG

#ifdef BB_DEBUG
# include <libart_lgpl/art_svp.h>
# include <libart_lgpl/art_uta_rect.h>
# include <libart_lgpl/art_svp_vpath_stroke.h>
# include <libart_lgpl/art_svp_ops.h>
# include <libgnomecanvas/gnome-canvas-util.h>
#endif

enum
{
 	PROP_ITEM = 1,
 	PROP_VISIBLE
};

/* Invariants (constraints that should *always* be true).
 */
#define INVARIANT() \
        g_assert (((DiaCanvasViewItem*)item)->item != NULL); \
        g_assert (DIA_IS_CANVAS_ITEM (((DiaCanvasViewItem*)item)->item));

static void dia_canvas_view_item_init	    (DiaCanvasViewItem *canvas_item);
static void dia_canvas_view_item_class_init (DiaCanvasViewItemClass  *klass);
static void dia_canvas_view_item_set_property	(GObject	*object,
						 guint		 param_id,
						 const GValue	*value,
						 GParamSpec	*pspec);
static void dia_canvas_view_item_get_property	(GObject	*object,
						 guint		 param_id,
						 GValue		*value,
						 GParamSpec	*pspec);
static void dia_canvas_view_item_dispose	(GObject	*object);

static void item_move_cb			(DiaCanvasItem	*item,
						 gdouble dx, gdouble dy,
						 gboolean interactive,
						 DiaCanvasViewItem *vitem);
static void item_notify_affine_cb		(DiaCanvasViewItem *item);
static void item_need_update_cb			(DiaCanvasItem	*item,
						 GnomeCanvasItem *vitem);
static void item_z_order_cb			(DiaCanvasItem *item,
						 gint positions,
						 DiaCanvasViewItem *vitem);
static void item_state_changed_cb		(DiaCanvasItem *item,
						 gint new_state,
						 DiaCanvasViewItem *vitem);
static gboolean item_has_state_cb		(DiaCanvasItem *item,
						 gint state,
						 DiaCanvasViewItem *vitem);
static void edit_item_start_editing_cb		(DiaCanvasEditable *editable,
						 DiaShapeText *text_shape,
						 DiaCanvasViewItem *vitem);
static void edit_item_editing_done_cb		(DiaCanvasEditable *editable,
						 DiaShapeText *text_shape,
						 const gchar *new_text,
						 DiaCanvasViewItem *vitem);
static void  group_item_add_cb			(DiaCanvasItem	*group,
						 DiaCanvasItem	*item,
						 DiaCanvasViewItem *vitem);
static void  group_item_remove_cb		(DiaCanvasItem	*group,
						 DiaCanvasItem	*item,
						 GnomeCanvasGroup *vgroup);
/* GnomeCanvasItem signals */
static void dia_canvas_view_item_update     (GnomeCanvasItem *item,
					     double *affine,
					     ArtSVP *clip_path, int flags);
static void dia_canvas_view_item_realize    (GnomeCanvasItem *item);
static void dia_canvas_view_item_unrealize  (GnomeCanvasItem *item);
static void dia_canvas_view_item_map        (GnomeCanvasItem *item);
static void dia_canvas_view_item_unmap      (GnomeCanvasItem *item);
static void dia_canvas_view_item_draw       (GnomeCanvasItem *item,
					     GdkDrawable *drawable,
					     int x, int y,
					     int width, int height);
static void dia_canvas_view_item_render     (GnomeCanvasItem *item,
					     GnomeCanvasBuf *buf);
static double dia_canvas_view_item_point    (GnomeCanvasItem *item,
					     double x, double y,
					     int cx, int cy,
					     GnomeCanvasItem **actual_item);
static void dia_canvas_view_item_bounds     (GnomeCanvasItem *item,
					     double *x1, double *y1,
					     double *x2, double *y2);

static void	empty_canvas_view_item		(DiaCanvasViewItem *vitem);

static GnomeCanvasGroupClass *parent_class = NULL;

GtkType
dia_canvas_view_item_get_type (void)
{
	static GtkType canvas_item_type = 0;

	if (!canvas_item_type) {
		static const GtkTypeInfo canvas_item_info =
		{
			"DiaCanvasViewItem",
			sizeof (DiaCanvasViewItem),
			sizeof (DiaCanvasViewItemClass),
			(GtkClassInitFunc) dia_canvas_view_item_class_init,
			(GtkObjectInitFunc) dia_canvas_view_item_init,
			/* reserved_1 */ NULL,
			/* reserved_2 */ NULL,
			(GtkClassInitFunc) NULL,
		};
		
		canvas_item_type = gtk_type_unique (gnome_canvas_group_get_type (),
						    &canvas_item_info);
	}

	return canvas_item_type;
}

static void
dia_canvas_view_item_class_init (DiaCanvasViewItemClass *klass)
{
	GObjectClass *object_class;
	GtkObjectClass *widget_class;
	GnomeCanvasItemClass *item_class;
  
	object_class = (GObjectClass*) klass;
	widget_class = (GtkObjectClass*) klass;
	item_class = (GnomeCanvasItemClass*) klass;
  
	parent_class = g_type_class_peek_parent (klass);

	object_class->dispose = dia_canvas_view_item_dispose;

	object_class->get_property = dia_canvas_view_item_get_property;
	object_class->set_property = dia_canvas_view_item_set_property;

	g_object_class_install_property (object_class,
					 PROP_ITEM,
					 g_param_spec_object ("item",
					 _("Item"),
					 _("DiaCanvasItem that should be visualized"),
					 DIA_TYPE_CANVAS_ITEM,
					 G_PARAM_READABLE
					 | G_PARAM_WRITABLE));
	g_object_class_install_property (object_class,
					 PROP_VISIBLE,
					 g_param_spec_boolean ("visible",
						 _("Visible"),
					 _("Whether the canvas item is visible"),
					 TRUE,
					 G_PARAM_READWRITE));

	item_class->update = dia_canvas_view_item_update;
	item_class->realize = dia_canvas_view_item_realize;
	item_class->unrealize = dia_canvas_view_item_unrealize;
	item_class->map = dia_canvas_view_item_map;
	item_class->unmap = dia_canvas_view_item_unmap;
	item_class->draw = dia_canvas_view_item_draw;
	item_class->render = dia_canvas_view_item_render;
	item_class->point = dia_canvas_view_item_point;
	item_class->bounds = dia_canvas_view_item_bounds;
}

static void
dia_canvas_view_item_init (DiaCanvasViewItem *item)
{
	item->item = NULL;
	GTK_OBJECT_SET_FLAGS (item, GNOME_CANVAS_ITEM_VISIBLE
			      | GNOME_CANVAS_ITEM_NEED_UPDATE
			      | GNOME_CANVAS_ITEM_NEED_AFFINE
			      | GNOME_CANVAS_ITEM_NEED_VIS
			      | GNOME_CANVAS_ITEM_AFFINE_FULL
			      | DIA_CANVAS_VIEW_ITEM_VISIBLE);
	item->redraw_area = NULL;
	item->n_handle_pos = 0;
	item->handle_pos = NULL;
	item->event_time = GDK_CURRENT_TIME;
}

static void
dia_canvas_view_item_set_property (GObject *object, guint property_id,
				   const GValue *value, GParamSpec *pspec)
{
	DiaCanvasViewItem *item = DIA_CANVAS_VIEW_ITEM (object);
	
	switch (property_id) {
	case PROP_ITEM:
		if (item->item) {
			g_error ("Setting an item while already an item is set!");
		} else {
			GObject *citem = g_value_get_object (value);
			g_return_if_fail (citem != NULL);
			
			item->item = (DiaCanvasItem*) citem;

			g_signal_connect (citem, "move",
					  G_CALLBACK (item_move_cb),
					  item);

			g_signal_connect_swapped (citem,
						  "notify::affine",
						  G_CALLBACK (item_notify_affine_cb),
						  item);

			g_signal_connect (citem, "need_update",
					  G_CALLBACK (item_need_update_cb),
					  item);

			g_signal_connect (citem, "z_order",
					  G_CALLBACK (item_z_order_cb),
					  item);

			g_signal_connect (citem, "state_changed",
					  G_CALLBACK (item_state_changed_cb),
					  item);

			g_signal_connect (citem, "has_state",
					  G_CALLBACK (item_has_state_cb),
					  item);

			/* Make sure the visibility is set correctly: */
			if (DIA_CANVAS_ITEM_FLAGS (citem) & DIA_VISIBLE)
				GTK_OBJECT_SET_FLAGS (item, GNOME_CANVAS_ITEM_VISIBLE);
			else
				GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_VISIBLE);

			/* Connect some extra signals if DiaCanvasItem is
			 * editable...
			 */
			if (DIA_IS_CANVAS_EDITABLE (citem)) {
				g_signal_connect_after (G_OBJECT (citem),
					"start_editing",
					G_CALLBACK (edit_item_start_editing_cb),
					item);
				g_signal_connect_after (G_OBJECT (citem),
					"editing_done",
					G_CALLBACK (edit_item_editing_done_cb),
					item);
			}
			/* If our item is a group, notify the CanvasViewItem
			 * that a new object has to be added, if a new object
			 * is added to the DiaCanvas. */
			if (DIA_IS_CANVAS_GROUPABLE (citem)) {
				//g_message (__FUNCTION__": Adding group signals");
				g_signal_connect_after (G_OBJECT (citem),
					"add",
					G_CALLBACK (group_item_add_cb),
					item);
				g_signal_connect_after (G_OBJECT (citem),
					"remove",
					G_CALLBACK (group_item_remove_cb),
					item);
			}

			/* Point directly to the transformation! */
			GNOME_CANVAS_ITEM (item)->xform = ((DiaCanvasItem*) citem)->affine;
			GTK_OBJECT_SET_FLAGS (GTK_OBJECT (item), 
					      GNOME_CANVAS_ITEM_AFFINE_FULL);
			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (item));
		}
		break;
	case PROP_VISIBLE:
		if (g_value_get_boolean (value)) {
			GTK_OBJECT_SET_FLAGS (object, DIA_CANVAS_VIEW_ITEM_VISIBLE);
			if (DIA_CANVAS_ITEM_VISIBLE (item->item))
				GTK_OBJECT_SET_FLAGS (object, GNOME_CANVAS_ITEM_VISIBLE);
			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (item));
		} else {
			DiaCanvasView *view = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM(object)->canvas);
			GTK_OBJECT_UNSET_FLAGS (object, GNOME_CANVAS_ITEM_VISIBLE | DIA_CANVAS_VIEW_ITEM_VISIBLE);
			if (view)
				dia_canvas_view_unselect (view, item);
			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (item));
		}
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
dia_canvas_view_item_get_property (GObject *object, guint property_id,
				   GValue *value, GParamSpec *pspec)
{
	DiaCanvasViewItem *item = DIA_CANVAS_VIEW_ITEM (object);
	
	switch (property_id) {
	case PROP_ITEM:
		g_value_set_object (value, (GObject*) DIA_CANVAS_VIEW_ITEM (item)->item);
		break;
	case PROP_VISIBLE:
		g_value_set_boolean (value, (GTK_OBJECT_FLAGS (object) & DIA_CANVAS_VIEW_ITEM_VISIBLE) != 0);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
dia_canvas_view_item_dispose (GObject *object)
{
	DiaCanvasViewItem *vitem = (DiaCanvasViewItem*) object;

	//g_message ("%s: begin (%x)", __FUNCTION__, (int)object);

	if (vitem->item) {
		empty_canvas_view_item (vitem);
	}

	/* Reset the transformation, since it's pointing to the
	 * transformation in DiaCanvasItem::affine! */
	((GnomeCanvasItem*) object)->xform = NULL;

	if (GNOME_CANVAS_ITEM (vitem)->canvas) {
		DiaCanvasView *canvas = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (vitem)->canvas);
		canvas->selected_items = g_list_remove (canvas->selected_items,
							vitem);

		/* If the object is set as focused object, unset it. */
		if (canvas->focus_item == vitem)
			canvas->focus_item = NULL;
	}

	/* Make sure that removing the item from its parent does not result
	 * in a total freeing of the object. */
	if (G_OBJECT_CLASS (parent_class)->dispose)
		G_OBJECT_CLASS (parent_class)->dispose (object);

}

static void
item_move_cb (DiaCanvasItem *item, gdouble dx, gdouble dy,
	      gboolean interactive, DiaCanvasViewItem *vitem)
{
	DiaCanvasView *view = DIA_CANVAS_VIEW (((GnomeCanvasItem* )vitem)->canvas);

	if (interactive && (view == dia_canvas_view_get_active_view ())) {
		gdouble i2w[6];
		/* First transform the dx, dy to world coordinates. */
		dia_canvas_item_affine_i2w (DIA_CANVAS_ITEM (item->parent), i2w);
		i2w[4] = dx * i2w[0] + dy * i2w[2];
		i2w[5] = dx * i2w[1] + dy * i2w[3];
		dia_canvas_view_move (view, i2w[4], i2w[5], vitem);
	}

	GTK_OBJECT_SET_FLAGS (vitem, DIA_CANVAS_VIEW_ITEM_UPDATE_SHAPES);
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (vitem));
}

static void
item_notify_affine_cb (DiaCanvasViewItem *item)
{
	GTK_OBJECT_SET_FLAGS (item, DIA_CANVAS_VIEW_ITEM_UPDATE_SHAPES);
}

/**
 * item_need_update_cb:
 * @item: 
 * @vitem: 
 * 
 * This function is called for every CanvasViewItem. This way ViewItems are
 * notified that the need updating.
 **/
static void
item_need_update_cb (DiaCanvasItem *item, GnomeCanvasItem *vitem)
{
	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vitem));

	gnome_canvas_item_request_update (vitem);
}

static void
item_z_order_cb (DiaCanvasItem *item, gint positions, DiaCanvasViewItem *vitem)
{
	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vitem));

	if (positions < 0)
		gnome_canvas_item_lower (GNOME_CANVAS_ITEM (vitem), -positions);
	else if (positions > 0)
		gnome_canvas_item_raise (GNOME_CANVAS_ITEM (vitem), positions);

	/* Both items should be on the same position now. */
	g_assert (dia_canvas_groupable_pos (DIA_CANVAS_GROUPABLE (item->parent), item) == g_list_index (GNOME_CANVAS_GROUP (GNOME_CANVAS_ITEM (vitem)->parent)->item_list, vitem));
}

static void
item_state_changed_cb (DiaCanvasItem *item, gint new_state,
		       DiaCanvasViewItem *vitem)
{
	DiaCanvasView *view;
	gboolean need_update = FALSE;

	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vitem));

	view = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (vitem)->canvas);

	/* Make the view item visible only if the canvas item is visible
	 * and the view item is allowed to be shown. */
	if (DIA_CANVAS_ITEM_FLAGS (item) & DIA_VISIBLE
	    && GTK_OBJECT_FLAGS(vitem) & DIA_CANVAS_VIEW_ITEM_VISIBLE) {
		if (!(GTK_OBJECT_FLAGS(vitem) & GNOME_CANVAS_ITEM_VISIBLE)) {
			GTK_OBJECT_SET_FLAGS (vitem, GNOME_CANVAS_ITEM_VISIBLE);
			need_update = TRUE;
		}
	} else {
		if (GTK_OBJECT_FLAGS(vitem) & GNOME_CANVAS_ITEM_VISIBLE) {
			GTK_OBJECT_UNSET_FLAGS (vitem, GNOME_CANVAS_ITEM_VISIBLE);
			dia_canvas_view_unselect (view, vitem);
			need_update = TRUE;
		}
	}
		
	if (view && view == dia_canvas_view_get_active_view ()
	    && new_state != DIA_UI_STATE_UNCHANGED
	    && GTK_OBJECT_FLAGS(vitem) & GNOME_CANVAS_ITEM_VISIBLE) {
		if (new_state == DIA_UI_STATE_GRABBED) {
#ifdef DIA_CANVAS_VIEW_ITEM_USE_GRAB
			gnome_canvas_item_grab (GNOME_CANVAS_ITEM (vitem),
						DIA_CANVAS_VIEW_EVENTS,
						NULL,
						vitem->event_time);
#endif
			need_update = TRUE;
		} else {
			gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (vitem),
						  vitem->event_time);
			need_update = TRUE;
		}

		if (new_state == DIA_UI_STATE_GRABBED
		    || new_state == DIA_UI_STATE_FOCUSED) {
			/* Only select if the item is not yet focused:
			 * a composite object might hold the focus.
			 * (item is automatically selected) */
			if (!dia_canvas_view_item_is_focused (vitem))
				dia_canvas_view_focus (view, vitem);
			need_update = TRUE;
		} else if (new_state == DIA_UI_STATE_SELECTED) {
			/* In case we're dealing with a focused item,
			 * just unfocus, else explicitly select it. */
			if (dia_canvas_view_item_is_focused (vitem))
				dia_canvas_view_focus (view, NULL);
			else
				dia_canvas_view_select (view, vitem);
			need_update = TRUE;
		} else {
			dia_canvas_view_unselect (view, vitem);
			need_update = TRUE;
		}
	}

	if (need_update)
		gnome_canvas_item_request_update ((GnomeCanvasItem*) vitem);
}

static gboolean
item_has_state_cb (DiaCanvasItem *item, gint state, DiaCanvasViewItem *vitem)
{
	static guint has_state_signal_id = 0;
	gboolean retval = TRUE;
	DiaCanvasView *view;

	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vitem));

	view = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (vitem)->canvas);

	if (view != dia_canvas_view_get_active_view ())
		return FALSE;

	/* g_message (G_STRLOC": have active view. request %s",
			(state & DIA_UI_STATE_GRABBED) ? "grabbed"
			: ((state & DIA_UI_STATE_FOCUSED) ? "focused"
			   : ((state & DIA_UI_STATE_SELECTED) ? "selected"
			      : "unknown"))); */

	switch (state) {
	case DIA_UI_STATE_SELECTED:
		retval = DIA_CANVAS_VIEW_ITEM_SELECT(vitem);
		break;
	case DIA_UI_STATE_FOCUSED:
		retval = DIA_CANVAS_VIEW_ITEM_FOCUS(vitem);
		break;
	case DIA_UI_STATE_GRABBED:
		retval = DIA_CANVAS_VIEW_ITEM_GRAB(vitem);
		break;
	default:
		break;
	}

	/* We are the active view, so stop further emission: */
	if (has_state_signal_id == 0)
		has_state_signal_id = g_signal_lookup ("has_state",
						       DIA_TYPE_CANVAS_ITEM);

	g_signal_stop_emission (item, has_state_signal_id, 0);

	return retval ? TRUE : FALSE;
}

/*
 * Signals for DiaCanvasEditables
 */

static void
edit_item_start_editing_cb (DiaCanvasEditable *editable,
			    DiaShapeText *text_shape,
			    DiaCanvasViewItem *vitem)
{
	DiaCanvasView *view = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (vitem)->canvas);

	g_assert (vitem->item == (DiaCanvasItem*) editable);

	if (view == dia_canvas_view_get_active_view ())
		dia_canvas_view_start_editing (view, vitem, text_shape);
}

static void
edit_item_editing_done_cb (DiaCanvasEditable *editable,
			   DiaShapeText *text_shape,
			   const gchar *text, DiaCanvasViewItem *vitem)
{
}

/*
 * Signals for DiaCanvasGroupables
 */
static void
group_item_add_cb (DiaCanvasItem *group, DiaCanvasItem *item,
		     DiaCanvasViewItem *vitem)
{
	/* So, `item' is added to `group' and now we should create a
	 * new child to `vitem'. */
	g_assert (DIA_IS_CANVAS_GROUPABLE (group));
	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vitem));

	//g_message (__FUNCTION__);
	if (item->parent == group)
		dia_canvas_view_item_add_items (GNOME_CANVAS_GROUP (vitem), item);
}

static void
group_item_remove_cb (DiaCanvasItem *group, DiaCanvasItem *item,
		      GnomeCanvasGroup *vgroup)
{
	GList *l;
	DiaCanvasViewItem *vitem = NULL;

	g_assert (DIA_IS_CANVAS_GROUPABLE (group));
	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_ITEM (vgroup));

	/* Do nothing if the canvas item has not been removed. */
	if (item->parent == group)
		return;

	//g_message (__FUNCTION__);
	for (l = vgroup->item_list; l != NULL; l = l->next) {
		if (DIA_CANVAS_VIEW_ITEM (l->data)->item == item) {
			vitem = l->data;
			break;
		}
	}
	g_assert (vitem != NULL);

	empty_canvas_view_item (vitem);

	gtk_object_destroy (GTK_OBJECT (vitem));
}

static void
remove_view_info_from_shapes (DiaCanvasViewItem *item)
{
	DiaCanvasIter iter;

	if (dia_canvas_item_get_shape_iter (item->item, &iter)) do {
		DiaShape *shape = dia_canvas_item_shape_value (item->item, &iter);
		DiaShapeViewInfo *view_info;

		view_info = dia_shape_get_view_info (shape, item);
		if (view_info)
			dia_shape_view_info_remove (shape, view_info);
	} while (dia_canvas_item_shape_next (item->item, &iter));
}

static void
empty_canvas_view_item (DiaCanvasViewItem *item)
{
	if (!item->item)
		return;

//	if (GNOME_CANVAS_ITEM (item)->canvas->grabbed_item == GNOME_CANVAS_ITEM (item))
//		gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (item),
//					  item->event_time);

	g_signal_handlers_disconnect_matched (item->item,
					      G_SIGNAL_MATCH_DATA,
					      0, /* signal_id */
					      0, /* detail */
					      NULL, /* closure */
					      NULL, /* func */
					      item /* data */);

	/* Remove view info from shapes!
	 * Do this for normal state, for selected, focused and grabbed states.
	 */
	remove_view_info_from_shapes (item);

	/* Request a redraw for the removed area: */
	if (item->redraw_area) {
		/* item->redraw_area is freed here: */
		gnome_canvas_request_redraw_uta (GNOME_CANVAS_ITEM (item)->canvas, item->redraw_area);
		item->redraw_area = NULL;
	}

	/* Request a redraw of the regions where the handles were, only if the
	 * handle layer is still in business. */
	if (DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (item)->canvas)->handle_layer)
		dia_handle_layer_update_handles ((DiaHandleLayer*) DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (item)->canvas)->handle_layer, item);

	g_free (item->handle_pos);
	item->n_handle_pos = 0;

	item->item = NULL;
}

static guint32
event_get_time (GdkEvent *event)
{
	if (event)
		switch (event->type) {
			case GDK_MOTION_NOTIFY:
			       return event->motion.time;
			case GDK_BUTTON_PRESS:
			case GDK_2BUTTON_PRESS:
			case GDK_3BUTTON_PRESS:
			case GDK_BUTTON_RELEASE:
			       return event->button.time;
			case GDK_KEY_PRESS:
			case GDK_KEY_RELEASE:
			       return event->key.time;
			default:
			       break;
		}
	return GDK_CURRENT_TIME;
}

/**
 * affine_clip:
 * @clip: 
 * @affine: 
 * @irect: OUT
 *
 * Generate a clip box in canvas coordinates (@irect), given a item
 * relative clip rectangle (@clip) and an affine matrix (@affine).
 *
 * Return value: TRUE if we have a clip path, FALSE if @clip contains no
 * clip path (left == right and top == bottom).
 **/
static inline gboolean
affine_clip (ArtDRect *clip, gdouble *affine, ArtIRect *irect)
{
	ArtDRect drect;

	art_drect_affine_transform (&drect, clip, affine);

	art_drect_to_irect (irect, &drect);

	return (art_irect_empty (irect) ? FALSE : TRUE);
}

/* GnomeCanvasItem signals */
/* clip_path is in canvas coordinates... */
static void
dia_canvas_view_item_update (GnomeCanvasItem *item,
			     double *affine, ArtSVP *clip, int flags)
{
	DiaCanvasItem *diaitem;
	DiaCanvasViewItem *cvitem;
	GnomeCanvas *canvas;
	DiaCanvasIter iter;
	gboolean free_clip = FALSE;
	ArtSVP *new_clip;
	/* Prototype for the update function. */
	ArtSVP* (* update_func) (DiaShape*, DiaCanvasViewItem*, double*, ArtSVP*, int);

	D((" "));

	INVARIANT ();
	
	canvas = item->canvas;
	if (!DIA_CANVAS_VIEW (canvas)->canvas)
		return;

	diaitem = DIA_CANVAS_VIEW_ITEM (item)->item;
	cvitem = DIA_CANVAS_VIEW_ITEM (item);

	g_assert (DIA_IS_CANVAS_ITEM (diaitem));

	//g_message (__FUNCTION__": BoundingBox (%x): %f, %f, %f, %f", 
	//(int)diaitem, item->x1, item->y1, item->x2, item->y2);

	dia_handle_layer_update_handles ((DiaHandleLayer*) DIA_CANVAS_VIEW (canvas)->handle_layer,
					 cvitem);

	/* Update the shapes drawn. */
	if (item->canvas->aa) {
		update_func = dia_shape_art_update;
	} else {
		update_func = dia_shape_x_update;
	}

	//dia_canvas_view_item_update_item_state (cvitem);

	if (cvitem->redraw_area) {
		/* redraw_area is freed here: */
		gnome_canvas_request_redraw_uta (item->canvas, cvitem->redraw_area);
		cvitem->redraw_area = NULL;
	}

	if (dia_canvas_item_get_shape_iter (diaitem, &iter)) do {
		DiaShape *shape = dia_canvas_item_shape_value (diaitem, &iter);

		if (shape) {
			/* update render info */
			new_clip = update_func (shape,
						DIA_CANVAS_VIEW_ITEM (item),
						affine, clip, flags);

			if (new_clip && clip && free_clip) {
				art_svp_free (clip);
			}
			if (new_clip) {
				clip = new_clip;
				free_clip = TRUE;
			}
		}
	} while (dia_canvas_item_shape_next (diaitem, &iter));

        if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
		GNOME_CANVAS_ITEM_CLASS (parent_class)->update (item, affine,
								clip, flags);

	GTK_OBJECT_UNSET_FLAGS (item, DIA_CANVAS_VIEW_ITEM_UPDATE_SHAPES);

	if (clip && free_clip)
		art_svp_free (clip);

	/* Update the bounding box, taking into account the possible
	 * affine transformation. */
	if (affine) {
		dia_canvas_item_bb_affine (diaitem, affine,
					   &item->x1, &item->y1,
					   &item->x2, &item->y2);
	} else {
		item->x1 = diaitem->bounds.left;
		item->y1 = diaitem->bounds.top;
		item->x2 = diaitem->bounds.right;
		item->y2 = diaitem->bounds.bottom;
	}
}

static void
dia_canvas_view_item_realize (GnomeCanvasItem *item)
{
	INVARIANT ();

	D((" "));

	if (!item->canvas->aa) {
		DIA_CANVAS_VIEW_ITEM (item)->gc = gdk_gc_new (item->canvas->layout.bin_window);
	}

	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->realize)
		(* GNOME_CANVAS_ITEM_CLASS (parent_class)->realize) (item);
}

static void
dia_canvas_view_item_unrealize (GnomeCanvasItem *item)
{
	//INVARIANT ();

	D((" "));

	if (!item->canvas->aa) {
		gdk_gc_unref (DIA_CANVAS_VIEW_ITEM (item)->gc);
		DIA_CANVAS_VIEW_ITEM (item)->gc = NULL;
	}

	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize)
		(* GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize) (item);
}

static void
dia_canvas_view_item_map (GnomeCanvasItem *item)
{
	INVARIANT ();

	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->map)
		(* GNOME_CANVAS_ITEM_CLASS (parent_class)->map) (item);
}

static void
dia_canvas_view_item_unmap (GnomeCanvasItem *item)
{
	//INVARIANT ();

	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->unmap)
		(* GNOME_CANVAS_ITEM_CLASS (parent_class)->unmap) (item);
}

static void
dia_canvas_view_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
			   int x, int y, int width, int height)
{
	DiaCanvasIter iter;
	DiaCanvasItem *diaitem;

	INVARIANT ();
	
	D((" "));
	diaitem = DIA_CANVAS_VIEW_ITEM (item)->item;

	//dia_canvas_view_item_update_item_state ((DiaCanvasViewItem*) item);

	if (dia_canvas_item_get_shape_iter (diaitem, &iter)) do {
		DiaShape *shape = dia_canvas_item_shape_value (diaitem, &iter);
		DiaShapeVisibility vis = dia_shape_get_visibility (shape);
		if ((vis == DIA_SHAPE_VISIBLE)
		    || ((vis == DIA_SHAPE_VISIBLE_IF_SELECTED)
			&& DIA_CANVAS_VIEW_ITEM_SELECT (item))
		    || ((vis == DIA_SHAPE_VISIBLE_IF_FOCUSED)
			&& DIA_CANVAS_VIEW_ITEM_FOCUS (item))
		    || ((vis == DIA_SHAPE_VISIBLE_IF_GRABBED)
		        && DIA_CANVAS_VIEW_ITEM_GRAB (item)))
			dia_shape_x_render (shape,
					    DIA_CANVAS_VIEW_ITEM (item),
					    drawable, x, y, width, height);
	} while (dia_canvas_item_shape_next (diaitem, &iter));

	//dia_canvas_view_item_reset_item_state (item);

	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->draw)
		GNOME_CANVAS_ITEM_CLASS (parent_class)->draw (item, drawable, x, y, width, height);
}

static void
dia_canvas_view_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf)
{
	DiaCanvasIter iter;
	DiaCanvasItem *diaitem;
	DiaCanvasView *view;
	D((" "));

	INVARIANT ();
	
	g_assert (DIA_IS_CANVAS_VIEW_ITEM (item));

	diaitem = DIA_CANVAS_VIEW_ITEM (item)->item;
	view = DIA_CANVAS_VIEW (item->canvas);

	gnome_canvas_buf_ensure_buf (buf);

	if (!view->canvas)
		return;

	//dia_canvas_view_item_update_item_state ((DiaCanvasViewItem*) item);

	if (dia_canvas_item_get_shape_iter (diaitem, &iter)) do {
		DiaShape *shape = dia_canvas_item_shape_value (diaitem, &iter);
		DiaShapeVisibility vis = dia_shape_get_visibility (shape);
		if ((vis == DIA_SHAPE_VISIBLE)
		    || ((vis == DIA_SHAPE_VISIBLE_IF_SELECTED)
			&& DIA_CANVAS_VIEW_ITEM_SELECT (item))
		    || ((vis == DIA_SHAPE_VISIBLE_IF_FOCUSED)
			&& DIA_CANVAS_VIEW_ITEM_FOCUS (item))
		    || ((vis == DIA_SHAPE_VISIBLE_IF_GRABBED)
		        && DIA_CANVAS_VIEW_ITEM_GRAB (item))) {
			if (shape != (DiaShape*) view->edited_shape)
				dia_shape_art_render (shape,
						      DIA_CANVAS_VIEW_ITEM (item),
						      buf);
		}
	} while (dia_canvas_item_shape_next (diaitem, &iter));
	
	//dia_canvas_view_item_reset_item_state (item);

#ifdef BB_DEBUG
	G_STMT_START {
		ArtVpath path[6];
		ArtSVP *svp;
		
		path[0].code = ART_MOVETO;
		path[0].x = item->x1;
		path[0].y = item->y1;
		path[1].code = ART_LINETO;
		path[1].x = item->x1;
		path[1].y = item->y2;
		path[2].code = ART_LINETO;
		path[2].x = item->x2;
		path[2].y = item->y2;
		path[3].code = ART_LINETO;
		path[3].x = item->x2;
		path[3].y = item->y1;
		path[4].code = ART_LINETO;
		path[4].x = item->x1;
		path[4].y = item->y1;
		path[5].code = ART_END;
		path[5].x = 0.0;
		path[5].y = 0.0;

		svp = art_svp_vpath_stroke (path, ART_PATH_STROKE_JOIN_MITER,
					    ART_PATH_STROKE_CAP_BUTT, 1.0,
					    4, 0.25);
		gnome_canvas_render_svp (buf, svp, 0x88888888);
		art_free (svp);
	} G_STMT_END;
#endif /* BB_DEBUG */
	//if (GNOME_CANVAS_ITEM_CLASS (parent_class)->render)
	GNOME_CANVAS_ITEM_CLASS (parent_class)->render (item, buf);

	D(("done."));
}

static double
dia_canvas_view_item_point (GnomeCanvasItem *item, double x, double y,
			    int cx, int cy, GnomeCanvasItem **actual_item)
{
	double dist1 = G_MAXDOUBLE, dist2 = G_MAXDOUBLE;
	DiaCanvasItemClass *citem_class;
	GnomeCanvasItem *actual_item1 = NULL, *actual_item2 = NULL;

	INVARIANT ();
	
	citem_class = DIA_CANVAS_ITEM_GET_CLASS (DIA_CANVAS_VIEW_ITEM (item)->item);
	
	if (!citem_class->point)
		return G_MAXDOUBLE;

	if (DIA_CANVAS_ITEM_INTERACTIVE (DIA_CANVAS_VIEW_ITEM (item)->item)) {
		dist1 = citem_class->point (DIA_CANVAS_VIEW_ITEM (item)->item,
					    x, y);
		actual_item1 = item;
	}
	
	dist2 = ((GnomeCanvasItemClass*) parent_class)->point (item, x, y,
							       cx, cy,
							       &actual_item2);

	if (!actual_item2 || dist1 < dist2) {
		*actual_item = actual_item1;
		return dist1;
	}
	*actual_item = actual_item2;
	return dist2;
}

static void
dia_canvas_view_item_bounds (GnomeCanvasItem *item,
			    double *x1, double *y1, double *x2, double *y2)
{
	DiaCanvasItem *citem;

	INVARIANT ();

	D((" "));

	citem = DIA_CANVAS_VIEW_ITEM (item)->item;

	dia_canvas_item_update_now (DIA_CANVAS_VIEW_ITEM (item)->item);
	
	if (item->canvas->need_update)
		gnome_canvas_update_now (item->canvas);

	*x1 = item->x1;
	*y1 = item->y1;
	*x2 = item->x2;
	*y2 = item->y2;
}


/**
 * dia_canvas_view_item_emit_event:
 * @item:
 * @event:
 *
 * Send a #DiaEvent to the #DiaCanvasItem refered to by @item.
 *
 * Return value: TRUE if the event is handled, FALSE otherwise.
 **/
gboolean
dia_canvas_view_item_emit_event (DiaCanvasViewItem *item, DiaEvent *event)
{
	static guint event_signal_id = 0;
	gboolean retval = FALSE;

	g_return_val_if_fail (DIA_IS_CANVAS_VIEW_ITEM (item), FALSE);
	g_return_val_if_fail (DIA_IS_CANVAS_ITEM (item->item), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	if (event_signal_id == 0)
		event_signal_id = g_signal_lookup ("event",
						   DIA_TYPE_CANVAS_ITEM);

	//dia_canvas_view_item_update_item_state (item);

	g_signal_emit (G_OBJECT (item->item), event_signal_id, 0, event, &retval);

	//dia_canvas_view_item_reset_item_state (item);

	item->event_time = GDK_CURRENT_TIME;

	return retval;
}

/**
 * dia_canvas_view_item_request_redraw_uta:
 * @item: A canvas view item.
 * @uta: Microtile array that specifies the area to be redrawn.  It will
 * be freed by this function, so the argument you pass will be invalid
 * after you call this function.
 *
 * Informs a canvas that the specified area, given as a microtile array, needs
 * to be repainted.  To be used only by item implementations.
 *
 * The interface is the same as gnome_canvas_request_redraw_uta(). This
 * function is also called to update the canvas' redraw area.
 **/
void
dia_canvas_view_item_request_redraw_uta (DiaCanvasViewItem *item, ArtUta *uta)
{
        //ArtIRect visible;
	GnomeCanvas *canvas;

        g_return_if_fail (DIA_IS_CANVAS_VIEW_ITEM (item));
        g_return_if_fail (uta != NULL);

	canvas = GNOME_CANVAS_ITEM (item)->canvas;
        if (!(canvas && GTK_WIDGET_DRAWABLE (canvas))) {
                art_uta_free (uta);
                return;
        }

        /* get_visible_region (canvas, &visible); from gnome-canvas.c: */
	/*
        visible.x0 = canvas->layout.hadjustment->value - canvas->zoom_xofs;
        visible.y0 = canvas->layout.vadjustment->value - canvas->zoom_yofs;
        visible.x1 = visible.x0 + GTK_WIDGET (canvas)->allocation.width;
        visible.y1 = visible.y0 + GTK_WIDGET (canvas)->allocation.height;
	*/

        if (item->redraw_area) {
                ArtUta *new_uta;

                //new_uta = uta_union_clip (item->redraw_area, uta, &visible);
                new_uta = art_uta_union (item->redraw_area, uta);
                art_uta_free (item->redraw_area);
                item->redraw_area = new_uta;
        } else {
                item->redraw_area = uta_dup (uta);
        }

	/* here uta is freed: */
	gnome_canvas_request_redraw_uta (canvas, uta);
}

/**
 * dia_canvas_view_item_add_items:
 * @item: 
 * @vitem:
 *
 * Add an item to the #DiaCanvasView's tree and connect some signals so it
 * can automatically update itself when something changes
 *
 * @vitem is of type #GnomeCanvasGroup, since the real root item of the
 * #DiaCanvasView is actually a #GnomeCanvasGroup.
 *
 * This function is used internally.
 * 
 * So, @item is added to the canvas and now we should create a new child
 * to @vitem in order to update the #DiaCanvasView (#GnomeCanvas).
 */
void
dia_canvas_view_item_add_items (GnomeCanvasGroup *vitem, DiaCanvasItem *item)
{
	DiaCanvasViewItem *new_vitem;
	gint item_index, vitem_index;
	DiaCanvasIter iter;

	g_assert (DIA_IS_CANVAS_ITEM (item));
	g_assert (GNOME_IS_CANVAS_GROUP (vitem));
	
	new_vitem = (DiaCanvasViewItem*) gnome_canvas_item_new (GNOME_CANVAS_GROUP (vitem), DIA_TYPE_CANVAS_VIEW_ITEM, "item", item, NULL);
	
	g_assert (new_vitem != NULL);
	
	//gnome_canvas_item_request_update (item);

	/* Move the newly created DiaCanvasViewItem to the right place in
	 * the item list (but not for the root item ;-). */
	if (item->parent) {
		item_index = dia_canvas_groupable_pos (DIA_CANVAS_GROUPABLE (item->parent), item);
		vitem_index = g_list_index (vitem->item_list, new_vitem);
		
		/* New items are always added on the top, so if the
		 * vitem's index is higher that item's there's something
		 * very wrong! */
		//g_assert (item_index <= vitem_index);
		if (item_index > vitem_index) {
			g_error("item index (%d) > view item index (%d)", item_index, vitem_index);
			g_assert_not_reached();
		}
			
		if (item_index < vitem_index) {
			gnome_canvas_item_lower (GNOME_CANVAS_ITEM (new_vitem),
					 vitem_index - item_index);
		}
	}

	/* If item itself has children, add them also */
	if (DIA_IS_CANVAS_GROUPABLE (item)
	    && dia_canvas_groupable_get_iter (DIA_CANVAS_GROUPABLE (item), &iter)) do {
		DiaCanvasItem *val = dia_canvas_groupable_value (DIA_CANVAS_GROUPABLE (item), &iter);

		if (val)
			dia_canvas_view_item_add_items (GNOME_CANVAS_GROUP (new_vitem),
							val);
	} while (dia_canvas_groupable_next (DIA_CANVAS_GROUPABLE (item), &iter));
}

static gint
do_foreach (DiaCanvasViewItem *item,
	    DiaCanvasViewItemForeachFunc func, gpointer data)
{
	GList *l;
	gint result = TRUE;

	if (!func (item, data))
		return FALSE;

	for (l = GNOME_CANVAS_GROUP (item)->item_list; l != NULL; l = l->next)
		if (DIA_IS_CANVAS_VIEW_ITEM (l->data))
			result &= dia_canvas_view_item_foreach ((DiaCanvasViewItem*) l->data,
								 func, data);

	return result;
}

/**
 * dia_canvas_view_item_foreach:
 * @item: 
 * @func: 
 * @data: 
 *
 * Call @func for each canvas view item. If @func returns %FALSE, no more
 * items are called.
 *
 * Return value: 
 **/
gint
dia_canvas_view_item_foreach (DiaCanvasViewItem *item,
			      DiaCanvasViewItemForeachFunc func, gpointer data)
{
	g_return_val_if_fail (DIA_IS_CANVAS_VIEW_ITEM (item), FALSE);
	g_return_val_if_fail (func != NULL, FALSE);

	return do_foreach (item, func, data);
}

/**
 * dia_canvas_view_item_is_focused:
 * @item: 
 *
 * Check if an item has the focus. If the real focus is held by a child object
 * of @item and the child has the #DIA_COMPOSITE flag, TRUE is returned.
 *
 * Return value: TRUE/FALSE.
 **/
gboolean
dia_canvas_view_item_is_focused (DiaCanvasViewItem *item)
{
	DiaCanvasViewItem *focus_item;

	g_return_val_if_fail (DIA_IS_CANVAS_VIEW_ITEM (item), FALSE);

	focus_item = DIA_CANVAS_VIEW (GNOME_CANVAS_ITEM (item)->canvas)->focus_item;
	if (!focus_item)
		return FALSE;

	do {
		if (item == focus_item)
			return TRUE;
		else if (DIA_CANVAS_ITEM_COMPOSITE (focus_item->item))
			focus_item = (DiaCanvasViewItem*) GNOME_CANVAS_ITEM (focus_item)->parent;
		else
			return FALSE;
	} while (focus_item != NULL);

	return FALSE;
}

gboolean
dia_canvas_view_item_is_selected (DiaCanvasViewItem *item)
{
	GList *sel_items;
	
	g_return_val_if_fail (DIA_IS_CANVAS_VIEW_ITEM (item), FALSE);

	sel_items = DIA_CANVAS_VIEW(GNOME_CANVAS_ITEM (item)->canvas)->selected_items;
	
	if (!sel_items)
		return FALSE;

	/* find non composite parent item */
	while (DIA_CANVAS_ITEM_COMPOSITE (item->item))
		item = (DiaCanvasViewItem*) GNOME_CANVAS_ITEM (item)->parent;

	return g_list_find (sel_items, item) ? TRUE : FALSE;

}

