/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 1998, 1999, 2000 Free Software Foundation
 * All rights reserved.
 *
 * This file is part of the Gnome Library.
 *
 * The Gnome Library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Gnome Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/*
  @NOTATION@
 */

/*
 * GnomeIconList widget - scrollable icon list
 *
 * Authors:
 *    Federico Mena <federico@ximian.com>
 *    Miguel de Icaza <miguel@ximian.com>
 *
 * Rewrote from scratch from the code written by Federico Mena
 * <federico@ximian.com> to be based on a GnomeCanvas, and
 * to support banding selection and allow inline icon renaming.
 *
 * Redone somewhat by Elliot to support gdk-pixbuf, and to use GArray instead of
 * GList for item storage.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkobject.h>
#include <gtk/gtksignal.h>
#include <gtk/gtkwidget.h>
#include <libgnomeui/gnome-icon-item.h>
#include <libgnomeui/gnome-canvas-rect-ellipse.h>
#include <gdk-pixbuf/gnome-canvas-pixbuf.h>
#include "e-icon-list.h"

#include "bad-icon.xpm"

/* Aliases to minimize screen use in my laptop */
#define EIL(x)       E_ICON_LIST(x)
#define EIL_CLASS(x) E_ICON_LIST_CLASS(x)
#define IS_EIL(x)    E_IS_ICON_LIST(x)

typedef EIconList Eil;
typedef EIconListClass EilClass;


/* default spacings */
#define DEFAULT_ROW_SPACING  4
#define DEFAULT_COL_SPACING  2
#define DEFAULT_TEXT_SPACING 2
#define DEFAULT_ICON_BORDER  2

/* Autoscroll timeout in milliseconds */
#define SCROLL_TIMEOUT 30


/* Signals */
enum {
	SELECT_ICON,
	UNSELECT_ICON,
	TEXT_CHANGED,
	LAST_SIGNAL
};

typedef enum {
	SYNC_INSERT,
	SYNC_REMOVE
} SyncType;

enum {
	ARG_0,
};

static guint eil_signals[LAST_SIGNAL] = { 0 };


static GnomeCanvasClass *parent_class;


/* Icon structure */
typedef struct {
	/* Icon image and text items */
	GnomeCanvasPixbuf *image;
	GnomeIconTextItem *text;

	/* Filename of the icon file. */
	gchar *icon_filename;

	/* User data and destroy notify function */
	gpointer data;
	GtkDestroyNotify destroy;

	/* ID for the text item's event signal handler */
	guint text_event_id;

	/* Whether the icon is selected, and temporary storage for rubberband
         * selections.
	 */
	guint selected : 1;
	guint tmp_selected : 1;
} Icon;

/* A row of icons */
typedef struct {
	GList *line_icons;
	gint16 y;
	gint16 icon_height, text_height;
} IconLine;

/* Private data of the EIconList structure */
struct _EIconListPrivate {
	/* List of icons */
	GArray *icon_list;

	/* List of rows of icons */
	GList *lines;

	/* Separators used to wrap the text below icons */
	char *separators;

	Icon *last_selected_icon;

	/* Rubberband rectangle */
	GnomeCanvasItem *sel_rect;

	/* Saved event for a pending selection */
	GdkEvent select_pending_event;

	/* Max of the height of all the icon rows and window height */
	int total_height;

	/* Selection mode */
	GtkSelectionMode selection_mode;

	/* A list of integers with the indices of the currently selected icons */
	GList *selection;

	/* Number of icons in the list */
	int icons;

	/* Freeze count */
	int frozen;

	/* Width allocated for icons */
	int icon_width;

	/* Spacing values */
	int row_spacing;
	int col_spacing;
	int text_spacing;
	int icon_border;

	/* Index and pointer to last selected icon */
	int last_selected_idx;

	/* Timeout ID for autoscrolling */
	guint timer_tag;

	/* Change the adjustment value by this amount when autoscrolling */
	int value_diff;

	/* Mouse position for autoscrolling */
	int event_last_x;
	int event_last_y;

	/* Selection start position */
	int sel_start_x;
	int sel_start_y;

	/* Modifier state when the selection began */
	guint sel_state;

	/* Whether the icon texts are editable */
	guint is_editable : 1;

	/* Whether the icon texts need to be copied */
	guint static_text : 1;

	/* Whether the icons need to be laid out */
	guint dirty : 1;

	/* Whether the user is performing a rubberband selection */
	guint selecting : 1;

	/* Whether editing an icon is pending after a button press */
	guint edit_pending : 1;

	/* Whether selection is pending after a button press */
	guint select_pending : 1;

	/* Whether the icon that is pending selection was selected to begin with */
	guint select_pending_was_selected : 1;
};


static inline int
icon_line_height (Eil *eil, IconLine *il)
{
	EIconListPrivate *priv;

	priv = eil->_priv;

	return il->icon_height + il->text_height + priv->row_spacing + priv->text_spacing;
}

static void
icon_get_height (Icon *icon, int *icon_height, int *text_height)
{
	double d_icon_height;
	gtk_object_get(GTK_OBJECT(icon->image), "height", &d_icon_height, NULL);
	*icon_height = d_icon_height;
	*text_height = icon->text->ti->height;
}

static int
eil_get_items_per_line (Eil *eil)
{
	EIconListPrivate *priv;
	int items_per_line;

	priv = eil->_priv;

	items_per_line = GTK_WIDGET (eil)->allocation.width / (priv->icon_width + priv->col_spacing);
	if (items_per_line == 0)
		items_per_line = 1;

	return items_per_line;
}

/**
 * e_icon_list_get_items_per_line:
 * @eil: An icon list.
 *
 * Returns the number of icons that fit in a line or row.
 */
int
e_icon_list_get_items_per_line (EIconList *eil)
{
	g_return_val_if_fail (eil != NULL, 1);
	g_return_val_if_fail (IS_EIL (eil), 1);

	return eil_get_items_per_line (eil);
}

static void
eil_place_icon (Eil *eil, Icon *icon, int x, int y, int icon_height)
{
	EIconListPrivate *priv;
	int x_offset, y_offset;
	double d_icon_image_height;
	double d_icon_image_width;
	int icon_image_height;
	int icon_image_width;

	priv = eil->_priv;

	gtk_object_get(GTK_OBJECT(icon->image), "height", &d_icon_image_height, NULL);
	icon_image_height = d_icon_image_height;
	g_assert(icon_image_height != 0);
	if (icon_height > icon_image_height)
		y_offset = (icon_height - icon_image_height) / 2;
	else
		y_offset = 0;

	gtk_object_get(GTK_OBJECT(icon->image), "width", &d_icon_image_width, NULL);
	icon_image_width = d_icon_image_width;
	g_assert(icon_image_width != 0);
	if (priv->icon_width > icon_image_width)
		x_offset = (priv->icon_width - icon_image_width) / 2;
	else
		x_offset = 0;

	gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->image),
			       "x",  (double) (x + x_offset),
			       "y",  (double) (y + y_offset),
			       NULL);
	gnome_icon_text_item_setxy (icon->text,
				    x,
				    y + icon_height + priv->text_spacing);
}

static void
eil_layout_line (Eil *eil, IconLine *il)
{
	EIconListPrivate *priv;
	GList *l;
	int x;

	priv = eil->_priv;

	x = 0;
	for (l = il->line_icons; l; l = l->next) {
		Icon *icon = l->data;

		eil_place_icon (eil, icon, x, il->y, il->icon_height);
		x += priv->icon_width + priv->col_spacing;
	}
}

static void
eil_add_and_layout_line (Eil *eil, GList *line_icons, int y,
			 int icon_height, int text_height)
{
	EIconListPrivate *priv;
	IconLine *il;

	priv = eil->_priv;

	il = g_new (IconLine, 1);
	il->line_icons = line_icons;
	il->y = y;
	il->icon_height = icon_height;
	il->text_height = text_height;

	eil_layout_line (eil, il);
	priv->lines = g_list_append (priv->lines, il);
}

static void
eil_relayout_icons_at (Eil *eil, int pos, int y)
{
	EIconListPrivate *priv;
	int col, row, text_height, icon_height;
	int items_per_line, n;
	GList *line_icons;

	priv = eil->_priv;
	items_per_line = eil_get_items_per_line (eil);

	col = row = text_height = icon_height = 0;
	line_icons = NULL;

	for (n = pos; n < priv->icon_list->len; n++) {
		Icon *icon = g_array_index(priv->icon_list, Icon*, n);
		int ih, th;

		if (!(n % items_per_line)) {
			if (line_icons) {
				eil_add_and_layout_line (eil, line_icons, y,
							 icon_height, text_height);
				line_icons = NULL;

				y += (icon_height + text_height
				      + priv->row_spacing + priv->text_spacing);
			}

			icon_height = 0;
			text_height = 0;
		}

		icon_get_height (icon, &ih, &th);

		icon_height = MAX (ih, icon_height);
		text_height = MAX (th, text_height);

		line_icons = g_list_append (line_icons, icon);
	}

	if (line_icons)
		eil_add_and_layout_line (eil, line_icons, y, icon_height, text_height);
}

static void
eil_free_line_info (Eil *eil)
{
	EIconListPrivate *priv;
	GList *l;

	priv = eil->_priv;

	for (l = priv->lines; l; l = l->next) {
		IconLine *il = l->data;

		g_list_free (il->line_icons);
		g_free (il);
	}

	g_list_free (priv->lines);
	priv->lines = NULL;
	priv->total_height = 0;
}

static void
eil_free_line_info_from (Eil *eil, int first_line)
{
	EIconListPrivate *priv;
	GList *l, *ll;

	priv = eil->_priv;
	ll = g_list_nth (priv->lines, first_line);

	for (l = ll; l; l = l->next) {
		IconLine *il = l->data;

		g_list_free (il->line_icons);
		g_free (il);
	}

	if (priv->lines) {
		if (ll->prev)
			ll->prev->next = NULL;
		else
			priv->lines = NULL;
	}

	g_list_free (ll);
}

static void
eil_layout_from_line (Eil *eil, int line)
{
	EIconListPrivate *priv;
	GList *l;
	int height;

	priv = eil->_priv;

	eil_free_line_info_from (eil, line);

	height = 0;
	for (l = priv->lines; l; l = l->next) {
		IconLine *il = l->data;

		height += icon_line_height (eil, il);
	}

	eil_relayout_icons_at (eil, line * eil_get_items_per_line (eil), height);
}

static void
eil_layout_all_icons (Eil *eil)
{
	EIconListPrivate *priv;

	priv = eil->_priv;

	if (!GTK_WIDGET_REALIZED (eil))
		return;

	eil_free_line_info (eil);
	eil_relayout_icons_at (eil, 0, 0);
	priv->dirty = FALSE;
}

static void
eil_scrollbar_adjust (Eil *eil)
{
	EIconListPrivate *priv;
	GtkAdjustment *adj;
	GList *l;
	double wx, wy, wx1, wy1, wx2, wy2;
	int height, step_increment;

	priv = eil->_priv;

	if (!GTK_WIDGET_REALIZED (eil))
		return;

	height = 0;
	step_increment = 0;
	for (l = priv->lines; l; l = l->next) {
		IconLine *il = l->data;

		height += icon_line_height (eil, il);

		if (l == priv->lines)
			step_increment = height;
	}

	if (!step_increment)
		step_increment = 10;

	priv->total_height = MAX (height, GTK_WIDGET (eil)->allocation.height);

	gnome_canvas_c2w (GNOME_CANVAS (eil), 0, 0, &wx1, &wy1);
	gnome_canvas_c2w (GNOME_CANVAS (eil),
			  GTK_WIDGET (eil)->allocation.width,
			  priv->total_height,
			  &wx2, &wy2);

	gnome_canvas_set_scroll_region (GNOME_CANVAS (eil),
					wx1, wy1, wx2, wy2);

	wx = wy = 0;
	gnome_canvas_window_to_world (GNOME_CANVAS (eil), 0, 0, &wx, &wy);

	adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

	adj->upper = priv->total_height;
	adj->step_increment = step_increment;
	adj->page_increment = GTK_WIDGET (eil)->allocation.height;
	adj->page_size = GTK_WIDGET (eil)->allocation.height;

	if (wy > adj->upper - adj->page_size)
		wy = adj->upper - adj->page_size;

	adj->value = wy;

	gtk_adjustment_changed (adj);
}

/* Emits the select_icon or unselect_icon signals as appropriate */
static void
emit_select (Eil *eil, int sel, int i, GdkEvent *event)
{
	gtk_signal_emit (GTK_OBJECT (eil),
			 eil_signals[sel ? SELECT_ICON : UNSELECT_ICON],
			 i,
			 event);
}

static int
eil_unselect_all (EIconList *eil, GdkEvent *event, gpointer keep)
{
	EIconListPrivate *priv;
	Icon *icon;
	int i, idx = 0;

	g_return_val_if_fail (eil != NULL, 0);
	g_return_val_if_fail (IS_EIL (eil), 0);

	priv = eil->_priv;

	for (i = 0; i < priv->icon_list->len; i++) {
		icon = g_array_index(priv->icon_list, Icon*, i);

		if (icon == keep)
			idx = i;
		else if (icon->selected)
			emit_select (eil, FALSE, i, event);
	}

	return idx;
}

/**
 * e_icon_list_unselect_all:
 * @eil:   An icon list.
 *
 * Returns: the number of icons in the icon list
 */
int
e_icon_list_unselect_all (EIconList *eil)
{
	return eil_unselect_all (eil, NULL, NULL);
}

static void
sync_selection (Eil *eil, int pos, SyncType type)
{
	GList *list;

	for (list = eil->_priv->selection; list; list = list->next) {
		if (GPOINTER_TO_INT (list->data) >= pos) {
			int i = GPOINTER_TO_INT (list->data);

			switch (type) {
			case SYNC_INSERT:
				list->data = GINT_TO_POINTER (i + 1);
				break;

			case SYNC_REMOVE:
				list->data = GINT_TO_POINTER (i - 1);
				break;

			default:
				g_assert_not_reached ();
			}
		}
	}
}

static int
eil_icon_to_index (Eil *eil, Icon *icon)
{
	EIconListPrivate *priv;
	int n;

	priv = eil->_priv;

	for (n = 0; n < priv->icon_list->len; n++)
		if (g_array_index(priv->icon_list, Icon*, n) == icon)
			return n;

	g_assert_not_reached ();
	return -1; /* Shut up the compiler */
}

/* Event handler for icons when we are in SINGLE or BROWSE mode */
static gint
selection_one_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
	EIconListPrivate *priv;
	GnomeIconTextItem *text;
	int retval;

	priv = eil->_priv;
	retval = FALSE;

	/* We use a separate variable and ref the object because it may be
	 * destroyed by one of the signal handlers.
	 */
	text = icon->text;
	gtk_object_ref (GTK_OBJECT (text));

	switch (event->type) {
	case GDK_BUTTON_PRESS:
		priv->edit_pending = FALSE;
		priv->select_pending = FALSE;

		/* Ignore wheel mouse clicks for now */
		if (event->button.button > 3)
			break;

		if (!icon->selected) {
			eil_unselect_all (eil, NULL, NULL);
			emit_select (eil, TRUE, idx, event);
		} else {
			if (priv->selection_mode == GTK_SELECTION_SINGLE
			    && (event->button.state & GDK_CONTROL_MASK))
				emit_select (eil, FALSE, idx, event);
			else if (on_text && priv->is_editable && event->button.button == 1)
				priv->edit_pending = TRUE;
			else
				emit_select (eil, TRUE, idx, event);
		}

		retval = TRUE;
		break;

	case GDK_2BUTTON_PRESS:
	case GDK_3BUTTON_PRESS:
		/* Ignore wheel mouse clicks for now */
		if (event->button.button > 3)
			break;

		emit_select (eil, TRUE, idx, event);
		retval = TRUE;
		break;

	case GDK_BUTTON_RELEASE:
		if (priv->edit_pending) {
			gnome_icon_text_item_start_editing (text);
			priv->edit_pending = FALSE;
		}

		retval = TRUE;
		break;

	default:
		break;
	}

	/* If the click was on the text and we actually did something, stop the
	 * icon text item's own handler from executing.
	 */
	if (on_text && retval)
		gtk_signal_emit_stop_by_name (GTK_OBJECT (text), "event");

	gtk_object_unref (GTK_OBJECT (text));

	return retval;
}

/* Handles range selections when clicking on an icon */
static void
select_range (Eil *eil, Icon *icon, int idx, GdkEvent *event)
{
	EIconListPrivate *priv;
	int a, b;
	Icon *i;

	priv = eil->_priv;

	if (priv->last_selected_idx == -1) {
		priv->last_selected_idx = idx;
		priv->last_selected_icon = icon;
	}

	if (idx < priv->last_selected_idx) {
		a = idx;
		b = priv->last_selected_idx;
	} else {
		a = priv->last_selected_idx;
		b = idx;
	}

	for (; a <= b; a++) {
		i = g_array_index(priv->icon_list, Icon*, a);

		if (!i->selected)
			emit_select (eil, TRUE, a, NULL);
	}

	/* Actually notify the client of the event */
	emit_select (eil, TRUE, idx, event);
}

/* Handles icon selection for MULTIPLE or EXTENDED selection modes */
static void
do_select_many (Eil *eil, Icon *icon, int idx, GdkEvent *event, int use_event)
{
	EIconListPrivate *priv;
	int range, additive;

	priv = eil->_priv;

	range = (event->button.state & GDK_SHIFT_MASK) != 0;
	additive = (event->button.state & GDK_CONTROL_MASK) != 0;

	if (!additive) {
		if (icon->selected)
			eil_unselect_all (eil, NULL, icon);
		else
			eil_unselect_all (eil, NULL, NULL);
	}

	if (!range) {
		if (additive)
			emit_select (eil, !icon->selected, idx, use_event ? event : NULL);
		else
			emit_select (eil, TRUE, idx, use_event ? event : NULL);

		priv->last_selected_idx = idx;
		priv->last_selected_icon = icon;
	} else
		select_range (eil, icon, idx, use_event ? event : NULL);
}

/* Event handler for icons when we are in MULTIPLE or EXTENDED mode */
static gint
selection_many_icon_event (Eil *eil, Icon *icon, int idx, int on_text, GdkEvent *event)
{
	EIconListPrivate *priv;
	GnomeIconTextItem *text;
	int retval;
	int additive, range;
	int do_select;

	priv = eil->_priv;
	retval = FALSE;

	/* We use a separate variable and ref the object because it may be
	 * destroyed by one of the signal handlers.
	 */
	text = icon->text;
	gtk_object_ref (GTK_OBJECT (text));

	range = (event->button.state & GDK_SHIFT_MASK) != 0;
	additive = (event->button.state & GDK_CONTROL_MASK) != 0;

	switch (event->type) {
	case GDK_BUTTON_PRESS:
		priv->edit_pending = FALSE;
		priv->select_pending = FALSE;

		/* Ignore wheel mouse clicks for now */
		if (event->button.button > 3)
			break;

		do_select = TRUE;

		if (additive || range) {
			if (additive && !range) {
				priv->select_pending = TRUE;
				priv->select_pending_event = *event;
				priv->select_pending_was_selected = icon->selected;

				/* We have to emit this so that the client will
				 * know about the click.
				 */
				emit_select (eil, TRUE, idx, event);
				do_select = FALSE;
			}
		} else if (icon->selected) {
			priv->select_pending = TRUE;
			priv->select_pending_event = *event;
			priv->select_pending_was_selected = icon->selected;

			if (on_text && priv->is_editable && event->button.button == 1)
				priv->edit_pending = TRUE;

			emit_select (eil, TRUE, idx, event);
			do_select = FALSE;
		}
#if 0
		} else if (icon->selected && on_text && priv->is_editable
			   && event->button.button == 1) {
			priv->edit_pending = TRUE;
			do_select = FALSE;
		}
#endif

		if (do_select)
			do_select_many (eil, icon, idx, event, TRUE);

		retval = TRUE;
		break;

	case GDK_2BUTTON_PRESS:
	case GDK_3BUTTON_PRESS:
		/* Ignore wheel mouse clicks for now */
		if (event->button.button > 3)
			break;

		emit_select (eil, TRUE, idx, event);
		retval = TRUE;
		break;

	case GDK_BUTTON_RELEASE:
		if (priv->select_pending) {
			icon->selected = priv->select_pending_was_selected;
			do_select_many (eil, icon, idx, &priv->select_pending_event, FALSE);
			priv->select_pending = FALSE;
			retval = TRUE;
		}

		if (priv->edit_pending) {
			gnome_icon_text_item_start_editing (text);
			priv->edit_pending = FALSE;
			retval = TRUE;
		}
#if 0
		if (priv->select_pending) {
			icon->selected = priv->select_pending_was_selected;
			do_select_many (eil, icon, idx, &priv->select_pending_event);
			priv->select_pending = FALSE;
			retval = TRUE;
		} else if (priv->edit_pending) {
			gnome_icon_text_item_start_editing (text);
			priv->edit_pending = FALSE;
			retval = TRUE;
		}
#endif

		break;

	default:
		break;
	}

	/* If the click was on the text and we actually did something, stop the
	 * icon text item's own handler from executing.
	 */
	if (on_text && retval)
		gtk_signal_emit_stop_by_name (GTK_OBJECT (text), "event");

	gtk_object_unref (GTK_OBJECT (text));

	return retval;
}

/* Event handler for icons in the icon list */
static gint
icon_event (GnomeCanvasItem *item, GdkEvent *event, gpointer data)
{
	Icon *icon;
	Eil *eil;
	EIconListPrivate *priv;
	int idx;
	int on_text;

	icon = data;
	eil = EIL (item->canvas);
	priv = eil->_priv;
	idx = eil_icon_to_index (eil, icon);
	on_text = item == GNOME_CANVAS_ITEM (icon->text);

	switch (priv->selection_mode) {
	case GTK_SELECTION_SINGLE:
	case GTK_SELECTION_BROWSE:
		return selection_one_icon_event (eil, icon, idx, on_text, event);

	case GTK_SELECTION_MULTIPLE:
	case GTK_SELECTION_EXTENDED:
		return selection_many_icon_event (eil, icon, idx, on_text, event);

	default:
		g_assert_not_reached ();
		return FALSE; /* Shut up the compiler */
	}
}

/* Handler for the editing_started signal of an icon text item.  We block the
 * event handler so that it will not be called while the text is being edited.
 */
static void
editing_started (GnomeIconTextItem *iti, gpointer data)
{
	Icon *icon;

	icon = data;
	gtk_signal_handler_block (GTK_OBJECT (iti), icon->text_event_id);
	eil_unselect_all (EIL (GNOME_CANVAS_ITEM (iti)->canvas), NULL, icon);
}

/* Handler for the editing_stopped signal of an icon text item.  We unblock the
 * event handler so that we can get events from it again.
 */
static void
editing_stopped (GnomeIconTextItem *iti, gpointer data)
{
	Icon *icon;

	icon = data;
	gtk_signal_handler_unblock (GTK_OBJECT (iti), icon->text_event_id);
}

static gboolean
text_changed (GnomeCanvasItem *item, Icon *icon)
{
	Eil *eil;
	gboolean accept;
	int idx;

	eil = EIL (item->canvas);
	accept = TRUE;

	idx = eil_icon_to_index (eil, icon);
	gtk_signal_emit (GTK_OBJECT (eil),
			 eil_signals[TEXT_CHANGED],
			 idx, gnome_icon_text_item_get_text (icon->text),
			 &accept);

	return accept;
}

static void
height_changed (GnomeCanvasItem *item, Icon *icon)
{
	Eil *eil;
	EIconListPrivate *priv;
	int n;

	eil = EIL (item->canvas);
	priv = eil->_priv;

	if (!GTK_WIDGET_REALIZED (eil))
		return;

	if (priv->frozen) {
		priv->dirty = TRUE;
		return;
	}

	n = eil_icon_to_index (eil, icon);
	eil_layout_from_line (eil, n / eil_get_items_per_line (eil));
	eil_scrollbar_adjust (eil);
}

static Icon *
icon_new_from_pixbuf (EIconList *eil, GdkPixbuf *im,
		      const char *icon_filename, const char *text)
{
	EIconListPrivate *priv;
	GnomeCanvas *canvas;
	GnomeCanvasGroup *group;
	Icon *icon;

	priv = eil->_priv;
	canvas = GNOME_CANVAS (eil);
	group = GNOME_CANVAS_GROUP (canvas->root);

	icon = g_new0 (Icon, 1);

	if (icon_filename)
		icon->icon_filename = g_strdup (icon_filename);
	else
		icon->icon_filename = NULL;

	if (im == NULL)
		im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm); 
	else
		gdk_pixbuf_ref (im);

	icon->image = GNOME_CANVAS_PIXBUF (gnome_canvas_item_new (
		group,
		gnome_canvas_pixbuf_get_type (),
		"x", 0.0,
		"y", 0.0,
		"width", (double) gdk_pixbuf_get_width (im),
		"height", (double) gdk_pixbuf_get_height (im),
		"pixbuf", im,
		NULL));
	gdk_pixbuf_unref (im);

	icon->text = GNOME_ICON_TEXT_ITEM (gnome_canvas_item_new (
		group,
		gnome_icon_text_item_get_type (),
		NULL));

	gnome_canvas_item_set (GNOME_CANVAS_ITEM (icon->text),
			       "use_broken_event_handling", FALSE,
			       NULL);
	
	/* FIXME: should this use a selectable font?? */
#warning "Ettore: should we allow this font to be selectable??"
	gnome_icon_text_item_configure (icon->text,
					0, 0, priv->icon_width, 
					"-adobe-helvetica-medium-r-normal-*-*-120-*-*-p-*-iso8859-1",
					text, priv->is_editable, priv->static_text);

	gtk_signal_connect (GTK_OBJECT (icon->image), "event",
			    GTK_SIGNAL_FUNC (icon_event),
			    icon);
	icon->text_event_id = gtk_signal_connect (GTK_OBJECT (icon->text), "event",
						  GTK_SIGNAL_FUNC (icon_event),
						  icon);

	gtk_signal_connect (GTK_OBJECT (icon->text), "editing_started",
			    GTK_SIGNAL_FUNC (editing_started),
			    icon);
	gtk_signal_connect (GTK_OBJECT (icon->text), "editing_stopped",
			    GTK_SIGNAL_FUNC (editing_stopped),
			    icon);

	gtk_signal_connect (GTK_OBJECT (icon->text), "text_changed",
			    GTK_SIGNAL_FUNC (text_changed),
			    icon);
	gtk_signal_connect (GTK_OBJECT (icon->text), "height_changed",
			    GTK_SIGNAL_FUNC (height_changed),
			    icon);

	return icon;
}

static Icon *
icon_new (Eil *eil, const char *icon_filename, const char *text)
{
	GdkPixbuf *im;
	Icon *retval;

	if (icon_filename) {
		im = gdk_pixbuf_new_from_file (icon_filename);

		/* Bad icon image 
		   Fixme. Need a better graphic. */
		if (im == NULL)
			im = gdk_pixbuf_new_from_xpm_data ((const char**) bad_icon_xpm); 
	} else
		im = NULL;

	retval = icon_new_from_pixbuf (eil, im, icon_filename, text);

	if(im)
		gdk_pixbuf_unref(im);

	return retval;
}

static int
icon_list_append (Eil *eil, Icon *icon)
{
	EIconListPrivate *priv;
	int pos;

	priv = eil->_priv;

	pos = priv->icons++;
	g_array_append_val(priv->icon_list, icon);

	switch (priv->selection_mode) {
	case GTK_SELECTION_BROWSE:
		e_icon_list_select_icon (eil, 0);
		break;

	default:
		break;
	}

	if (!priv->frozen) {
		/* FIXME: this should only layout the last line */
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	} else
		priv->dirty = TRUE;

	return priv->icons - 1;
}

static void
icon_list_insert (Eil *eil, int pos, Icon *icon)
{
	EIconListPrivate *priv;

	priv = eil->_priv;

	if (pos == priv->icons) {
		icon_list_append (eil, icon);
		return;
	}

	g_array_insert_val(priv->icon_list, pos, icon);
	priv->icons++;

	switch (priv->selection_mode) {
	case GTK_SELECTION_BROWSE:
		e_icon_list_select_icon (eil, 0);
		break;

	default:
		break;
	}

	if (!priv->frozen) {
		/* FIXME: this should only layout the lines from then one
		 * containing the Icon to the end.
		 */
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	} else
		priv->dirty = TRUE;

	sync_selection (eil, pos, SYNC_INSERT);
}

/**
 * e_icon_list_insert_pixbuf:
 * @eil:      An icon list.
 * @pos:      Position at which the new icon should be inserted.
 * @im:       Pixbuf image with the icon image.
 * @filename: Filename of the image file.
 * @text:     Text to be used for the icon's caption.
 *
 * Inserts an icon in the specified icon list.  The icon is created from the
 * specified Imlib image, and it is inserted at the @pos index.
 */
void
e_icon_list_insert_pixbuf (EIconList *eil, int pos, GdkPixbuf *im,
			       const char *icon_filename, const char *text)
{
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
	icon_list_insert (eil, pos, icon);
	return;
}

/**
 * e_icon_list_insert:
 * @eil:           An icon list.
 * @pos:           Position at which the new icon should be inserted.
 * @icon_filename: Name of the file that holds the icon's image.
 * @text:          Text to be used for the icon's caption.
 *
 * Inserts an icon in the specified icon list.  The icon's image is loaded
 * from the specified file, and it is inserted at the @pos index.
 */
void
e_icon_list_insert (EIconList *eil, int pos, const char *icon_filename, const char *text)
{
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	icon = icon_new (eil, icon_filename, text);
	icon_list_insert (eil, pos, icon);
	return;
}

/**
 * e_icon_list_append_pixbuf:
 * @eil:      An icon list.
 * @im:       Pixbuf image with the icon image.
 * @filename: Filename of the image file.
 * @text:     Text to be used for the icon's caption.
 *
 * Appends an icon to the specified icon list.  The icon is created from
 * the specified Imlib image.
 */
int
e_icon_list_append_pixbuf (EIconList *eil, GdkPixbuf *im,
			       const char *icon_filename, const char *text)
{
	Icon *icon;

	g_return_val_if_fail (eil != NULL, -1);
	g_return_val_if_fail (IS_EIL (eil), -1);

	icon = icon_new_from_pixbuf (eil, im, icon_filename, text);
	return icon_list_append (eil, icon);
}

/**
 * e_icon_list_append:
 * @eil:           An icon list.
 * @icon_filename: Name of the file that holds the icon's image.
 * @text:          Text to be used for the icon's caption.
 *
 * Appends an icon to the specified icon list.  The icon's image is loaded from
 * the specified file, and it is inserted at the @pos index.
 */
int
e_icon_list_append (EIconList *eil, const char *icon_filename,
			const char *text)
{
	Icon *icon;

	g_return_val_if_fail (eil != NULL, -1);
	g_return_val_if_fail (IS_EIL (eil), -1);

	icon = icon_new (eil, icon_filename, text);
	return icon_list_append (eil, icon);
}

static void
icon_destroy (Icon *icon)
{
	if (icon->destroy)
		(* icon->destroy) (icon->data);

	g_free (icon->icon_filename);

	gtk_object_destroy (GTK_OBJECT (icon->image));
	gtk_object_destroy (GTK_OBJECT (icon->text));
	g_free (icon);
}

/**
 * e_icon_list_remove:
 * @eil: An icon list.
 * @pos: Index of the icon that should be removed.
 *
 * Removes the icon at index position @pos.  If a destroy handler was specified
 * for that icon, it will be called with the appropriate data.
 */
void
e_icon_list_remove (EIconList *eil, int pos)
{
	EIconListPrivate *priv;
	int was_selected;
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

	priv = eil->_priv;

	was_selected = FALSE;

	icon = g_array_index(priv->icon_list, Icon*, pos);

	if (icon->selected) {
		was_selected = TRUE;

		switch (priv->selection_mode) {
		case GTK_SELECTION_SINGLE:
		case GTK_SELECTION_BROWSE:
		case GTK_SELECTION_MULTIPLE:
		case GTK_SELECTION_EXTENDED:
			e_icon_list_unselect_icon (eil, pos);
			break;

		default:
			break;
		}
	}

	g_array_remove_index(priv->icon_list, pos);
	priv->icons--;

	sync_selection (eil, pos, SYNC_REMOVE);

	if (was_selected) {
		switch (priv->selection_mode) {
		case GTK_SELECTION_BROWSE:
			if (pos == priv->icons)
				e_icon_list_select_icon (eil, pos - 1);
			else
				e_icon_list_select_icon (eil, pos);

			break;

		default:
			break;
		}
	}

	if (priv->icons >= priv->last_selected_idx)
		priv->last_selected_idx = -1;

	if (priv->last_selected_icon == icon)
		priv->last_selected_icon = NULL;

	icon_destroy (icon);

	if (!priv->frozen) {
		/* FIXME: Optimize, only re-layout from pos to end */
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	} else
		priv->dirty = TRUE;
}

/**
 * e_icon_list_clear:
 * @eil: An icon list.
 *
 * Clears the contents for the icon list by removing all the icons.  If destroy
 * handlers were specified for any of the icons, they will be called with the
 * appropriate data.
 */
void
e_icon_list_clear (EIconList *eil)
{
	EIconListPrivate *priv;
	int i;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;

	for (i = 0; i < priv->icon_list->len; i++)
		icon_destroy (g_array_index (priv->icon_list, Icon*, i));

	eil_free_line_info (eil);

	g_list_free (priv->selection);
	priv->selection = NULL;
	g_array_set_size(priv->icon_list, 0);
	priv->icons = 0;
	priv->last_selected_idx = -1;
	priv->last_selected_icon = NULL;

	if (!priv->frozen) {
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	} else
		priv->dirty = TRUE;
}

static void
eil_destroy (GtkObject *object)
{
	Eil *eil;

	/* remember, destroy can be run multiple times! */

	eil = EIL (object);

	g_free (eil->_priv->separators);
	eil->_priv->separators = NULL;

	eil->_priv->frozen = 1;
	eil->_priv->dirty  = TRUE;
	if(eil->_priv->icon_list) {
		e_icon_list_clear (eil);
		g_array_free(eil->_priv->icon_list, TRUE);
	}
	eil->_priv->icon_list = NULL;

	if (eil->_priv->timer_tag != 0) {
		gtk_timeout_remove (eil->_priv->timer_tag);
		eil->_priv->timer_tag = 0;
	}

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

static void
eil_finalize (GtkObject *object)
{
	Eil *eil;

	eil = EIL (object);

	g_free (eil->_priv);
	eil->_priv = NULL;

	if (GTK_OBJECT_CLASS (parent_class)->finalize)
		(*GTK_OBJECT_CLASS (parent_class)->finalize) (object);
}


static void
select_icon (Eil *eil, int pos, GdkEvent *event)
{
	EIconListPrivate *priv;
	gint i;
	Icon *icon;

	priv = eil->_priv;

	switch (priv->selection_mode) {
	case GTK_SELECTION_SINGLE:
	case GTK_SELECTION_BROWSE:
		i = 0;

		for (i = 0; i < priv->icon_list->len; i++) {
			icon = g_array_index (priv->icon_list, Icon*, i);

			if (i != pos && icon->selected)
				emit_select (eil, FALSE, i, event);
		}

		emit_select (eil, TRUE, pos, event);
		break;

	case GTK_SELECTION_MULTIPLE:
	case GTK_SELECTION_EXTENDED:
		emit_select (eil, TRUE, pos, event);
		break;

	default:
		break;
	}
}

/**
 * e_icon_list_select_icon:
 * @eil: An icon list.
 * @pos: Index of the icon to be selected.
 *
 * Selects the icon at the index specified by @pos.
 */
void
e_icon_list_select_icon (EIconList *eil, int pos)
{
	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

	select_icon (eil, pos, NULL);
}

static void
unselect_icon (Eil *eil, int pos, GdkEvent *event)
{
	EIconListPrivate *priv;

	priv = eil->_priv;

	switch (priv->selection_mode) {
	case GTK_SELECTION_SINGLE:
	case GTK_SELECTION_BROWSE:
	case GTK_SELECTION_MULTIPLE:
	case GTK_SELECTION_EXTENDED:
		emit_select (eil, FALSE, pos, event);
		break;

	default:
		break;
	}
}

/**
 * e_icon_list_unselect_icon:
 * @eil: An icon list.
 * @pos: Index of the icon to be unselected.
 *
 * Unselects the icon at the index specified by @pos.
 */
void
e_icon_list_unselect_icon (EIconList *eil, int pos)
{
	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

	unselect_icon (eil, pos, NULL);
}

static void
eil_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
	requisition->width = 1;
	requisition->height = 1;
}

static void
eil_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
	Eil *eil;
	EIconListPrivate *priv;

	eil = EIL (widget);
	priv = eil->_priv;

	if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
		(* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);

	if (priv->frozen)
		return;

	eil_layout_all_icons (eil);
}

static void
eil_realize (GtkWidget *widget)
{
	Eil *eil;
	EIconListPrivate *priv;
	GtkStyle *style;

	eil = EIL (widget);
	priv = eil->_priv;

	priv->frozen++;

	if (GTK_WIDGET_CLASS (parent_class)->realize)
		(* GTK_WIDGET_CLASS (parent_class)->realize) (widget);

	priv->frozen--;

	/* Change the style to use the base color as the background */

	style = gtk_style_copy (gtk_widget_get_style (widget));
	style->bg[GTK_STATE_NORMAL] = style->base[GTK_STATE_NORMAL];
	gtk_widget_set_style (widget, style);

	gdk_window_set_background (GTK_LAYOUT (eil)->bin_window,
				   &widget->style->bg[GTK_STATE_NORMAL]);

	if (priv->frozen)
		return;

	if (priv->dirty) {
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	}
}

static void
real_select_icon (Eil *eil, gint num, GdkEvent *event)
{
	EIconListPrivate *priv;
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (num >= 0 && num < eil->_priv->icons);

	priv = eil->_priv;

	icon = g_array_index (priv->icon_list, Icon*, num);

	if (icon->selected)
		return;

	icon->selected = TRUE;
	gnome_icon_text_item_select (icon->text, TRUE);
	priv->selection = g_list_append (priv->selection, GINT_TO_POINTER (num));
}

static void
real_unselect_icon (Eil *eil, gint num, GdkEvent *event)
{
	EIconListPrivate *priv;
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (num >= 0 && num < eil->_priv->icons);

	priv = eil->_priv;

	icon = g_array_index (priv->icon_list, Icon*, num);

	if (!icon->selected)
		return;

	icon->selected = FALSE;
	gnome_icon_text_item_select (icon->text, FALSE);
	priv->selection = g_list_remove (priv->selection, GINT_TO_POINTER (num));
}

/* Saves the selection of the icon list to temporary storage */
static void
store_temp_selection (Eil *eil)
{
	EIconListPrivate *priv;
	int i;
	Icon *icon;

	priv = eil->_priv;

	for (i = 0; i < priv->icon_list->len; i++) {
		icon = g_array_index(priv->icon_list, Icon*, i);

		icon->tmp_selected = icon->selected;
	}
}

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

/* Button press handler for the icon list */
static gint
eil_button_press (GtkWidget *widget, GdkEventButton *event)
{
	Eil *eil;
	EIconListPrivate *priv;
	int only_one;
	GdkBitmap *stipple;
	double tx, ty;

	eil = EIL (widget);
	priv = eil->_priv;

	/* Invoke the canvas event handler and see if an item picks up the event */

	if ((* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event))
		return TRUE;

	if (!(event->type == GDK_BUTTON_PRESS
	      && event->button == 1
	      && priv->selection_mode != GTK_SELECTION_BROWSE))
		return FALSE;

	only_one = priv->selection_mode == GTK_SELECTION_SINGLE;

	if (only_one || (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0)
		eil_unselect_all (eil, NULL, NULL);

	if (only_one)
		return TRUE;

	if (priv->selecting)
		return FALSE;

	gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &tx, &ty);
	priv->sel_start_x = tx;
	priv->sel_start_y = ty;
	priv->sel_state = event->state;
	priv->selecting = TRUE;

	store_temp_selection (eil);

	stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
	priv->sel_rect = gnome_canvas_item_new (gnome_canvas_root (GNOME_CANVAS (eil)),
						gnome_canvas_rect_get_type (),
						"x1", tx,
						"y1", ty,
						"x2", tx,
						"y2", ty,
						"outline_color", "black",
						"width_pixels", 1,
						"outline_stipple", stipple,
						NULL);
	gdk_bitmap_unref (stipple);

	gnome_canvas_item_grab (priv->sel_rect, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
				NULL, event->time);

	return TRUE;
}

/* Returns whether the specified icon is at least partially inside the specified
 * rectangle.
 */
static int
icon_is_in_area (Icon *icon, int x1, int y1, int x2, int y2)
{
	double ix1, iy1, ix2, iy2;

	if (x1 == x2 && y1 == y2)
		return FALSE;

	gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->image), &ix1, &iy1, &ix2, &iy2);

	if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
		return TRUE;

	gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (icon->text), &ix1, &iy1, &ix2, &iy2);

	if (ix1 <= x2 && iy1 <= y2 && ix2 >= x1 && iy2 >= y1)
		return TRUE;

	return FALSE;
}

/* Updates the rubberband selection to the specified point */
static void
update_drag_selection (Eil *eil, int x, int y)
{
	EIconListPrivate *priv;
	int x1, x2, y1, y2;
	int i;
	Icon *icon;
	int additive, invert;

	priv = eil->_priv;

	/* Update rubberband */

	if (priv->sel_start_x < x) {
		x1 = priv->sel_start_x;
		x2 = x;
	} else {
		x1 = x;
		x2 = priv->sel_start_x;
	}

	if (priv->sel_start_y < y) {
		y1 = priv->sel_start_y;
		y2 = y;
	} else {
		y1 = y;
		y2 = priv->sel_start_y;
	}

	if (x1 < 0)
		x1 = 0;

	if (y1 < 0)
		y1 = 0;

	if (x2 >= GTK_WIDGET (eil)->allocation.width)
		x2 = GTK_WIDGET (eil)->allocation.width - 1;

	if (y2 >= priv->total_height)
		y2 = priv->total_height - 1;

	gnome_canvas_item_set (priv->sel_rect,
			       "x1", (double) x1,
			       "y1", (double) y1,
			       "x2", (double) x2,
			       "y2", (double) y2,
			       NULL);

	/* Select or unselect icons as appropriate */

	additive = priv->sel_state & GDK_SHIFT_MASK;
	invert = priv->sel_state & GDK_CONTROL_MASK;

	for (i = 0; i < priv->icon_list->len; i++) {
		icon = g_array_index(priv->icon_list, Icon*, i);

		if (icon_is_in_area (icon, x1, y1, x2, y2)) {
			if (invert) {
				if (icon->selected == icon->tmp_selected)
					emit_select (eil, !icon->selected, i, NULL);
			} else if (additive) {
				if (!icon->selected)
					emit_select (eil, TRUE, i, NULL);
			} else {
				if (!icon->selected)
					emit_select (eil, TRUE, i, NULL);
			}
		} else if (icon->selected != icon->tmp_selected)
			emit_select (eil, icon->tmp_selected, i, NULL);
	}
}

/* Button release handler for the icon list */
static gint
eil_button_release (GtkWidget *widget, GdkEventButton *event)
{
	Eil *eil;
	EIconListPrivate *priv;
	double x, y;

	eil = EIL (widget);
	priv = eil->_priv;

	if (!priv->selecting)
		return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);

	if (event->button != 1)
		return FALSE;

	gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
	update_drag_selection (eil, x, y);
	gnome_canvas_item_ungrab (priv->sel_rect, event->time);

	gtk_object_destroy (GTK_OBJECT (priv->sel_rect));
	priv->sel_rect = NULL;
	priv->selecting = FALSE;

	if (priv->timer_tag != 0) {
		gtk_timeout_remove (priv->timer_tag);
		priv->timer_tag = 0;
	}

	return TRUE;
}

/* Autoscroll timeout handler for the icon list */
static gint
scroll_timeout (gpointer data)
{
	Eil *eil;
	EIconListPrivate *priv;
	GtkAdjustment *adj;
	double x, y;
	int value;

	eil = data;
	priv = eil->_priv;

	GDK_THREADS_ENTER ();

	adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

	value = adj->value + priv->value_diff;
	if (value > adj->upper - adj->page_size)
		value = adj->upper - adj->page_size;

	gtk_adjustment_set_value (adj, value);

	gnome_canvas_window_to_world (GNOME_CANVAS (eil),
				      priv->event_last_x, priv->event_last_y,
				      &x, &y);
	update_drag_selection (eil, x, y);

	GDK_THREADS_LEAVE();

	return TRUE;
}

/* Motion event handler for the icon list */
static gint
eil_motion_notify (GtkWidget *widget, GdkEventMotion *event)
{
	Eil *eil;
	EIconListPrivate *priv;
	double x, y;

	eil = EIL (widget);
	priv = eil->_priv;

	if (!priv->selecting)
		return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);

	gnome_canvas_window_to_world (GNOME_CANVAS (eil), event->x, event->y, &x, &y);
	update_drag_selection (eil, x, y);

	/* If we are out of bounds, schedule a timeout that will do the scrolling */

	if (event->y < 0 || event->y > widget->allocation.height) {
		if (priv->timer_tag == 0)
			priv->timer_tag = gtk_timeout_add (SCROLL_TIMEOUT, scroll_timeout, eil);

		if (event->y < 0)
			priv->value_diff = event->y;
		else
			priv->value_diff = event->y - widget->allocation.height;

		priv->event_last_x = event->x;
		priv->event_last_y = event->y;

		/* Make the steppings be relative to the mouse distance from the
		 * canvas.  Also notice the timeout above is small to give a
		 * more smooth movement.
		 */
		priv->value_diff /= 5;
	} else if (priv->timer_tag != 0) {
		gtk_timeout_remove (priv->timer_tag);
		priv->timer_tag = 0;
	}

	return TRUE;
}

typedef gboolean (*xGtkSignal_BOOL__INT_POINTER) (GtkObject * object,
						  gint     arg1,
						  gpointer arg2,
						  gpointer user_data);
static void
xgtk_marshal_BOOL__INT_POINTER (GtkObject *object, GtkSignalFunc func, gpointer func_data,
				GtkArg *args)
{
  xGtkSignal_BOOL__INT_POINTER rfunc;
  gboolean *return_val;

  return_val = GTK_RETLOC_BOOL (args[2]);
  rfunc = (xGtkSignal_BOOL__INT_POINTER) func;
  *return_val = (*rfunc) (object,
			  GTK_VALUE_INT (args[0]),
			  GTK_VALUE_POINTER (args[1]),
			  func_data);
}

static void
eil_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
	EIconList *eil;

	eil = E_ICON_LIST (object);

	switch (arg_id) {
	default:
		break;
	}
}

static void
eil_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
	EIconList *eil;
	EIconListPrivate *priv;

	eil = E_ICON_LIST (object);
	priv = eil->_priv;

	switch (arg_id) {
	default:
		arg->type = GTK_TYPE_INVALID;
		break;
	}
}

static void
eil_class_init (EilClass *eil_class)
{
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;
	GtkLayoutClass *layout_class;
	GnomeCanvasClass *canvas_class;

	object_class = (GtkObjectClass *)   eil_class;
	widget_class = (GtkWidgetClass *)   eil_class;
	layout_class = (GtkLayoutClass *)   eil_class;
	canvas_class = (GnomeCanvasClass *) eil_class;

	parent_class = gtk_type_class (gnome_canvas_get_type ());

	eil_signals[SELECT_ICON] =
		gtk_signal_new (
			"select_icon",
			GTK_RUN_FIRST,
			object_class->type,
			GTK_SIGNAL_OFFSET (EIconListClass, select_icon),
			gtk_marshal_NONE__INT_POINTER,
			GTK_TYPE_NONE, 2,
			GTK_TYPE_INT,
			GTK_TYPE_GDK_EVENT);

	eil_signals[UNSELECT_ICON] =
		gtk_signal_new (
			"unselect_icon",
			GTK_RUN_FIRST,
			object_class->type,
			GTK_SIGNAL_OFFSET (EIconListClass, unselect_icon),
			gtk_marshal_NONE__INT_POINTER,
			GTK_TYPE_NONE, 2,
			GTK_TYPE_INT,
			GTK_TYPE_GDK_EVENT);

	eil_signals[TEXT_CHANGED] =
		gtk_signal_new (
			"text_changed",
			GTK_RUN_LAST,
			object_class->type,
			GTK_SIGNAL_OFFSET (EIconListClass, text_changed),
			xgtk_marshal_BOOL__INT_POINTER,
			GTK_TYPE_BOOL, 2,
			GTK_TYPE_INT,
			GTK_TYPE_POINTER);

	gtk_object_class_add_signals (object_class, eil_signals, LAST_SIGNAL);

	object_class->destroy = eil_destroy;
	object_class->finalize = eil_finalize;
	object_class->set_arg = eil_set_arg;
	object_class->get_arg = eil_get_arg;

	widget_class->size_request = eil_size_request;
	widget_class->size_allocate = eil_size_allocate;
	widget_class->realize = eil_realize;
	widget_class->button_press_event = eil_button_press;
	widget_class->button_release_event = eil_button_release;
	widget_class->motion_notify_event = eil_motion_notify;

	eil_class->select_icon = real_select_icon;
	eil_class->unselect_icon = real_unselect_icon;
}

static void
eil_init (Eil *eil)
{
	eil->_priv = g_new0 (EIconListPrivate, 1);

	eil->_priv->icon_list = g_array_new(FALSE, FALSE, sizeof(gpointer));
	eil->_priv->row_spacing = DEFAULT_ROW_SPACING;
	eil->_priv->col_spacing = DEFAULT_COL_SPACING;
	eil->_priv->text_spacing = DEFAULT_TEXT_SPACING;
	eil->_priv->icon_border = DEFAULT_ICON_BORDER;
	eil->_priv->separators = g_strdup (" ");

	eil->_priv->selection_mode = GTK_SELECTION_SINGLE;
	eil->_priv->dirty = TRUE;

	gnome_canvas_set_scroll_region (GNOME_CANVAS (eil), 0.0, 0.0, 1000000.0, 1000000.0);
	gnome_canvas_scroll_to (GNOME_CANVAS (eil), 0, 0);
}

/**
 * e_icon_list_get_type:
 *
 * Registers the &EIconList class if necessary, and returns the type ID
 * associated to it.
 *
 * Returns: The type ID of the &EIconList class.
 */
guint
e_icon_list_get_type (void)
{
	static guint eil_type = 0;

	if (!eil_type) {
		GtkTypeInfo eil_info = {
			"EIconList",
			sizeof (EIconList),
			sizeof (EIconListClass),
			(GtkClassInitFunc) eil_class_init,
			(GtkObjectInitFunc) eil_init,
			NULL,
			NULL,
			NULL
		};

		eil_type = gtk_type_unique (gnome_canvas_get_type (),
					    &eil_info);
	}

	return eil_type;
}

/**
 * e_icon_list_set_icon_width:
 * @eil: An icon list.
 * @w:   New width for the icon columns.
 *
 * Sets the amount of horizontal space allocated to the icons, i.e. the column
 * width of the icon list.
 */
void
e_icon_list_set_icon_width (EIconList *eil, int w)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;

	priv->icon_width  = w;

	if (priv->frozen) {
		priv->dirty = TRUE;
		return;
	}

	eil_layout_all_icons (eil);
	eil_scrollbar_adjust (eil);
}


/**
 * e_icon_list_construct:
 * @eil: An icon list.
 * @icon_width: Width for the icon columns.
 * @flags: A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
 *
 * Constructor for the icon list, to be used by derived classes.
 **/
void
e_icon_list_construct (EIconList *eil, guint icon_width, int flags)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;

	e_icon_list_set_icon_width (eil, icon_width);
	priv->is_editable = (flags & E_ICON_LIST_IS_EDITABLE) != 0;
	priv->static_text = (flags & E_ICON_LIST_STATIC_TEXT) != 0;
}


/**
 * e_icon_list_new: [constructor]
 * @icon_width: Width for the icon columns.
 * @flags:      A combination of %E_ICON_LIST_IS_EDITABLE and %E_ICON_LIST_STATIC_TEXT.
 *
 * Creates a new icon list widget.  The icon columns are allocated a width of
 * @icon_width pixels.  Icon captions will be word-wrapped to this width as
 * well.
 *
 * If @flags has the %E_ICON_LIST_IS_EDITABLE flag set, then the user will be
 * able to edit the text in the icon captions, and the "text_changed" signal
 * will be emitted when an icon's text is changed.
 *
 * If @flags has the %E_ICON_LIST_STATIC_TEXT flags set, then the text
 * for the icon captions will not be copied inside the icon list; it will only
 * store the pointers to the original text strings specified by the application.
 * This is intended to save memory.  If this flag is not set, then the text
 * strings will be copied and managed internally.
 *
 * Returns: a newly-created icon list widget
 */
GtkWidget *
e_icon_list_new (guint icon_width, int flags)
{
	Eil *eil;

	gtk_widget_push_colormap (gdk_rgb_get_cmap ());
	eil = EIL (gtk_type_new (e_icon_list_get_type ()));
	gtk_widget_pop_colormap ();

	e_icon_list_construct (eil, icon_width, flags);

	return GTK_WIDGET (eil);
}


/**
 * e_icon_list_freeze:
 * @eil:  An icon list.
 *
 * Freezes an icon list so that any changes made to it will not be
 * reflected on the screen until it is thawed with e_icon_list_thaw().
 * It is recommended to freeze the icon list before inserting or deleting
 * many icons, for example, so that the layout process will only be executed
 * once, when the icon list is finally thawed.
 *
 * You can call this function multiple times, but it must be balanced with the
 * same number of calls to e_icon_list_thaw() before the changes will take
 * effect.
 */
void
e_icon_list_freeze (EIconList *eil)
{
	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	eil->_priv->frozen++;

	/* We hide the root so that the user will not see any changes while the
	 * icon list is doing stuff.
	 */

	if (eil->_priv->frozen == 1)
		gnome_canvas_item_hide (GNOME_CANVAS (eil)->root);
}

/**
 * e_icon_list_thaw:
 * @eil:  An icon list.
 *
 * Thaws the icon list and performs any pending layout operations.  This
 * is to be used in conjunction with e_icon_list_freeze().
 */
void
e_icon_list_thaw (EIconList *eil)
{
	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (eil->_priv->frozen > 0);

	eil->_priv->frozen--;

	if (eil->_priv->dirty) {
                eil_layout_all_icons (eil);
                eil_scrollbar_adjust (eil);
        }

	if (eil->_priv->frozen == 0)
		gnome_canvas_item_show (GNOME_CANVAS (eil)->root);
}

/**
 * e_icon_list_set_selection_mode
 * @eil:  An icon list.
 * @mode: New selection mode.
 *
 * Sets the selection mode for an icon list.  The %GTK_SELECTION_MULTIPLE and
 * %GTK_SELECTION_EXTENDED modes are considered equivalent.
 */
void
e_icon_list_set_selection_mode (EIconList *eil, GtkSelectionMode mode)
{
	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	eil->_priv->selection_mode = mode;
	eil->_priv->last_selected_idx = -1;
	eil->_priv->last_selected_icon = NULL;
}

/**
 * e_icon_list_set_icon_data_full:
 * @eil:     An icon list.
 * @pos:     Index of an icon.
 * @data:    User data to set on the icon.
 * @destroy: Destroy notification handler for the icon.
 *
 * Associates the @data pointer to the icon at the index specified by @pos.  The
 * @destroy argument points to a function that will be called when the icon is
 * destroyed, or NULL if no function is to be called when this happens.
 */
void
e_icon_list_set_icon_data_full (EIconList *eil,
				    int pos, gpointer data,
				    GtkDestroyNotify destroy)
{
	Icon *icon;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);

	icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
	icon->data = data;
	icon->destroy = destroy;
}

/**
 * e_icon_list_get_icon_data:
 * @eil:  An icon list.
 * @pos:  Index of an icon.
 * @data: User data to set on the icon.
 *
 * Associates the @data pointer to the icon at the index specified by @pos.
 */
void
e_icon_list_set_icon_data (EIconList *eil, int pos, gpointer data)
{
	e_icon_list_set_icon_data_full (eil, pos, data, NULL);
}

/**
 * e_icon_list_get_icon_data:
 * @eil: An icon list.
 * @pos: Index of an icon.
 *
 * Returns the user data pointer associated to the icon at the index specified
 * by @pos.
 */
gpointer
e_icon_list_get_icon_data (EIconList *eil, int pos)
{
	Icon *icon;

	g_return_val_if_fail (eil != NULL, NULL);
	g_return_val_if_fail (IS_EIL (eil), NULL);
	g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons, NULL);

	icon = g_array_index (eil->_priv->icon_list, Icon*, pos);
	return icon->data;
}

/**
 * e_icon_list_find_icon_from_data:
 * @eil:    An icon list.
 * @data:   Data pointer associated to an icon.
 *
 * Returns the index of the icon whose user data has been set to @data,
 * or -1 if no icon has this data associated to it.
 */
int
e_icon_list_find_icon_from_data (EIconList *eil, gpointer data)
{
	EIconListPrivate *priv;
	int n;
	Icon *icon;

	g_return_val_if_fail (eil != NULL, -1);
	g_return_val_if_fail (IS_EIL (eil), -1);

	priv = eil->_priv;

	for (n = 0; n < priv->icon_list->len; n++) {
		icon = g_array_index(priv->icon_list, Icon*, n);
		if (icon->data == data)
			return n;
	}

	return -1;
}

/* Sets an integer value in the private structure of the icon list, and updates it */
static void
set_value (EIconList *eil, EIconListPrivate *priv, int *dest, int val)
{
	if (val == *dest)
		return;

	*dest = val;

	if (!priv->frozen) {
		eil_layout_all_icons (eil);
		eil_scrollbar_adjust (eil);
	} else
		priv->dirty = TRUE;
}

/**
 * e_icon_list_set_row_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels for inter-row spacing.
 *
 * Sets the spacing to be used between rows of icons.
 */
void
e_icon_list_set_row_spacing (EIconList *eil, int pixels)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;
	set_value (eil, priv, &priv->row_spacing, pixels);
}

/**
 * e_icon_list_set_col_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels for inter-column spacing.
 *
 * Sets the spacing to be used between columns of icons.
 */
void
e_icon_list_set_col_spacing (EIconList *eil, int pixels)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;
	set_value (eil, priv, &priv->col_spacing, pixels);
}

/**
 * e_icon_list_set_text_spacing:
 * @eil:    An icon list.
 * @pixels: Number of pixels between an icon's image and its caption.
 *
 * Sets the spacing to be used between an icon's image and its text caption.
 */
void
e_icon_list_set_text_spacing (EIconList *eil, int pixels)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;
	set_value (eil, priv, &priv->text_spacing, pixels);
}

/**
 * e_icon_list_set_icon_border:
 * @eil:    An icon list.
 * @pixels: Number of border pixels to be used around an icon's image.
 *
 * Sets the width of the border to be displayed around an icon's image.  This is
 * currently not implemented.
 */
void
e_icon_list_set_icon_border (EIconList *eil, int pixels)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));

	priv = eil->_priv;
	set_value (eil, priv, &priv->icon_border, pixels);
}

/**
 * e_icon_list_set_separators:
 * @eil: An icon list.
 * @sep: String with characters to be used as word separators.
 *
 * Sets the characters that can be used as word separators when doing
 * word-wrapping in the icon text captions.
 */
void
e_icon_list_set_separators (EIconList *eil, const char *sep)
{
	EIconListPrivate *priv;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (sep != NULL);

	priv = eil->_priv;

	if (priv->separators)
		g_free (priv->separators);

	priv->separators = g_strdup (sep);

	if (priv->frozen) {
		priv->dirty = TRUE;
		return;
	}

	eil_layout_all_icons (eil);
	eil_scrollbar_adjust (eil);
}

/**
 * e_icon_list_moveto:
 * @eil:    An icon list.
 * @pos:    Index of an icon.
 * @yalign: Vertical alignment of the icon.
 *
 * Makes the icon whose index is @pos be visible on the screen.  The icon list
 * gets scrolled so that the icon is visible.  An alignment of 0.0 represents
 * the top of the visible part of the icon list, and 1.0 represents the bottom.
 * An icon can be centered on the icon list.
 */
void
e_icon_list_moveto (EIconList *eil, int pos, double yalign)
{
	EIconListPrivate *priv;
	GtkAdjustment *adj;
	IconLine *il;
	GList *l;
	int i, y, uh, line;

	g_return_if_fail (eil != NULL);
	g_return_if_fail (IS_EIL (eil));
	g_return_if_fail (pos >= 0 && pos < eil->_priv->icons);
	g_return_if_fail (yalign >= 0.0 && yalign <= 1.0);

	priv = eil->_priv;

	g_return_if_fail (priv->lines != NULL);

	line = pos / eil_get_items_per_line (eil);

	y = 0;
	for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
		il = l->data;
		y += icon_line_height (eil, il);
	}

	il = l->data;

	uh = GTK_WIDGET (eil)->allocation.height - icon_line_height (eil,il);
	adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));
	gtk_adjustment_set_value (adj, y - uh * yalign);
}

/**
 * e_icon_list_is_visible:
 * @eil: An icon list.
 * @pos: Index of an icon.
 *
 * Returns whether the icon at the index specified by @pos is visible.  This
 * will be %GTK_VISIBILITY_NONE if the icon is not visible at all,
 * %GTK_VISIBILITY_PARTIAL if the icon is at least partially shown, and
 * %GTK_VISIBILITY_FULL if the icon is fully visible.
 */
GtkVisibility
e_icon_list_icon_is_visible (EIconList *eil, int pos)
{
	EIconListPrivate *priv;
	GtkAdjustment *adj;
	IconLine *il;
	GList *l;
	int line, y1, y2, i;

	g_return_val_if_fail (eil != NULL, GTK_VISIBILITY_NONE);
	g_return_val_if_fail (IS_EIL (eil), GTK_VISIBILITY_NONE);
	g_return_val_if_fail (pos >= 0 && pos < eil->_priv->icons,
			      GTK_VISIBILITY_NONE);

	priv = eil->_priv;

	if (priv->lines == NULL)
		return GTK_VISIBILITY_NONE;

	line = pos / eil_get_items_per_line (eil);
	y1 = 0;
	for (i = 0, l = priv->lines; l && i < line; l = l->next, i++) {
		il = l->data;
		y1 += icon_line_height (eil, il);
	}

	y2 = y1 + icon_line_height (eil, (IconLine *) l->data);

	adj = gtk_layout_get_vadjustment (GTK_LAYOUT (eil));

	if (y2 < adj->value)
		return GTK_VISIBILITY_NONE;

	if (y1 > adj->value + GTK_WIDGET (eil)->allocation.height)
		return GTK_VISIBILITY_NONE;

	if (y2 <= adj->value + GTK_WIDGET (eil)->allocation.height &&
	    y1 >= adj->value)
		return GTK_VISIBILITY_FULL;

	return GTK_VISIBILITY_PARTIAL;
}

/**
 * e_icon_list_get_icon_at:
 * @eil: An icon list.
 * @x:   X position in the icon list window.
 * @y:   Y position in the icon list window.
 *
 * Returns the index of the icon that is under the specified coordinates, which
 * are relative to the icon list's window.  If there is no icon in that
 * position, -1 is returned.
 */
int
e_icon_list_get_icon_at (EIconList *eil, int x, int y)
{
	EIconListPrivate *priv;
	double wx, wy;
	double dx, dy;
	int cx, cy;
	int n;
	GnomeCanvasItem *item;
	double dist;

	g_return_val_if_fail (eil != NULL, -1);
	g_return_val_if_fail (IS_EIL (eil), -1);

	priv = eil->_priv;

	dx = x;
	dy = y;

	gnome_canvas_window_to_world (GNOME_CANVAS (eil), dx, dy, &wx, &wy);
	gnome_canvas_w2c (GNOME_CANVAS (eil), wx, wy, &cx, &cy);

	for (n = 0; n < priv->icon_list->len; n++) {
		Icon *icon = g_array_index(priv->icon_list, Icon*, n);
		GnomeCanvasItem *image = GNOME_CANVAS_ITEM (icon->image);
		GnomeCanvasItem *text = GNOME_CANVAS_ITEM (icon->text);

		if (wx >= image->x1 && wx <= image->x2 && wy >= image->y1 && wy <= image->y2) {
			dist = (* GNOME_CANVAS_ITEM_CLASS (GTK_OBJECT (image)->klass)->point) (
				image,
				wx, wy,
				cx, cy,
				&item);

			if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
			    <= GNOME_CANVAS (eil)->close_enough)
				return n;
		}

		if (wx >= text->x1 && wx <= text->x2 && wy >= text->y1 && wy <= text->y2) {
			dist = (* GNOME_CANVAS_ITEM_CLASS (GTK_OBJECT (text)->klass)->point) (
				text,
				wx, wy,
				cx, cy,
				&item);

			if ((int) (dist * GNOME_CANVAS (eil)->pixels_per_unit + 0.5)
			    <= GNOME_CANVAS (eil)->close_enough)
				return n;
		}
	}

	return -1;
}


/**
 * e_icon_list_get_num_icons:
 * @eil: An icon list.
 *
 * Returns the number of icons in the icon list.
 */
guint
e_icon_list_get_num_icons (EIconList *eil)
{
	g_return_val_if_fail (E_IS_ICON_LIST (eil), 0);

	return eil->_priv->icons;
}


/**
 * e_icon_list_get_selection:
 * @eil: An icon list.
 *
 * Returns a list of integers with the indices of the currently selected icons.
 */
GList *
e_icon_list_get_selection (EIconList *eil)
{
	g_return_val_if_fail (E_IS_ICON_LIST (eil), NULL);

	return eil->_priv->selection;
}


/**
 * e_icon_list_get_selection:
 * @eil: An icon list.
 * @idx: Index of an @icon.
 *
 * Returns the filename of the icon with index @idx.
 */
gchar *
e_icon_list_get_icon_filename (EIconList *eil, int idx)
{
	Icon *icon;

	g_return_val_if_fail (eil != NULL, NULL);
	g_return_val_if_fail (IS_EIL (eil), NULL);
	g_return_val_if_fail (idx >= 0 && idx < eil->_priv->icons, NULL);

	icon = g_array_index (eil->_priv->icon_list, Icon*, idx);
	return icon->icon_filename;
}


/**
 * e_icon_list_find_icon_from_filename:
 * @eil:       An icon list.
 * @filename:  Filename of an icon.
 *
 * Returns the index of the icon whose filename is @filename or -1 if
 * there is no icon with this filename.
 */
int
e_icon_list_find_icon_from_filename (EIconList *eil,
				     const gchar *filename)
{
	EIconListPrivate *priv;
	int n;
	Icon *icon;

	g_return_val_if_fail (eil != NULL, -1);
	g_return_val_if_fail (IS_EIL (eil), -1);
	g_return_val_if_fail (filename != NULL, -1);

	priv = eil->_priv;

	for (n = 0; n < priv->icon_list->len; n++) {
		icon = g_array_index(priv->icon_list, Icon*, n);
		if (!strcmp (icon->icon_filename, filename))
			return n;
	}

	return -1;
}

