/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * e-table-item.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Chris Lahey <clahey@ximian.com>
 *		Miguel de Icaza <miguel@gnu.org>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */
/*
 * TODO:
 *   Add a border to the thing, so that focusing works properly.
 */
#include <config.h>

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "gal-a11y-e-table-item-factory.h"
#include "gal-a11y-e-table-item.h"
#include <glib/gi18n.h>
#include "e-util/e-util.h"
#include "misc/e-canvas.h"
#include "misc/e-canvas-utils.h"
#include "misc/e-hsv-utils.h"

#include "e-cell.h"
#include "e-table-item.h"
#include "e-table-subset.h"

/* workaround for avoiding API breakage */
#define eti_get_type e_table_item_get_type
G_DEFINE_TYPE (ETableItem, eti, GNOME_TYPE_CANVAS_ITEM)

#define FOCUSED_BORDER 2

#define d(x)

#if d(!)0
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
#else
#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
#endif

static void eti_check_cursor_bounds (ETableItem *eti);
static void eti_cancel_drag_due_to_model_change (ETableItem *eti);

/* FIXME: Do an analysis of which cell functions are needed before
   realize and make sure that all of them are doable by all the cells
   and that all of the others are only done after realization. */

enum {
	CURSOR_CHANGE,
	CURSOR_ACTIVATED,
	DOUBLE_CLICK,
	RIGHT_CLICK,
	CLICK,
	KEY_PRESS,
	START_DRAG,
	STYLE_SET,
	SELECTION_MODEL_REMOVED,
	SELECTION_MODEL_ADDED,
	LAST_SIGNAL
};

static guint eti_signals[LAST_SIGNAL] = { 0, };

enum {
	PROP_0,
	PROP_TABLE_HEADER,
	PROP_TABLE_MODEL,
	PROP_SELECTION_MODEL,
	PROP_TABLE_ALTERNATING_ROW_COLORS,
	PROP_TABLE_HORIZONTAL_DRAW_GRID,
	PROP_TABLE_VERTICAL_DRAW_GRID,
	PROP_TABLE_DRAW_FOCUS,
	PROP_CURSOR_MODE,
	PROP_LENGTH_THRESHOLD,
	PROP_CURSOR_ROW,
	PROP_UNIFORM_ROW_HEIGHT,

	PROP_MINIMUM_WIDTH,
	PROP_WIDTH,
	PROP_HEIGHT
};

#define DOUBLE_CLICK_TIME      250
#define TRIPLE_CLICK_TIME      500

static gint eti_get_height (ETableItem *eti);
static gint eti_row_height (ETableItem *eti, gint row);
static void e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state);
static void eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
static void eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
static void eti_selection_change (ESelectionModel *selection, ETableItem *eti);
static void eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti);
static void e_table_item_redraw_row (ETableItem *eti, gint row);

#define ETI_SINGLE_ROW_HEIGHT(eti) ((eti)->uniform_row_height_cache != -1 ? (eti)->uniform_row_height_cache : eti_row_height((eti), -1))
#define ETI_MULTIPLE_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))
#define ETI_ROW_HEIGHT(eti,row) ((eti)->uniform_row_height ? ETI_SINGLE_ROW_HEIGHT ((eti)) : ETI_MULTIPLE_ROW_HEIGHT((eti),(row)))

inline static gint
model_to_view_row (ETableItem *eti, gint row)
{
	gint i;
	if (row == -1)
		return -1;
	if (eti->uses_source_model) {
		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
		if (eti->row_guess >= 0 && eti->row_guess < etss->n_map) {
			if (etss->map_table[eti->row_guess] == row) {
				return eti->row_guess;
			}
		}
		for (i = 0; i < etss->n_map; i++) {
			if (etss->map_table[i] == row)
				return i;
		}
		return -1;
	} else
		return row;
}

inline static gint
view_to_model_row (ETableItem *eti, gint row)
{
	if (eti->uses_source_model) {
		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
		if (row >= 0 && row < etss->n_map) {
			eti->row_guess = row;
			return etss->map_table[row];
		} else
			return -1;
	} else
		return row;
}

inline static gint
model_to_view_col (ETableItem *eti, gint col)
{
	gint i;
	if (col == -1)
		return -1;
	for (i = 0; i < eti->cols; i++) {
		ETableCol *ecol = e_table_header_get_column (eti->header, i);
		if (ecol->col_idx == col)
			return i;
	}
	return -1;
}

inline static gint
view_to_model_col (ETableItem *eti, gint col)
{
	ETableCol *ecol = e_table_header_get_column (eti->header, col);
	return ecol ? ecol->col_idx : -1;
}

static void
grab_cancelled (ECanvas *canvas, GnomeCanvasItem *item, gpointer data)
{
	ETableItem *eti = data;

	eti->grab_cancelled = TRUE;
}

inline static void
eti_grab (ETableItem *eti, guint32 time)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	d(g_print ("%s: time: %d\n", __FUNCTION__, time));
	if (eti->grabbed_count == 0) {
		eti->gtk_grabbed = FALSE;
		eti->grab_cancelled = FALSE;
		if (e_canvas_item_grab (E_CANVAS (item->canvas),
				       item,
				       GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK | GDK_BUTTON3_MOTION_MASK
				       | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK,
				       NULL, time,
				       grab_cancelled,
				       eti) != GDK_GRAB_SUCCESS) {
			d(g_print ("%s: gtk_grab_add\n", __FUNCTION__));
			gtk_grab_add (GTK_WIDGET (item->canvas));
			eti->gtk_grabbed = TRUE;
		}
	}
	eti->grabbed_count++;
}

inline static void
eti_ungrab (ETableItem *eti, guint32 time)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	d(g_print ("%s: time: %d\n", __FUNCTION__, time));
	eti->grabbed_count--;
	if (eti->grabbed_count == 0) {
		if (eti->grab_cancelled) {
			eti->grab_cancelled = FALSE;
		} else {
			if (eti->gtk_grabbed) {
				d(g_print ("%s: gtk_grab_remove\n", __FUNCTION__));
				gtk_grab_remove (GTK_WIDGET (item->canvas));
				eti->gtk_grabbed = FALSE;
			}
			gnome_canvas_item_ungrab (item, time);
			eti->grabbed_col = -1;
			eti->grabbed_row = -1;
		}
	}
}

inline static gboolean
eti_editing (ETableItem *eti)
{
	d(g_print("%s: %s\n", __FUNCTION__, (eti->editing_col == -1) ? "false":"true"));

	if (eti->editing_col == -1)
		return FALSE;
	else
		return TRUE;
}

inline static GdkColor *
eti_get_cell_background_color (ETableItem *eti, gint row, gint col, gboolean selected, gboolean *allocatedp)
{
	ECellView *ecell_view = eti->cell_views[col];
	GtkWidget *canvas;
	GdkColor *background, bg;
	GtkStyle *style;
	gchar *color_spec = NULL;
	gboolean allocated = FALSE;

	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
	style = gtk_widget_get_style (canvas);

	if (selected) {
		if (gtk_widget_has_focus (canvas))
			background = &style->bg[GTK_STATE_SELECTED];
		else
			background = &style->bg[GTK_STATE_ACTIVE];
	} else {
		background = &style->base[GTK_STATE_NORMAL];
	}

	color_spec = e_cell_get_bg_color (ecell_view, row);

	if (color_spec != NULL) {
		if (gdk_color_parse (color_spec, &bg)) {
			background = gdk_color_copy (&bg);
			allocated = TRUE;
		}
	}

	if (eti->alternating_row_colors) {
		if (row % 2) {

		} else {
			if (!allocated) {
				background = gdk_color_copy (background);
				allocated = TRUE;
			}
			e_hsv_tweak (background, 0.0f, 0.0f, -0.07f);
		}
	}
	if (allocatedp)
		*allocatedp = allocated;

	return background;
}

inline static GdkColor *
eti_get_cell_foreground_color (ETableItem *eti, gint row, gint col, gboolean selected, gboolean *allocated)
{
	GtkWidget *canvas;
	GdkColor *foreground;
	GtkStyle *style;

	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
	style = gtk_widget_get_style (canvas);

	if (allocated)
		*allocated = FALSE;

	if (selected) {
		if (gtk_widget_has_focus (canvas))
			foreground = &style->fg[GTK_STATE_SELECTED];
		else
			foreground = &style->fg[GTK_STATE_ACTIVE];
	} else {
		foreground = &style->text[GTK_STATE_NORMAL];
	}

	return foreground;
}

static void
eti_free_save_state (ETableItem *eti)
{
	if (eti->save_row == -1 ||
	    !eti->cell_views_realized)
		return;

	e_cell_free_state (eti->cell_views[eti->save_col], view_to_model_col (eti, eti->save_col),
			   eti->save_col, eti->save_row, eti->save_state);
	eti->save_row = -1;
	eti->save_col = -1;
	eti->save_state = NULL;
}

/*
 * During realization, we have to invoke the per-ecell realize routine
 * (On our current setup, we have one e-cell per column.
 *
 * We might want to optimize this to only realize the unique e-cells:
 * ie, a strings-only table, uses the same e-cell for every column, and
 * we might want to avoid realizing each e-cell.
 */
static void
eti_realize_cell_views ( ETableItem *eti)
{
	GnomeCanvasItem *item;
	gint i;

	item = GNOME_CANVAS_ITEM (eti);

	if (eti->cell_views_realized)
		return;

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	for (i = 0; i < eti->n_cells; i++)
		e_cell_realize (eti->cell_views[i]);
	eti->cell_views_realized = 1;
}

static void
eti_attach_cell_views (ETableItem *eti)
{
	gint i;

	g_return_if_fail (eti->header);
	g_return_if_fail (eti->table_model);

	/* this is just c&p from model pre change, but it fixes things */
	eti_cancel_drag_due_to_model_change (eti);
	eti_check_cursor_bounds (eti);
	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);
	eti->motion_row = -1;
	eti->motion_col = -1;

	/*
	 * Now realize the various ECells
	 */
	eti->n_cells = eti->cols;
	eti->cell_views = g_new (ECellView *, eti->n_cells);

	for (i = 0; i < eti->n_cells; i++) {
		ETableCol *ecol = e_table_header_get_column (eti->header, i);

		eti->cell_views[i] = e_cell_new_view (ecol->ecell, eti->table_model, eti);
	}

	eti->needs_compute_height = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/*
 * During unrealization: we invoke every e-cell (one per column in the current
 * setup) to dispose all X resources allocated
 */
static void
eti_unrealize_cell_views (ETableItem *eti)
{
	gint i;

	if (eti->cell_views_realized == 0)
		return;

	eti_free_save_state (eti);

	for (i = 0; i < eti->n_cells; i++)
		e_cell_unrealize (eti->cell_views[i]);
	eti->cell_views_realized = 0;
}

static void
eti_detach_cell_views (ETableItem *eti)
{
	gint i;

	eti_free_save_state (eti);

	for (i = 0; i < eti->n_cells; i++) {
		e_cell_kill_view (eti->cell_views[i]);
		eti->cell_views[i] = NULL;
	}

	g_free (eti->cell_views);
	eti->cell_views = NULL;
	eti->n_cells = 0;
}

static void
eti_bounds (GnomeCanvasItem *item, gdouble *x1, gdouble *y1, gdouble *x2, gdouble *y2)
{
	cairo_matrix_t i2c;
	ETableItem *eti = E_TABLE_ITEM (item);

	/* Wrong BBox's are the source of redraw nightmares */

	*x1 = 0;
	*y1 = 0;
	*x2 = eti->width;
	*y2 = eti->height;

	gnome_canvas_item_i2c_matrix (GNOME_CANVAS_ITEM (eti), &i2c);
	gnome_canvas_matrix_transform_rect (&i2c, x1, y1, x2, y2);
}

static void
eti_reflow (GnomeCanvasItem *item, gint flags)
{
	ETableItem *eti = E_TABLE_ITEM (item);

	if (eti->needs_compute_height) {
		gint new_height = eti_get_height (eti);

		if (new_height != eti->height) {
			eti->height = new_height;
			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
			eti->needs_redraw = 1;
			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
		}
		eti->needs_compute_height = 0;
	}
	if (eti->needs_compute_width) {
		gint new_width = e_table_header_total_width (eti->header);
		if (new_width != eti->width) {
			eti->width = new_width;
			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
			eti->needs_redraw = 1;
			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
		}
		eti->needs_compute_width = 0;
	}
}

/*
 * GnomeCanvasItem::update method
 */
static void
eti_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	gdouble x1, x2, y1, y2;

	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)
		(*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)(item, i2c, flags);

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

	eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
	if (item->x1 != x1 ||
	    item->y1 != y1 ||
	    item->x2 != x2 ||
	    item->y2 != y2) {
		gnome_canvas_request_redraw (item->canvas, x1, y1, x2, y2);
		eti->needs_redraw = 1;
	}

	if (eti->needs_redraw) {
		gnome_canvas_request_redraw (item->canvas, item->x1, item->y1,
					     item->x2, item->y2);
		eti->needs_redraw = 0;
	}
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_table_model (ETableItem *eti)
{
	if (!eti->table_model)
		return;

	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_pre_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_no_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_row_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_cell_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_rows_inserted_id);
	g_signal_handler_disconnect (G_OBJECT (eti->table_model),
			       eti->table_model_rows_deleted_id);
	g_object_unref (eti->table_model);
	if (eti->source_model)
		g_object_unref (eti->source_model);

	eti->table_model_pre_change_id = 0;
	eti->table_model_no_change_id = 0;
	eti->table_model_change_id = 0;
	eti->table_model_row_change_id = 0;
	eti->table_model_cell_change_id = 0;
	eti->table_model_rows_inserted_id = 0;
	eti->table_model_rows_deleted_id = 0;
	eti->table_model = NULL;
	eti->source_model = NULL;
	eti->uses_source_model = 0;
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_selection_model (ETableItem *eti)
{
	if (!eti->selection)
		return;

	g_signal_handler_disconnect (eti->selection,
				     eti->selection_change_id);
	g_signal_handler_disconnect (eti->selection,
				     eti->selection_row_change_id);
	g_signal_handler_disconnect (eti->selection,
				     eti->cursor_change_id);
	g_signal_handler_disconnect (eti->selection,
				     eti->cursor_activated_id);
	g_object_unref (eti->selection);

	eti->selection_change_id = 0;
	eti->selection_row_change_id = 0;
	eti->cursor_activated_id = 0;
	eti->selection = NULL;
}

/*
 * eti_remove_header_model:
 *
 * Invoked to release the header model associated with this ETableItem
 */
static void
eti_remove_header_model (ETableItem *eti)
{
	if (!eti->header)
		return;

	g_signal_handler_disconnect (G_OBJECT (eti->header),
				     eti->header_structure_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->header),
				     eti->header_dim_change_id);
	g_signal_handler_disconnect (G_OBJECT (eti->header),
				     eti->header_request_width_id);

	if (eti->cell_views) {
		eti_unrealize_cell_views (eti);
		eti_detach_cell_views (eti);
	}
	g_object_unref (eti->header);

	eti->header_structure_change_id = 0;
	eti->header_dim_change_id = 0;
	eti->header_request_width_id = 0;
	eti->header = NULL;
}

/*
 * eti_row_height_real:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static gint
eti_row_height_real (ETableItem *eti, gint row)
{
	const gint cols = e_table_header_count (eti->header);
	gint col;
	gint h, max_h;

	g_return_val_if_fail (cols == 0 || eti->cell_views, 0);

	max_h = 0;

	for (col = 0; col < cols; col++) {
		h = e_cell_height (eti->cell_views[col], view_to_model_col (eti, col), col, row);

		if (h > max_h)
			max_h = h;
	}
	return max_h;
}

static void
confirm_height_cache (ETableItem *eti)
{
	gint i;

	if (eti->uniform_row_height || eti->height_cache)
		return;
	eti->height_cache = g_new (int, eti->rows);
	for (i = 0; i < eti->rows; i++) {
		eti->height_cache[i] = -1;
	}
}

static gboolean
height_cache_idle (ETableItem *eti)
{
	gint changed = 0;
	gint i;
	confirm_height_cache (eti);
	for (i = eti->height_cache_idle_count; i < eti->rows; i++) {
		if (eti->height_cache[i] == -1) {
			eti_row_height (eti, i);
			changed++;
			if (changed >= 20)
				break;
		}
	}
	if (changed >= 20) {
		eti->height_cache_idle_count = i;
		return TRUE;
	}
	eti->height_cache_idle_id = 0;
	return FALSE;
}

static void
free_height_cache (ETableItem *eti)
{
	GnomeCanvasItem *item;

	item = GNOME_CANVAS_ITEM (eti);

	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
		if (eti->height_cache)
			g_free (eti->height_cache);
		eti->height_cache = NULL;
		eti->height_cache_idle_count = 0;
		eti->uniform_row_height_cache = -1;

		if (eti->uniform_row_height && eti->height_cache_idle_id != 0) {
			g_source_remove (eti->height_cache_idle_id);
			eti->height_cache_idle_id = 0;
		}

		if ((!eti->uniform_row_height) && eti->height_cache_idle_id == 0)
			eti->height_cache_idle_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL);
	}
}

static void
calculate_height_cache (ETableItem *eti)
{
	free_height_cache (eti);
	confirm_height_cache (eti);
}

/*
 * eti_row_height:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static gint
eti_row_height (ETableItem *eti, gint row)
{
	if (eti->uniform_row_height) {
		eti->uniform_row_height_cache = eti_row_height_real (eti, -1);
		return eti->uniform_row_height_cache;
	} else {
		if (!eti->height_cache) {
			calculate_height_cache (eti);
		}
		if (eti->height_cache[row] == -1) {
			eti->height_cache[row] = eti_row_height_real (eti, row);
			if (row > 0 &&
			    eti->length_threshold != -1 &&
			    eti->rows > eti->length_threshold &&
			    eti->height_cache[row] != eti_row_height (eti, 0)) {
				eti->needs_compute_height = 1;
				e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
			}
		}
		return eti->height_cache[row];
	}
}

/*
 * eti_get_height:
 *
 * Returns the height of the ETableItem.
 *
 * The ETableItem might compute the whole height by asking every row its
 * size.  There is a special mode (designed to work when there are too
 * many rows in the table that performing the previous step could take
 * too long) set by the ETableItem->length_threshold that would determine
 * when the height is computed by using the first row as the size for
 * every other row in the ETableItem.
 */
static gint
eti_get_height (ETableItem *eti)
{
	const gint rows = eti->rows;
	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

	if (rows == 0)
		return 0;

	if (eti->uniform_row_height) {
		gint row_height = ETI_ROW_HEIGHT (eti, -1);
		return ((row_height + height_extra) * rows + height_extra);
	} else {
		gint height;
		gint row;
		if (eti->length_threshold != -1) {
			if (rows > eti->length_threshold) {
				gint row_height = ETI_ROW_HEIGHT (eti, 0);
				if (eti->height_cache) {
					height = 0;
					for (row = 0; row < rows; row++) {
						if (eti->height_cache[row] == -1) {
							height += (row_height + height_extra) * (rows - row);
							break;
						}
						else
							height += eti->height_cache[row] + height_extra;
					}
				} else
					height = (ETI_ROW_HEIGHT (eti, 0) + height_extra) * rows;

				/*
				 * 1 pixel at the top
				 */
				return height + height_extra;
			}
		}

		height = height_extra;
		for (row = 0; row < rows; row++)
			height += ETI_ROW_HEIGHT (eti, row) + height_extra;

		return height;
	}
}

static void
eti_item_region_redraw (ETableItem *eti, gint x0, gint y0, gint x1, gint y1)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	gdouble dx1, dy1, dx2, dy2;
	cairo_matrix_t i2c;

	dx1 = x0;
	dy1 = y0;
	dx2 = x1;
	dy2 = y1;

	gnome_canvas_item_i2c_matrix (item, &i2c);
	gnome_canvas_matrix_transform_rect (&i2c, &dx1, &dy1, &dx2, &dy2);

	gnome_canvas_request_redraw (item->canvas, floor (dx1), floor (dy1), ceil (dx2), ceil (dy2));
}

/*
 * Computes the distance between @start_row and @end_row in pixels
 */
gint
e_table_item_row_diff (ETableItem *eti, gint start_row, gint end_row)
{
	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

	if (start_row < 0)
		start_row = 0;
	if (end_row > eti->rows)
		end_row = eti->rows;

	if (eti->uniform_row_height) {
		return ((end_row - start_row) * (ETI_ROW_HEIGHT (eti, -1) + height_extra));
	} else {
		gint row, total;
		total = 0;
		for (row = start_row; row < end_row; row++)
			total += ETI_ROW_HEIGHT (eti, row) + height_extra;

		return total;
	}
}

static void
eti_get_region (ETableItem *eti,
		gint start_col, gint start_row,
		gint end_col, gint end_row,
		gint *x1p, gint *y1p,
		gint *x2p, gint *y2p)
{
	gint x1, y1, x2, y2;

	x1 = e_table_header_col_diff (eti->header, 0, start_col);
	y1 = e_table_item_row_diff (eti, 0, start_row);
	x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
	y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1);
	if (x1p)
		*x1p = x1;
	if (y1p)
		*y1p = y1;
	if (x2p)
		*x2p = x2;
	if (y2p)
		*y2p = y2;
}

/*
 * eti_request_region_redraw:
 *
 * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell).
 *
 * The @border argument is a number of pixels around the region that should also be queued
 * for redraw.   This is typically used by the focus routines to queue a redraw for the
 * border as well.
 */
static void
eti_request_region_redraw (ETableItem *eti,
			   gint start_col, gint start_row,
			   gint end_col, gint end_row, gint border)
{
	gint x1, y1, x2, y2;

	if (eti->rows > 0) {

		eti_get_region (eti,
				start_col, start_row,
				end_col, end_row,
				&x1, &y1, &x2, &y2);

		eti_item_region_redraw (eti, x1 - border,
					y1 - border,
					x2 + 1 + border,
					y2 + 1 + border);
	}
}

/*
 * eti_request_region_show
 *
 * Request a canvas show on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell).
 */
static void
eti_request_region_show (ETableItem *eti,
			 gint start_col, gint start_row,
			 gint end_col, gint end_row, gint delay)
{
	gint x1, y1, x2, y2;

	eti_get_region (eti,
			start_col, start_row,
			end_col, end_row,
			&x1, &y1, &x2, &y2);

	if (delay)
		e_canvas_item_show_area_delayed (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2, delay);
	else
		e_canvas_item_show_area (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
}

static void
eti_show_cursor (ETableItem *eti, gint delay)
{
	GnomeCanvasItem *item;
	gint cursor_row;

	item = GNOME_CANVAS_ITEM (eti);

	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
		return;

	if (eti->frozen_count > 0) {
		eti->queue_show_cursor = TRUE;
		return;
	}

#if 0
	g_object_get (eti->selection,
		     "cursor_row", &cursor_row,
		     NULL);
#else
	cursor_row = e_selection_model_cursor_row (eti->selection);
#endif

	d(g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));

	if (cursor_row != -1) {
		cursor_row = model_to_view_row (eti, cursor_row);
		eti_request_region_show (eti,
					 0, cursor_row, eti->cols - 1, cursor_row,
					 delay);
	}
}

static void
eti_check_cursor_bounds (ETableItem *eti)
{
	GnomeCanvasItem *item;
	gint x1, y1, x2, y2;
	gint cursor_row;

	item = GNOME_CANVAS_ITEM (eti);

	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
		return;

	if (eti->frozen_count > 0) {
		return;
	}

	g_object_get (eti->selection,
		     "cursor_row", &cursor_row,
		     NULL);

	if (cursor_row == -1) {
		eti->cursor_x1 = -1;
		eti->cursor_y1 = -1;
		eti->cursor_x2 = -1;
		eti->cursor_y2 = -1;
		eti->cursor_on_screen = TRUE;
		return;
	}

	d(g_print ("%s: model cursor row: %d\n", __FUNCTION__, cursor_row));

	cursor_row = model_to_view_row (eti, cursor_row);

	d(g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));

	eti_get_region (eti,
			0, cursor_row, eti->cols - 1, cursor_row,
			&x1, &y1, &x2, &y2);
	eti->cursor_x1 = x1;
	eti->cursor_y1 = y1;
	eti->cursor_x2 = x2;
	eti->cursor_y2 = y2;
	eti->cursor_on_screen = e_canvas_item_area_shown (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);

	d(g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
}

static void
eti_maybe_show_cursor (ETableItem *eti, gint delay)
{
	d(g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
	if (eti->cursor_on_screen)
		eti_show_cursor (eti, delay);
	eti_check_cursor_bounds (eti);
}

static gboolean
eti_idle_show_cursor_cb (gpointer data)
{
	ETableItem *eti = data;

	if (eti->selection) {
		eti_show_cursor (eti, 0);
		eti_check_cursor_bounds (eti);
	}

	eti->cursor_idle_id = 0;
	g_object_unref (eti);
	return FALSE;
}

static void
eti_idle_maybe_show_cursor (ETableItem *eti)
{
	d(g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
	if (eti->cursor_on_screen) {
		g_object_ref (eti);
		if (!eti->cursor_idle_id)
			eti->cursor_idle_id = g_idle_add (eti_idle_show_cursor_cb, eti);
	}
}

static void
eti_cancel_drag_due_to_model_change (ETableItem *eti)
{
	if (eti->maybe_in_drag) {
		eti->maybe_in_drag = FALSE;
		if (!eti->maybe_did_something)
			e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
	}
	if (eti->in_drag) {
		eti->in_drag = FALSE;
	}
}

static void
eti_freeze (ETableItem *eti)
{
	eti->frozen_count++;
	d(g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
}

static void
eti_unfreeze (ETableItem *eti)
{
	if (eti->frozen_count <= 0)
		return;

	eti->frozen_count--;
	d(g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
	if (eti->frozen_count == 0 && eti->queue_show_cursor) {
		eti_show_cursor (eti, 0);
		eti_check_cursor_bounds (eti);
		eti->queue_show_cursor = FALSE;
	}
}

/*
 * Callback routine: invoked before the ETableModel suffers a change
 */
static void
eti_table_model_pre_change (ETableModel *table_model, ETableItem *eti)
{
	eti_cancel_drag_due_to_model_change (eti);
	eti_check_cursor_bounds (eti);
	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);
	eti->motion_row = -1;
	eti->motion_col = -1;
	eti_freeze (eti);
}

/*
 * Callback routine: invoked when the ETableModel has not suffered a change
 */
static void
eti_table_model_no_change (ETableModel *table_model, ETableItem *eti)
{
	eti_unfreeze (eti);
}

/*
 * Callback routine: invoked when the ETableModel has suffered a change
 */

static void
eti_table_model_changed (ETableModel *table_model, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
		eti_unfreeze (eti);
		return;
	}

	eti->rows = e_table_model_row_count (eti->table_model);

	free_height_cache (eti);

	eti_unfreeze (eti);

	eti->needs_compute_height = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));

	eti_idle_maybe_show_cursor (eti);
}

static void
eti_table_model_row_changed (ETableModel *table_model, gint row, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
		eti_unfreeze (eti);
		return;
	}

	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
		eti_table_model_changed (table_model, eti);
		return;
	}

	eti_unfreeze (eti);

	e_table_item_redraw_row (eti, row);
}

static void
eti_table_model_cell_changed (ETableModel *table_model, gint col, gint row, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
		eti_unfreeze (eti);
		return;
	}

	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
		eti_table_model_changed (table_model, eti);
		return;
	}

	eti_unfreeze (eti);

	e_table_item_redraw_row (eti, row);
}

static void
eti_table_model_rows_inserted (ETableModel *table_model, gint row, gint count, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
		eti_unfreeze (eti);
		return;
	}
	eti->rows = e_table_model_row_count (eti->table_model);

	if (eti->height_cache) {
		gint i;
		eti->height_cache = g_renew (int, eti->height_cache, eti->rows);
		memmove (eti->height_cache + row + count, eti->height_cache + row, (eti->rows - count - row) * sizeof (gint));
		for (i = row; i < row + count; i++)
			eti->height_cache[i] = -1;
	}

	eti_unfreeze (eti);

	eti_idle_maybe_show_cursor (eti);

	eti->needs_compute_height = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_table_model_rows_deleted (ETableModel *table_model, gint row, gint count, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
		eti_unfreeze (eti);
		return;
	}

	eti->rows = e_table_model_row_count (eti->table_model);

	if (eti->height_cache && (eti->rows > row)) {
		memmove (eti->height_cache + row, eti->height_cache + row + count, (eti->rows - row) * sizeof (gint));
	}

	eti_unfreeze (eti);

	eti_idle_maybe_show_cursor (eti);

	eti->needs_compute_height = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/**
 * e_table_item_redraw_range
 * @eti: %ETableItem which will be redrawn
 * @start_col: The first col to redraw.
 * @start_row: The first row to redraw.
 * @end_col: The last col to redraw.
 * @end_row: The last row to redraw.
 *
 * This routine redraws the given %ETableItem in the range given.  The
 * range is inclusive at both ends.
 */
void
e_table_item_redraw_range (ETableItem *eti,
			   gint start_col, gint start_row,
			   gint end_col, gint end_row)
{
	gint border;
	gint cursor_col, cursor_row;

	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));

	g_object_get (eti->selection,
		     "cursor_col", &cursor_col,
		     "cursor_row", &cursor_row,
		     NULL);

	if ((start_col == cursor_col) ||
	    (end_col   == cursor_col) ||
	    (view_to_model_row (eti, start_row) == cursor_row) ||
	    (view_to_model_row (eti, end_row)   == cursor_row))
		border = 2;
	else
		border = 0;

	eti_request_region_redraw (eti, start_col, start_row, end_col, end_row, border);
}

static void
e_table_item_redraw_row (ETableItem *eti,
			 gint row)
{
	if (row != -1)
		e_table_item_redraw_range (eti, 0, row, eti->cols - 1, row);
}

static void
eti_add_table_model (ETableItem *eti, ETableModel *table_model)
{
	g_return_if_fail (eti->table_model == NULL);

	eti->table_model = table_model;
	g_object_ref (eti->table_model);

	eti->table_model_pre_change_id = g_signal_connect (
		G_OBJECT (table_model), "model_pre_change",
		G_CALLBACK (eti_table_model_pre_change), eti);

	eti->table_model_no_change_id = g_signal_connect (
		G_OBJECT (table_model), "model_no_change",
		G_CALLBACK (eti_table_model_no_change), eti);

	eti->table_model_change_id = g_signal_connect (
		G_OBJECT (table_model), "model_changed",
		G_CALLBACK (eti_table_model_changed), eti);

	eti->table_model_row_change_id = g_signal_connect (
		G_OBJECT (table_model), "model_row_changed",
		G_CALLBACK (eti_table_model_row_changed), eti);

	eti->table_model_cell_change_id = g_signal_connect (
		G_OBJECT (table_model), "model_cell_changed",
		G_CALLBACK (eti_table_model_cell_changed), eti);

	eti->table_model_rows_inserted_id = g_signal_connect (
		G_OBJECT (table_model), "model_rows_inserted",
		G_CALLBACK (eti_table_model_rows_inserted), eti);

	eti->table_model_rows_deleted_id = g_signal_connect (
		G_OBJECT (table_model), "model_rows_deleted",
		G_CALLBACK (eti_table_model_rows_deleted), eti);

	if (eti->header) {
		eti_detach_cell_views (eti);
		eti_attach_cell_views (eti);
	}

	if (E_IS_TABLE_SUBSET (table_model)) {
		eti->uses_source_model = 1;
		eti->source_model = E_TABLE_SUBSET (table_model)->source;
		if (eti->source_model)
			g_object_ref (eti->source_model);
	}

	eti_freeze (eti);

	eti_table_model_changed (table_model, eti);
}

static void
eti_add_selection_model (ETableItem *eti, ESelectionModel *selection)
{
	g_return_if_fail (eti->selection == NULL);

	eti->selection = selection;
	g_object_ref (eti->selection);

	eti->selection_change_id = g_signal_connect (
		selection, "selection_changed",
		G_CALLBACK (eti_selection_change), eti);

	eti->selection_row_change_id = g_signal_connect (
		selection, "selection_row_changed",
		G_CALLBACK (eti_selection_row_change), eti);

	eti->cursor_change_id = g_signal_connect (
		selection, "cursor_changed",
		G_CALLBACK (eti_cursor_change), eti);

	eti->cursor_activated_id = g_signal_connect (
		selection, "cursor_activated",
		G_CALLBACK (eti_cursor_activated), eti);

	eti_selection_change (selection, eti);
	g_signal_emit_by_name (G_OBJECT (eti),
                               "selection_model_added", eti->selection);
}

static void
eti_header_dim_changed (ETableHeader *eth, gint col, ETableItem *eti)
{
	eti->needs_compute_width = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_header_structure_changed (ETableHeader *eth, ETableItem *eti)
{
	eti->cols = e_table_header_count (eti->header);

	/*
	 * There should be at least one column
	 *  BUT: then you can't remove all columns from a header and add new ones.
	 */

	if (eti->cell_views) {
		eti_unrealize_cell_views (eti);
		eti_detach_cell_views (eti);
		eti_attach_cell_views (eti);
		eti_realize_cell_views (eti);
	} else {
		if (eti->table_model) {
			eti_attach_cell_views (eti);
			eti_realize_cell_views (eti);
		}
	}
	eti->needs_compute_width = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static gint
eti_request_column_width (ETableHeader *eth, gint col, ETableItem *eti)
{
	gint width = 0;

	if (eti->cell_views && eti->cell_views_realized) {
		width = e_cell_max_width (eti->cell_views[col], view_to_model_col (eti, col), col);
	}

	return width;
}

static void
eti_add_header_model (ETableItem *eti, ETableHeader *header)
{
	g_return_if_fail (eti->header == NULL);

	eti->header = header;
	g_object_ref (header);

	eti_header_structure_changed (header, eti);

	eti->header_dim_change_id = g_signal_connect (
		G_OBJECT (header), "dimension_change",
		G_CALLBACK (eti_header_dim_changed), eti);

	eti->header_structure_change_id = g_signal_connect (
		G_OBJECT (header), "structure_change",
		G_CALLBACK (eti_header_structure_changed), eti);

	eti->header_request_width_id = g_signal_connect
		(G_OBJECT (header), "request_width",
		 G_CALLBACK (eti_request_column_width), eti);
}

/*
 * GObject::dispose method
 */
static void
eti_dispose (GObject *object)
{
	ETableItem *eti = E_TABLE_ITEM (object);

	eti_remove_header_model (eti);
	eti_remove_table_model (eti);
	eti_remove_selection_model (eti);

	if (eti->height_cache_idle_id) {
		g_source_remove (eti->height_cache_idle_id);
		eti->height_cache_idle_id = 0;
	}
	eti->height_cache_idle_count = 0;

	if (eti->cursor_idle_id) {
		g_source_remove (eti->cursor_idle_id);
		eti->cursor_idle_id = 0;
	}

	if (eti->height_cache)
		g_free (eti->height_cache);
	eti->height_cache = NULL;

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (eti_parent_class)->dispose (object);
}

static void
eti_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object);
	ETableItem *eti = E_TABLE_ITEM (object);
	gint cursor_col;

	switch (prop_id) {
	case PROP_TABLE_HEADER:
		eti_remove_header_model (eti);
		eti_add_header_model (eti, E_TABLE_HEADER (g_value_get_object (value)));
		break;

	case PROP_TABLE_MODEL:
		eti_remove_table_model (eti);
		eti_add_table_model (eti, E_TABLE_MODEL (g_value_get_object (value)));
		break;

	case PROP_SELECTION_MODEL:
		g_signal_emit_by_name (G_OBJECT (eti),
				       "selection_model_removed", eti->selection);
		eti_remove_selection_model (eti);
		if (g_value_get_object (value))
			eti_add_selection_model (eti, E_SELECTION_MODEL (g_value_get_object (value)));
		break;

	case PROP_LENGTH_THRESHOLD:
		eti->length_threshold = g_value_get_int (value);
		break;

	case PROP_TABLE_ALTERNATING_ROW_COLORS:
		eti->alternating_row_colors = g_value_get_boolean (value);
		break;

	case PROP_TABLE_HORIZONTAL_DRAW_GRID:
		eti->horizontal_draw_grid = g_value_get_boolean (value);
		break;

	case PROP_TABLE_VERTICAL_DRAW_GRID:
		eti->vertical_draw_grid = g_value_get_boolean (value);
		break;

	case PROP_TABLE_DRAW_FOCUS:
		eti->draw_focus = g_value_get_boolean (value);
		break;

	case PROP_CURSOR_MODE:
		eti->cursor_mode = g_value_get_int (value);
		break;

	case PROP_MINIMUM_WIDTH:
	case PROP_WIDTH:
		if ((eti->minimum_width == eti->width && g_value_get_double (value) > eti->width) ||
		    g_value_get_double (value) < eti->width) {
			eti->needs_compute_width = 1;
			e_canvas_item_request_reflow (item);
		}
		eti->minimum_width = g_value_get_double (value);
		break;
	case PROP_CURSOR_ROW:
		g_object_get (eti->selection,
			     "cursor_col", &cursor_col,
			     NULL);

		e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row (eti, g_value_get_int (value)), 0);
		break;
	case PROP_UNIFORM_ROW_HEIGHT:
		if (eti->uniform_row_height != g_value_get_boolean (value)) {
			eti->uniform_row_height = g_value_get_boolean (value);
			if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
				free_height_cache (eti);
				eti->needs_compute_height = 1;
				e_canvas_item_request_reflow (item);
				eti->needs_redraw = 1;
				gnome_canvas_item_request_update (item);
			}
		}
		break;
	}
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (item);
}

static void
eti_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	ETableItem *eti;
	gint row;

	eti = E_TABLE_ITEM (object);

	switch (prop_id) {
	case PROP_WIDTH:
		g_value_set_double (value, eti->width);
		break;
	case PROP_HEIGHT:
		g_value_set_double (value, eti->height);
		break;
	case PROP_MINIMUM_WIDTH:
		g_value_set_double (value, eti->minimum_width);
		break;
	case PROP_CURSOR_ROW:
		g_object_get (eti->selection,
			     "cursor_row", &row,
			     NULL);
		g_value_set_int (value, model_to_view_row (eti, row));
		break;
	case PROP_UNIFORM_ROW_HEIGHT:
		g_value_set_boolean (value, eti->uniform_row_height);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
eti_init (ETableItem *eti)
{
	eti->motion_row	       = -1;
	eti->motion_col	       = -1;
	eti->editing_col               = -1;
	eti->editing_row               = -1;
	eti->height                    = 0;
	eti->width                     = 0;
	eti->minimum_width             = 0;

	eti->save_col                  = -1;
	eti->save_row                  = -1;
	eti->save_state                = NULL;

	eti->click_count               = 0;

	eti->height_cache              = NULL;
	eti->height_cache_idle_id      = 0;
	eti->height_cache_idle_count   = 0;

	eti->length_threshold          = -1;
	eti->uniform_row_height        = FALSE;

	eti->uses_source_model         = 0;
	eti->source_model              = NULL;

	eti->row_guess                 = -1;
	eti->cursor_mode               = E_CURSOR_SIMPLE;

	eti->selection_change_id       = 0;
	eti->selection_row_change_id   = 0;
	eti->cursor_change_id          = 0;
	eti->cursor_activated_id       = 0;
	eti->selection                 = NULL;

	eti->old_cursor_row            = -1;

	eti->needs_redraw              = 0;
	eti->needs_compute_height      = 0;

	eti->in_key_press              = 0;

	eti->maybe_did_something       = TRUE;

	eti->grabbed_count             = 0;
	eti->gtk_grabbed               = 0;

	eti->in_drag                   = 0;
	eti->maybe_in_drag             = 0;
	eti->grabbed                   = 0;

	eti->grabbed_col               = -1;
	eti->grabbed_row               = -1;

	eti->cursor_on_screen          = FALSE;
	eti->cursor_x1                 = -1;
	eti->cursor_y1                 = -1;
	eti->cursor_x2                 = -1;
	eti->cursor_y2                 = -1;

	eti->rows                      = -1;
	eti->cols                      = -1;

	eti->frozen_count              = 0;
	eti->queue_show_cursor         = FALSE;

	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
}

#define gray50_width 2
#define gray50_height 2
static const gchar gray50_bits[] = {
	0x02, 0x01, };

static gboolean
eti_tree_unfreeze (GtkWidget *widget,  GdkEvent *event, ETableItem *eti)
{

	if (widget)
		g_object_set_data (G_OBJECT (widget), "freeze-cursor", NULL);

	return FALSE;
}

static void
eti_realize (GnomeCanvasItem *item)
{
	ETableItem *eti = E_TABLE_ITEM (item);

	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)
		(*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)(item);

	eti->rows = e_table_model_row_count (eti->table_model);

	g_signal_connect (GTK_LAYOUT(item->canvas), "scroll_event", G_CALLBACK (eti_tree_unfreeze), eti);

	if (eti->cell_views == NULL)
		eti_attach_cell_views (eti);

	eti_realize_cell_views (eti);

	free_height_cache (eti);

	if (item->canvas->focused_item == NULL && eti->selection) {
		gint row;

		row = e_selection_model_cursor_row (E_SELECTION_MODEL (eti->selection));
		row = model_to_view_row (eti, row);
		if (row != -1) {
			e_canvas_item_grab_focus (item, FALSE);
			eti_show_cursor (eti, 0);
			eti_check_cursor_bounds (eti);
		}
	}

	eti->needs_compute_height = 1;
	eti->needs_compute_width = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_unrealize (GnomeCanvasItem *item)
{
	ETableItem *eti = E_TABLE_ITEM (item);

	if (eti->grabbed_count > 0) {
		d(g_print ("%s: eti_ungrab\n", __FUNCTION__));
		eti_ungrab (eti, -1);
	}

	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);

	if (eti->height_cache_idle_id) {
		g_source_remove (eti->height_cache_idle_id);
		eti->height_cache_idle_id = 0;
	}

	if (eti->height_cache)
		g_free (eti->height_cache);
	eti->height_cache = NULL;
	eti->height_cache_idle_count = 0;

	eti_unrealize_cell_views (eti);

	eti->height = 0;

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

static void
eti_draw_grid_line (ETableItem *eti, cairo_t *cr, GtkStyle *style,
                    gint x1, gint y1, gint x2, gint y2)
{
	cairo_save (cr);

	cairo_set_line_width (cr, 1.0);
	gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);

	cairo_move_to (cr, x1 + 0.5, y1 + 0.5);
	cairo_line_to (cr, x2 + 0.5, y2 + 0.5);
	cairo_stroke (cr);

	cairo_restore (cr);
}

static void
eti_draw (GnomeCanvasItem *item, cairo_t *cr, gint x, gint y, gint width, gint height)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	const gint rows = eti->rows;
	const gint cols = eti->cols;
	gint row, col;
	gint first_col, last_col, x_offset;
	gint first_row, last_row, y_offset, yd;
	gint x1, x2;
	gint f_x1, f_x2, f_y1, f_y2;
	gboolean f_found;
	cairo_matrix_t i2c;
	gdouble eti_base_x, eti_base_y, lower_right_y, lower_right_x;
	GtkWidget *canvas = GTK_WIDGET (item->canvas);
	GtkStyle *style = gtk_widget_get_style (canvas);
	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

	/*
	 * Find out our real position after grouping
	 */
	gnome_canvas_item_i2c_matrix (item, &i2c);
	eti_base_x = 0;
	eti_base_y = 0;
	cairo_matrix_transform_point (&i2c, &eti_base_x, &eti_base_y);

	lower_right_x = eti->width;
	lower_right_y = eti->height;
	cairo_matrix_transform_point (&i2c, &lower_right_x, &lower_right_y);

	/*
	 * First column to draw, last column to draw
	 */
	first_col = -1;
	x_offset = 0;
	x1 = floor (eti_base_x);
	for (col = 0; col < cols; col++, x1 = x2) {
		ETableCol *ecol = e_table_header_get_column (eti->header, col);

		x2 = x1 + ecol->width;

		if (x1 > (x + width))
			break;
		if (x2 < x)
			continue;
		if (first_col == -1) {
			x_offset = x1 - x;
			first_col = col;
		}
	}
	last_col = col;

	/*
	 * Nothing to paint
	 */
	if (first_col == -1)
		return;

	/*
	 * Compute row span.
	 */
	if (eti->uniform_row_height) {
		first_row = (y          - floor (eti_base_y) - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
		last_row  = (y + height - floor (eti_base_y)               ) / (ETI_ROW_HEIGHT (eti, -1) + height_extra) + 1;
		if (first_row > last_row)
			return;
		y_offset = floor (eti_base_y) - y + height_extra + first_row * (ETI_ROW_HEIGHT (eti, -1) + height_extra);
		if (first_row < 0)
			first_row = 0;
		if (last_row > eti->rows)
			last_row = eti->rows;
	} else {
		gint y1, y2;

		y_offset = 0;
		first_row = -1;

		y1 = y2 = floor (eti_base_y) + height_extra;
		for (row = 0; row < rows; row++, y1 = y2) {

			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;

			if (y1 > y + height)
				break;

			if (y2 < y)
				continue;

			if (first_row == -1) {
				y_offset = y1 - y;
				first_row = row;
			}
		}
		last_row = row;

		if (first_row == -1)
			return;
	}

	if (first_row == -1)
		return;

	/*
	 * Draw cells
	 */
	yd = y_offset;
	f_x1 = f_x2 = f_y1 = f_y2 = -1;
	f_found = FALSE;

	if (eti->horizontal_draw_grid && first_row == 0)
		eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);

	yd += height_extra;

	for (row = first_row; row < last_row; row++) {
		gint xd;
		gboolean selected;
		gint cursor_col, cursor_row;

		height = ETI_ROW_HEIGHT (eti, row);

		xd = x_offset;

		selected = e_selection_model_is_row_selected (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti,row));

		g_object_get (eti->selection,
			     "cursor_col", &cursor_col,
			     "cursor_row", &cursor_row,
			     NULL);

		for (col = first_col; col < last_col; col++) {
			ETableCol *ecol = e_table_header_get_column (eti->header, col);
			ECellView *ecell_view = eti->cell_views[col];
			gboolean col_selected = selected;
			gboolean cursor = FALSE;
			ECellFlags flags;
			gboolean free_background;
			GdkColor *background;
			gint x1, x2, y1, y2;
			cairo_pattern_t *pat;

			switch (eti->cursor_mode) {
			case E_CURSOR_SIMPLE:
			case E_CURSOR_SPREADSHEET:
				if (cursor_col == ecol->col_idx && cursor_row == view_to_model_row (eti, row)) {
					col_selected = !col_selected;
					cursor = TRUE;
				}
				break;
			case E_CURSOR_LINE:
				/* Nothing */
				break;
			}

			x1 = xd;
			y1 = yd+1;
			x2 = x1 + ecol->width;
			y2 = yd+height;

			background = eti_get_cell_background_color (eti, row, col, col_selected, &free_background);

			cairo_save (cr);
			pat = cairo_pattern_create_linear (0, y1, 0, y2);
			cairo_pattern_add_color_stop_rgba (pat, 0.0, background->red/65535.0 ,
								     background->green/65535.0,
								     background->blue/65535.0, selected ? 0.8: 1.0);
			if (selected)
				cairo_pattern_add_color_stop_rgba (pat, 0.5, background->red/65535.0 ,
									     background->green/65535.0,
									     background->blue/65535.0, 0.9);

			cairo_pattern_add_color_stop_rgba (pat, 1, background->red/65535.0 ,
								   background->green/65535.0,
								   background->blue/65535.0, selected ? 0.8 : 1.0);
			cairo_rectangle (cr, x1, y1, ecol->width, height-1);
			cairo_set_source (cr, pat);
			cairo_fill_preserve (cr);
			cairo_pattern_destroy (pat);
			cairo_set_line_width (cr, 0);
			cairo_stroke (cr);
			cairo_restore (cr);

			cairo_save (cr);
			cairo_set_line_width (cr, 1.0);
			cairo_set_source_rgba (cr, background->red/65535.0 ,
						   background->green/65535.0,
						   background->blue/65535.0, 1);
			cairo_move_to (cr, x1, y1);
			cairo_line_to (cr, x2, y1);
			cairo_stroke (cr);

			cairo_set_line_width (cr, 1.0);
			cairo_set_source_rgba (cr, background->red/65535.0 ,
						  background->green/65535.0,
						  background->blue/65535.0, 1);
			cairo_move_to (cr, x1, y2);
			cairo_line_to (cr, x2, y2);
			cairo_stroke (cr);
			cairo_restore (cr);

			if (free_background)
				gdk_color_free (background);

			flags = col_selected ? E_CELL_SELECTED : 0;
			flags |= gtk_widget_has_focus (canvas) ? E_CELL_FOCUSED : 0;
			flags |= cursor ? E_CELL_CURSOR : 0;

			switch (ecol->justification) {
			case GTK_JUSTIFY_LEFT:
				flags |= E_CELL_JUSTIFY_LEFT;
				break;
			case GTK_JUSTIFY_RIGHT:
				flags |= E_CELL_JUSTIFY_RIGHT;
				break;
			case GTK_JUSTIFY_CENTER:
				flags |= E_CELL_JUSTIFY_CENTER;
				break;
			case GTK_JUSTIFY_FILL:
				flags |= E_CELL_JUSTIFY_FILL;
				break;
			}

			e_cell_draw (ecell_view, cr, ecol->col_idx, col, row, flags,
				     xd, yd, xd + ecol->width, yd + height);

			if (!f_found && !selected) {
				switch (eti->cursor_mode) {
				case E_CURSOR_LINE:
					if (view_to_model_row (eti, row) == cursor_row) {
						f_x1 = floor (eti_base_x) - x;
						f_x2 = floor (lower_right_x) - x;
						f_y1 = yd+1;
						f_y2 = yd + height;
						f_found = TRUE;
					}
					break;
				case E_CURSOR_SIMPLE:
				case E_CURSOR_SPREADSHEET:
					if (view_to_model_col (eti, col) == cursor_col && view_to_model_row (eti, row) == cursor_row) {
						f_x1 = xd;
						f_x2 = xd + ecol->width;
						f_y1 = yd;
						f_y2 = yd + height;
						f_found = TRUE;
					}
					break;
				}
			}

			xd += ecol->width;
		}
		yd += height;

		if (eti->horizontal_draw_grid) {
			eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
			yd++;
		}
	}

	if (eti->vertical_draw_grid) {
		gint xd = x_offset;

		for (col = first_col; col <= last_col; col++) {
			ETableCol *ecol = e_table_header_get_column (eti->header, col);

			eti_draw_grid_line (eti, cr, style, xd, y_offset, xd, yd - 1);

			/*
			 * This looks wierd, but it is to draw the last line
			 */
			if (ecol)
				xd += ecol->width;
		}
	}

	/*
	 * Draw focus
	 */
	if (eti->draw_focus && f_found) {
		static const double dash[] = { 1.0, 1.0 };
		cairo_set_line_width (cr, 1.0);
		cairo_rectangle (cr,
				 f_x1 + 0.5, f_x2 + 0.5,
				 f_x2 - f_x1 - 1, f_y2 - f_y1);

		gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
		cairo_stroke_preserve (cr);

		cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0.0);
		gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
		cairo_stroke (cr);
	}
}

static GnomeCanvasItem *
eti_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy)
{
	return item;
}

static gboolean
find_cell (ETableItem *eti, gdouble x, gdouble y, gint *view_col_res, gint *view_row_res, gdouble *x1_res, gdouble *y1_res)
{
	const gint cols = eti->cols;
	const gint rows = eti->rows;
	gdouble x1, y1, x2, y2;
	gint col, row;

	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;

	/* FIXME: this routine is inneficient, fix later */

	if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) {
		*view_col_res = eti->grabbed_col;
		*view_row_res = eti->grabbed_row;
		*x1_res = x - e_table_header_col_diff (eti->header, 0, eti->grabbed_col);
		*y1_res = y - e_table_item_row_diff (eti, 0, eti->grabbed_row);
		return TRUE;
	}

	if (cols == 0 || rows == 0)
		return FALSE;

	x1 = 0;
	for (col = 0; col < cols - 1; col++, x1 = x2) {
		ETableCol *ecol = e_table_header_get_column (eti->header, col);

		if (x < x1)
			return FALSE;

		x2 = x1 + ecol->width;

		if (x <= x2)
			break;
	}

	if (eti->uniform_row_height) {
		if (y < height_extra)
			return FALSE;
		row = (y - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
		y1 = row * (ETI_ROW_HEIGHT (eti, -1) + height_extra) + height_extra;
		if (row >= eti->rows)
			return FALSE;
	} else {
		y1 = y2 = height_extra;
		if (y < height_extra)
			return FALSE;
		for (row = 0; row < rows; row++, y1 = y2) {
			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;

			if (y <= y2)
				break;
		}

		if (row == rows)
			return FALSE;
	}
	*view_col_res = col;
	if (x1_res)
		*x1_res = x - x1;
	*view_row_res = row;
	if (y1_res)
		*y1_res = y - y1;
	return TRUE;
}

static void
eti_cursor_move (ETableItem *eti, gint row, gint column)
{
	e_table_item_leave_edit_(eti);
	e_table_item_focus (eti, view_to_model_col (eti, column), view_to_model_row (eti, row), 0);
}

static void
eti_cursor_move_left (ETableItem *eti)
{
	gint cursor_col, cursor_row;
	g_object_get (eti->selection,
		     "cursor_col", &cursor_col,
		     "cursor_row", &cursor_row,
		     NULL);

	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) - 1);
}

static void
eti_cursor_move_right (ETableItem *eti)
{
	gint cursor_col, cursor_row;
	g_object_get (eti->selection,
		     "cursor_col", &cursor_col,
		     "cursor_row", &cursor_row,
		     NULL);

	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) + 1);
}

static gint
eti_e_cell_event     (ETableItem *item, ECellView *ecell_view, GdkEvent *event, gint time, gint model_col, gint view_col, gint row, ECellFlags flags)
{
	ECellActions actions = 0;
	gint ret_val;

	ret_val = e_cell_event (ecell_view, event, model_col, view_col, row, flags, &actions);

	if (actions & E_CELL_GRAB) {
		d(g_print ("%s: eti_grab\n", __FUNCTION__));
		eti_grab (item, time);
		item->grabbed_col = view_col;
		item->grabbed_row = row;
	}

	if (actions & E_CELL_UNGRAB) {
		d(g_print ("%s: eti_ungrab\n", __FUNCTION__));
		eti_ungrab (item, time);
		item->grabbed_col = -1;
		item->grabbed_row = -1;
	}

	return ret_val;
}

/* FIXME: cursor */
static gint
eti_event (GnomeCanvasItem *item, GdkEvent *e)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	ECellView *ecell_view;
	gboolean return_val = TRUE;
#if d(!)0
	gboolean leave = FALSE;
#endif

	if (!eti->header)
		return FALSE;

	switch (e->type) {
	case GDK_BUTTON_PRESS: {
		gdouble x1, y1;
		gdouble realx, realy;
		GdkEventButton button;
		gint col, row;
		gint cursor_row, cursor_col;
		gint new_cursor_row, new_cursor_col;
		ECellFlags flags = 0;

		d(g_print("%s: GDK_BUTTON_PRESS received, button %d\n", __FUNCTION__, e->button.button));

		switch (e->button.button) {
		case 1: /* Fall through. */
		case 2:
			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
			gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);

			realx = e->button.x;
			realy = e->button.y;

			if (!find_cell (eti, realx, realy, &col, &row, &x1, &y1)) {
				if (eti_editing (eti))
					e_table_item_leave_edit_(eti);
				return TRUE;
			}

			ecell_view = eti->cell_views[col];
			button = *(GdkEventButton *) e;
			button.x = x1;
			button.y = y1;

			g_object_get (eti->selection,
				     "cursor_row", &cursor_row,
				     "cursor_col", &cursor_col,
				     NULL);

			if (cursor_col == view_to_model_col (eti, col) && cursor_row == view_to_model_row (eti, row)) {
				flags = E_CELL_CURSOR;
			} else {
				flags = 0;
			}

			return_val = eti_e_cell_event (eti, ecell_view, (GdkEvent *) &button, button.time, view_to_model_col (eti, col), col, row, flags);
			if (return_val)
				return TRUE;

			g_signal_emit (eti, eti_signals[CLICK], 0,
				       row, view_to_model_col (eti, col), &button, &return_val);

			if (return_val) {
				eti->click_count = 0;
				return TRUE;
			}

			g_object_get (eti->selection,
				     "cursor_row", &cursor_row,
				     "cursor_col", &cursor_col,
				     NULL);

			eti->maybe_did_something =
				e_selection_model_maybe_do_something (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti, row), view_to_model_col (eti, col), button.state);
			g_object_get (eti->selection,
				     "cursor_row", &new_cursor_row,
				     "cursor_col", &new_cursor_col,
				     NULL);

			if (cursor_row != new_cursor_row || cursor_col != new_cursor_col) {
				eti->click_count = 1;
			} else {
				eti->click_count++;
				eti->row_guess = row;

				if ((!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
					e_table_item_enter_edit (eti, col, row);
				}

				/*
				 * Adjust the event positions
				 */

				if (eti_editing (eti)) {
					return_val = eti_e_cell_event (eti, ecell_view, (GdkEvent *) &button, button.time,
								       view_to_model_col (eti, col), col, row, E_CELL_EDITING | E_CELL_CURSOR);
					if (return_val)
						return TRUE;
				}
			}

			if (e->button.button == 1) {
				return_val = TRUE;

				eti->maybe_in_drag = TRUE;
				eti->drag_row      = new_cursor_row;
				eti->drag_col      = new_cursor_col;
				eti->drag_x        = realx;
				eti->drag_y        = realy;
				eti->drag_state    = e->button.state;
				eti->grabbed       = TRUE;
				d(g_print ("%s: eti_grab\n", __FUNCTION__));
				eti_grab (eti, e->button.time);
			}

			break;
		case 3:
			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
			gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);
			if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1))
				return TRUE;

			e_selection_model_right_click_down (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti, row), view_to_model_col (eti, col), 0);

			g_signal_emit (eti, eti_signals[RIGHT_CLICK], 0,
				       row, view_to_model_col (eti, col), e, &return_val);
			if (!return_val)
				e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
			break;
		case 4:
		case 5:
			return FALSE;

		}
		break;
	}

	case GDK_BUTTON_RELEASE: {
		gdouble x1, y1;
		gint col, row;
		gint cursor_row, cursor_col;

		d(g_print("%s: GDK_BUTTON_RELEASE received, button %d\n", __FUNCTION__, e->button.button));

		if (eti->grabbed_count > 0) {
			d(g_print ("%s: eti_ungrab\n", __FUNCTION__));
			eti_ungrab (eti, e->button.time);
		}

		if (e->button.button == 1) {
			if (eti->maybe_in_drag) {
				eti->maybe_in_drag = FALSE;
				if (!eti->maybe_did_something)
					e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
			}
			if (eti->in_drag) {
				eti->in_drag = FALSE;
			}
		}

		switch (e->button.button) {
		case 1: /* Fall through. */
		case 2:

			gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);
#if d(!)0
			{
				gboolean cell_found = find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1);
				g_print("%s: find_cell(%f, %f) = %s(%d, %d, %f, %f)\n", __FUNCTION__, e->button.x, e->button.y,
					cell_found?"true":"false", col, row, x1, y1);
			}
#endif

			if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1))
				return TRUE;

			g_object_get (eti->selection,
				     "cursor_row", &cursor_row,
				     "cursor_col", &cursor_col,
				     NULL);

			d(g_print("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n"
				  "eti_editing: %s, row:%d:%d, col:%d:%d\n", __FUNCTION__, e->button.button, __LINE__,
				  eti_editing(eti)?"true":"false", cursor_row, view_to_model_row(eti, row), cursor_col, view_to_model_col(eti, col)));

			if (eti_editing (eti) && cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {

				d(g_print("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n", __FUNCTION__, e->button.button, __LINE__))
;

				ecell_view = eti->cell_views[col];

				/*
				 * Adjust the event positions
				 */
				e->button.x = x1;
				e->button.y = y1;

				return_val = eti_e_cell_event (eti, ecell_view, e, e->button.time,
							       view_to_model_col (eti, col), col, row, E_CELL_EDITING | E_CELL_CURSOR);
			}
			break;
		case 3:
			e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
			return_val = TRUE;
			break;
		case 4:
		case 5:
			return FALSE;

		}
		break;
	}

	case GDK_2BUTTON_PRESS: {
		gint model_col, model_row;
#if 0
		gdouble x1, y1;
#endif

		d(g_print("%s: GDK_2BUTTON_PRESS received, button %d\n", __FUNCTION__, e->button.button));

		/*
		 * click_count is so that if you click on two
		 * different rows we don't send a double click signal.
		 */

		if (eti->click_count >= 2) {

			gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);

#if 0
			if (!find_cell (eti, e->button.x, e->button.y, &current_col, &current_row, &x1, &y1))
				return TRUE;
#endif

			g_object_get (eti->selection,
				     "cursor_row", &model_row,
				     "cursor_col", &model_col,
				     NULL);

			e->button.x -= e_table_header_col_diff (eti->header, 0, model_to_view_col (eti, model_col));
			e->button.y -= e_table_item_row_diff (eti, 0, model_to_view_row (eti, model_row));

			if (e->button.button == 1) {
				if (eti->maybe_in_drag) {
					eti->maybe_in_drag = FALSE;
					if (!eti->maybe_did_something)
						e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
				}
				if (eti->in_drag) {
					eti->in_drag = FALSE;
				}
				if (eti_editing (eti))
					e_table_item_leave_edit_ (eti);

			}

			if (eti->grabbed_count > 0) {
				d(g_print ("%s: eti_ungrab\n", __FUNCTION__));
				eti_ungrab (eti, e->button.time);
			}

			if (model_row != -1 && model_col != -1) {
				g_signal_emit (eti, eti_signals[DOUBLE_CLICK], 0,
					       model_row, model_col, e);
			}
		}
		break;
	}
	case GDK_MOTION_NOTIFY: {
		gint col, row, flags;
		gdouble x1, y1;
		gint cursor_col, cursor_row;

		gnome_canvas_item_w2i (item, &e->motion.x, &e->motion.y);

		if (eti->maybe_in_drag) {
			if (abs (e->motion.x - eti->drag_x) >= 3 ||
			    abs (e->motion.y - eti->drag_y) >= 3) {
				gboolean drag_handled;

				eti->maybe_in_drag = 0;
				g_signal_emit (eti, eti_signals[START_DRAG], 0,
					       eti->drag_row, eti->drag_col, e, &drag_handled);
				if (drag_handled)
					eti->in_drag = 1;
				else
					eti->in_drag = 0;
			}
		}

		if (!find_cell (eti, e->motion.x, e->motion.y, &col, &row, &x1, &y1))
			return TRUE;

		if (eti->motion_row != -1 && eti->motion_col != -1 &&
		    (row != eti->motion_row || col != eti->motion_col)) {
			GdkEvent *cross = gdk_event_new (GDK_LEAVE_NOTIFY);
			cross->crossing.time = e->motion.time;
			return_val = eti_e_cell_event (eti, eti->cell_views[eti->motion_col],
						       cross, cross->crossing.time,
						       view_to_model_col (eti, eti->motion_col),
						       eti->motion_col, eti->motion_row, 0);
		}

		eti->motion_row = row;
		eti->motion_col = col;

		g_object_get (eti->selection,
			     "cursor_row", &cursor_row,
			     "cursor_col", &cursor_col,
			     NULL);

		flags = 0;
		if (cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
			flags = E_CELL_EDITING | E_CELL_CURSOR;
		}

		ecell_view = eti->cell_views[col];

		/*
		 * Adjust the event positions
		 */
		e->motion.x = x1;
		e->motion.y = y1;

		return_val = eti_e_cell_event (eti, ecell_view, e, e->motion.time,
					       view_to_model_col (eti, col), col, row, flags);
		break;
	}

	case GDK_KEY_PRESS: {
		gint cursor_row, cursor_col;
		gint handled = TRUE;

		d(g_print("%s: GDK_KEY_PRESS received, keyval: %d\n", __FUNCTION__, (gint) e->key.keyval));

		g_object_get (eti->selection,
			     "cursor_row", &cursor_row,
			     "cursor_col", &cursor_col,
			     NULL);

		if (cursor_row == -1 && cursor_col == -1)
			return FALSE;

		eti->in_key_press = TRUE;

		switch (e->key.keyval) {
		case GDK_KEY_Left:
		case GDK_KEY_KP_Left:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}

			g_signal_emit (eti, eti_signals[KEY_PRESS], 0,
				       model_to_view_row (eti, cursor_row), cursor_col, e, &return_val);
			if ((!return_val) &&
			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
			   cursor_col != view_to_model_col (eti, 0))
				eti_cursor_move_left (eti);
			return_val = 1;
			break;

		case GDK_KEY_Right:
		case GDK_KEY_KP_Right:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}

			g_signal_emit (eti, eti_signals[KEY_PRESS], 0,
				       model_to_view_row (eti, cursor_row), cursor_col, e, &return_val);
			if ((!return_val) &&
			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
			   cursor_col != view_to_model_col (eti, eti->cols - 1))
				eti_cursor_move_right (eti);
			return_val = 1;
			break;

		case GDK_KEY_Up:
		case GDK_KEY_KP_Up:
		case GDK_KEY_Down:
		case GDK_KEY_KP_Down:
			if ((e->key.state & GDK_MOD1_MASK)
			    && ((e->key.keyval == GDK_KEY_Down ) || (e->key.keyval == GDK_KEY_KP_Down))) {
				gint view_col = model_to_view_col (eti, cursor_col);

				if ((view_col >= 0) && (view_col < eti->cols))
					if (eti_e_cell_event (eti, eti->cell_views[view_col], e, ((GdkEventKey *) e)->time, cursor_col, view_col, model_to_view_row (eti, cursor_row),  E_CELL_CURSOR))
						return TRUE;
			} else
			return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
			break;
		case GDK_KEY_Home:
		case GDK_KEY_KP_Home:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}

			if (eti->cursor_mode != E_CURSOR_LINE) {
				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), 0);
				return_val = TRUE;
			} else
				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
			break;
		case GDK_KEY_End:
		case GDK_KEY_KP_End:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}

			if (eti->cursor_mode != E_CURSOR_LINE) {
				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), eti->cols - 1);
				return_val = TRUE;
			} else
				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
			break;
		case GDK_KEY_Tab:
		case GDK_KEY_KP_Tab:
		case GDK_KEY_ISO_Left_Tab:
			if ((e->key.state & GDK_CONTROL_MASK) != 0) {
				return_val = FALSE;
				break;
			}
			if (eti->cursor_mode == E_CURSOR_SPREADSHEET) {
				if ((e->key.state & GDK_SHIFT_MASK) != 0) {
				/* shift tab */
					if (cursor_col != view_to_model_col (eti, 0))
						eti_cursor_move_left (eti);
					else if (cursor_row != view_to_model_row (eti, 0))
						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) - 1, eti->cols - 1);
					else
						return_val = FALSE;
				} else {
					if (cursor_col != view_to_model_col (eti, eti->cols - 1))
						eti_cursor_move_right (eti);
					else if (cursor_row != view_to_model_row (eti, eti->rows - 1))
						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) + 1, 0);
					else
						return_val = FALSE;
				}
				g_object_get (eti->selection,
					     "cursor_row", &cursor_row,
					     "cursor_col", &cursor_col,
					     NULL);

				if (cursor_col >= 0 && cursor_row >= 0 && return_val &&
				    (!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, model_to_view_row (eti, cursor_row))) {
					e_table_item_enter_edit (eti, model_to_view_col (eti, cursor_col), model_to_view_row (eti, cursor_row));
				}
				break;
			} else {
			/* Let tab send you to the next widget. */
			return_val = FALSE;
			break;
			}

		case GDK_KEY_Return:
		case GDK_KEY_KP_Enter:
		case GDK_KEY_ISO_Enter:
		case GDK_KEY_3270_Enter:
			if (eti_editing (eti)) {
				ecell_view = eti->cell_views[eti->editing_col];
				return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
							       view_to_model_col (eti, eti->editing_col),
							       eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR | E_CELL_PREEDIT);
				if (!return_val)
					break;
			}
			g_signal_emit (eti, eti_signals[KEY_PRESS], 0,
				       model_to_view_row (eti, cursor_row), cursor_col, e, &return_val);
			if (!return_val)
				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
			break;

		default:
			handled = FALSE;
			break;
		}

		if (!handled) {
			switch (e->key.keyval) {
			case GDK_KEY_Scroll_Lock:
			case GDK_KEY_Sys_Req:
			case GDK_KEY_Shift_L:
			case GDK_KEY_Shift_R:
			case GDK_KEY_Control_L:
			case GDK_KEY_Control_R:
			case GDK_KEY_Caps_Lock:
			case GDK_KEY_Shift_Lock:
			case GDK_KEY_Meta_L:
			case GDK_KEY_Meta_R:
			case GDK_KEY_Alt_L:
			case GDK_KEY_Alt_R:
			case GDK_KEY_Super_L:
			case GDK_KEY_Super_R:
			case GDK_KEY_Hyper_L:
			case GDK_KEY_Hyper_R:
			case GDK_KEY_ISO_Lock:
				break;

			default:
				if (!eti_editing (eti)) {
					gint col, row;
					row = model_to_view_row (eti, cursor_row);
					col = model_to_view_col (eti, cursor_col);
					if (col != -1 && row != -1 && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
						e_table_item_enter_edit (eti, col, row);
					}
				}
				if (!eti_editing (eti)) {
					g_signal_emit (eti, eti_signals[KEY_PRESS], 0,
						       model_to_view_row (eti, cursor_row), cursor_col, e, &return_val);
					if (!return_val)
						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
				} else {
					ecell_view = eti->cell_views[eti->editing_col];
					return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
								       view_to_model_col (eti, eti->editing_col),
								       eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
					if (!return_val)
						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) e);
				}
				break;
			}
		}
		eti->in_key_press = FALSE;
		break;
	}

	case GDK_KEY_RELEASE: {
		gint cursor_row, cursor_col;

		d(g_print("%s: GDK_KEY_RELEASE received, keyval: %d\n", __FUNCTION__, (gint) e->key.keyval));

		g_object_get (eti->selection,
			     "cursor_row", &cursor_row,
			     "cursor_col", &cursor_col,
			     NULL);

		if (cursor_col == -1)
			return FALSE;

		if (eti_editing (eti)) {
			ecell_view = eti->cell_views[eti->editing_col];
			return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
						       view_to_model_col (eti, eti->editing_col),
						       eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
		}
		break;
	}

	case GDK_LEAVE_NOTIFY:
		d (leave = TRUE);
	case GDK_ENTER_NOTIFY:
		d(g_print("%s: %s received\n", __FUNCTION__, leave ? "GDK_LEAVE_NOTIFY" : "GDK_ENTER_NOTIFY"));
		if (eti->motion_row != -1 && eti->motion_col != -1)
			return_val = eti_e_cell_event (eti, eti->cell_views[eti->motion_col],
						       e, e->crossing.time,
						       view_to_model_col (eti, eti->motion_col),
						       eti->motion_col, eti->motion_row, 0);
		eti->motion_row = -1;
		eti->motion_col = -1;

		break;

	case GDK_FOCUS_CHANGE:
		d(g_print("%s: GDK_FOCUS_CHANGE received, %s\n", __FUNCTION__, e->focus_change.in ? "in": "out"));
		if (e->focus_change.in) {
			if (eti->save_row != -1 &&
			    eti->save_col != -1 &&
			    !eti_editing (eti) &&
			    e_table_model_is_cell_editable (eti->table_model, view_to_model_col (eti, eti->save_col), eti->save_row)) {
				e_table_item_enter_edit (eti, eti->save_col, eti->save_row);
				e_cell_load_state (eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->save_col),
						   eti->save_col, eti->save_row, eti->edit_ctx, eti->save_state);
				eti_free_save_state (eti);
			}
		} else {
			if (eti_editing (eti)) {
				eti_free_save_state (eti);

				eti->save_row   = eti->editing_row;
				eti->save_col   = eti->editing_col;
				eti->save_state = e_cell_save_state (eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->editing_col),
								     eti->editing_col, eti->editing_row, eti->edit_ctx);
				e_table_item_leave_edit_(eti);
			}
		}

	default:
		return_val = FALSE;
	}
	/* d(g_print("%s: returning: %s\n", __FUNCTION__, return_val?"true":"false"));*/

	return return_val;
}

static void
eti_style_set (ETableItem *eti, GtkStyle *previous_style)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	if (eti->cell_views_realized) {
		gint i;
		gint n_cells = eti->n_cells;

		for (i = 0; i < n_cells; i++) {
			e_cell_style_set (eti->cell_views[i], previous_style);
		}
	}

	eti->needs_compute_height = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));

	free_height_cache (eti);

	eti_idle_maybe_show_cursor (eti);
}

static void
eti_class_init (ETableItemClass *class)
{
	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
	GObjectClass *object_class = G_OBJECT_CLASS (class);

	object_class->dispose       = eti_dispose;
	object_class->set_property  = eti_set_property;
	object_class->get_property  = eti_get_property;

	item_class->update          = eti_update;
	item_class->realize         = eti_realize;
	item_class->unrealize       = eti_unrealize;
	item_class->draw            = eti_draw;
	item_class->point           = eti_point;
	item_class->event           = eti_event;

	class->cursor_change    = NULL;
	class->cursor_activated = NULL;
	class->double_click     = NULL;
	class->right_click      = NULL;
	class->click            = NULL;
	class->key_press        = NULL;
	class->start_drag       = NULL;
	class->style_set        = eti_style_set;
	class->selection_model_removed = NULL;
	class->selection_model_added = NULL;

	g_object_class_install_property (object_class, PROP_TABLE_HEADER,
					 g_param_spec_object ("ETableHeader",
							      "Table header",
							      "Table header",
							      E_TYPE_TABLE_HEADER,
							      G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_TABLE_MODEL,
					 g_param_spec_object ("ETableModel",
							      "Table model",
							      "Table model",
							      E_TYPE_TABLE_MODEL,
							      G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_SELECTION_MODEL,
					 g_param_spec_object ("selection_model",
							      "Selection model",
							      "Selection model",
							      E_TYPE_SELECTION_MODEL,
							      G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_TABLE_ALTERNATING_ROW_COLORS,
					 g_param_spec_boolean ("alternating_row_colors",
							       "Alternating Row Colors",
							       "Alternating Row Colors",
							       FALSE,
							       G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_TABLE_HORIZONTAL_DRAW_GRID,
					 g_param_spec_boolean ("horizontal_draw_grid",
							       "Horizontal Draw Grid",
							       "Horizontal Draw Grid",
							       FALSE,
							       G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_TABLE_VERTICAL_DRAW_GRID,
					 g_param_spec_boolean ("vertical_draw_grid",
							       "Vertical Draw Grid",
							       "Vertical Draw Grid",
							       FALSE,
							       G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_TABLE_DRAW_FOCUS,
					 g_param_spec_boolean ("drawfocus",
							       "Draw focus",
							       "Draw focus",
							       FALSE,
							       G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_CURSOR_MODE,
					 g_param_spec_int ("cursor_mode",
							   "Cursor mode",
							   "Cursor mode",
							   E_CURSOR_LINE, E_CURSOR_SPREADSHEET, E_CURSOR_LINE,
							   G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_LENGTH_THRESHOLD,
					 g_param_spec_int ("length_threshold",
							   "Length Threshold",
							   "Length Threshold",
							   -1, G_MAXINT, 0,
							   G_PARAM_WRITABLE));

	g_object_class_install_property (object_class, PROP_MINIMUM_WIDTH,
					 g_param_spec_double ("minimum_width",
							      "Minimum width",
							      "Minimum Width",
							      0.0, G_MAXDOUBLE, 0.0,
							      G_PARAM_READWRITE));

	g_object_class_install_property (object_class, PROP_WIDTH,
					 g_param_spec_double ("width",
							      "Width",
							      "Width",
							      0.0, G_MAXDOUBLE, 0.0,
							      G_PARAM_READWRITE));
	g_object_class_install_property (object_class, PROP_HEIGHT,
					 g_param_spec_double ("height",
							      "Height",
							      "Height",
							      0.0, G_MAXDOUBLE, 0.0,
							      G_PARAM_READABLE));

	g_object_class_install_property (object_class, PROP_CURSOR_ROW,
					 g_param_spec_int ("cursor_row",
							   "Cursor row",
							   "Cursor row",
							   0, G_MAXINT, 0,
							   G_PARAM_READWRITE));

	g_object_class_install_property (object_class, PROP_UNIFORM_ROW_HEIGHT,
					 g_param_spec_boolean ("uniform_row_height",
							       "Uniform row height",
							       "Uniform row height",
							       FALSE,
							       G_PARAM_READWRITE));

	eti_signals[CURSOR_CHANGE] =
		g_signal_new ("cursor_change",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, cursor_change),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__INT,
			      G_TYPE_NONE, 1, G_TYPE_INT);

	eti_signals[CURSOR_ACTIVATED] =
		g_signal_new ("cursor_activated",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, cursor_activated),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__INT,
			      G_TYPE_NONE, 1, G_TYPE_INT);

	eti_signals[DOUBLE_CLICK] =
		g_signal_new ("double_click",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, double_click),
			      NULL, NULL,
			      e_marshal_NONE__INT_INT_BOXED,
			      G_TYPE_NONE, 3,
			      G_TYPE_INT,
			      G_TYPE_INT,
			      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

	eti_signals[START_DRAG] =
		g_signal_new ("start_drag",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, start_drag),
			      g_signal_accumulator_true_handled, NULL,
			      e_marshal_BOOLEAN__INT_INT_BOXED,
			      G_TYPE_BOOLEAN, 3,
			      G_TYPE_INT,
			      G_TYPE_INT,
			      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

	eti_signals[RIGHT_CLICK] =
		g_signal_new ("right_click",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, right_click),
			      g_signal_accumulator_true_handled, NULL,
			      e_marshal_BOOLEAN__INT_INT_BOXED,
			      G_TYPE_BOOLEAN, 3,
			      G_TYPE_INT,
			      G_TYPE_INT,
			      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

	eti_signals[CLICK] =
		g_signal_new ("click",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, click),
			      g_signal_accumulator_true_handled, NULL,
			      e_marshal_BOOLEAN__INT_INT_BOXED,
			      G_TYPE_BOOLEAN, 3,
			      G_TYPE_INT,
			      G_TYPE_INT,
			      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

	eti_signals[KEY_PRESS] =
		g_signal_new ("key_press",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, key_press),
			      g_signal_accumulator_true_handled, NULL,
			      e_marshal_BOOLEAN__INT_INT_BOXED,
			      G_TYPE_BOOLEAN, 3,
			      G_TYPE_INT,
			      G_TYPE_INT,
			      GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

	eti_signals[STYLE_SET] =
		g_signal_new ("style_set",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ETableItemClass, style_set),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__OBJECT,
			      G_TYPE_NONE, 1, GTK_TYPE_STYLE);

	eti_signals[SELECTION_MODEL_REMOVED] =
		g_signal_new ("selection_model_removed",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			      G_STRUCT_OFFSET (ETableItemClass, selection_model_removed),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__POINTER,
			      G_TYPE_NONE, 1,
			      G_TYPE_POINTER);

	eti_signals[SELECTION_MODEL_ADDED] =
		g_signal_new ("selection_model_added",
			      G_TYPE_FROM_CLASS (object_class),
			      G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
			      G_STRUCT_OFFSET (ETableItemClass, selection_model_added),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__POINTER,
			      G_TYPE_NONE, 1,
			      G_TYPE_POINTER);

	/* A11y Init */
	gal_a11y_e_table_item_init ();
}

/**
 * e_table_item_set_cursor:
 * @eti: %ETableItem which will have the cursor set.
 * @col: Column to select.  -1 means the last column.
 * @row: Row to select.  -1 means the last row.
 *
 * This routine sets the cursor of the %ETableItem canvas item.
 */
void
e_table_item_set_cursor    (ETableItem *eti, gint col, gint row)
{
	e_table_item_focus (eti, col, view_to_model_row (eti, row), 0);
}

static void
e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state)
{
	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));

	if (row == -1) {
		row = view_to_model_row (eti, eti->rows - 1);
	}

	if (col == -1) {
		col = eti->cols - 1;
	}

	if (row != -1) {
		e_selection_model_do_something (E_SELECTION_MODEL (eti->selection),
					       row, col,
					       state);
	}
}

/**
 * e_table_item_get_focused_column:
 * @eti: %ETableItem which will have the cursor retrieved.
 *
 * This routine gets the cursor of the %ETableItem canvas item.
 *
 * Returns: The current cursor column.
 */
gint
e_table_item_get_focused_column (ETableItem *eti)
{
	gint cursor_col;

	g_return_val_if_fail (eti != NULL, -1);
	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);

	g_object_get (eti->selection,
		     "cursor_col", &cursor_col,
		     NULL);

	return cursor_col;
}

static void
eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	gint view_row;

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	view_row = model_to_view_row (eti, row);

	if (eti->old_cursor_row != -1 && view_row != eti->old_cursor_row)
		e_table_item_redraw_row (eti, eti->old_cursor_row);

	if (view_row == -1) {
		e_table_item_leave_edit_(eti);
		eti->old_cursor_row = -1;
		return;
	}

	if (!e_table_model_has_change_pending (eti->table_model)) {
		if (!eti->in_key_press) {
			eti_maybe_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
		} else {
			eti_maybe_show_cursor (eti, 0);
		}
	}

	e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), FALSE);
	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);
	g_signal_emit (eti, eti_signals[CURSOR_CHANGE], 0,
		       view_row);

	e_table_item_redraw_row (eti, view_row);

	eti->old_cursor_row = view_row;
}

static void
eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	gint view_row;
	gint view_col;

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	view_row = model_to_view_row (eti, row);
	view_col = model_to_view_col (eti, col);

	if (view_row != -1 && view_col != -1) {
		if (!e_table_model_has_change_pending (eti->table_model)) {
			if (!eti->in_key_press) {
				eti_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
			} else {
				eti_show_cursor (eti, 0);
			}
			eti_check_cursor_bounds (eti);
		}
	}

	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);

	if (view_row != -1)
		g_signal_emit (eti, eti_signals[CURSOR_ACTIVATED], 0,
			       view_row);
}

static void
eti_selection_change (ESelectionModel *selection, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	eti->needs_redraw = TRUE;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);

	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
		return;

	if (!eti->needs_redraw) {
		e_table_item_redraw_row (eti, model_to_view_row (eti, row));
	}
}

/**
 * e_table_item_enter_edit
 * @eti: %ETableItem which will start being edited
 * @col: The view col to edit.
 * @row: The view row to edit.
 *
 * This routine starts the given %ETableItem editing at the given view
 * column and row.
 */
void
e_table_item_enter_edit (ETableItem *eti, gint col, gint row)
{
	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));

	d(g_print("%s: %d, %d, eti_editing() = %s\n", __FUNCTION__, col, row, eti_editing(eti)?"true":"false"));

	if (eti_editing (eti))
		e_table_item_leave_edit_(eti);

	eti->editing_col = col;
	eti->editing_row = row;

	eti->edit_ctx = e_cell_enter_edit (eti->cell_views[col], view_to_model_col (eti, col), col, row);
}

/**
 * e_table_item_leave_edit_
 * @eti: %ETableItem which will stop being edited
 *
 * This routine stops the given %ETableItem from editing.
 */
void
e_table_item_leave_edit (ETableItem *eti)
{
	gint col, row;
	gpointer edit_ctx;

	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));

	d(g_print("%s: eti_editing() = %s\n", __FUNCTION__, eti_editing(eti)?"true":"false"));

	if (!eti_editing (eti))
		return;

	col = eti->editing_col;
	row = eti->editing_row;
	edit_ctx = eti->edit_ctx;

	eti->editing_col = -1;
	eti->editing_row = -1;
	eti->edit_ctx = NULL;

	e_cell_leave_edit (eti->cell_views[col],
			   view_to_model_col (eti, col),
			   col, row, edit_ctx);
}

/**
 * e_table_item_compute_location
 * @eti: %ETableItem to look in.
 * @x: A pointer to the x location to find in the %ETableItem.
 * @y: A pointer to the y location to find in the %ETableItem.
 * @row: A pointer to the location to store the found row in.
 * @col: A pointer to the location to store the found col in.
 *
 * This routine locates the pixel location (*x, *y) in the
 * %ETableItem.  If that location is in the %ETableItem, *row and *col
 * are set to the view row and column where it was found.  If that
 * location is not in the %ETableItem, the height of the %ETableItem
 * is removed from the value y points to.
 */
void
e_table_item_compute_location (ETableItem        *eti,
			       gint               *x,
			       gint               *y,
			       gint               *row,
			       gint               *col)
{
	/* Save the grabbed row but make sure that we don't get flawed
	   results because the cursor is grabbed. */
	gint grabbed_row = eti->grabbed_row;
	eti->grabbed_row = -1;

	if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
		*y -= eti->height;
	}

	eti->grabbed_row = grabbed_row;
}

/**
 * e_table_item_compute_mouse_over:
 * Similar to e_table_item_compute_location, only here recalculating
 * the position inside the item too.
 **/
void
e_table_item_compute_mouse_over (ETableItem        *eti,
				 gint                x,
				 gint                y,
				 gint               *row,
				 gint               *col)
{
	gdouble realx, realy;
	/* Save the grabbed row but make sure that we don't get flawed
	   results because the cursor is grabbed. */
	gint grabbed_row = eti->grabbed_row;
	eti->grabbed_row = -1;

	realx = x;
	realy = y;

	gnome_canvas_item_w2i (GNOME_CANVAS_ITEM (eti), &realx, &realy);

	if (!find_cell (eti, (gint) realx, (gint) realy, col, row, NULL, NULL)) {
		*row = -1;
		*col = -1;
	}

	eti->grabbed_row = grabbed_row;
}

void
e_table_item_get_cell_geometry   (ETableItem        *eti,
				  gint               *row,
				  gint               *col,
				  gint               *x,
				  gint               *y,
				  gint               *width,
				  gint               *height)
{
	if (eti->rows > *row) {
		if (x)
			*x = e_table_header_col_diff (eti->header, 0, *col);
		if (y)
			*y = e_table_item_row_diff (eti, 0, *row);
		if (width)
			*width = e_table_header_col_diff (eti->header, *col, *col + 1);
		if (height)
			*height = ETI_ROW_HEIGHT (eti, *row);
		*row = -1;
		*col = -1;
	} else {
		*row -= eti->rows;
	}
}

typedef struct {
	ETableItem *item;
	gint rows_printed;
} ETableItemPrintContext;

static gdouble *
e_table_item_calculate_print_widths (ETableHeader *eth, gdouble width)
{
	gint i;
	gdouble extra;
	gdouble expansion;
	gint last_resizable = -1;
	gdouble scale = 1.0L;
	gdouble *widths = g_new (gdouble, e_table_header_count (eth));
	/* - 1 to account for the last pixel border. */
	extra = width - 1;
	expansion = 0;
	for (i = 0; i < eth->col_count; i++) {
		extra -= eth->columns[i]->min_width * scale;
		if (eth->columns[i]->resizable && eth->columns[i]->expansion > 0)
			last_resizable = i;
		expansion += eth->columns[i]->resizable ? eth->columns[i]->expansion : 0;
		widths[i] = eth->columns[i]->min_width * scale;
	}
	for (i = 0; i <= last_resizable; i++) {
		widths[i] += extra * (eth->columns[i]->resizable ? eth->columns[i]->expansion : 0)/expansion;
	}

	return widths;
}

static gdouble
eti_printed_row_height (ETableItem *eti, gdouble *widths, GtkPrintContext *context, gint row)
{
	gint col;
	gint cols = eti->cols;
	gdouble height = 0;
	for (col = 0; col < cols; col++) {
		ECellView *ecell_view = eti->cell_views[col];
		gdouble this_height = e_cell_print_height (ecell_view, context, view_to_model_col (eti, col), col, row,
							   widths[col] - 1);
		if (this_height > height)
			height = this_height;
	}
	return height;
}

#define CHECK(x) if((x) == -1) return -1;

static gint
gp_draw_rect (GtkPrintContext *context, gdouble x, gdouble y, gdouble width, gdouble height)
{
	cairo_t *cr;
	cr = gtk_print_context_get_cairo_context (context);
	cairo_save (cr);
	cairo_rectangle (cr, x, y, width, height);
	cairo_set_line_width (cr, 0.5);
	cairo_stroke (cr);
	cairo_restore (cr);
	return 0;
}

static void
e_table_item_print_page  (EPrintable *ep,
			  GtkPrintContext *context,
			  gdouble width,
			  gdouble height,
			  gboolean quantize,
			  ETableItemPrintContext *itemcontext)
{
	ETableItem *eti = itemcontext->item;
	const gint rows = eti->rows;
	const gint cols = eti->cols;
	gdouble max_height;
	gint rows_printed = itemcontext->rows_printed;
	gint row, col, next_page = 0;
	gdouble yd = height;
	cairo_t *cr;
	gdouble *widths;

	cr = gtk_print_context_get_cairo_context (context);
	max_height = gtk_print_context_get_height (context);
	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

	/*
	 * Draw cells
	 */

	if (eti->horizontal_draw_grid) {
		gp_draw_rect (context, 0, yd, width, 1);
	}
	yd++;

	for (row = rows_printed; row < rows; row++) {
		gdouble xd = 1, row_height;
		row_height = eti_printed_row_height (eti, widths, context, row);

		if (quantize) {
			if (yd + row_height + 1 > max_height && row != rows_printed) {
				next_page = 1;
				break;
			}
		} else {
			if (yd > max_height) {
				next_page = 1;
				break;
			}
		}

		for (col = 0; col < cols; col++) {
			ECellView *ecell_view = eti->cell_views[col];

			cairo_save (cr);
			cairo_translate (cr, xd, yd);
			cairo_rectangle (cr, 0, 0, widths[col] - 1, row_height);
			cairo_clip (cr);

			e_cell_print (ecell_view, context,
				      view_to_model_col (eti, col),
				      col,
				      row,
				      widths[col] - 1,
				      row_height + 2 );

			cairo_restore (cr);

			xd += widths[col];
		}

	yd += row_height;
		if (eti->horizontal_draw_grid) {
			gp_draw_rect (context, 0, yd, width, 1);
		}
		yd++;
	}

	itemcontext->rows_printed = row;
	if (eti->vertical_draw_grid) {
		gdouble xd = 0;
		for (col = 0; col < cols; col++) {
			gp_draw_rect (context, xd, height, 1, yd - height);
			xd += widths[col];
		}
		gp_draw_rect (context, xd, height, 1, yd - height);
	}

	if (next_page)
		cairo_show_page (cr);

	g_free (widths);
}

static gboolean
e_table_item_data_left   (EPrintable *ep,
			  ETableItemPrintContext *itemcontext)
{
	ETableItem *item = itemcontext->item;
	gint rows_printed = itemcontext->rows_printed;

	g_signal_stop_emission_by_name(ep, "data_left");
	return rows_printed < item->rows;
}

static void
e_table_item_reset       (EPrintable *ep,
			  ETableItemPrintContext *itemcontext)
{
	itemcontext->rows_printed = 0;
}

static gdouble
e_table_item_height      (EPrintable *ep,
			  GtkPrintContext *context,
			  gdouble width,
			  gdouble max_height,
			  gboolean quantize,
			  ETableItemPrintContext *itemcontext)
{
	ETableItem *item = itemcontext->item;
	const gint rows = item->rows;
	gint rows_printed = itemcontext->rows_printed;
	gdouble *widths;
	gint row;
	gdouble yd = 0;

	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

	/*
	 * Draw cells
	 */
	yd++;

	for (row = rows_printed; row < rows; row++) {
		gdouble row_height;

		row_height = eti_printed_row_height (item, widths, context, row);
		if (quantize) {
			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
				break;
			}
		} else {
			if (max_height != -1 && yd > max_height) {
				break;
			}
		}

		yd += row_height;

		yd++;
	}

	g_free (widths);

	if (max_height != -1 && (!quantize) && yd > max_height)
		yd = max_height;

	g_signal_stop_emission_by_name(ep, "height");
	return yd;
}

static gboolean
e_table_item_will_fit     (EPrintable *ep,
			   GtkPrintContext *context,
			   gdouble width,
			   gdouble max_height,
			   gboolean quantize,
			   ETableItemPrintContext *itemcontext)
{
	ETableItem *item = itemcontext->item;
	const gint rows = item->rows;
	gint rows_printed = itemcontext->rows_printed;
	gdouble *widths;
	gint row;
	gdouble yd = 0;
	gboolean ret_val = TRUE;

	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

	/*
	 * Draw cells
	 */
	yd++;

	for (row = rows_printed; row < rows; row++) {
		gdouble row_height;

		row_height = eti_printed_row_height (item, widths, context, row);
		if (quantize) {
			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
				ret_val = FALSE;
				break;
			}
		} else {
			if (max_height != -1 && yd > max_height) {
				ret_val = FALSE;
				break;
			}
		}

		yd += row_height;

		yd++;
	}

	g_free (widths);

	g_signal_stop_emission_by_name(ep, "will_fit");
	return ret_val;
}

static void
e_table_item_printable_destroy (gpointer data,
				GObject *where_object_was)
{
	ETableItemPrintContext *itemcontext = data;

	g_object_unref (itemcontext->item);
	g_free (itemcontext);
}

/**
 * e_table_item_get_printable
 * @eti: %ETableItem which will be printed
 *
 * This routine creates and returns an %EPrintable that can be used to
 * print the given %ETableItem.
 *
 * Returns: The %EPrintable.
 */
EPrintable *
e_table_item_get_printable (ETableItem *item)
{
	EPrintable *printable = e_printable_new ();
	ETableItemPrintContext *itemcontext;

	itemcontext = g_new (ETableItemPrintContext, 1);
	itemcontext->item = item;
	g_object_ref (item);
	itemcontext->rows_printed = 0;

	g_signal_connect (printable,
			  "print_page",
			  G_CALLBACK (e_table_item_print_page),
			  itemcontext);
	g_signal_connect (printable,
			  "data_left",
			  G_CALLBACK (e_table_item_data_left),
			  itemcontext);
	g_signal_connect (printable,
			  "reset",
			  G_CALLBACK (e_table_item_reset),
			  itemcontext);
	g_signal_connect (printable,
			  "height",
			  G_CALLBACK (e_table_item_height),
			  itemcontext);
	g_signal_connect (printable,
			  "will_fit",
			  G_CALLBACK (e_table_item_will_fit),
			  itemcontext);
	g_object_weak_ref (G_OBJECT (printable),
			   e_table_item_printable_destroy,
			   itemcontext);

	return printable;
}
