#include <gtk/gtk.h>

#include "guiutils.h"
#include "fprompt.h"
#include "editclist.h"

#include "images/icon_add_16x16.xpm"
#include "images/icon_edit_16x16.xpm"
#include "images/icon_remove_16x16.xpm"
#include "images/icon_add_20x20.xpm"
#include "images/icon_edit_20x20.xpm"
#include "images/icon_remove_20x20.xpm"


/* Callbacks */
static gint EditCListButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
static void EditCListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
static void EditCListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
static void EditCListAddCB(
	GtkWidget *widget, gpointer data
);
static void EditCListEditCB(
	GtkWidget *widget, gpointer data
);
static void EditCListRemoveCB(
	GtkWidget *widget, gpointer data
);
static void EditCListShiftUpCB(
	GtkWidget *widget, gpointer data
);
static void EditCListShiftDownCB(
	GtkWidget *widget, gpointer data
);

static void EditCListFPromptCancelCB(gpointer data);
static void EditCListFPromptApplyCB(
	gpointer data, const gchar *value
);
static void EditCListFPromptMap(
	editclist_struct *ec, gint row, gint column
);


/* Utilities */
GtkWidget *EditCListGetCList(editclist_struct *ec);

/* Row/Cell Operations */
gint EditCListInsertRow(editclist_struct *ec, gint row);
gint EditCListAppendRow(editclist_struct *ec);
void EditCListSetCellText(
	editclist_struct *ec, gint row, gint column,
	const gchar *text
);
void EditCListSetCellPixText(
	editclist_struct *ec, gint row, gint column,
	const gchar *text, gint spacing,
	GdkPixmap *pixmap, GdkBitmap *mask
);
void EditCListSetCellPixmap(
	editclist_struct *ec, gint row, gint column,
	GdkPixmap *pixmap, GdkBitmap *mask
);
void EditCListGetCellText(
	editclist_struct *ec, gint row, gint column,
	gchar **text
);
void EditCListGetCellPixText(
	editclist_struct *ec, gint row, gint column,
	gchar **text, gint *spacing,
	GdkPixmap **pixmap, GdkBitmap **mask
);
void EditCListGetCellPixmap(
	editclist_struct *ec, gint row, gint column,
	GdkPixmap **pixmap, GdkBitmap **mask
);
void EditCListRemoveRow(editclist_struct *ec, gint row);
void EditCListClear(editclist_struct *ec);

void EditCListSetRowData(
	editclist_struct *ec, gint row,
	gpointer data, GtkDestroyNotify destroy_func_cb
);
gpointer EditCListGetRowData(editclist_struct *ec, gint row);

/* Front Ends */
editclist_struct *EditCListNew(
	GtkWidget *parent,              /* GtkBox or GtkContainer */
	gint width, gint height,
	gboolean expand, gboolean fill, guint padding,
	gchar **heading, gint columns
);
void EditCListSetCellSelectedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
);
void EditCListSetCellUnselectedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
);
void EditCListSetRowAddedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gpointer),
	gpointer data
);
void EditCListSetCellChangedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
);
void EditCListSetRowRemovedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gpointer),
	gpointer data
);
void EditCListUpdateMenus(editclist_struct *ec);
void EditCListMap(editclist_struct *ec);
void EditCListUnmap(editclist_struct *ec);
void EditCListDelete(editclist_struct *ec);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	GtkCList "button_press_event" or "button_release_event" signal
 *	callback.
 */
static gint EditCListButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if((widget == NULL) || (button == NULL) || (ec == NULL))
	    return(status);

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return(status);

	if(clist->clist_window != ((GdkEventAny *)button)->window)
	    return(status);

	/* Get event type */
	etype = button->type;

	/* Check which widget this event is for */
	if(widget == EDITCLIST_CLIST(ec))
	{
	    gint row, column;
	    GtkWidget *w;


	    /* Find row and column based on given coordinates */
	    if(!gtk_clist_get_selection_info(
		clist, (gint)button->x, (gint)button->y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Handle by event type */
	    switch(etype)
	    {
	      case GDK_BUTTON_PRESS:
		/* Handle by button number */
		switch(button->button)
		{
		  case 3:
		    /* Need to select this cell so that operations on
		     * menu operate on this cell
		     */
		    gtk_clist_select_row(clist, row, column);

		    /* Get right click menu widget and map it */
		    w = ec->menu;
		    if(w != NULL)
			gtk_menu_popup(
			    GTK_MENU(w), NULL, NULL,
			    NULL, NULL,
			    button->button, button->time
			);
		    break;

		  case 2:
		    /* Map the Float Prompt over the clicked on cell */
		    EditCListFPromptMap(ec, row, column);
		    break;
		}
		status = TRUE;
		break;

	      case GDK_2BUTTON_PRESS:
		/* Handle by button number */
		switch(button->button)
		{
		  case 1:
		    /* Map the Float Prompt over the clicked on cell */
		    EditCListFPromptMap(ec, row, column);
		    break;
		}
		status = TRUE;
		break;
	    }
	}

	return(status);
}

/*
 *	GtkCList "select_row" signal callback.
 */
static void EditCListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	/* Record selected row and column */
	ec->sel_row = row;
	ec->sel_column = column;

	/* Scroll if row is not visible */
	if(gtk_clist_row_is_visible(clist, row) != GTK_VISIBILITY_FULL)
	    gtk_clist_moveto(
		clist,
		row, -1,	/* Row, column */
		0.5f, 0.0f	/* Row, column */
	    );

	/* Call cell selected callback? */
	if(ec->cell_selected_cb != NULL)
	    ec->cell_selected_cb(
		ec,			/* Edit CList */
		row, column,		/* Row, Column */
		ec->cell_selected_data	/* Data */
	    );

	EditCListUpdateMenus(ec);
}

/*
 *      GtkCList "unselect_row" signal callback.
 */
static void EditCListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	/* Mark current row and column as unselected */
	ec->sel_row = -1;
	ec->sel_column = -1;

	/* Call cell unselected callback? */
	if(ec->cell_unselected_cb != NULL)
	    ec->cell_unselected_cb(
		ec,				/* Edit CList */
		row, column,			/* Row, Column */
		ec->cell_unselected_data	/* Data */
	    );

	EditCListUpdateMenus(ec);
}

/*
 *	Add row callback.
 */
static void EditCListAddCB(GtkWidget *widget, gpointer data)
{
	gint sel_row, new_row;
	GList *glist;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Get selected row */
	glist = clist->selection_end;
	sel_row = (glist != NULL) ? (gint)glist->data : -1;

	/* Check if selected row is in bounds */
	if((sel_row >= 0) && (sel_row < clist->rows))
	{
	    /* Insert row */
	    new_row = EditCListInsertRow(ec, sel_row);
	}
	else
	{
	    /* Append row */
	    new_row = EditCListAppendRow(ec);
	}

	/* Added new row? */
	if(new_row > -1)
	{
	    /* Select new row */
	    gtk_clist_select_row(clist, new_row, 0);
	}
}

/*
 *	Edit selected cell callback.
 */
static void EditCListEditCB(GtkWidget *widget, gpointer data)
{
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Map the Float Prompt over the last selected cell */
	EditCListFPromptMap(
	    ec, ec->sel_row, ec->sel_column
	);
}

/*
 *	Remove selected row callback.
 */
static void EditCListRemoveCB(GtkWidget *widget, gpointer data)
{
	gint sel_row;
	GList *glist;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Get selected row */
	glist = clist->selection_end;
	sel_row = (glist != NULL) ? (gint)glist->data : -1;

	/* Got selected row? */
	if(sel_row > -1)
	{
	    /* Remove row */
	    EditCListRemoveRow(ec, sel_row);
	}
}

/*
 *	Shift selected row up callback.
 */
static void EditCListShiftUpCB(GtkWidget *widget, gpointer data)
{
	gint sel_row;
	GList *glist;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Get selected row */
	glist = clist->selection_end;
	sel_row = (glist != NULL) ? (gint)glist->data : -1;

	/* Can shift up? */
	if(sel_row > 0)
	{
	    /* Shift selected row up */
	    gtk_clist_swap_rows(clist, sel_row, sel_row - 1);

	    /* Call row changed callback */
	    if(ec->cell_changed_cb != NULL)
	    {
		ec->cell_changed_cb(
		    ec, sel_row, 0, ec->cell_changed_data
		);
		ec->cell_changed_cb(
		    ec, sel_row - 1, 0, ec->cell_changed_data
		);
	    }
	}

	EditCListUpdateMenus(ec);
}

/*
 *	Shift selected row down callback.
 */
static void EditCListShiftDownCB(GtkWidget *widget, gpointer data)
{
	gint sel_row;
	GList *glist;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Get selected row */
	glist = clist->selection_end;
	sel_row = (glist != NULL) ? (gint)glist->data : -1;

	/* Can shift down? */
	if((sel_row > -1) && (sel_row < (clist->rows - 1)))
	{
	    /* Shift selected row down */
	    gtk_clist_swap_rows(clist, sel_row, sel_row + 1);

	    /* Call row changed callback */
	    if(ec->cell_changed_cb != NULL)
	    {
		ec->cell_changed_cb(
		    ec, sel_row, 0, ec->cell_changed_data
		);
		ec->cell_changed_cb(
		    ec, sel_row + 1, 0, ec->cell_changed_data
		);
	    }
	}

	EditCListUpdateMenus(ec);
}


/*
 *	Float Prompt cancel callback.
 */
static void EditCListFPromptCancelCB(gpointer data)
{
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	/* Do nothing */
}

/*
 *	Float Prompt apply callback.
 */
static void EditCListFPromptApplyCB(gpointer data, const gchar *value)
{
	GtkCList *clist;
	gint row, column;
	editclist_struct *ec = EDITCLIST(data);
	if(ec == NULL)
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	row = ec->fprompt_row;
	column = ec->fprompt_column;

	/* Inputs valid? */
	if((ec != NULL) && (clist != NULL) && (row >= 0) && (column >= 0) &&
	   (value != NULL)
	)
	{
	    gchar *s;
	    guint8 spacing;
	    GdkPixmap *pixmap;
	    GdkBitmap *mask;

	    /* Set new value from float prompt to the selected cell
	     * on the clist
	     */
	    switch(gtk_clist_get_cell_type(clist, row, column))
	    {
	      case GTK_CELL_TEXT:
		gtk_clist_set_text(
		    clist, row, column, value
		);
		break;
	      case GTK_CELL_PIXTEXT:
		gtk_clist_get_pixtext(
		    clist, row, column, &s,
		    &spacing, &pixmap, &mask
		);
		gtk_clist_set_pixtext(
		    clist, row, column, value,
		    spacing, pixmap, mask
		);
		break;
	      case GTK_CELL_PIXMAP:
	      case GTK_CELL_WIDGET:
	      case GTK_CELL_EMPTY:
		break;
	    }

	    /* Call cell changed callback */
	    if(ec->cell_changed_cb != NULL)
		ec->cell_changed_cb(
		    ec,				/* Edit CList */
		    row, column,		/* Row, Column */
		    ec->cell_changed_data	/* Data */
		);

	    EditCListUpdateMenus(ec);
	}
}

/*
 *	Maps the Float Prompt over the specified cell on the Edit
 *	CList.
 */
static void EditCListFPromptMap(
	editclist_struct *ec, gint row, gint column
)
{
	gint cx, cy, px, py, pwidth, pheight;
	gchar *s, *value;
	GtkCList *clist;
	guint8 spacing;
	GdkPixmap *pixmap;
	GdkBitmap *mask;

	if((ec == NULL) || FPromptIsQuery())
	    return;

	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Cell exists? */
	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	    return;

	/* Get cell geometry */
	if(!gtk_clist_get_cell_geometry(
	    clist, column, row,
	    &cx, &cy, &pwidth, &pheight
	))
	    return;

	/* Get root window relative coordinates */
	px = 0;
	py = 0;
	gdk_window_get_deskrelative_origin(
	    clist->clist_window, &px, &py
	);
	px += cx + 0;
	py += cy - 2;   /* Move up a little */


	/* Get cell text (if any) */
	switch(gtk_clist_get_cell_type(clist, row, column))
	{
	  case GTK_CELL_TEXT:
	    gtk_clist_get_text(
		clist, row, column, &s
	    );
	    break;
	  case GTK_CELL_PIXTEXT:
	    gtk_clist_get_pixtext(
		clist, row, column, &s,
		&spacing, &pixmap, &mask
	    );
	    break;
	  case GTK_CELL_PIXMAP:
	  case GTK_CELL_WIDGET:
	  case GTK_CELL_EMPTY:
	    s = NULL;
	    break;
	}
	value = STRDUP(s);


	/* Is the clicked on column the name column or given column is -1
	 * implying any column is acceptable?
	 */
	if(value != NULL)
	{
	    GtkWidget *toplevel = gtk_widget_get_toplevel(ec->toplevel);

	    /* Record row and column float prompt mapped over */
	    ec->fprompt_row = row;
	    ec->fprompt_column = column;

	    /* Map Float Prompt to change values */
	    FPromptSetTransientFor(toplevel);
	    FPromptSetPosition(px, py);
	    FPromptMapQuery(
		NULL,			/* No label */
		value,			/* Initial value */
		NULL,			/* No tooltip message */
		FPROMPT_MAP_NO_MOVE,	/* Map code */
		pwidth, -1,		/* Width and height */
		0,			/* No buttons */
		ec,			/* Data */
		NULL,			/* No browse callback */
		EditCListFPromptApplyCB,
		EditCListFPromptCancelCB
	    );
	}

	g_free(value);
}


/*
 *	Gets the Edit CList's GtkCList.
 */
GtkWidget *EditCListGetCList(editclist_struct *ec)
{
	return(EDITCLIST_CLIST(ec));
}


/*
 *	Inserts a new row on the Edit CList.
 *
 *	Returns the index of the new row or -1 on error.
 */
gint EditCListInsertRow(editclist_struct *ec, gint row)
{
	gint i, new_row;
	gchar **strv;
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return(-1);

	if(clist->columns < 1)
	    return(-1);

	if(row < 0)
	    row = 0;

	/* Allocate row cell values */
	strv = (gchar **)g_malloc(clist->columns * sizeof(gchar *));
	for(i = 0; i < clist->columns; i++)
	    strv[i] = "";

	/* Insert row */
	new_row = gtk_clist_insert(clist, row, strv);

	/* Delete row cell values */
	g_free(strv);

	/* New row acced? */
	if(new_row > -1)
	{
	    /* Call row changed callback */
	    if(ec->row_added_cb != NULL)
		ec->row_added_cb(
		    ec,			/* Edit CList */
		    new_row,		/* Row */
		    ec->row_added_data	/* Data */
		);
	}

	EditCListUpdateMenus(ec);

	return(new_row);
}

/*
 *	Appends a new row on the Edit CList.
 *
 *	Returns the index of the new row or -1 on error.
 */
gint EditCListAppendRow(editclist_struct *ec)
{
	gint i, new_row;
	gchar **strv;
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return(-1);

	if(clist->columns < 1)
	    return(-1);

	/* Allocate row cell values */
	strv = (gchar **)g_malloc(clist->columns * sizeof(gchar *));
	for(i = 0; i < clist->columns; i++)
	    strv[i] = "";

	/* Append row */
	new_row = gtk_clist_append(clist, strv);

	/* Delete row cell values */
	g_free(strv);

	/* New row acced? */
	if(new_row > -1)
	{
	    /* Call row changed callback */
	    if(ec->row_added_cb != NULL)
		ec->row_added_cb(
		    ec,                 /* Edit CList */
		    new_row,            /* Row */
		    ec->row_added_data  /* Data */
		);
	}

	EditCListUpdateMenus(ec);

	return(new_row);
}

/*
 *	Sets the Edit CList's cell text.
 */
void EditCListSetCellText(
	editclist_struct *ec, gint row, gint column,
	const gchar *text
)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Cell exists? */
	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	    return;

	/* Set cell text */
	gtk_clist_set_text(
	    clist, row, column,
	    (text != NULL) ? text : ""
	);

	EditCListUpdateMenus(ec);
}

/*
 *	Sets the Edit CList's cell pixtext.
 */
void EditCListSetCellPixText(
	editclist_struct *ec, gint row, gint column,
	const gchar *text, gint spacing,
	GdkPixmap *pixmap, GdkBitmap *mask
)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Cell exists? */
	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	    return;

	/* Set cell pixtext */
	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, row, column,
		(text != NULL) ? text : "",
		(guint8)spacing,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column,
		(text != NULL) ? text : ""
	    );

	EditCListUpdateMenus(ec);
}

/*
 *	Sets the Edit CList's cell pixmap.
 */
void EditCListSetCellPixmap(
	editclist_struct *ec, gint row, gint column,
	GdkPixmap *pixmap, GdkBitmap *mask
)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Cell exists? */
	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	    return;

	/* Set cell pixtext */
	if(pixmap != NULL)
	    gtk_clist_set_pixmap(
		clist, row, column,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column, ""
	    );

	EditCListUpdateMenus(ec);
}

/*
 *	Gets the Edit CList's cell text.
 */
void EditCListGetCellText(
	editclist_struct *ec, gint row, gint column,
	gchar **text
)
{
	EditCListGetCellPixText(
	    ec, row, column,
	    text, NULL,
	    NULL, NULL
	);
}

/*
 *	Gets the Edit CList's cell pixtext.
 */
void EditCListGetCellPixText(
	editclist_struct *ec, gint row, gint column,
	gchar **text, gint *spacing,
	GdkPixmap **pixmap, GdkBitmap **mask
)
{
	gchar *ltext = NULL;
	guint8 lspacing = 0;
	GdkPixmap *lpixmap = NULL;
	GdkBitmap *lmask = NULL;
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);

#define UPDATE_RETURNS	{	\
 if(text != NULL)		\
  *text = ltext;		\
 if(spacing != NULL)		\
  *spacing = lspacing;		\
 if(pixmap != NULL)		\
  *pixmap = lpixmap;		\
 if(mask != NULL)		\
  *mask = lmask;		\
}

	if(clist == NULL)
	{
	    UPDATE_RETURNS
	    return;
	}

	if((row < 0) || (row >= clist->rows) ||
	   (column < 0) || (column >= clist->columns)
	)
	{
	    UPDATE_RETURNS
	    return;
	}

	switch(gtk_clist_get_cell_type(clist, row, column))
	{
	  case GTK_CELL_TEXT:
	    gtk_clist_get_text(
		clist, row, column, &ltext
	    );
	    break;
	  case GTK_CELL_PIXTEXT:
	    gtk_clist_get_pixtext(
		clist, row, column, &ltext,
		&lspacing, &lpixmap, &lmask
	    );
	    break;
	  case GTK_CELL_PIXMAP:
	    gtk_clist_get_pixmap(
		clist, row, column,
		&lpixmap, &lmask
	    );
	    break;
	  case GTK_CELL_WIDGET:
	  case GTK_CELL_EMPTY:
	    break;
	}

	UPDATE_RETURNS

#undef UPDATE_RETURNS
}

/*
 *	Gets the Edit CList's cell pixmap.
 */
void EditCListGetCellPixmap(
	editclist_struct *ec, gint row, gint column,
	GdkPixmap **pixmap, GdkBitmap **mask
)
{
	EditCListGetCellPixText(
	    ec, row, column,
	    NULL, NULL,
	    pixmap, mask
	);
}

/*
 *	Removes a row from the Edit CList.
 */
void EditCListRemoveRow(editclist_struct *ec, gint row)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	/* Specified row exists? */
	if((row < 0) || (row >= clist->rows))
	    return;

	/* Call row removed callback? */
	if(ec->row_removed_cb != NULL)
	    ec->row_removed_cb(
		ec,			/* Edit CList */
		row,			/* Row */
		ec->row_removed_data	/* Data */
	    );

	/* Remove row */
	gtk_clist_remove(clist, row);

	EditCListUpdateMenus(ec);
}

/*
 *	Removes all rows from the Edit CList.
 */
void EditCListClear(editclist_struct *ec)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	if(clist->rows <= 0)
	    return;

	/* Call row removed callback? */
	if(ec->row_removed_cb != NULL)
	{
	    gint i, m;

	    for(i = 0, m = clist->rows; i < m; i++)
		ec->row_removed_cb(
		    ec,				/* Edit CList */
		    i,				/* Row */
		    ec->row_removed_data	/* Data */
	    );
	}

	/* Remove all rows */
	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);

	EditCListUpdateMenus(ec);
}


/*
 *	Sets Edit CList's row data.
 */
void EditCListSetRowData(
	editclist_struct *ec, gint row,
	gpointer data, GtkDestroyNotify destroy_func_cb
)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return;

	if((row < 0) || (row >= clist->rows))
	    return;

	if(destroy_func_cb != NULL)
	    gtk_clist_set_row_data_full(
		clist, row, data, destroy_func_cb
	    );
	else
	    gtk_clist_set_row_data(
		clist, row, data
	    );
}

/*
 *	Gets the Edit CList's row data.
 */
gpointer EditCListGetRowData(editclist_struct *ec, gint row)
{
	GtkCList *clist = (GtkCList *)EDITCLIST_CLIST(ec);
	if(clist == NULL)
	    return(NULL);

	return(gtk_clist_get_row_data(clist, row));
}


/*
 *	Creates a new Edit Clist.
 */
editclist_struct *EditCListNew(
	GtkWidget *parent,              /* GtkBox or GtkContainer */
	gint width, gint height,
	gboolean expand, gboolean fill, guint padding,
	gchar **heading, gint columns
)
{
	GtkWidget *w, *menu, *parent2, *parent3, *parent4;
	GtkCList *clist;
	editclist_struct *ec = EDITCLIST(
	    g_malloc0(sizeof(editclist_struct))
	);
	if(ec == NULL)
	    return(ec);

	/* Reset values */
	ec->sel_row = -1;
	ec->sel_column = -1;

	ec->fprompt_row = -1;
	ec->fprompt_column = -1;

	ec->cell_selected_cb = NULL;
	ec->cell_selected_data = NULL;
	ec->cell_unselected_cb = NULL;
	ec->cell_unselected_data = NULL;
	ec->row_added_cb = NULL;
	ec->row_added_data = NULL;
	ec->cell_changed_cb = NULL;
	ec->cell_changed_data = NULL;
	ec->row_removed_cb = NULL;
	ec->row_removed_data = NULL;


	/* Begin creating widgets */

	/* Main vbox */
	ec->toplevel = w = gtk_vbox_new(FALSE, 0);
	gtk_widget_set_usize(w, width, height);
	if(parent != NULL)
	{
	    if(GTK_IS_BOX(parent))
		gtk_box_pack_start(
		    GTK_BOX(parent), w, expand, fill, padding
		);
	    else if(GTK_IS_CONTAINER(parent))
		gtk_container_add(GTK_CONTAINER(parent), w);
	}
	parent2 = w;

	/* Hbox for buttons */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;
	/* Alignment to align to the right */
	w = gtk_alignment_new(1.0f, 0.5f, 0.0f, 0.0f);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent3 = w;
	/* Buttons set hbox */
	w = gtk_hbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent3 = w;

	/* Add Button */
	ec->add_btn = w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)icon_add_16x16_xpm
	);
	gtk_widget_set_usize(w, 16 + (2 * 2), 16 + (2 * 2));
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(EditCListAddCB), ec
	);
	gtk_widget_show(w);

	/* Edit Button */
	ec->edit_btn = w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)icon_edit_16x16_xpm
	);
	gtk_widget_set_usize(w, 16 + (2 * 2), 16 + (2 * 2));
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(EditCListEditCB), ec
	);
	gtk_widget_show(w);

	/* Remove Button */
	ec->remove_btn = w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)icon_remove_16x16_xpm
	);
	gtk_widget_set_usize(w, 16 + (2 * 2), 16 + (2 * 2));
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(EditCListRemoveCB), ec
	);
	gtk_widget_show(w);

	/* Shift Up Button */
	ec->up_btn = w = gtk_button_new();
	gtk_widget_set_usize(w, 16 + (2 * 2), 16 + (2 * 2));
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(EditCListShiftUpCB), ec
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);

	/* Shift Down Button */
	ec->down_btn = w = gtk_button_new();
	gtk_widget_set_usize(w, 16 + (2 * 2), 16 + (2 * 2));
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(EditCListShiftDownCB), ec
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);



	/* Scrolled window for clist */
	w = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC,
	    GTK_POLICY_AUTOMATIC
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* CList */
	ec->clist = w = gtk_clist_new_with_titles(columns, heading);
	clist = GTK_CLIST(w);
	gtk_widget_add_events(w, GDK_BUTTON_PRESS_MASK);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(EditCListButtonPressEventCB), ec
	);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_signal_connect(
	    GTK_OBJECT(w), "select_row",
	    GTK_SIGNAL_FUNC(EditCListSelectRowCB), ec
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "unselect_row",
	    GTK_SIGNAL_FUNC(EditCListUnselectRowCB), ec
	);
	gtk_widget_show(w);


	/* Right-click menu for clist */
	ec->menu = menu = (GtkWidget *)GUIMenuCreate();
	if(menu != NULL)
	{
	    GtkAccelGroup *accelgrp = NULL;
	    guint accel_key, accel_mods;
	    guint8 **icon;
	    const gchar *label;
	    void (*func_cb)(GtkWidget *, gpointer);
	    gpointer data = ec;

#define DO_ADD_MENU_ITEM_LABEL				\
{ w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,		\
  icon, label, accel_key, accel_mods, NULL,		\
  data, func_cb						\
 ); }
#define DO_ADD_MENU_SEP					\
{ w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,		\
  NULL, NULL, 0, 0, NULL,				\
  NULL, NULL						\
 ); }

	    icon = (guint8 **)icon_add_20x20_xpm;
	    label = "Add";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = EditCListAddCB;
	    DO_ADD_MENU_ITEM_LABEL
	    ec->add_mi = w;

	    icon = (guint8 **)icon_edit_20x20_xpm;
	    label = "Edit";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = EditCListEditCB;
	    DO_ADD_MENU_ITEM_LABEL
	    ec->edit_mi = w;

	    icon = (guint8 **)icon_remove_20x20_xpm;
	    label = "Remove";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = EditCListRemoveCB;
	    DO_ADD_MENU_ITEM_LABEL
	    ec->remove_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Shift Up";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = EditCListShiftUpCB;
	    DO_ADD_MENU_ITEM_LABEL
	    ec->up_mi = w;

	    icon = NULL;
	    label = "Shift Down";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = EditCListShiftDownCB;
	    DO_ADD_MENU_ITEM_LABEL
	    ec->down_mi = w;

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_SEP
	}


	EditCListUpdateMenus(ec);

	return(ec);
}

/*
 *	Sets the Edit CList's cell selected callback.
 */
void EditCListSetCellSelectedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
)
{
	if(ec == NULL)
	    return;

	ec->cell_selected_cb = func_cb;
	ec->cell_selected_data = data;
}

/*
 *	Sets the Edit CList's cell unselected callback.
 */
void EditCListSetCellUnselectedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
)
{
	if(ec == NULL)
	    return;

	ec->cell_unselected_cb = func_cb;
	ec->cell_unselected_data = data;
}

/*
 *      Sets the Edit CList's row added callback.
 */
void EditCListSetRowAddedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gpointer),
	gpointer data
)
{
	if(ec == NULL)
	    return;

	ec->row_added_cb = func_cb;
	ec->row_added_data = data;
}

/*
 *	Sets the Edit CList's cell changed callback.
 */
void EditCListSetCellChangedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gint, gpointer),
	gpointer data
)
{
	if(ec == NULL)
	    return;

	ec->cell_changed_cb = func_cb;
	ec->cell_changed_data = data;
}

/*
 *	Sets the Edit CList's row removed callback.
 */
void EditCListSetRowRemovedCB(
	editclist_struct *ec,
	void (*func_cb)(editclist_struct *, gint, gpointer),
	gpointer data
)
{
	if(ec == NULL)
	    return;

	ec->row_removed_cb = func_cb;
	ec->row_removed_data = data;
}

/*
 *	Updates the Edit Clist's widgets to reflect current values.
 */
void EditCListUpdateMenus(editclist_struct *ec)
{
	gboolean sensitive;
	gint sel_row;
	GList *glist;
	GtkCList *clist;

	if(ec == NULL)
	    return;

	/* Get selected row on clist */
	clist = (GtkCList *)EDITCLIST_CLIST(ec);
	glist = (clist != NULL) ? clist->selection_end : NULL;
	sel_row = (glist != NULL) ? (gint)glist->data : -1;

	/* Update widgets */
	sensitive = (sel_row > -1) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(ec->edit_btn, sensitive)
	GTK_WIDGET_SET_SENSITIVE(ec->remove_btn, sensitive)
	GTK_WIDGET_SET_SENSITIVE(ec->edit_mi, sensitive)
	GTK_WIDGET_SET_SENSITIVE(ec->remove_mi, sensitive)

	sensitive = (sel_row > 0) ? TRUE : FALSE;
/*	GTK_WIDGET_SET_SENSITIVE(ec->up_btn, sensitive) */
	GTK_WIDGET_SET_SENSITIVE(ec->up_mi, sensitive)

	sensitive = ((sel_row > -1) && (sel_row < (clist->rows - 1))) ?
	    TRUE : FALSE;
/*	GTK_WIDGET_SET_SENSITIVE(ec->down_btn, sensitive) */
	GTK_WIDGET_SET_SENSITIVE(ec->down_mi, sensitive)
}

/*
 *	Maps the Edit Clist.
 */
void EditCListMap(editclist_struct *ec)
{
	GtkWidget *w = EDITCLIST_TOPLEVEL(ec);
	if(w == NULL)
	    return;

	gtk_widget_show(w);
}

/*
 *	Unmaps the Edit Clist.
 */
void EditCListUnmap(editclist_struct *ec)
{
	GtkWidget *w = EDITCLIST_TOPLEVEL(ec);
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
}

/*
 *	Deletes the Edit Clist.
 */
void EditCListDelete(editclist_struct *ec)
{
	if(ec == NULL)
	    return;

	/* Remove all rows */
	EditCListClear(ec);

	/* Begin destroying widgets */
	GTK_WIDGET_DESTROY(ec->menu)

        GTK_WIDGET_DESTROY(ec->clist)

        GTK_WIDGET_DESTROY(ec->add_btn)
        GTK_WIDGET_DESTROY(ec->edit_btn)
        GTK_WIDGET_DESTROY(ec->remove_btn)
        GTK_WIDGET_DESTROY(ec->up_btn)
        GTK_WIDGET_DESTROY(ec->down_btn)

        GTK_WIDGET_DESTROY(ec->toplevel)

	g_free(ec);
}
