/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * E-table-item.c: A GnomeCanvasItem that is a view of an ETableModel.
 *
 * Author:
 *   Miguel de Icaza (miguel@gnu.org)
 *
 * Copyright 1999, Helix Code, Inc.
 *
 * TODO:
 *   Add a border to the thing, so that focusing works properly.
 *
 */
#include <config.h>
#include <stdio.h>
#include <gtk/gtksignal.h>
#include <gdk/gdkkeysyms.h>
#include <math.h>
#include "e-table-item.h"
#include "e-table-subset.h"
#include "e-cell.h"
#include "gal/widgets/e-canvas.h"
#include "gal/widgets/e-canvas-utils.h"
#include "gal/util/e-util.h"

#define PARENT_OBJECT_TYPE gnome_canvas_item_get_type ()

#define FOCUSED_BORDER 2

static GnomeCanvasItemClass *eti_parent_class;

enum {
	CURSOR_CHANGE,
	DOUBLE_CLICK,
	RIGHT_CLICK,
	CLICK,
	KEY_PRESS,
	LAST_SIGNAL
};

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

enum {
	ARG_0,
	ARG_TABLE_HEADER,
	ARG_TABLE_MODEL,
	ARG_TABLE_SELECTION_MODEL,
	ARG_TABLE_DRAW_GRID,
	ARG_TABLE_DRAW_FOCUS,
	ARG_CURSOR_MODE,
	ARG_LENGTH_THRESHOLD,
	ARG_CURSOR_ROW,
	
	ARG_MINIMUM_WIDTH,
	ARG_WIDTH,
	ARG_HEIGHT,
};

static int eti_get_height (ETableItem *eti);
static int eti_get_minimum_width (ETableItem *eti);
static int eti_row_height (ETableItem *eti, int row);
static void e_table_item_focus (ETableItem *eti, int col, int row, GdkModifierType state);
static void eti_cursor_change (ETableSelectionModel *selection, int row, int col, ETableItem *eti);
static void eti_selection_change (ETableSelectionModel *selection, ETableItem *eti);
#if 0
static void eti_request_region_show (ETableItem *eti,
				     int start_col, int start_row,
				     int end_col, int end_row);
#endif
#define ETI_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))

inline static gint
model_to_view_row(ETableItem *eti, int row)
{
	int i;
	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, int row)
{
	if (eti->uses_source_model) {
		ETableSubset *etss = E_TABLE_SUBSET(eti->table_model);
		if (row >= 0 && row < etss->n_map)
			return etss->map_table[row];
		else
			return -1;
	} else
		return row;
}

inline static gint
model_to_view_col(ETableItem *eti, int col)
{
	int i;
	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, int col)
{
	ETableCol *ecol = e_table_header_get_column (eti->header, col);
	return ecol->col_idx;
}

static gboolean
eti_editing (ETableItem *eti)
{
	if (eti->editing_col == -1)
		return FALSE;
	else
		return TRUE;
}

/*
 * 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)
{
	int i;
	
	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)
{
	int i;

	g_assert (eti->header);
	g_assert (eti->table_model);
	
	/*
	 * 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)
{
	int i;

	if (eti->cell_views_realized == 0)
		return;
	
	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)
{
	int i;
	
	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, double *x1, double *y1, double *x2, double *y2)
{
	double   i2c [6];
	ArtPoint c1, c2, i1, i2;
	ETableItem *eti = E_TABLE_ITEM (item);

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

	gnome_canvas_item_i2c_affine (GNOME_CANVAS_ITEM (eti), i2c);
	
	i1.x = eti->x1;
	i1.y = eti->y1;
	i2.x = eti->x1 + eti->width;
	i2.y = eti->y1 + eti->height;
	art_affine_point (&c1, &i1, i2c);
	art_affine_point (&c2, &i2, i2c);
	
	*x1 = c1.x;
	*y1 = c1.y;
	*x2 = c2.x + 1;
	*y2 = c2.y + 1;
}

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

	if (eti->needs_compute_height) {
		int 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) {
		int new_width = eti_get_minimum_width (eti);
		new_width = MAX(new_width, eti->minimum_width);
		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, double *affine, ArtSVP *clip_path, int flags)
{
	ArtPoint o1, o2;
	ETableItem *eti = E_TABLE_ITEM (item);

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

	o1.x = item->x1;
	o1.y = item->y1;
	o2.x = item->x2;
	o2.y = item->y2;

	eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
	if (item->x1 != o1.x ||
	    item->y1 != o1.y ||
	    item->x2 != o2.x ||
	    item->y2 != o2.y) {
		gnome_canvas_request_redraw (item->canvas, o1.x, o1.y, o2.x, o2.y);
		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;

	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_pre_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_row_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_cell_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_row_inserted_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
			       eti->table_model_row_deleted_id);
	gtk_object_unref (GTK_OBJECT (eti->table_model));
	if (eti->source_model)
		gtk_object_unref (GTK_OBJECT (eti->source_model));

	eti->table_model_pre_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_row_inserted_id = 0;
	eti->table_model_row_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_table_selection_model (ETableItem *eti)
{
	if (!eti->selection)
		return;

	gtk_signal_disconnect (GTK_OBJECT (eti->selection),
			       eti->selection_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->selection),
			       eti->cursor_change_id);
	gtk_object_unref (GTK_OBJECT (eti->selection));

	eti->selection_change_id = 0;
	eti->cursor_change_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;

	gtk_signal_disconnect (GTK_OBJECT (eti->header),
			       eti->header_structure_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->header),
			       eti->header_dim_change_id);
	gtk_signal_disconnect (GTK_OBJECT (eti->header),
			       eti->header_request_width_id);
	
	if (eti->cell_views){
		eti_unrealize_cell_views (eti);
		eti_detach_cell_views (eti);
	}
	gtk_object_unref (GTK_OBJECT (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 int
eti_row_height_real (ETableItem *eti, int row)
{
	const int cols = e_table_header_count (eti->header);
	int col;
	int h, max_h;

	g_assert (cols == 0 || eti->cell_views);
	
	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 gboolean
height_cache_idle(ETableItem *eti)
{
	int changed = 0;
	int i;
	if (!eti->height_cache) {
		eti->height_cache = g_new(int, eti->rows);
	}
	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)
{
	if (eti->height_cache)
		g_free (eti->height_cache);
	eti->height_cache = NULL;
	eti->height_cache_idle_count = 0;
	
	if (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)
{
	int i;
	free_height_cache(eti);
	eti->height_cache = g_new(int, eti->rows);
	for (i = 0; i < eti->rows; i++) {
		eti->height_cache[i] = -1;
	}
}


/*
 * eti_row_height:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static int
eti_row_height (ETableItem *eti, int row)
{
	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 int
eti_get_height (ETableItem *eti)
{
	const int rows = eti->rows;
	int row;
	int height;

	if (rows == 0)
		return 0;

	if (eti->length_threshold != -1){
		if (rows > eti->length_threshold){
			int 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 + 1) * (rows - row);
						break;
					}
					else
						height += eti->height_cache[row] + 1;
				}
			} else
				height = (eti_row_height (eti, 0) + 1) * rows;

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

	height = 1;
	for (row = 0; row < rows; row++)
		height += eti_row_height (eti, row) + 1;

	return height;
}

static int
eti_get_minimum_width (ETableItem *eti)
{
	int width = 0;
	int col;
	for (col = 0; col < eti->cols; col++){
		ETableCol *ecol = e_table_header_get_column (eti->header, col);
		
		width += ecol->min_width;
	}
	return width;
}

static void
eti_item_region_redraw (ETableItem *eti, int x0, int y0, int x1, int y1)
{
	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
	ArtDRect rect;
	double i2c [6];
	
	rect.x0 = x0;
	rect.y0 = y0;
	rect.x1 = x1;
	rect.y1 = y1;

	gnome_canvas_item_i2c_affine (item, i2c);
	art_drect_affine_transform (&rect, &rect, i2c);

	gnome_canvas_request_redraw (item->canvas, rect.x0, rect.y0, rect.x1, rect.y1);
}

/*
 * Callback routine: invoked before the ETableModel has suffers a change
 */
static void
eti_table_model_pre_change (ETableModel *table_model, ETableItem *eti)
{
	if (eti_editing (eti))
		e_table_item_leave_edit (eti);
}

/*
 * Callback routine: invoked when the ETableModel has suffered a change
 */
static void
eti_table_model_changed (ETableModel *table_model, ETableItem *eti)
{
#if 0
	int view_row;
#endif

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

	free_height_cache(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));

#if 0
	view_row = model_to_view_row(eti, eti->cursor_row);
	if (view_row >= 0 && eti->cursor_col >= 0)
		eti_request_region_show (eti, eti->cursor_col, view_row, eti->cursor_col, view_row);
#endif
}

/*
 * Computes the distance between @start_row and @end_row in pixels
 */
static int
eti_row_diff (ETableItem *eti, int start_row, int end_row)
{
	int row, total;

	total = 0;

	for (row = start_row; row < end_row; row++)
		total += eti_row_height (eti, row) + 1;

	return total;
}

/*
 * 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,
			   int start_col, int start_row,
			   int end_col, int end_row, int border)
{
	int x1, y1, width, height;

	if (eti->rows > 0) {
		
		x1 = e_table_header_col_diff (eti->header, 0, start_col);
		y1 = eti_row_diff (eti, 0, start_row);
		width = e_table_header_col_diff (eti->header, start_col, end_col + 1);
		height = eti_row_diff (eti, start_row, end_row + 1);
		
		eti_item_region_redraw (eti, eti->x1 + x1 - border,
					eti->y1 + y1 - border,
					eti->x1 + x1 + width + 1 + border,
					eti->y1 + y1 + height + 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,
			 int start_col, int start_row,
			 int end_col, int end_row)
{
	int x1, y1, x2, y2;
	
	x1 = e_table_header_col_diff (eti->header, 0, start_col);
	y1 = eti_row_diff (eti, 0, start_row);
	x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
	y2 = y1 + eti_row_diff (eti, start_row, end_row + 1);

	e_canvas_item_show_area(GNOME_CANVAS_ITEM(eti), x1, y1, x2, y2); 
}

static void
eti_table_model_row_changed (ETableModel *table_model, int row, ETableItem *eti)
{
	if (eti->renderers_can_change_size &&
	    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_request_region_redraw (eti, 0, row, eti->cols, row, 0);
}

static void
eti_table_model_cell_changed (ETableModel *table_model, int col, int row, ETableItem *eti)
{
	if (eti->renderers_can_change_size &&
	    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_request_region_redraw (eti, 0, row, eti->cols -1, row, 0);
}

static void
eti_table_model_row_inserted (ETableModel *table_model, int row, ETableItem *eti)
{
	eti->rows = e_table_model_row_count (eti->table_model);

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

	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_row_deleted (ETableModel *table_model, int row, ETableItem *eti)
{
	eti->rows = e_table_model_row_count (eti->table_model);

	if (eti->height_cache)
		memmove(eti->height_cache + row, eti->height_cache + row + 1, (eti->rows - row) * sizeof(int));

	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));
}

void
e_table_item_redraw_range (ETableItem *eti,
			   int start_col, int start_row,
			   int end_col, int end_row)
{
	int border;
	int cursor_col, cursor_row;
	
	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));
	
	gtk_object_get(GTK_OBJECT(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
eti_add_table_model (ETableItem *eti, ETableModel *table_model)
{
	g_assert (eti->table_model == NULL);
	
	eti->table_model = table_model;
	gtk_object_ref (GTK_OBJECT (eti->table_model));

	eti->table_model_pre_change_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_pre_change",
		GTK_SIGNAL_FUNC (eti_table_model_pre_change), eti);

	eti->table_model_change_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_changed",
		GTK_SIGNAL_FUNC (eti_table_model_changed), eti);

	eti->table_model_row_change_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_row_changed",
		GTK_SIGNAL_FUNC (eti_table_model_row_changed), eti);

	eti->table_model_cell_change_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_cell_changed",
		GTK_SIGNAL_FUNC (eti_table_model_cell_changed), eti);

	eti->table_model_row_inserted_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_row_inserted",
		GTK_SIGNAL_FUNC (eti_table_model_row_inserted), eti);

	eti->table_model_row_deleted_id = gtk_signal_connect (
		GTK_OBJECT (table_model), "model_row_deleted",
		GTK_SIGNAL_FUNC (eti_table_model_row_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)
			gtk_object_ref(GTK_OBJECT(eti->source_model));
	}
	
	eti_table_model_changed (table_model, eti);
}

static void
eti_add_table_selection_model (ETableItem *eti, ETableSelectionModel *selection)
{
	g_assert (eti->selection == NULL);
	
	eti->selection = selection;
	gtk_object_ref (GTK_OBJECT (eti->selection));

	eti->selection_change_id = gtk_signal_connect (
		GTK_OBJECT (selection), "selection_changed",
		GTK_SIGNAL_FUNC (eti_selection_change), eti);

	eti->cursor_change_id = gtk_signal_connect (
		GTK_OBJECT (selection), "cursor_changed",
		GTK_SIGNAL_FUNC (eti_cursor_change), eti);

	eti_selection_change(selection, eti);
}

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

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

	/*
	 * There should be at least one column
	 *  BUT: then you can't remove all columns from a header and add new ones.
	 */
	/*g_assert (eti->cols != 0);*/

	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_detach_cell_views (eti);
			eti_attach_cell_views (eti);
		}
	}
	eti->needs_compute_width = 1;
	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
}

static int
eti_request_column_width (ETableHeader *eth, int col, ETableItem *eti)
{
	int width = 0;
	
	if (eti->cell_views) {
		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_assert (eti->header == NULL);
	
	eti->header = header;
	gtk_object_ref (GTK_OBJECT (header));

	eti_header_structure_changed (header, eti);
	
	eti->header_dim_change_id = gtk_signal_connect (
		GTK_OBJECT (header), "dimension_change",
		GTK_SIGNAL_FUNC (eti_header_dim_changed), eti);

	eti->header_structure_change_id = gtk_signal_connect (
		GTK_OBJECT (header), "structure_change",
		GTK_SIGNAL_FUNC (eti_header_structure_changed), eti);

	eti->header_request_width_id = gtk_signal_connect 
		(GTK_OBJECT (header), "request_width",
		 GTK_SIGNAL_FUNC (eti_request_column_width), eti);
}

/*
 * GtkObject::destroy method
 */
static void
eti_destroy (GtkObject *object)
{
	ETableItem *eti = E_TABLE_ITEM (object);

	eti_remove_header_model (eti);
	eti_remove_table_model (eti);
	eti_remove_table_selection_model (eti);

	if (eti->height_cache_idle_id)
		g_source_remove(eti->height_cache_idle_id);

	g_free (eti->height_cache);

	if (eti->tooltip->window)
		gtk_widget_destroy (eti->tooltip->window);
	if (eti->tooltip->timer) {
		gtk_timeout_remove (eti->tooltip->timer);
		eti->tooltip->timer = 0;
	}
	g_free (eti->tooltip);

	if (GTK_OBJECT_CLASS (eti_parent_class)->destroy)
		(*GTK_OBJECT_CLASS (eti_parent_class)->destroy) (object);
}

static void
eti_set_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
	GnomeCanvasItem *item;
	ETableItem *eti;
	int cursor_col;

	item = GNOME_CANVAS_ITEM (o);
	eti = E_TABLE_ITEM (o);

	switch (arg_id){
	case ARG_TABLE_HEADER:
		eti_remove_header_model (eti);
		eti_add_header_model (eti, E_TABLE_HEADER(GTK_VALUE_OBJECT (*arg)));
		break;

	case ARG_TABLE_MODEL:
		eti_remove_table_model (eti);
		eti_add_table_model (eti, E_TABLE_MODEL(GTK_VALUE_OBJECT (*arg)));
		break;
		
	case ARG_TABLE_SELECTION_MODEL:
		eti_remove_table_selection_model (eti);
		if (GTK_VALUE_OBJECT (*arg))
			eti_add_table_selection_model (eti, E_TABLE_SELECTION_MODEL(GTK_VALUE_OBJECT (*arg)));
		break;
		
	case ARG_LENGTH_THRESHOLD:
		eti->length_threshold = GTK_VALUE_INT (*arg);
		break;

	case ARG_TABLE_DRAW_GRID:
		eti->draw_grid = GTK_VALUE_BOOL (*arg);
		break;

	case ARG_TABLE_DRAW_FOCUS:
		eti->draw_focus = GTK_VALUE_BOOL (*arg);
		break;

	case ARG_CURSOR_MODE:
		eti->cursor_mode = GTK_VALUE_INT (*arg);
		break;

	case ARG_MINIMUM_WIDTH:
	case ARG_WIDTH:
		if (eti->minimum_width == eti->width && GTK_VALUE_DOUBLE (*arg) > eti->width)
			e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
		eti->minimum_width = GTK_VALUE_DOUBLE (*arg);
		if (eti->minimum_width < eti->width)
			e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
		break;
	case ARG_CURSOR_ROW:
		gtk_object_get(GTK_OBJECT(eti->selection),
			       "cursor_col", &cursor_col,
			       NULL);

		e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row(eti, GTK_VALUE_INT (*arg)), 0);
		break;
	}
	eti->needs_redraw = 1;
	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(eti));
}

static void
eti_get_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
	GnomeCanvasItem *item;
	ETableItem *eti;
	int row;

	item = GNOME_CANVAS_ITEM (o);
	eti = E_TABLE_ITEM (o);

	switch (arg_id){
	case ARG_WIDTH:
		GTK_VALUE_DOUBLE (*arg) = eti->width;
		break;
	case ARG_HEIGHT:
		GTK_VALUE_DOUBLE (*arg) = eti->height;
		break;
	case ARG_MINIMUM_WIDTH:
		GTK_VALUE_DOUBLE (*arg) = eti->minimum_width;
		break;
	case ARG_CURSOR_ROW:
		gtk_object_get(GTK_OBJECT(eti->selection),
			       "cursor_row", &row,
			       NULL);
		GTK_VALUE_INT (*arg) = model_to_view_row(eti, row);
		break;
	default:
		arg->type = GTK_TYPE_INVALID;
	}
}

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

	eti->editing_col = -1;
	eti->editing_row = -1;
	eti->height = 0;
	eti->width = 0;
	eti->minimum_width = 0;

	eti->height_cache = NULL;
	eti->height_cache_idle_id = 0;
	eti->height_cache_idle_count = 0;
	
	eti->length_threshold = -1;
	eti->renderers_can_change_size = 1;

	eti->uses_source_model = 0;
	eti->source_model = NULL;
	
	eti->row_guess = -1;
	eti->cursor_mode = E_TABLE_CURSOR_SIMPLE;

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

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

	eti->tooltip = g_new0 (ETableTooltip, 1);
	eti->tooltip->timer = 0;
	eti->tooltip->window = NULL;
	eti->tooltip->eti = GNOME_CANVAS_ITEM (eti);

	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
}

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

static void
eti_realize (GnomeCanvasItem *item)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	GtkWidget *canvas_widget = GTK_WIDGET (item->canvas);
	GdkWindow *window;
	
	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)
                (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)(item);

	/*
	 * Gdk Resource allocation
	 */
	window = canvas_widget->window;

	eti->fill_gc = gdk_gc_new (window);

	eti->grid_gc = gdk_gc_new (window);
#if 0
	/* This sets it to gray */
/*	gdk_gc_set_foreground (eti->grid_gc, &canvas_widget->style->bg [GTK_STATE_NORMAL]); */
#else
	gdk_gc_set_foreground (eti->grid_gc, &canvas_widget->style->dark [GTK_STATE_NORMAL]);
#endif
	eti->focus_gc = gdk_gc_new (window);
	gdk_gc_set_foreground (eti->focus_gc, &canvas_widget->style->bg [GTK_STATE_NORMAL]);
	gdk_gc_set_background (eti->focus_gc, &canvas_widget->style->fg [GTK_STATE_NORMAL]);
	eti->stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
	gdk_gc_set_ts_origin (eti->focus_gc, 0, 0);
	gdk_gc_set_stipple (eti->focus_gc, eti->stipple);
	gdk_gc_set_fill (eti->focus_gc, GDK_OPAQUE_STIPPLED);

	if (eti->cell_views == NULL)
		eti_attach_cell_views (eti);
	
	eti_realize_cell_views (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_unrealize (GnomeCanvasItem *item)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	
	if (eti_editing (eti))
		e_table_item_leave_edit (eti);

	gdk_gc_unref (eti->fill_gc);
	eti->fill_gc = NULL;
	gdk_gc_unref (eti->grid_gc);
	eti->grid_gc = NULL;
	gdk_gc_unref (eti->focus_gc);
	eti->focus_gc = NULL;
	gdk_bitmap_unref (eti->stipple);
	eti->stipple = NULL;
	
	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 (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	const int rows = eti->rows;
	const int cols = eti->cols;
	int row, col, y1, y2;
	int first_col, last_col, x_offset;
	int first_row, last_row, y_offset, yd;
	int x1, x2;
	int f_x1, f_x2, f_y1, f_y2;
	gboolean f_found;
	double i2c [6];
	ArtPoint eti_base, eti_base_item;
	GtkWidget *canvas = GTK_WIDGET(item->canvas);
	GdkColor *background;
	
	/*
	 * Clear the background
	 */
#if 0
	gdk_draw_rectangle (
		drawable, eti->fill_gc, TRUE,
		eti->x1 - x, eti->y1 - y, eti->width, eti->height);
#endif

	/*
	 * Find out our real position after grouping
	 */
	gnome_canvas_item_i2c_affine (item, i2c);
	eti_base_item.x = eti->x1;
	eti_base_item.y = eti->y1;
	art_affine_point (&eti_base, &eti_base_item, i2c);

	/*
	 * First column to draw, last column to draw
	 */
	first_col = -1;
	last_col = x_offset = 0;
	x1 = x2 = 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.
	 */
	first_row = -1;
	y_offset = 0;
	y1 = y2 = floor (eti_base.y) + 1;
	for (row = 0; row < rows; row++, y1 = y2){

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

		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;

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

	if (eti->draw_grid && first_row == 0){
		gdk_draw_line (
			drawable, eti->grid_gc,
				eti_base.x - x, yd, eti_base.x + eti->width - x, yd);
	}
	yd++;
	
	for (row = first_row; row < last_row; row++){
		int xd, height;
		gboolean selected;
		gint cursor_col, cursor_row;
		
		height = ETI_ROW_HEIGHT (eti, row);

		xd = x_offset;
/*		printf ("paint: %d %d\n", yd, yd + height); */
		
		selected = e_table_selection_model_is_row_selected(eti->selection, view_to_model_row(eti,row));
		
		gtk_object_get(GTK_OBJECT(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;
			ECellFlags flags;
			switch (eti->cursor_mode) {
			case E_TABLE_CURSOR_SIMPLE:
				if (cursor_col == ecol->col_idx && cursor_row == view_to_model_row(eti, row))
					col_selected = !col_selected;
				break;
			case E_TABLE_CURSOR_LINE:
				/* Nothing */
				break;
			}

			if (col_selected){
				if (GTK_WIDGET_HAS_FOCUS(canvas))
					background = &canvas->style->bg [GTK_STATE_SELECTED];
				else
					background = &canvas->style->bg [GTK_STATE_ACTIVE];
			} else {
#if 0
				if (row % 2)
#endif
					background = &canvas->style->base [GTK_STATE_NORMAL];
#if 0
				else
					background = &canvas->style->base [GTK_STATE_SELECTED];
#endif
			}

			gdk_gc_set_foreground (eti->fill_gc, background);
			gdk_draw_rectangle (drawable, eti->fill_gc, TRUE,
					    xd, yd, ecol->width, height);

			flags = col_selected ? E_CELL_SELECTED : 0;
			flags |= GTK_WIDGET_HAS_FOCUS(canvas) ? E_CELL_FOCUSED : 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, drawable, ecol->col_idx, col, row, flags,
				     xd, yd, xd + ecol->width, yd + height);
			
			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;
			}

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

		if (eti->draw_grid)
			gdk_draw_line (
				drawable, eti->grid_gc,
				eti_base.x - x, yd, eti_base.x + eti->width - x, yd);
		yd++;
	}

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

			gdk_draw_line (
				drawable, eti->grid_gc,
				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 (f_found && eti->draw_focus){
		gdk_draw_rectangle (drawable, eti->focus_gc, FALSE,
				    f_x1 + 1, f_y1, f_x2 - f_x1 - 2, f_y2 - f_y1 - 1);
	}
}

static double
eti_point (GnomeCanvasItem *item, double x, double y, int cx, int cy,
	   GnomeCanvasItem **actual_item)
{
	*actual_item = item;

	return 0.0;
}

static gboolean
find_cell (ETableItem *eti, double x, double y, int *col_res, int *row_res, double *x1_res, double *y1_res)
{
	const int cols = eti->cols;
	const int rows = eti->rows;
	gdouble x1, y1, x2, y2;
	int col, row;
	
	/* FIXME: this routine is inneficient, fix later */
	
	if (cols == 0 || rows == 0)
		return FALSE;

	x -= eti->x1;
	y -= eti->y1;
	
	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;
	}

	y1 = y2 = 0;
	for (row = 0; row < rows - 1; row++, y1 = y2){
		if (y < y1)
			return FALSE;
		
		y2 += ETI_ROW_HEIGHT (eti, row) + 1;

		if (y <= y2)
			break;
	}
	*col_res = col;
	if (x1_res)
		*x1_res = x - x1;
	*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)
{
	int cursor_col, cursor_row;
	gtk_object_get(GTK_OBJECT(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)
{
	int cursor_col, cursor_row;
	gtk_object_get(GTK_OBJECT(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_up (ETableItem *eti)
{
	int cursor_col, cursor_row;
	gtk_object_get(GTK_OBJECT(eti->selection),
		       "cursor_col", &cursor_col,
		       "cursor_row", &cursor_row,
		       NULL);

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

static void
eti_cursor_move_down (ETableItem *eti)
{
	int cursor_col, cursor_row;
	gtk_object_get(GTK_OBJECT(eti->selection),
		       "cursor_col", &cursor_col,
		       "cursor_row", &cursor_row,
		       NULL);

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

static int
_do_tooltip (ETableItem *eti)
{
	ECellView *ecell_view;
	int x = 0, y = 0;
	int i;

	if (eti_editing (eti))
		return FALSE;

	ecell_view = eti->cell_views[eti->tooltip->col];

	for (i = 0; i < eti->tooltip->col; i++)
		x += eti->header->columns[i]->width;
	eti->tooltip->x = x;

	for (i = 0; i < eti->tooltip->row; i++)
		y += (ETI_ROW_HEIGHT (eti, i) + 1);
	eti->tooltip->y = y;
	eti->tooltip->row_height = ETI_ROW_HEIGHT (eti, i);
	
	e_cell_show_tooltip (ecell_view, 
			     view_to_model_col (eti, eti->tooltip->col),
			     eti->tooltip->col,
			     eti->tooltip->row,
			     eti->tooltip);
	return FALSE;
}

/* FIXME: cursor */
static int
eti_event (GnomeCanvasItem *item, GdkEvent *e)
{
	ETableItem *eti = E_TABLE_ITEM (item);
	ECellView *ecell_view;
	gint return_val = TRUE;

	switch (e->type){
	case GDK_BUTTON_PRESS: {
		double x1, y1;
		int col, row;
		gint cursor_row, cursor_col;

		if (eti->tooltip->timer) {
			gtk_timeout_remove (eti->tooltip->timer);
			eti->tooltip->timer = 0;
		}
		e_canvas_item_grab_focus(GNOME_CANVAS_ITEM(eti));

		switch (e->button.button) {
		case 1: /* Fall through. */
		case 2:
			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;

			return_val = FALSE;

			gtk_signal_emit (GTK_OBJECT (eti), eti_signals [CLICK],
					 row, view_to_model_col(eti, col), e, &return_val);

			if (return_val)
				return TRUE;

			e_table_selection_model_do_something(eti->selection, view_to_model_row(eti, row), view_to_model_col(eti, col), e->button.state);

			gtk_object_get(GTK_OBJECT(eti->selection),
				       "cursor_row", &cursor_row,
				       "cursor_col", &cursor_col,
				       NULL);
			
			if (cursor_row == view_to_model_row(eti, row) && cursor_col == view_to_model_col(eti, col)){

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

				ecell_view = eti->cell_views [col];
				
				/*
				 * Adjust the event positions
				 */
				e->button.x = x1;
				e->button.y = y1;
				
				e_cell_event (ecell_view, e, view_to_model_col(eti, col), col, row);
			}

			break;
		case 3:
			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_table_selection_model_maybe_do_something(eti->selection, view_to_model_row(eti, row), view_to_model_col(eti, col), 0);
			
			gtk_signal_emit (GTK_OBJECT (eti), eti_signals [RIGHT_CLICK],
					 row, view_to_model_col(eti, col), e, &return_val);
			break;
		case 4:
		case 5:
			return FALSE;
			break;
			
		}
		break;
	}

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

		if (eti->tooltip->timer) {
			gtk_timeout_remove (eti->tooltip->timer);
			eti->tooltip->timer = 0;
		}
		if (eti->tooltip->window) {
			gtk_widget_destroy (eti->tooltip->window);
			eti->tooltip->window = NULL;
		}
		switch (e->button.button) {
		case 1: /* Fall through. */
		case 2:
			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;

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

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

				ecell_view = eti->cell_views [col];

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

				e_cell_event (ecell_view, e, view_to_model_col(eti, col), col, row);
			}
			break;
		case 3:
		case 4:
		case 5:
			return FALSE;
			break;
			
		}
		break;
	}

	case GDK_2BUTTON_PRESS: {
		double x1, y1;
		int col, row;

		if (e->button.button == 5 ||
		    e->button.button == 4)
			return FALSE;

		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;

		gtk_signal_emit (GTK_OBJECT (eti), eti_signals [DOUBLE_CLICK],
				 row);
		break;
	}
	case GDK_MOTION_NOTIFY: {
		int col, row;
		double x1, y1;
		gint cursor_col, cursor_row;

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

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

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

		if (eti->tooltip->timer > 0)
			gtk_timeout_remove (eti->tooltip->timer);
		eti->tooltip->col = col;
		eti->tooltip->row = row;
		eti->tooltip->cx = e->motion.x;
		eti->tooltip->cy = e->motion.y;
		eti->tooltip->timer = gtk_timeout_add (1000, (GSourceFunc)_do_tooltip, eti);

		if (cursor_row == view_to_model_row(eti, row) && cursor_col == view_to_model_col(eti, col)){
			ecell_view = eti->cell_views [col];

			/*
			 * Adjust the event positions
			 */
			e->motion.x = x1;
			e->motion.y = y1;
			
			e_cell_event (ecell_view, e, view_to_model_col(eti, col), col, row);
		}
		break;
	}
		
	case GDK_KEY_PRESS: {
		gint cursor_row, cursor_col;
		gint handled = TRUE;
		gtk_object_get(GTK_OBJECT(eti->selection),
			       "cursor_row", &cursor_row,
			       "cursor_col", &cursor_col,
			       NULL);

		if (cursor_col == -1)
			return FALSE;

		switch (e->key.keyval){
		case GDK_Left:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}
			
			if (cursor_col != view_to_model_col(eti, 0))
				eti_cursor_move_left (eti);
			break;
			
		case GDK_Right:
			if (eti_editing (eti)) {
				handled = FALSE;
				break;
			}

			if (cursor_col != view_to_model_col(eti, eti->cols - 1))
				eti_cursor_move_right (eti);
			break;
			
		case GDK_Up:
			if (cursor_row != view_to_model_row(eti, 0))
				eti_cursor_move_up (eti);
			else
				return_val = FALSE;
			break;
			
		case GDK_Down:
			if (cursor_row != view_to_model_row(eti, eti->rows - 1))
				eti_cursor_move_down (eti);
			else
				return_val = FALSE;
			break;

		case GDK_Tab:
		case GDK_KP_Tab:
		case GDK_ISO_Left_Tab:
			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;
			}
			gtk_object_get(GTK_OBJECT(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->selection->model, cursor_col, cursor_row)) {
				e_table_item_enter_edit (eti, model_to_view_col(eti, cursor_col), model_to_view_row(eti, cursor_row));
			}
			break;

		case GDK_Return:
		case GDK_KP_Enter:
		case GDK_ISO_Enter:
		case GDK_3270_Enter:
			if (eti_editing (eti)){
				e_table_item_leave_edit (eti);
#if 0
				ecell_view = eti->cell_views [eti->editing_col];
				e_cell_event (ecell_view, e, view_to_model_col(eti, eti->editing_col), eti->editing_col, eti->editing_row);
#endif
			}
			return_val = FALSE;
			gtk_signal_emit (GTK_OBJECT (eti), eti_signals [KEY_PRESS],
					 model_to_view_row(eti, cursor_row), cursor_col, e, &return_val);
			break;
			
		default:
			handled = FALSE;
			break;
		}
		if (!handled) {
			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->selection->model, cursor_col, cursor_row)) {
					e_table_item_enter_edit (eti, col, row);
				}
			}
			if (!eti_editing (eti)){
				gtk_signal_emit (GTK_OBJECT (eti), eti_signals [KEY_PRESS],
						 model_to_view_row(eti, cursor_row), cursor_col, e, &return_val);
			} else {
				ecell_view = eti->cell_views [eti->editing_col];
				e_cell_event (ecell_view, e, view_to_model_col(eti, eti->editing_col), eti->editing_col, eti->editing_row);
			}
		}
		break;
	}
	
	case GDK_KEY_RELEASE: {
		gint cursor_row, cursor_col;

		gtk_object_get(GTK_OBJECT(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];
			e_cell_event (ecell_view, e, view_to_model_col(eti, eti->editing_col), eti->editing_col, eti->editing_row);
		}
		break;
	}

	case GDK_LEAVE_NOTIFY:
	case GDK_ENTER_NOTIFY:
		if (eti->tooltip->timer > 0)
			gtk_timeout_remove (eti->tooltip->timer);
		eti->tooltip->timer = 0;
		break;

	default:
		return_val = FALSE;
	}
	return return_val;
}

static void
eti_class_init (GtkObjectClass *object_class)
{
	GnomeCanvasItemClass *item_class = (GnomeCanvasItemClass *) object_class;
	ETableItemClass *eti_class = (ETableItemClass *) object_class;
	
	eti_parent_class = gtk_type_class (PARENT_OBJECT_TYPE);
	
	object_class->destroy = eti_destroy;
	object_class->set_arg = eti_set_arg;
	object_class->get_arg = eti_get_arg;

	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;
	
	eti_class->cursor_change = NULL;
	eti_class->double_click  = NULL;
	eti_class->right_click   = NULL;
	eti_class->click         = NULL;
	eti_class->key_press     = NULL;

	gtk_object_add_arg_type ("ETableItem::ETableHeader", GTK_TYPE_OBJECT,
				 GTK_ARG_WRITABLE, ARG_TABLE_HEADER);
	gtk_object_add_arg_type ("ETableItem::ETableModel", GTK_TYPE_OBJECT,
				 GTK_ARG_WRITABLE, ARG_TABLE_MODEL);
	gtk_object_add_arg_type ("ETableItem::table_selection_model", GTK_TYPE_OBJECT,
				 GTK_ARG_WRITABLE, ARG_TABLE_SELECTION_MODEL);
	gtk_object_add_arg_type ("ETableItem::drawgrid", GTK_TYPE_BOOL,
				 GTK_ARG_WRITABLE, ARG_TABLE_DRAW_GRID);
	gtk_object_add_arg_type ("ETableItem::drawfocus", GTK_TYPE_BOOL,
				 GTK_ARG_WRITABLE, ARG_TABLE_DRAW_FOCUS);
	gtk_object_add_arg_type ("ETableItem::cursor_mode", GTK_TYPE_INT,
				 GTK_ARG_WRITABLE, ARG_CURSOR_MODE);
	gtk_object_add_arg_type ("ETableItem::length_threshold", GTK_TYPE_INT,
				 GTK_ARG_WRITABLE, ARG_LENGTH_THRESHOLD);

	gtk_object_add_arg_type ("ETableItem::minimum_width", GTK_TYPE_DOUBLE, 
				 GTK_ARG_READWRITE, ARG_MINIMUM_WIDTH); 
	gtk_object_add_arg_type ("ETableItem::width", GTK_TYPE_DOUBLE, 
				 GTK_ARG_READWRITE, ARG_WIDTH); 
	gtk_object_add_arg_type ("ETableItem::height", GTK_TYPE_DOUBLE, 
				 GTK_ARG_READABLE, ARG_HEIGHT);
	gtk_object_add_arg_type ("ETableItem::cursor_row", GTK_TYPE_INT,
				 GTK_ARG_READWRITE, ARG_CURSOR_ROW);

	eti_signals [CURSOR_CHANGE] =
		gtk_signal_new ("cursor_change",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableItemClass, cursor_change),
				gtk_marshal_NONE__INT,
				GTK_TYPE_NONE, 1, GTK_TYPE_INT);

	eti_signals [DOUBLE_CLICK] =
		gtk_signal_new ("double_click",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableItemClass, double_click),
				gtk_marshal_NONE__INT,
				GTK_TYPE_NONE, 1, GTK_TYPE_INT);

	eti_signals [RIGHT_CLICK] =
		gtk_signal_new ("right_click",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableItemClass, right_click),
				e_marshal_INT__INT_INT_POINTER,
				GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

	eti_signals [CLICK] =
		gtk_signal_new ("click",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableItemClass, click),
				e_marshal_INT__INT_INT_POINTER,
				GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

	eti_signals [KEY_PRESS] =
		gtk_signal_new ("key_press",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (ETableItemClass, key_press),
				e_marshal_INT__INT_INT_POINTER,
				GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

	gtk_object_class_add_signals (object_class, eti_signals, LAST_SIGNAL);

}

GtkType
e_table_item_get_type (void)
{
	static GtkType type = 0;

	if (!type){
		GtkTypeInfo info = {
			"ETableItem",
			sizeof (ETableItem),
			sizeof (ETableItemClass),
			(GtkClassInitFunc) eti_class_init,
			(GtkObjectInitFunc) eti_init,
			NULL, /* reserved 1 */
			NULL, /* reserved 2 */
			(GtkClassInitFunc) NULL
		};

		type = gtk_type_unique (PARENT_OBJECT_TYPE, &info);
	}

	return type;
}

void
e_table_item_set_cursor    (ETableItem *eti, int col, int row)
{
	e_table_item_focus(eti, col, view_to_model_row(eti, row), 0);
}

static void
e_table_item_focus (ETableItem *eti, int col, int 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_table_selection_model_do_something(eti->selection,
						     row, col,
						     state);
	}
}

gint
e_table_item_get_focused_column (ETableItem *eti)
{	
	int cursor_col;

	g_return_val_if_fail (eti != NULL, -1);
	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);
	
	gtk_object_get(GTK_OBJECT(eti->selection),
		       "cursor_col", &cursor_col,
		       NULL);

	return cursor_col;
}

gboolean
e_table_item_is_row_selected (ETableItem *eti, int row)
{
	g_return_val_if_fail (eti != NULL, FALSE);
	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), FALSE);

	return e_table_selection_model_is_row_selected(eti->selection, row);
}

static void
eti_cursor_change (ETableSelectionModel *selection, int row, int col, ETableItem *eti)
{
	int view_row = model_to_view_row(eti, row);
	int view_col = model_to_view_col(eti, col);

	if (view_row == -1 || view_col == -1) {
		e_table_item_leave_edit (eti);
		return;
	}

	eti_request_region_show (eti, view_col, view_row, view_col, view_row);
	e_canvas_item_grab_focus(GNOME_CANVAS_ITEM(eti));
	if (eti_editing(eti))
		e_table_item_leave_edit (eti);
	gtk_signal_emit (GTK_OBJECT (eti), eti_signals [CURSOR_CHANGE],
			 view_row);
	eti->needs_redraw = TRUE;
	gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(eti));
}

static void
eti_selection_change (ETableSelectionModel *selection, ETableItem *eti)
{
	eti->needs_redraw = TRUE;
	gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(eti));
}

void
e_table_item_enter_edit (ETableItem *eti, int col, int row)
{
	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));
	
	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);
}

void
e_table_item_leave_edit (ETableItem *eti)
{
	int col, row;
	void *edit_ctx;
	
	g_return_if_fail (eti != NULL);
	g_return_if_fail (E_IS_TABLE_ITEM (eti));

	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);
}

void 
e_table_item_compute_location (ETableItem        *eti,
			       int               *x,
			       int               *y,
			       int               *row,
			       int               *col)
{
	if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
		*y -= eti_get_height(eti);
	}
	
}

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

static gdouble *
e_table_item_calculate_print_widths (ETableHeader *eth, gdouble width)
{
	int i;
	double extra;
	double expansion;
	int last_resizable = -1;
	gdouble scale = 300.0L / 70.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]->resizeable && eth->columns[i]->expansion > 0)
			last_resizable = i;
		expansion += eth->columns[i]->resizeable ? 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]->resizeable ? eth->columns[i]->expansion : 0)/expansion;
	}

	return widths;
}

static gdouble
eti_printed_row_height (ETableItem *eti, gdouble *widths, GnomePrintContext *context, gint row)
{
	int col;
	int 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 (GnomePrintContext *context, gdouble x, gdouble y, gdouble width, gdouble height)
{
	CHECK(gnome_print_moveto(context, x, y));
	CHECK(gnome_print_lineto(context, x + width, y));
	CHECK(gnome_print_lineto(context, x + width, y - height));
	CHECK(gnome_print_lineto(context, x, y - height));
	CHECK(gnome_print_lineto(context, x, y));
	return gnome_print_fill(context);
}

static void
e_table_item_print_page  (EPrintable *ep,
			  GnomePrintContext *context,
			  gdouble width,
			  gdouble height,
			  gboolean quantize,
			  ETableItemPrintContext *itemcontext)
{
	ETableItem *eti = itemcontext->item;
	const int rows = eti->rows;
	const int cols = eti->cols;
	int rows_printed = itemcontext->rows_printed;
	gdouble *widths;
	int row, col;
	gdouble yd = height;
	
	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

	/*
	 * Draw cells
	 */
	if (eti->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 < 0 && row != rows_printed) {
				break;
			}
		} else {
			if (yd < 0) {
				break;
			}
		}

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

			if (gnome_print_gsave(context) == -1)
				/* FIXME */;
			if (gnome_print_translate(context, xd, yd - row_height) == -1)
				/* FIXME */;

			if (gnome_print_moveto(context, 0, 0) == -1)
				/* FIXME */;
			if (gnome_print_lineto(context, widths[col] - 1, 0) == -1)
				/* FIXME */;
			if (gnome_print_lineto(context, widths[col] - 1, row_height) == -1)
				/* FIXME */;
			if (gnome_print_lineto(context, 0, row_height) == -1)
				/* FIXME */;
			if (gnome_print_lineto(context, 0, 0) == -1)
				/* FIXME */;
			if (gnome_print_clip(context) == -1)
				/* FIXME */;

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

			if (gnome_print_grestore(context) == -1)
				/* FIXME */;
			
			xd += widths[col];
		}
		yd -= row_height;

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

	itemcontext->rows_printed = row;

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

	g_free (widths);
}

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

	gtk_signal_emit_stop_by_name(GTK_OBJECT(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,
			  GnomePrintContext *context,
			  gdouble width,
			  gdouble max_height,
			  gboolean quantize,
			  ETableItemPrintContext *itemcontext)
{
	ETableItem *item = itemcontext->item;
	const int rows = item->rows;
	int rows_printed = itemcontext->rows_printed;
	gdouble *widths;
	int 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;

	gtk_signal_emit_stop_by_name(GTK_OBJECT(ep), "height");
	return yd;
}

static gboolean
e_table_item_will_fit     (EPrintable *ep,
			   GnomePrintContext *context,
			   gdouble width,
			   gdouble max_height,
			   gboolean quantize,
			   ETableItemPrintContext *itemcontext)
{
	ETableItem *item = itemcontext->item;
	const int rows = item->rows;
	int rows_printed = itemcontext->rows_printed;
	gdouble *widths;
	int 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);

	gtk_signal_emit_stop_by_name(GTK_OBJECT(ep), "will_fit");
	return ret_val;
}

static void
e_table_item_printable_destroy (GtkObject *object,
				ETableItemPrintContext *itemcontext)
{
	gtk_object_unref(GTK_OBJECT(itemcontext->item));
	g_free(itemcontext);
}

EPrintable *
e_table_item_get_printable (ETableItem *item)
{
	EPrintable *printable = e_printable_new();
	ETableItemPrintContext *itemcontext;

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

	gtk_signal_connect (GTK_OBJECT(printable),
			    "print_page",
			    GTK_SIGNAL_FUNC(e_table_item_print_page),
			    itemcontext);
	gtk_signal_connect (GTK_OBJECT(printable),
			    "data_left",
			    GTK_SIGNAL_FUNC(e_table_item_data_left),
			    itemcontext);
	gtk_signal_connect (GTK_OBJECT(printable),
			    "reset",
			    GTK_SIGNAL_FUNC(e_table_item_reset),
			    itemcontext);
	gtk_signal_connect (GTK_OBJECT(printable),
			    "height",
			    GTK_SIGNAL_FUNC(e_table_item_height),
			    itemcontext);
	gtk_signal_connect (GTK_OBJECT(printable),
			    "will_fit",
			    GTK_SIGNAL_FUNC(e_table_item_will_fit),
			    itemcontext);
	gtk_signal_connect (GTK_OBJECT(printable),
			    "destroy",
			    GTK_SIGNAL_FUNC(e_table_item_printable_destroy),
			    itemcontext);

	return printable;
}