/* dia-canvas.c
 * Copyright (C) 2000, 2001  James Henstridge, 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 <math.h>
#include <glib/garray.h>
#include <libart_lgpl/art_rect.h>
#include <libart_lgpl/art_affine.h>
#include <pango/pangoft2.h>
#include "dia-canvas.h"
#include "dia-canvas-group.h"
#include "dia-canvas-i18n.h"
#include "diamarshal.h"
#include "../config.h"

/* This is a nice place to include the marshal functions... */
#include "diamarshal.c"

//#define UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE + 50)
#define UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE)

enum {
	EXTENTS_CHANGED,
	REDRAW_VIEW,
	UNDO,
	LAST_SIGNAL
};

enum {
	PROP_SNAP_TO_GRID = 1,
	PROP_STATIC_EXTENTS,
	PROP_EXTENTS,
	PROP_ALLOW_STATE_REQUESTS,
	PROP_ALLOW_UNDO,
	PROP_GRID_INT_X,
	PROP_GRID_INT_Y,
	PROP_GRID_OFS_X,
	PROP_GRID_OFS_Y,
	PROP_GRID_COLOR,
	PROP_GRID_BG,
	PROP_PANGO_LAYOUT
};

typedef struct {
	GObject *object;
	const gchar *property_name;
	GValue value;
} UndoData;

typedef struct  {
	gchar *comment;
	GArray *undo_data; /* List of UndoData. */
} UndoEntry;

static UndoEntry* undo_entry_new	(void);
static void undo_entry_free		(UndoEntry *undo_entry);
static void undo_entry_append		(UndoEntry *undo_entry,
					 GObject *object,
					 GParamSpec *pspec,
					 const GValue *value,
					 gboolean last);
static void undo_entry_apply		(UndoEntry *undo_entry);

/* DiaCanvas static (private) functions. */
static void dia_canvas_init		(DiaCanvas	*canvas);
static void dia_canvas_class_init	(DiaCanvasClass	*klass);

static void dia_canvas_set_property	(GObject	*object,
					 guint		 property_id,
					 const GValue	*value,
					 GParamSpec	*pspec);
static void dia_canvas_get_property	(GObject	*object,
					 guint		 property_id,
					 GValue		*value,
					 GParamSpec	*pspec);

static void dia_canvas_dispose		(GObject	*object);
static void dia_canvas_finalize		(GObject	*object);
static void dia_real_canvas_update	(DiaCanvas	*canvas);

static GObjectClass *parent_class = NULL;
static guint canvas_signals[LAST_SIGNAL] = { 0 };

GType
dia_canvas_get_type (void)
{
	static GType object_type = 0;

	if (!object_type) {
		static const GTypeInfo object_info = {
			sizeof (DiaCanvasClass),
			(GBaseInitFunc) NULL,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) dia_canvas_class_init,
			(GClassFinalizeFunc) NULL,
			(gconstpointer) NULL, /* class_data */
			sizeof (DiaCanvas),
			(guint16) 0, /* n_preallocs */
			(GInstanceInitFunc) dia_canvas_init,
		};
		object_type = g_type_register_static (G_TYPE_OBJECT,
						      "DiaCanvas",
						      &object_info, 0);
	}

	return object_type;
}

static void
dia_canvas_class_init (DiaCanvasClass *klass)
{
	GObjectClass *object_class;

	object_class = (GObjectClass*) klass;
  
	parent_class = g_type_class_peek_parent (klass);

	object_class->dispose = dia_canvas_dispose;
	object_class->finalize = dia_canvas_finalize;
	object_class->set_property = dia_canvas_set_property;
	object_class->get_property = dia_canvas_get_property;

	canvas_signals[EXTENTS_CHANGED] = 
	  g_signal_new ("extents_changed",
			G_TYPE_FROM_CLASS (object_class),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET(DiaCanvasClass,
					extents_changed),
			NULL, NULL,
			dia_marshal_VOID__POINTER,
			G_TYPE_NONE, 1,
			G_TYPE_POINTER /* DiaRectangle */);

	//canvas_signals[MOVE] = 
	//  g_signal_new ("move",
	//		G_TYPE_FROM_CLASS (object_class),
	//		G_SIGNAL_RUN_FIRST,
	//		G_STRUCT_OFFSET(DiaCanvasClass, move),
	//		NULL, NULL,
	//		dia_marshal_VOID__DOUBLE_DOUBLE,
	//		G_TYPE_NONE, 2,
	//		G_TYPE_DOUBLE, G_TYPE_DOUBLE);

	canvas_signals[REDRAW_VIEW] = 
	  g_signal_new ("redraw_view",
			G_TYPE_FROM_CLASS (object_class),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET(DiaCanvasClass, redraw_view),
			NULL, NULL,
			dia_marshal_VOID__VOID,
			G_TYPE_NONE, 0);
			
	canvas_signals[UNDO] = 
	  g_signal_new ("undo",
			G_TYPE_FROM_CLASS (object_class),
			G_SIGNAL_RUN_FIRST,
			G_STRUCT_OFFSET(DiaCanvasClass, undo),
			NULL, NULL,
			dia_marshal_VOID__VOID,
			G_TYPE_NONE, 0);

	g_object_class_install_property (object_class,
					 PROP_SNAP_TO_GRID,
					 g_param_spec_boolean ("snap_to_grid",
						 _("Snap to the grid"),
						 _("Turn snap to grid option on and off."),
						 FALSE,
						 G_PARAM_READWRITE));

	g_object_class_install_property (object_class,
					 PROP_STATIC_EXTENTS,
					 g_param_spec_boolean ("static_extents",
						 _("Static extents"),
						 _("Should the canvas extents be static or change depending on the contents."),
						 FALSE,
						 G_PARAM_READWRITE));

	g_object_class_install_property (object_class,
					 PROP_EXTENTS,
					 g_param_spec_boxed ("extents",
						 _("Set extents"),
						 _("Set extents"),
						 DIA_TYPE_RECTANGLE,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_ALLOW_STATE_REQUESTS,
					 g_param_spec_boolean ("allow_state_requests",
						 _("Allow state requests"),
						 _("Let canvas items query their visual state (selected, focused, grabbed)."),
						 TRUE,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_ALLOW_UNDO,
					 g_param_spec_boolean ("allow_undo",
						 _("Allow undo"),
						 _("Save item properties in order to allow the user to undo them."),
						 FALSE,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_INT_X,
					 g_param_spec_double ("grid_int_x",
						 _("Grid X interval"),
						 _("Grid X interval"),
						 0.0,
						 G_MAXDOUBLE,
						 10.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_INT_Y,
					 g_param_spec_double ("grid_int_y",
						 _("Grid Y interval"),
						 _("Grid Y interval"),
						 0.0,
						 G_MAXDOUBLE,
						 10.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_OFS_X,
					 g_param_spec_double ("grid_ofs_x",
						 _("Grid X offset"),
						 _("Grid X offset"),
						 0.0,
						 G_MAXDOUBLE,
						 0.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_OFS_Y,
					 g_param_spec_double ("grid_ofs_y",
						 _("Grid Y offset"),
						 _("Grid Y offset"),
						 0.0,
						 G_MAXDOUBLE,
						 0.0,
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_COLOR,
					 g_param_spec_ulong ("grid_color",
						 _("Grid foreground color"),
						 _("Grid foreground color"),
						 0,
						 G_MAXULONG,
						 DIA_COLOR (0,0,128),
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_GRID_BG,
					 g_param_spec_ulong ("grid_bg",
						 _("Grid background color"),
						 _("Grid background color"),
						 0,
						 G_MAXULONG,
						 DIA_COLOR (200,200,200),
						 G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
					 PROP_PANGO_LAYOUT,
					 g_param_spec_object ("pango_layout",
						 _("A newly created pango layout"),
						 _("A newly created pango layout"),
						 PANGO_TYPE_LAYOUT,
						 G_PARAM_READABLE));

	klass->extents_changed = NULL;
	//klass->move = NULL;
	klass->update = dia_real_canvas_update;
	klass->undo = NULL;
}

static void
dia_canvas_init (DiaCanvas *canvas)
{
	canvas->static_extents = FALSE;
	canvas->snap_to_grid = FALSE;
	//canvas->first_move = TRUE;

	canvas->extents.left = 0.0;
	canvas->extents.top = 0.0;
	canvas->extents.right = 0.0;
	canvas->extents.bottom = 0.0;

	canvas->root = g_object_new (DIA_TYPE_CANVAS_GROUP,
				     NULL);
	
	canvas->root->canvas = canvas;

	canvas->interval_x = 10.0;
	canvas->interval_y = 10.0;
	canvas->offset_x = 0.0;
	canvas->offset_y = 0.0;
	canvas->grid_color = DIA_COLOR (0,0,128);
	canvas->grid_bg = DIA_COLOR (255, 255, 255);

	canvas->solver = dia_solver_new ();

	canvas->idle_id = 0;

	canvas->allow_state_requests = TRUE;
	canvas->allow_undo = FALSE;
	canvas->in_undo = FALSE;
	canvas->stack_depth = 10;
	canvas->undo = NULL;
	canvas->redo = NULL;
}

static void
dia_canvas_set_property (GObject *object, guint property_id,
			 const GValue *value, GParamSpec *pspec)
{
	DiaCanvas *canvas = (DiaCanvas*) object;

	switch (property_id) {
	case PROP_SNAP_TO_GRID:
		//g_object_freeze_notify (object);
		dia_canvas_set_snap_to_grid (canvas,
					     g_value_get_boolean (value));
		//g_object_thaw_notify (object);
		break;
	case PROP_STATIC_EXTENTS:
		//g_object_freeze_notify (object);
		dia_canvas_set_static_extents (canvas,
					       g_value_get_boolean (value));
		//g_object_thaw_notify (object);
		break;
	case PROP_EXTENTS:
		g_object_freeze_notify (object);
		dia_canvas_set_extents (canvas,
					(DiaRectangle*)g_value_get_boxed (value));
		g_object_thaw_notify (object);
		break;
	case PROP_ALLOW_STATE_REQUESTS:
		canvas->allow_state_requests = g_value_get_boolean (value);
		break;
	case PROP_ALLOW_UNDO:
		canvas->allow_undo = g_value_get_boolean (value);
		break;
	case PROP_GRID_INT_X:
		canvas->interval_x = g_value_get_double (value);
		dia_canvas_redraw_views (canvas);
		break;
	case PROP_GRID_INT_Y:
		canvas->interval_y = g_value_get_double (value);
		dia_canvas_redraw_views (canvas);
		break;
	case PROP_GRID_OFS_X:
		canvas->offset_x = g_value_get_double (value);
		dia_canvas_redraw_views (canvas);
		break;
	case PROP_GRID_OFS_Y:
		canvas->offset_y = g_value_get_double (value);
		dia_canvas_redraw_views (canvas);
		break;
	case PROP_GRID_COLOR:
		canvas->grid_color = g_value_get_ulong (value);
		dia_canvas_redraw_views (canvas);
		break;
	case PROP_GRID_BG:
		canvas->grid_bg = g_value_get_ulong (value);
		dia_canvas_redraw_views (canvas);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
dia_canvas_get_property (GObject *object, guint property_id,
			 GValue *value, GParamSpec *pspec)
{
	DiaCanvas *canvas = (DiaCanvas*) object;

	switch (property_id) {
	case PROP_SNAP_TO_GRID:
		g_value_set_boolean (value, canvas->snap_to_grid);
		break;
	case PROP_STATIC_EXTENTS:
		g_value_set_boolean (value, canvas->static_extents);
		break;
	case PROP_EXTENTS:
		g_value_set_boxed (value, &canvas->extents);
		break;
	case PROP_ALLOW_STATE_REQUESTS:
		g_value_set_boolean (value, canvas->allow_state_requests);
		break;
	case PROP_ALLOW_UNDO:
		g_value_set_boolean (value, canvas->allow_undo);
		break;
	case PROP_GRID_INT_X:
		g_value_set_double (value, canvas->interval_x);
		break;
	case PROP_GRID_INT_Y:
		g_value_set_double (value, canvas->interval_y);
		break;
	case PROP_GRID_OFS_X:
		g_value_set_double (value, canvas->offset_x);
		break;
	case PROP_GRID_OFS_Y:
		g_value_set_double (value, canvas->offset_y);
		break;
	case PROP_GRID_COLOR:
		g_value_set_ulong (value, canvas->grid_color);
		break;
	case PROP_GRID_BG:
		g_value_set_ulong (value, canvas->grid_bg);
		break;
	case PROP_PANGO_LAYOUT:
		g_value_set_object (value, dia_canvas_get_pango_layout());
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}


static void
dia_canvas_dispose (GObject *object)
{
	DiaCanvas *canvas = (DiaCanvas*) object;
#ifdef ENABLE_DEBUG
	g_message (G_STRLOC": begin. idle_id = %d", canvas->idle_id);
#endif

	canvas->allow_undo = FALSE;

	/* Now get rid of undo and redo information. */
	if (canvas->undo_entry) {
		undo_entry_free (canvas->undo_entry);
		canvas->undo_entry = NULL;
	}

	if (canvas->undo) {
		GSList *l;
		for (l = canvas->undo; l != NULL; l = l->next)
			undo_entry_free (l->data);
		g_slist_free (canvas->undo);
		canvas->undo = NULL;
	}

	if (canvas->redo) {
		GSList *l;
		for (l = canvas->redo; l != NULL; l = l->next)
			undo_entry_free (l->data);
		g_slist_free (canvas->redo);
		canvas->redo = NULL;
	}

	/* Remove idle handler, we set the idle_id to -1, so it won't
	 * be set again. */
	if (canvas->idle_id) {
		g_source_remove (canvas->idle_id);
		canvas->idle_id = -1;
	}

	/* Do this one last, so we're sure no canvas item try to update. */
#ifdef ENABLE_DEBUG
	g_message (G_STRLOC": idle_id = %d", canvas->idle_id);
#endif

	parent_class->dispose (object);

#ifdef ENABLE_DEBUG
	g_message (G_STRLOC": done.");
#endif
}

static void
dia_canvas_finalize (GObject *object)
{
	DiaCanvas *canvas = (DiaCanvas*) object;

#ifdef ENABLE_DEBUG
	g_message ("%s: begin.", G_STRLOC);
#endif

	/* First dispose the canvas items. */
	if (canvas->root) {
		canvas->root->canvas = NULL;
		g_object_unref (G_OBJECT (canvas->root));
		canvas->root = NULL;
	}

	if (canvas->solver) {
		g_object_unref (G_OBJECT (canvas->solver));
		canvas->solver = NULL;
	}


	parent_class->finalize (object);

#ifdef ENABLE_DEBUG
	g_message (G_STRLOC": done.");
#endif
}

/**
 * dia_real_canvas_update:
 * @canvas: 
 *
 * The update formula consists of 3 steps:
 * 1. Resolve the constraints.
 * 2. Invoke the objects own updater.
 **/
static void
dia_real_canvas_update (DiaCanvas *canvas)
{
	//dia_canvas_item_update_now (canvas->root);

	dia_canvas_resolve_now (canvas);

	dia_canvas_item_update_now (canvas->root);

	if (!canvas->static_extents)
		dia_canvas_set_extents (canvas, &canvas->root->bounds);
}


static gboolean
idle_handler (gpointer data)
{
	DiaCanvas *canvas;

	g_assert (DIA_IS_CANVAS (data));

	/* TODO: create mutex. */
//	GDK_THREADS_ENTER ();

	canvas = DIA_CANVAS (data);

	DIA_CANVAS_CLASS (DIA_CANVAS_GET_CLASS (canvas))->update (canvas);

	/* Reset idle id */
	canvas->idle_id = 0;

//	GDK_THREADS_LEAVE ();

	return FALSE;
}


/**
 * dia_canvas_new:
 *
 * Create a new canvas.
 *
 * Return value: A new canvas.
 **/
DiaCanvas*
dia_canvas_new (void)
{
	DiaCanvas *canvas = g_object_new(DIA_TYPE_CANVAS, NULL);
	g_assert (canvas != NULL);

	return canvas;
}

/**
 * dia_canvas_set_extents:
 * @canvas: 
 * @extents: new extents for the canvas.
 *
 * Set the boundries for the canvas. Use this in combination with the
 * "static_extents" option.
 **/
void
dia_canvas_set_extents (DiaCanvas *canvas, const DiaRectangle *extents)
{
	g_return_if_fail (canvas != NULL);
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (extents->top <= extents->bottom);
	g_return_if_fail (extents->left <= extents->right);

	if ((extents->top != canvas->extents.top)
	    || (extents->left != canvas->extents.left)
	    || (extents->bottom != canvas->extents.bottom)
	    || (extents->right != canvas->extents.right)) {
		g_signal_emit (G_OBJECT(canvas),
			       canvas_signals[EXTENTS_CHANGED], 0, extents);
		canvas->extents = *extents;

		g_object_notify (G_OBJECT (canvas), "extents");
	}
}

/**
 * dia_canvas_set_static_extents:
 * @canvas: 
 * @stat: Turn static extents on (TRUE) or off (FALSE).
 *
 * Set extents. Normally (with this option turned off) the boundries of the
 * canvas will grow or shrink depending on the position of the objects (so the
 * placement of the objects determine the actual size).
 *
 * Turn this option on if you don't want the canvas to dynamically resize.
 **/
void
dia_canvas_set_static_extents (DiaCanvas *canvas, gboolean stat)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	canvas->static_extents = stat;
	if (!stat)
		dia_canvas_set_extents (canvas, &canvas->root->bounds);

	g_object_notify (G_OBJECT (canvas), "static_extents");
}

/**
 * dia_canvas_set_snap_to_grid:
 * @canvas: 
 * @snap: Turn snap on (TRUE) or off (FALSE).
 *
 * Turn the snap to grid option on or off.
 **/
void
dia_canvas_set_snap_to_grid (DiaCanvas *canvas, gboolean snap)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	canvas->snap_to_grid = snap;

	g_object_notify (G_OBJECT (canvas), "snap_to_grid");
}

/**
 * dia_canvas_snap_to_grid:
 * @canvas: 
 * @x: X pos to be snapped.
 * @y: Y pos to be snapped.
 *
 * This function takes two arguments (@x and @y) and returnes them with a grid
 * position. If the "snap_to_grid" option is turned off, this function does
 * nothing.
 **/
void
dia_canvas_snap_to_grid (DiaCanvas *canvas, gdouble *x, gdouble *y)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (x != NULL);
	g_return_if_fail (y != NULL);

	if (canvas->snap_to_grid) {
		*x = floor ((*x / canvas->interval_x) + .5) * canvas->interval_x
			+ canvas->offset_x;
		*y = floor ((*y / canvas->interval_y) + .5) * canvas->interval_y
			+ canvas->offset_y;
	}
}


static GList*
real_find_objects_in_rectangle (DiaCanvasItem *item, ArtDRect *rect)
{
	GList *objs = NULL;
	DiaCanvasIter iter;

	if (DIA_CANVAS_ITEM_COMPOSITE (item))
		return NULL;

	if (item->parent
	    && (item->bounds.left > rect->x0)
	    && (item->bounds.right < rect->x1)
	    && (item->bounds.top > rect->y0)
	    && (item->bounds.bottom < rect->y1)) {
		objs = g_list_append (objs, item);
	}

	/* ... And now for the children... */
	if (DIA_IS_CANVAS_GROUPABLE (item)
	    && dia_canvas_groupable_get_iter (DIA_CANVAS_GROUPABLE (item), &iter)) do {
		ArtDRect rdest;
		DiaCanvasItem *child = dia_canvas_groupable_value (DIA_CANVAS_GROUPABLE (item), &iter);
		GList *new_objs;
		gdouble inv[6];

		art_affine_invert (inv, child->affine);
		art_drect_affine_transform (&rdest, rect, inv);
		new_objs = real_find_objects_in_rectangle (child,
							   &rdest);
		if (new_objs)
			objs = g_list_concat (objs, new_objs);
	} while (dia_canvas_groupable_next (DIA_CANVAS_GROUPABLE (item), &iter));

	return objs;
}

/**
 * dia_canvas_find_objects_in_rectangle:
 * @canvas: 
 * @rect: A rectangle that determines the boundries in which objects should
 *        live.
 *
 * Find all objects that are within rectangle @rect and return them in a #GList.
 * The returned list should be freed by the caller.
 * Note that the root object is not included in the selection, only
 * non-composite leaf objects are selected.
 *
 * Return value: A list of objects that are within the rectangle.
 **/
GList*
dia_canvas_find_objects_in_rectangle (DiaCanvas *canvas, DiaRectangle *rect)
{
	GList *l;
	ArtDRect r;

	g_return_val_if_fail (DIA_IS_CANVAS (canvas), NULL);
	g_return_val_if_fail (rect != NULL, NULL);

	r.x0 = rect->left;
	r.y0 = rect->top;
	r.x1 = rect->right;
	r.y1 = rect->bottom;

	l = real_find_objects_in_rectangle (canvas->root, &r);
	return l;
}


/**
 * dia_canvas_request_update:
 * @canvas: 
 *
 * Ask the canvas to schedule a update handler. The actual update will happen
 * diuring idle time or if dia_canvas_update_now() is called.
 **/
void
dia_canvas_request_update (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->idle_id == 0)
		canvas->idle_id = g_idle_add_full (UPDATE_PRIORITY,
						   idle_handler, canvas,
						   NULL /* destroyNotify */);
}


/**
 * dia_canvas_resolve_now:
 * @canvas: 
 *
 * Ask the constraint solver to resolve it's constraints.
 **/
void
dia_canvas_resolve_now (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	dia_solver_resolve (canvas->solver);
}

/**
 * dia_canvas_update_now:
 * @canvas: 
 *
 * Force an update of the internat state of the canvas. This will set the
 * canvas in a consistent state by running the update() callback of every
 * #DiaCanvasItem that requested an update.
 **/
void
dia_canvas_update_now (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->idle_id != 0) {
		g_source_remove (canvas->idle_id);
		canvas->idle_id = 0;
	}

	idle_handler (canvas);
}


typedef struct {
	const DiaHandle *handle;
	gdouble orig_x;
	gdouble orig_y;
	gdouble glue_x;
	gdouble glue_y;
	gdouble dist;
	DiaCanvasItem *item;
} GlueHandleHelper;

static gint
real_glue_handle (DiaCanvasItem *item, GlueHandleHelper *helper)
{
	gdouble x, y, dist;

	if (!DIA_CANVAS_ITEM_VISIBLE (helper->handle->owner))
		return TRUE;

	/* Do not check yourself. */
	if (helper->handle->owner == item)
		return FALSE;

	if (DIA_CANVAS_ITEM_GET_CLASS (item)->glue) {
		x = helper->orig_x;
		y = helper->orig_y;
		dist  = DIA_CANVAS_ITEM_GET_CLASS (item)->glue (item, DIA_HANDLE (helper->handle), &x, &y);

		if (dist < helper->dist) {
			helper->dist = dist;
			helper->glue_x = x;
			helper->glue_y = y;
			helper->item = item;
		}
	}
	return FALSE;
}

/**
 * dia_canvas_glue_handle:
 * @canvas: Canvas to find a connection point on.
 * @handle: Handle to be glued to an item somewhere on the canvas.
 * @dest_x: (to be) position of the handle.
 * @dest_y: 
 * @glue_x: [OUT] closest glue point.
 * @glue_y: [OUT] ,,
 * @item: [OUT] The item the handle should connect() to.
 *
 * Find a point to glue the handle to. Point (dest_x, dest_y) is the position
 * where the handle should move to if no glue point is found. This can be useful
 * if the handle is in a motion and you have to calculate the place where the
 * handle should move to.
 *
 * Return value: The distance from the handle to the closest connection point.
 */
gdouble
dia_canvas_glue_handle (DiaCanvas *canvas, const DiaHandle *handle,
			const gdouble dest_x, const gdouble dest_y,
			gdouble *glue_x, gdouble *glue_y,
			DiaCanvasItem **item)
{
	GlueHandleHelper helper;

	g_return_val_if_fail (DIA_IS_CANVAS (canvas), G_MAXDOUBLE);
	g_return_val_if_fail (DIA_IS_HANDLE (handle), G_MAXDOUBLE);
	g_return_val_if_fail (glue_x != NULL, G_MAXDOUBLE);
	g_return_val_if_fail (glue_y != NULL, G_MAXDOUBLE);

	helper.handle = handle;
	helper.orig_x = dest_x;
	helper.orig_y = dest_y;
	helper.glue_x = dest_x;
	helper.glue_y = dest_y;
	helper.dist = G_MAXDOUBLE;
	helper.item = NULL;

	dia_canvas_group_foreach (canvas->root, (DiaCanvasItemForeachFunc) real_glue_handle, &helper);

	*glue_x = helper.glue_x;
	*glue_y = helper.glue_y;
	*item = helper.item;

	return helper.dist;
}

/**
 * dia_canvas_add_constraint:
 * @canvas: 
 * @c: Constraint to add
 *
 * Add a constraint to the canvas' constraint solver.
 **/
void
dia_canvas_add_constraint (DiaCanvas *canvas, DiaConstraint *c)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (DIA_IS_CONSTRAINT (c));

	dia_solver_add_constraint (canvas->solver, c);
}

/**
 * dia_canvas_remove_constraint:
 * @canvas: 
 * @c: Constraint to be removed.
 *
 * Remove constraint @c from the canvas.
 **/
void
dia_canvas_remove_constraint (DiaCanvas *canvas, DiaConstraint *c)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (DIA_IS_CONSTRAINT (c));

	dia_solver_remove_constraint (canvas->solver, c);
} 

/**
 * dia_canvas_get_pango_layout:
 *
 * Returns a usable #PangoLayout object. 
 *
 * Return value: A newly created PangoLayout object.
 **/
PangoLayout*
dia_canvas_get_pango_layout (void)
{
	/* Make the context static, this will save time for future references.
	 */
	static PangoContext *context = NULL;

	if (!context) {
		context = pango_ft2_get_context (100.0, 100.0);
	}
	return pango_layout_new (context);
}


/**
 * dia_canvas_redraw_views:
 * @canvas: 
 *
 * This function will emit a signal that will cause all views to update and
 * redraw itself. There are very few cases in which this function is useful.
 * Calling this function often will dramatically reduce the speed of the canvas.
 **/
void
dia_canvas_redraw_views (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	g_signal_emit (canvas, canvas_signals[REDRAW_VIEW], 0);
}


/*
 * UNDO/REDO
 */

/* Debug info for Undo/Redo part: */
//#define UNDOINFO(...) g_message (G_STRLOC": " __VA_ARGS__);
#define UNDOINFO(...)

/**
 * verify_stack_depth:
 * @stack:
 * @depth: 
 *
 * Ensure that the undo stack is not deeper that @depth entries.
 **/
static void
verify_stack_depth (GSList *stack, guint depth)
{
	if (!stack)
		return;
	while (g_slist_length (stack) > depth) {
		GSList *last = g_slist_last (stack);
		UNDOINFO("stack to deep (max %d allowed)", depth);
		undo_entry_free (last->data);
		g_slist_delete_link (stack, last);
	}
}

static void
emit_undo(DiaCanvas *canvas)
{
	g_signal_emit (canvas, canvas_signals[UNDO], 0);
}

static void
preserve (DiaCanvas *canvas, GObject *object,
	  GParamSpec *pspec, const GValue *value, gboolean last)
{
	gboolean new = FALSE;

	if (!canvas->allow_undo)
		return;

	if (canvas->undo_entry == NULL) {
		canvas->undo_entry = undo_entry_new ();
		new = TRUE;
	}

	/* Once the user starts adding new undo information, the redo stack is
	 * considered garbage. */
	if (!canvas->in_undo)
		dia_canvas_clear_redo (canvas);

	undo_entry_append (canvas->undo_entry, object, pspec, value, last);

	if (new)
		emit_undo(canvas);
}

/**
 * dia_canvas_preserve:
 * @canvas: 
 * @object: object that should be undone
 * @property_name: static const string for a property (should NOT be freed!)
 * @value: data to be undone
 * @last: append rather than prepend the property.
 *
 * Put a variable on the undo stack. The variable is described by its
 * @property_name and the @undo_data.
 *
 * If an @object/@property_name combination is preserved multiple times, only
 * the first occurance is stored. 
 **/
void
dia_canvas_preserve (DiaCanvas *canvas, GObject *object,
		     const char *property_name, const GValue *value,
		     gboolean last)
{
	GParamSpec *pspec;

	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (G_IS_OBJECT (object));
	g_return_if_fail (property_name != NULL);
	g_return_if_fail (value != NULL);

	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),
					      property_name);
	if (pspec == NULL) {
		g_warning (G_STRLOC": property '%s' doesn't not exist for class type '%s'.", property_name, G_OBJECT_TYPE_NAME (object));
		return;
	}

	preserve (canvas, object, pspec, value, last);
}

static void
preserve_property (DiaCanvas *canvas, GObject *object,
		   const char *property_name, gboolean last)
{
	GValue value = { 0, };
	GParamSpec *pspec;
	
	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),
					      property_name);
	if (pspec == NULL) {
		g_warning (G_STRLOC": property '%s' doesn't exist for class type '%s'.", property_name, G_OBJECT_TYPE_NAME (object));
		return;
	}

	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));

	g_object_get_property (object, property_name, &value);
	
	preserve (canvas, object, pspec, &value, last);

	g_value_unset (&value);
}

/**
 * dia_canvas_preserve_property:
 * @canvas: 
 * @object: 
 * @property_name: Property to preserve
 *
 * This is an easy way to save properties. The value of the property is
 * preserved so the change can be undone by dia_canvas_undo_pop().
 **/
void
dia_canvas_preserve_property (DiaCanvas *canvas, GObject *object,
			      const char *property_name)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (G_IS_OBJECT (object));
	g_return_if_fail (property_name != NULL);

	preserve_property (canvas, object, property_name, FALSE);
}

/**
 * dia_canvas_preserve_property_last:
 * @canvas: 
 * @object: 
 * @property_name: 
 *
 * This is a special version of dia_canvas_preserve_property(): it preserves
 * a property, but adds it to the end of the list, in stead of the beginning.
 * This is needed for some special actions.
 * 
 * This function won't be used in normal usage.
 **/
void
dia_canvas_preserve_property_last (DiaCanvas *canvas, GObject *object,
				    const char *property_name)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));
	g_return_if_fail (G_IS_OBJECT (object));
	g_return_if_fail (property_name != NULL);

	preserve_property (canvas, object, property_name, TRUE);
}

/**
 * dia_canvas_push_undo:
 * @canvas: 
 * @optional_comment: An optional comment. May be NULL.
 *
 * Push a bunch of preserved values on the undo stack. If no values are stored
 * using dia_canvas_preserve() or dia_canvas_preserve_property(), nothing is
 * pushed on the stack.
 **/
void
dia_canvas_push_undo (DiaCanvas *canvas, const char* optional_comment)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (!canvas->allow_undo)
		return;

	if (canvas->undo_entry &&
	    (((UndoEntry*) canvas->undo_entry)->undo_data->len > 0)) { 
		canvas->undo = g_slist_prepend (canvas->undo,
						canvas->undo_entry);
		verify_stack_depth (canvas->undo, canvas->stack_depth);
		UNDOINFO("stack pushed %p %p (depth = %d)", canvas->undo_entry, canvas->undo->data, g_slist_length(canvas->undo));
		canvas->undo_entry = NULL;
		/* undo signal is emited by preserve() as soon as
		   canvas->undo_entry is used. */
	}
}
	

/**
 * dia_canvas_pop_undo:
 * @canvas: 
 *
 * Undo a set of actions. Undone actions are stored on the redo-stack, so they
 * can be undone too.
 **/
void
dia_canvas_pop_undo (DiaCanvas *canvas)
{
	UndoEntry *undo_entry = NULL;

	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->undo_entry && ((UndoEntry*)canvas->undo_entry)->undo_data->len > 0) {
		UNDOINFO("from undo_entry");
		undo_entry = canvas->undo_entry;
		canvas->undo_entry = NULL;
	} else if (canvas->undo) {
		UNDOINFO("from undo list");
		undo_entry = canvas->undo->data;
		canvas->undo = g_slist_delete_link (canvas->undo, canvas->undo);
	}

	if (!undo_entry)
		return;

	UNDOINFO("Ready to undo %d actions", undo_entry->undo_data->len);

	canvas->in_undo = TRUE;
	
	undo_entry_apply (undo_entry);

	//dia_canvas_update_now (canvas);

	canvas->in_undo = FALSE;

	if (canvas->undo_entry && ((UndoEntry*) canvas->undo_entry)->undo_data->len) {
		canvas->redo = g_slist_prepend (canvas->redo, canvas->undo_entry);
		verify_stack_depth (canvas->redo, canvas->stack_depth);
		canvas->undo_entry = NULL;
	}

	undo_entry_free (undo_entry);

	emit_undo(canvas);
}

/**
 * dia_canvas_pop_redo:
 * @canvas: 
 *
 * Opposite of dia_canvas_undo_pop(). Undone actions are undone themselves ;-)
 **/
void
dia_canvas_pop_redo (DiaCanvas *canvas)
{
	UndoEntry *redo_entry;

	UNDOINFO();
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->redo == NULL)
		return;

	redo_entry = canvas->redo->data;
	canvas->redo = g_slist_delete_link (canvas->redo, canvas->redo);

	if (canvas->undo_entry) {
		dia_canvas_push_undo (canvas, NULL);
	}

	canvas->in_undo = TRUE;

	undo_entry_apply (redo_entry);

	//dia_canvas_update_now (canvas);

	canvas->in_undo = FALSE;

	if (canvas->undo_entry && ((UndoEntry*) canvas->undo_entry)->undo_data->len) {
		canvas->undo = g_slist_prepend (canvas->undo, canvas->undo_entry);
		verify_stack_depth (canvas->undo, canvas->stack_depth);
		canvas->undo_entry = NULL;
	}

	undo_entry_free (redo_entry);

	emit_undo(canvas);
}

/**
 * dia_canvas_clear_undo:
 * @canvas: 
 *
 * Clear the undo stack.
 **/
void
dia_canvas_clear_undo (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->undo) {
		GSList *l;
		for (l = canvas->undo; l != NULL; l = l->next)
			undo_entry_free (l->data);
		g_slist_free (canvas->undo);
		canvas->undo = NULL;
		emit_undo(canvas);
	}
	if (canvas->undo_entry) {
		undo_entry_free (canvas->undo_entry);
		canvas->undo_entry = NULL;
	}
}

/**
 * dia_canvas_clear_redo:
 * @canvas: 
 *
 * Clear the redo stack. This should be automatically done after an
 * interactive operation on the canvas (e.g. an item is moved).
 **/
void
dia_canvas_clear_redo (DiaCanvas *canvas)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	if (canvas->redo) {
		GSList *l;
		for (l = canvas->redo; l != NULL; l = l->next)
			undo_entry_free (l->data);
		g_slist_free (canvas->redo);
		canvas->redo = NULL;
		emit_undo(canvas);
	}
}

/**
 * dia_canvas_get_undo_depth:
 * @canvas: 
 *
 * Return the amount of undo entries on the undo stack. The depth can be
 * at most the dia_canvas_get_stack_depth() + 1 (the current undo entry).
 *
 * Return value: 
 **/
guint
dia_canvas_get_undo_depth (DiaCanvas *canvas)
{
	g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0);

	return (canvas->undo ? g_slist_length (canvas->undo) : 0)
		+ (canvas->undo_entry && ((UndoEntry*)canvas->undo_entry)->undo_data->len ? 1 : 0);
}

/**
 * dia_canvas_get_redo_depth:
 * @canvas: 
 *
 * Return the amount of undo entries on the undo stack.
 *
 * Return value: 
 **/
guint
dia_canvas_get_redo_depth (DiaCanvas *canvas)
{
	g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0);

	return canvas->redo ? g_slist_length (canvas->redo) : 0;
}

/**
 * dia_canvas_set_undo_stack_depth:
 * @canvas: 
 * @depth: New depth for the undo/redo stack.
 *
 * Set a limit of the length of the undo/redo stack (default 10).
 **/
void
dia_canvas_set_undo_stack_depth (DiaCanvas *canvas, guint depth)
{
	g_return_if_fail (DIA_IS_CANVAS (canvas));

	canvas->stack_depth = depth;

	verify_stack_depth (canvas->undo, depth);
	verify_stack_depth (canvas->redo, depth);
}

/**
 * dia_canvas_get_undo_stack_depth:
 * @canvas: 
 *
 * Get the current stack depth.
 *
 * Return value: 
 **/
guint
dia_canvas_get_undo_stack_depth (DiaCanvas *canvas)
{
	g_return_val_if_fail (DIA_IS_CANVAS (canvas), 0);

	return canvas->stack_depth;
}

/*
 * UndoEntry
 */

/* Create a new undo entry. An undo entry contains a collection of actions that
 * have to be undone in one sweep.
 */
static UndoEntry*
undo_entry_new	(void)
{
	UndoEntry *entry;

	entry = g_new (UndoEntry, 1);

	entry->comment = NULL;
	entry->undo_data = g_array_new (FALSE, FALSE, sizeof (UndoData));

	return entry;
}

static void
undo_entry_free (UndoEntry *entry)
{
	guint i;

	UNDOINFO();
	for (i = 0; i < entry->undo_data->len; i++) {
		UndoData data;
		data = g_array_index (entry->undo_data, UndoData, i);
		UNDOINFO(G_STRLOC": %p with refcount %d", data.object, data.object->ref_count);
		g_object_unref (data.object);
		g_value_unset (&data.value);
	}
	g_array_free (entry->undo_data, TRUE);
	if (entry->comment)
		g_free (entry->comment);
	g_free (entry);
}

/**
 * undo_entry_append:
 * @entry: 
 * @object: is referenced.
 * @property_name: property name used to retrieve the GParamSpec.
 * @value:  Value is copied.
 * @last: 
 *
 **/
static void
undo_entry_append (UndoEntry *entry, GObject *object,
		   GParamSpec *pspec, const GValue *value, gboolean last)
{
	UndoData data = { NULL, NULL, { 0, } };
	guint i;
	const gchar *property_name = g_param_spec_get_name (pspec);

	/* Check if the object/property combination is not already in entry. */
	for (i = 0; i < entry->undo_data->len; i++) {
		UndoData ud = g_array_index (entry->undo_data, UndoData, i);
		if ((ud.object == object)
		    && (strcmp (ud.property_name, property_name) == 0))
			return;
	}

	data.object = object;
	g_object_ref (object);
	data.property_name = property_name;
	g_value_init (&data.value, G_VALUE_TYPE (value));
	g_value_copy (value, &data.value);
	if (last)
		g_array_prepend_val (entry->undo_data, data);
	else
		g_array_append_val (entry->undo_data, data);

	UNDOINFO("Added property %p '%s' (#%d)", object,
		 data.property_name, entry->undo_data->len);
}

static void
undo_entry_apply (UndoEntry *entry)
{
	guint i;

	g_return_if_fail (entry != NULL);
	g_return_if_fail (entry->undo_data->len > 0);

	for (i = entry->undo_data->len; i > 0; i--) {
		UndoData data = g_array_index (entry->undo_data, UndoData, i - 1);

		UNDOINFO("Applying property '%s' %x (%d of %d)", data.property_name, (int) data.value.data[0].v_pointer, i, entry->undo_data->len);
		g_object_set_property (data.object,
				       data.property_name, &data.value);
	}
}

