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

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

#include "guiutils.h"

#include "stacklist.h"

#include "images/icon_add_20x20.xpm"
#include "images/icon_remove_20x20.xpm"


#ifndef DEBUG_LEVEL
# define DEBUG_LEVEL	0
#endif


/* Callbacks */
static void StackListAddCB(GtkWidget *widget, gpointer data);
static void StackListRemoveCB(GtkWidget *widget, gpointer data);
static void StackListShiftUpCB(GtkWidget *widget, gpointer data);
static void StackListShiftDownCB(GtkWidget *widget, gpointer data);
static gint StackListCListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint StackListCListButtonEventMapMenuCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
static void StackListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
static void StackListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);

static stack_list_item_struct *StackListMatchItemByID(
	stack_list_struct *slist, gint id
);
static void StackListSetDescriptionFromItem(
	stack_list_struct *slist, stack_list_item_struct *item
);


/* Staci List Item */
void StackListAppend(
	stack_list_struct *slist,
	const gchar *name,
	const gchar *description,
	guint8 **icon_data,
	gpointer client_data,
	gint id,
	gboolean allow_multiple, gboolean stay_on_target
);
void StackListClear(stack_list_struct *slist);

void StackListItemAppendSrc(stack_list_struct *slist, gint id);
void StackListItemAppendTar(stack_list_struct *slist, gint id);
void StackListItemSetAllFromCacheSrc(stack_list_struct *slist);

void StackListItemRemoveByIDSrc(
	stack_list_struct *slist, gint id,
	gboolean exclude_allowed_multiples
);
void StackListItemRemoveByIDTar(
	stack_list_struct *slist, gint id,
	gboolean exclude_allowed_multiples
);

void StackListItemClearSrc(stack_list_struct *slist);
void StackListItemClearTar(stack_list_struct *slist);

gint *StackListItemGetSrc(stack_list_struct *slist, gint *total);
gint *StackListItemGetTar(stack_list_struct *slist, gint *total);


/* Stack List */
stack_list_struct *StackListNew(
	GtkWidget *parent,
	const gchar *src_title,
	const gchar *tar_title
);
void StackListShowDescription(
	stack_list_struct *slist,
	gboolean show_src_desc,
	gboolean show_tar_desc
);
void StackListSetMaintainSourceOrder(
	stack_list_struct *slist, gboolean maintain_source_order
);
void StackListSetChangedCB(
	stack_list_struct *slist, 
	void (*cb)(
		stack_list_struct *,            /* Stack List */
		gpointer                        /* Data */
	),
	gpointer data   
);
void StackListSetAddedCB(
	stack_list_struct *slist,  
	void (*cb)(
		stack_list_struct *,            /* Stack List */
		stack_list_item_struct *,       /* Stack List Item */
		gpointer                        /* Data */
	),
	gpointer data
);
void StackListSetRemovedCB(
	stack_list_struct *slist, 
	void (*cb)(
		stack_list_struct *,            /* Stack List */
		stack_list_item_struct *,       /* Stack List Item */
		gpointer                        /* Data */
	),                                                
	gpointer data
);
void StackListUpdateMenus(stack_list_struct *slist);
gboolean StackListMapped(stack_list_struct *slist);
void StackListMap(stack_list_struct *slist);
void StackListUnmap(stack_list_struct *slist);
void StackListDelete(stack_list_struct *slist);


#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)


#define STACK_LIST_ROW_SPACING		20

#define STACK_LIST_ARROW_WIDTH		20
#define STACK_LIST_ARROW_HEIGHT		20


/*
 *	Add callback.
 *
 *	Inserts all selected items from the source list to the target
 *	list at the last selected item on the target list, or appends
 *	them to the target list if nothing is selected on the target
 *	list.
 */
static void StackListAddCB(GtkWidget *widget, gpointer data)
{
	gchar **strv;
	gint i, src_row, tar_row, last_new_row = -1;
	GList *glist;
	GtkCList *src_clist, *tar_clist;
	stack_list_item_struct *item;
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListAddCB(): slist=0x%.8x\n", (guint32)slist
);
#endif

	/* Get source and target clists */
	src_clist = (GtkCList *)slist->src_clist;
	tar_clist = (GtkCList *)slist->tar_clist;
	if((src_clist == NULL) || (tar_clist == NULL))
	    return;

	if((src_clist->columns <= 0) || (tar_clist->columns <= 0))
	    return;

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

	/* Get the target row index to insert at or -1 for append */
	glist = tar_clist->selection_end;
	tar_row = (glist != NULL) ? (gint)glist->data : -1;

#if 0
/* Do not unselect any target rows, this makes placement more convient
 * for subsequent adds
 */
	/* Unselect all rows on the target list */
	gtk_clist_unselect_all(tar_clist);
#endif

	/* Iterate through selected rows on the source list */
	while(src_clist->selection != NULL)
	{
	    /* Get selected source row index and item */
	    src_row = (gint)src_clist->selection->data;
	    item = STACK_LIST_ITEM(
		gtk_clist_get_row_data(src_clist, src_row)
	    );

	    gtk_clist_freeze(src_clist);
	    gtk_clist_freeze(tar_clist);

	    if(item != NULL)
	    {
		/* Create a new row on the target list for this item */
		const gint new_row = (tar_row > -1) ?
		    gtk_clist_insert(tar_clist, tar_row, strv) :
		    gtk_clist_append(tar_clist, strv);
#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListAddCB(): Adding source row %i to target row %i for item \"%s\".\n",
 src_row, tar_row, item->name
);
#endif
		if(new_row > -1)
		{
		    /* Set new target row cell values */
		    gint column = 0;
		    if(column < tar_clist->columns)
		    {
		        if((item->icon_pixmap != NULL) &&
		           (item->name != NULL)
		        )
			    gtk_clist_set_pixtext(
			        tar_clist, new_row, column,
			        item->name,
			        2,
			        item->icon_pixmap, item->icon_mask
			    );
		        else if(item->icon_pixmap != NULL)
			    gtk_clist_set_pixmap(
			        tar_clist, new_row, column,
			        item->icon_pixmap, item->icon_mask
			    );
		        else if(item->name != NULL)
			    gtk_clist_set_text(
			        tar_clist, new_row, column,
			        item->name
			    );
		    }

		    /* Record last new row */
		    last_new_row = new_row;

		    /* Set row data, but no destroy function since the
		     * row data is shared and not suppose to be
		     * deleted when the clist row is deleted
		     */
		    gtk_clist_set_row_data(tar_clist, new_row, item);

#if 0
/* Do not select the new target row, this makes placement more convient
 * for subsequent adds
 */
		    gtk_clist_select_row(tar_clist, new_row, 0);
#endif
		}

		/* If multiple occurances of the item is not allowed
		 * then remove this item from the source list
		 */
		if(!item->allow_multiple)
		    gtk_clist_remove(src_clist, src_row);
		else
		    gtk_clist_unselect_row(src_clist, src_row, 0);
	    }

	    gtk_clist_thaw(src_clist);
	    gtk_clist_thaw(tar_clist);
	}

	/* If there is a last new row on the target list then scroll
	 * to it as needed
	 */
	if(last_new_row > -1)
	{
	    const gint row = last_new_row;
	    if(gtk_clist_row_is_visible(tar_clist, row) !=
		GTK_VISIBILITY_FULL
	    )
		gtk_clist_moveto(
		    tar_clist,
		    row, -1,		/* Row, column */
		    0.5f, 0.0f		/* Row, column */
		);
	}

	StackListUpdateMenus(slist);

	/* Call changed callback */
	if(slist->changed_cb != NULL)
	    slist->changed_cb(
		slist,			/* Stack List */
		slist->changed_data	/* Data */
	    );

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

/*
 *	Remove callback.
 *
 *	Removes the selected items from the target list and (if
 *	item->allow_multiple is FALSE) puts them back in order on the
 *	source list.
 */
static void StackListRemoveCB(GtkWidget *widget, gpointer data)
{
	gchar **strv;
	gint i, tar_row, last_new_row = -1;
	GtkCList *src_clist, *tar_clist;
	stack_list_flags flags;
	stack_list_item_struct *item;
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListRemoveCB(): slist=0x%.8x\n", (guint32)slist
);
#endif

	flags = slist->flags;

	/* Get source and target clists */
	src_clist = (GtkCList *)slist->src_clist;
	tar_clist = (GtkCList *)slist->tar_clist;
	if((src_clist == NULL) || (tar_clist == NULL))
	    return;

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

#if 0 
/* Do not unselect any source rows, this makes placement more convient
 * for subsequent removes
 */
	/* Unselect all rows on the source list */
	gtk_clist_unselect_all(src_clist);
#endif

	/* Iterate through selected rows on the target list */
	while(tar_clist->selection != NULL)
	{
	    /* Get selected target row index and item */
	    tar_row = (gint)tar_clist->selection->data;
	    item = STACK_LIST_ITEM(gtk_clist_get_row_data(
		tar_clist, tar_row
	    ));

	    gtk_clist_freeze(src_clist);
	    gtk_clist_freeze(tar_clist);

	    /* Does the item need to be put back on the source list? */
	    if((item != NULL) ?
		(!item->allow_multiple && !item->stay_on_target) : FALSE
	    )
	    {
		gint new_row, src_row;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListRemoveCB(): Placing item \"%s\" back to source list\n",
 item->name
);
#endif
		/* Need to maintain order of items on the source list? */
 		if(flags & STACK_LIST_MAINTAIN_SOURCE_ORDER)
		{
		    /* Put the item back on the source list, find the
		     * row index on the source list where we should put
		     * it by checking the Stack List Item cache
		     */
		    const gint	id = item->id,
				total_items = slist->total_items;
		    stack_list_item_struct *src_item;

		    src_row = 0;
		    for(i = 0; i < total_items; i++)
		    {
			src_item = slist->item[i];
		        if(src_item == NULL)
			    continue;

			/* If this cache item is on the source list then
			 * increment src_row
			 */
			if(gtk_clist_find_row_from_data(src_clist, src_item) > -1)
			    src_row++;

			/* Stop iterating when we encounter this item
			 * item in the Stack List Item cache
			 */
			if(src_item->id == id)
			    break;
		    }

#if (DEBUG_LEVEL >= 2)
g_print(
 "StackListRemoveCB(): Maintaining source list order, placing item at row %i\n",
 src_row
);
#endif
		}
		else
		{
		    GList *glist = src_clist->selection_end;
		    src_row = (glist != NULL) ? (gint)glist->data : -1;
#if (DEBUG_LEVEL >= 2)
g_print(
 "StackListRemoveCB(): Placing item back to source list at row %i\n",
 src_row
);
#endif
		}

		/* Insert or append item back into the source list */
		if((src_row >= 0) && (src_row < src_clist->rows))
		    new_row = gtk_clist_insert(src_clist, src_row, strv);
		else
		    new_row = gtk_clist_append(src_clist, strv);
		if(new_row > -1)
		{
		    /* Set new source row cell values */
		    gint column = 0;
		    if(column < src_clist->columns)
		    {
		        if((item->icon_pixmap != NULL) &&
		           (item->name != NULL)
		        )
			    gtk_clist_set_pixtext(
			        src_clist, new_row, column,
			        item->name,
			        2,
			        item->icon_pixmap, item->icon_mask
			    );
		        else if(item->icon_pixmap != NULL)
			    gtk_clist_set_pixmap(
			        src_clist, new_row, column,
			        item->icon_pixmap, item->icon_mask
			    );
		        else if(item->name != NULL)
			    gtk_clist_set_text(
			        src_clist, new_row, column,
			        item->name
			    );
		    }

		    /* Record last new row */
		    last_new_row = new_row;   

		    /* Set row data, but no destroy function since the
		     * row data is shared and not suppose to be
		     * deleted when the clist row is deleted
		     */
		    gtk_clist_set_row_data(src_clist, new_row, item);

#if 0
/* Do not select the new source row, this makes placement more convient
 * for subsequent removes
 */
		    gtk_clist_select_row(src_clist, new_row, 0);
#endif
		}
	    }

	    /* Remove or unselect this row from target list? */
	    if((item != NULL) ? !item->stay_on_target : FALSE)
		gtk_clist_remove(tar_clist, tar_row);
	    else
		gtk_clist_unselect_row(tar_clist, tar_row, 0);

	    gtk_clist_thaw(src_clist);
	    gtk_clist_thaw(tar_clist);
	}

	/* If there is a last new row on the source list then scroll
	 * to it as needed
	 */
	if(last_new_row > -1)
	{
	    const gint row = last_new_row;
	    if(gtk_clist_row_is_visible(src_clist, row) !=
		GTK_VISIBILITY_FULL
	    )
		gtk_clist_moveto(
		    src_clist,
		    row, -1,		/* Row, column */
		    0.5f, 0.0f		/* Row, column */
		);
	}

	StackListUpdateMenus(slist);

	/* Call changed callback */
	if(slist->changed_cb != NULL)
	    slist->changed_cb(
		slist,			/* Stack List */
		slist->changed_data	/* Data */
	    );

	/* Delete row cell value */
	g_free(strv);
}

/*
 *	Shift last selected target item up callback.
 */
static void StackListShiftUpCB(GtkWidget *widget, gpointer data)
{
	gint row, rows;
	GList *glist, *selection;
	GtkCList *clist;
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(             
 "StackListShiftUpCB(): slist=0x%.8x\n", (guint32)slist
);
#endif

	/* Get target list */
	clist = (GtkCList *)slist->tar_clist;
	if(clist == NULL)
	    return;

	/* Recreate a selection list that is ordered from first to
	 * last row
	 */
	selection = NULL;
	rows = clist->rows;
	for(row = 0; row < clist->rows; row++)
	{
	    /* Is this row selected? */
	    for(glist = clist->selection;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		if((gint)glist->data != row)
		    continue;

		selection = g_list_append(selection, (gpointer)row);
		break;
	    }
	}
	if(selection == NULL)
	    return;

	/* If the first row is already at top then do not shift */
	if((gint)selection->data <= 0)
	{
	    g_list_free(selection);
	    return;
	}

	/* Iterate through selection list and shift rows up */
	gtk_clist_freeze(clist);
	for(glist = selection; glist != NULL; glist = g_list_next(glist))
	{
	    row = (gint)glist->data;
	    gtk_clist_swap_rows(clist, row, row - 1);
	}
	gtk_clist_thaw(clist);

	/* Delete selection list */
	g_list_free(selection);

	StackListUpdateMenus(slist);

	/* Call changed callback */
	if(slist->changed_cb != NULL)
	    slist->changed_cb(
		slist,			/* Stack List */
		slist->changed_data	/* Data */
	    );
}

/*
 *	Shift last selected target item down callback.
 */
static void StackListShiftDownCB(GtkWidget *widget, gpointer data)
{
	gint row, rows;
	GList *glist, *selection;
	GtkCList *clist;
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(             
 "StackListShiftDownCB(): slist=0x%.8x\n", (guint32)slist
);
#endif

	/* Get target clist */
	clist = (GtkCList *)slist->tar_clist;
	if(clist == NULL)
	    return;

	/* Recreate a selection list that is ordered from last to
	 * first row
	 */
	selection = NULL;
	rows = clist->rows;
	for(row = rows - 1; row >= 0; row--)
	{
	    /* Is this row selected? */
	    for(glist = clist->selection;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		if((gint)glist->data != row)
		    continue;

		selection = g_list_append(selection, (gpointer)row);
		break;
	    }
	}
	if(selection == NULL)
	    return;

	/* If the first row is already at bottom then do not shift */
	if((gint)selection->data >= (rows - 1))
	{
	    g_list_free(selection);
	    return;
	}

	/* Iterate through selection list and shift rows down */
	gtk_clist_freeze(clist);
	for(glist = selection; glist != NULL; glist = g_list_next(glist))
	{
	    row = (gint)glist->data;
	    gtk_clist_swap_rows(clist, row, row + 1);
	}
	gtk_clist_thaw(clist);

	/* Delete selection list */
	g_list_free(selection);

	StackListUpdateMenus(slist);

	/* Call changed callback */
	if(slist->changed_cb != NULL)
	    slist->changed_cb(
		slist,			/* Stack List */
		slist->changed_data	/* Data */
	    );
}

/*
 *	GtkCList event signal callback.
 */
static gint StackListCListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gboolean status = FALSE;
	gint etype, x, y, row, column;
	gboolean press;
	guint keyval, state;
	GdkEventKey *key;
	GdkEventCrossing *crossing;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GtkCList *clist;
	stack_list_flags flags;
	stack_list_struct *slist = STACK_LIST(data);
	if((widget == NULL) || (event == NULL) || (slist == NULL))
	    return(status);

	clist = GTK_CLIST(widget);
	if(clist->clist_window != ((GdkEventAny *)event)->window)
	    return(status);

	flags = slist->flags;

	etype = event->type;
	switch(etype)
	{
	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
#define DO_STOP_KEY_SIGNAL_EMIT	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
  press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}
	    key = (GdkEventKey *)event;
	    press = (etype == GDK_KEY_PRESS) ? TRUE : FALSE;
	    keyval = key->keyval;
	    state = key->state;
#if (DEBUG_LEVEL >= 1)
g_print(             
 "StackListCListEventCB(): slist=0x%.8x %s keyval=0x%.8x(%c) state=0x%.8x\n",
 (guint32)slist,
 press ? "key_press_event" : "key_release_event",
 (guint32)keyval, (gchar)keyval,
 (guint32)state
);
#endif
	    switch(keyval)
	    {
	      case GDK_Return:
	      case GDK_KP_Enter:
	      case GDK_ISO_Enter:
	      case GDK_3270_Enter:
		if(press)
		{
		    if(widget == slist->src_clist)
			StackListAddCB(NULL, data);
		    else if(widget == slist->tar_clist)
			StackListRemoveCB(NULL, data);
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_plus:
	      case GDK_equal:
	      case GDK_KP_Add:
	      case GDK_Insert:
	      case GDK_greater:
		if(press)
		    StackListAddCB(NULL, data);
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_minus:
	      case GDK_underscore:
	      case GDK_KP_Subtract:
	      case GDK_Delete:
	      case GDK_less:
		if(press)
		    StackListRemoveCB(NULL, data);
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;


	    }
	    break;
#undef DO_STOP_KEY_SIGNAL_EMIT

	  case GDK_ENTER_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
#if (DEBUG_LEVEL >= 1)   
g_print(
 "StackListCListEventCB(): slist=0x%.8x enter_notify_event\n",
 (guint32)slist
);
#endif

	    /* Clear description */
	    StackListSetDescriptionFromItem(slist, NULL);

	    status = TRUE;
	    break;

	  case GDK_LEAVE_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
#if (DEBUG_LEVEL >= 1)   
g_print(
 "StackListCListEventCB(): slist=0x%.8x leave_notify_event\n",
 (guint32)slist
);
#endif

	    /* Clear description */ 
	    StackListSetDescriptionFromItem(slist, NULL);

	    status = TRUE;
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    x = (gint)button->x;
	    y = (gint)button->y;
	    slist->button = button->button;
	    if(!gtk_clist_get_selection_info(
		clist, x, y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Handle by button number */
	    switch(button->button)
	    {
	      case GDK_BUTTON2:
		gdk_pointer_grab(
		    button->window,
		    FALSE,
		    GDK_BUTTON_PRESS_MASK |
		    GDK_BUTTON_RELEASE_MASK |
		    GDK_POINTER_MOTION_MASK |
		    GDK_POINTER_MOTION_HINT_MASK,
		    NULL,
		    slist->translate_cur,
		    button->time
		);
		slist->drag_last_x = (gint)button->x;
		slist->drag_last_y = (gint)button->y;
		status = TRUE;
		break;
	    }
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case GDK_BUTTON2:
		if(slist->button > 0)
		    status = TRUE;
		if(gdk_pointer_is_grabbed())
		    gdk_pointer_ungrab(button->time);
		break;
	    }
	    slist->button = 0;
	    slist->drag_last_x = 0;
	    slist->drag_last_y = 0;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    x = (gint)motion->x;
	    y = (gint)motion->y;

	    /* Get row and column */
	    if(!gtk_clist_get_selection_info(
		clist, x, y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

#define DO_SCROLL(adj,d)	{			\
 if((d) != 0) {						\
  if(((adj)->upper - (adj)->page_size) > (adj)->lower) {\
   gtk_adjustment_set_value(				\
    adj,						\
    CLIP(						\
     (adj)->value - (d),				\
     (adj)->lower, ((adj)->upper - (adj)->page_size)	\
    )							\
   );							\
  }							\
 }							\
}

	    /* Check which widget this event is for */
	    if((widget == slist->src_clist) &&
	       (flags & STACK_LIST_SHOW_DESCRIPTION_SOURCE)
	    )
	    {
		if(slist->button == GDK_BUTTON2)
		{
		    gint dx, dy;
		    GtkCList *clist = GTK_CLIST(slist->src_clist);
		    GtkAdjustment *hadj = clist->hadjustment,
				  *vadj = clist->vadjustment;
		    if(motion->is_hint)
		    {
			gint x, y;
			GdkModifierType mask;
			gdk_window_get_pointer(
			    motion->window, &x, &y, &mask
			);
			dx = x - slist->drag_last_x;
			dy = y - slist->drag_last_y;
			slist->drag_last_x = x;
			slist->drag_last_y = y;
		    }
		    else
		    {
			dx = (gint)(motion->x - slist->drag_last_x);
			slist->drag_last_x = (gint)motion->x;
			dy = (gint)(motion->y - slist->drag_last_y);
			slist->drag_last_y = (gint)motion->y;
		    }
		    DO_SCROLL(hadj, dx);
		    DO_SCROLL(vadj, dy);
		}
		else
		{
		    /* Get item (if any) from row data */
		    stack_list_item_struct *item = STACK_LIST_ITEM(
			gtk_clist_get_row_data(clist, row)
		    );
		    /* Set description from item (if any) */
		    StackListSetDescriptionFromItem(slist, item);
		}
		status = TRUE;
	    }
	    else if((widget == slist->tar_clist) &&  
		    (flags & STACK_LIST_SHOW_DESCRIPTION_TARGET)
	    )
	    {
		if(slist->button == GDK_BUTTON2)
		{
		    gint dx, dy;
		    GtkCList *clist = GTK_CLIST(slist->tar_clist);
		    GtkAdjustment *hadj = clist->hadjustment,
				  *vadj = clist->vadjustment;
		    if(motion->is_hint)
		    {
			gint x, y;
			GdkModifierType mask;
			gdk_window_get_pointer(
			    motion->window, &x, &y, &mask
			);
			dx = x - slist->drag_last_x;
			dy = y - slist->drag_last_y;
			slist->drag_last_x = x;
			slist->drag_last_y = y;
		    }
		    else
		    {
			dx = (gint)(motion->x - slist->drag_last_x);
			slist->drag_last_x = (gint)motion->x;
			dy = (gint)(motion->y - slist->drag_last_y);
			slist->drag_last_y = (gint)motion->y;
		    }
		    DO_SCROLL(hadj, dx);
		    DO_SCROLL(vadj, dy);
		}
		else
		{
		    /* Get item (if any) from row data */
		    stack_list_item_struct *item = STACK_LIST_ITEM( 
			gtk_clist_get_row_data(clist, row)
		    );
		    /* Set description from item (if any) */
		    StackListSetDescriptionFromItem(slist, item);
		}
		status = TRUE;
	    }

#undef DO_SCROLL
	    break;
	}

	return(status);
}

/*
 *	GtkCList "button_press_event" signal callback for mapping
 *	the popup menus.
 */
static gint StackListCListButtonEventMapMenuCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint x, y, row, column;
	GtkWidget *w;
	GtkCList *clist;
	stack_list_struct *slist = STACK_LIST(data);
	if((widget == NULL) || (button == NULL) || (slist == NULL))
	    return(status);

	clist = GTK_CLIST(widget);

	switch((gint)button->type)
	{
	  case GDK_BUTTON_PRESS:
	    switch(button->button)
	    {
	      case GDK_BUTTON3:
		/* Get the row and column */
		x = (gint)button->x;
		y = (gint)button->y;
		if(!gtk_clist_get_selection_info(
		    clist, x, y, &row, &column
		))
		{
		    row = -1;
		    column = 0;
		}

		gtk_clist_freeze(clist);
		if(!(button->state & GDK_CONTROL_MASK) &&
		   !(button->state & GDK_SHIFT_MASK)
		)
		    gtk_clist_unselect_all(clist);
		gtk_clist_select_row(clist, row, column);
		gtk_clist_thaw(clist);

		/* Check which widget this event is for and map the
		 * menu over the appropriate one
		 */
		if(widget == slist->src_clist)
		    w = slist->src_menu;
		else if(widget == slist->tar_clist)
		    w = slist->tar_menu;
		else
		    w = NULL;
		if(w != NULL)
		    gtk_menu_popup(
			GTK_MENU(w), NULL, NULL,
			NULL, NULL,
			button->button, button->time
		    );
		status = TRUE;
		break;
	    }
	    break;
	}

	return(status);
}

/*
 *      GtkCList "select_row" signal callback.
 */
static void StackListSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	GdkEventButton *button;
	stack_list_flags flags;
	stack_list_item_struct *item;
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListSelectRowCB(): slist=0x%.8x clist=0x%.8x row=%i column=%i\n",
 (guint32)slist, (guint32)clist, row, column
);
#endif

	flags = slist->flags;

	/* Check which clist this event is for */
	if(clist == (GtkCList *)slist->src_clist)
	{
	    slist->src_last_selected = row;

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

	    /* Check for double click */
	    if((event != NULL) ? (event->type == GDK_2BUTTON_PRESS) : FALSE)
	    {
		button = (GdkEventButton *)event;
		switch(button->button)
		{
		  case 1:
		    StackListAddCB(NULL, slist);

		    /* Get item (if any) from row data and update
		     * the description
		     */
		    if(flags & STACK_LIST_SHOW_DESCRIPTION_SOURCE)
		    {
			item = STACK_LIST_ITEM(
			    gtk_clist_get_row_data(clist, row)
			);
			StackListSetDescriptionFromItem(slist, item);
		    }
		    break;
		}
	    }
	}
	else if(clist == (GtkCList *)slist->tar_clist)
	{
	    slist->tar_last_selected = row;

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

	    /* Check for double click */
	    if((event != NULL) ? (event->type == GDK_2BUTTON_PRESS) : FALSE)
	    {
		button = (GdkEventButton *)event;
		switch(button->button)
		{
		  case 1:
		    StackListRemoveCB(NULL, slist);

		    /* Get item (if any) from row data and update  
		     * the description   
		     */
		    if(flags & STACK_LIST_SHOW_DESCRIPTION_TARGET)
		    {
			item = STACK_LIST_ITEM(
			    gtk_clist_get_row_data(clist, row)
			);
			StackListSetDescriptionFromItem(slist, item);
		    }
		    break;
		}
	    }
	}

	StackListUpdateMenus(slist);
}

/*
 *      GtkCList "unselect_row" signal callback.
 */
static void StackListUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	stack_list_struct *slist = STACK_LIST(data);
	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListUnselectRowCB(): slist=0x%.8x clist=0x%.8x row=%i column=%i\n", 
 (guint32)slist, (guint32)clist, row, column
);
#endif

	/* Check which clist this event is for */
	if(clist == (GtkCList *)slist->src_clist)
	{
	    if(row == slist->src_last_selected)
		slist->src_last_selected = -1;
	}
	else if(clist == (GtkCList *)slist->tar_clist)
	{
	   if(row == slist->tar_last_selected)
		slist->tar_last_selected = -1;
	}

	StackListUpdateMenus(slist);
}


/*
 *	Returns the Stack List Item that matches the specified ID.
 */
static stack_list_item_struct *StackListMatchItemByID(
	stack_list_struct *slist, gint id
)
{
	gint i;
	stack_list_item_struct *item;

	if(slist == NULL)
	    return(NULL);

	for(i = 0; i < slist->total_items; i++)
	{
	    item = slist->item[i];
	    if((item != NULL) ? (item->id == id) : FALSE)
		return(item);
	}

	return(NULL);
}


/*
 *	Updates the Stack List's description with the specified item.
 */
static void StackListSetDescriptionFromItem(
	stack_list_struct *slist, stack_list_item_struct *item
)
{
	GtkWidget *w = (slist != NULL) ?
	    slist->description_label : NULL;
	if(w == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListSetDescriptionFromItem(): slist=0x%.8x item=0x%.8x\n",
 (guint32)slist, (guint32)item
);
#endif

	/* Change in items? */
	if(item != slist->last_description_item)
	{
	    const gchar *s = (item != NULL) ? item->description : NULL;
#if (DEBUG_LEVEL >= 2)
g_print(
 "StackListSetDescriptionFromItem(): Item changed from 0x%.8x to 0x%.8x\n",
 (guint32)slist->last_description_item, (guint32)item
);
#endif
	    gtk_label_set_text(GTK_LABEL(w), (s != NULL) ? s : "");
	    slist->last_description_item = item;
	}
}


/*
 *	Appends the specified item to the Stack List's item cache.
 */
void StackListAppend(
	stack_list_struct *slist,
	const gchar *name,
	const gchar *description,
	guint8 **icon_data,
	gpointer client_data,
	gint id,
	gboolean allow_multiple, gboolean stay_on_target
)
{
	gint i;
	stack_list_item_struct *item;

	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListAppend(): slist=0x%.8x name=\"%s\" description=\"%s\"\
 icon_data=0x%.8x client_data=0x%.8x id=%i\
 allow_multiple=%s stay_on_target=%s\n",
 (guint32)slist, name, description,
 (guint32)icon_data, (guint32)client_data, id,
 allow_multiple ? "TRUE" : "FALSE",
 stay_on_target ? "TRUE" : "FALSE"
);
#endif

	/* Allocate more pointers */
	i = MAX(slist->total_items, 0);
	slist->total_items = i + 1;
	slist->item = (stack_list_item_struct **)g_realloc(
	    slist->item,
	    slist->total_items * sizeof(stack_list_item_struct *)
	);
	if(slist->item == NULL)
	{
	    slist->total_items = 0;
	    return;
	}

	/* Allocate a new Stack List Item */
	slist->item[i] = item = STACK_LIST_ITEM(
	    g_malloc0(sizeof(stack_list_item_struct))
	);
	if(item == NULL)
	    return;

	/* Set values on the new Stack List Item */
	item->name = STRDUP(name);
	item->description = STRDUP(description);
	item->client_data = client_data;
	item->id = id;
	item->allow_multiple = allow_multiple;
	item->stay_on_target = stay_on_target;
	item->icon_pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(
	    &item->icon_mask, (guint8 **)icon_data
	);
}

/*
 *	Deletes all items in the Stack List's Cache and clears all the
 *	lists.
 */
void StackListClear(stack_list_struct *slist)
{
	gint i;
	stack_list_item_struct *item;

	if(slist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListClear(): slist=0x%.8x slist->total_items=%i\n",
 (guint32)slist, slist->total_items
);
#endif

	/* Clear the source and target lists */
	StackListItemClearSrc(slist);
	StackListItemClearTar(slist);

	/* Delete all Stack List Items in the Stack List's cache */
	for(i = 0; i < slist->total_items; i++)
	{
	    item = slist->item[i];
	    if(item == NULL)
		continue;

#if (DEBUG_LEVEL >= 2)
g_print(
 "StackListClear(): Deleting item %i \"%s\"\n",
 i, item->name
);
#endif
	    g_free(item->name);
	    g_free(item->description);
	    GDK_PIXMAP_UNREF(item->icon_pixmap)
	    GDK_BITMAP_UNREF(item->icon_mask)
	    g_free(item);
	}

	g_free(slist->item);
	slist->item = NULL;
	slist->total_items = 0;

	slist->last_description_item = NULL;
}


/*
 *	Appends an item specified by id to the Stack List's source
 *	list.
 */
void StackListItemAppendSrc(stack_list_struct *slist, gint id)
{
	gint i, row;
	gchar **strv;
	stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->src_clist : NULL;
	if(clist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListItemAppendSrc(): slist=0x%.8x id=%i\n",
 (guint32)slist, id
);
#endif

	/* Get Stack List Item that matches the specified id */
	item = StackListMatchItemByID(slist, id);
	if(item == NULL)
	    return;

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

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

	/* Append a new item to the clist */
	row = gtk_clist_append(clist, strv);

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

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListItemAppendSrc(): Appended item \"%s\" at source row %i\n",
 item->name, row
);
#endif

	/* Set new row */
	if(row > -1)
	{
	    const gint column = 0;
	    if((item->icon_pixmap != NULL) && (item->name != NULL))
		gtk_clist_set_pixtext(
		    clist, row, column,
		    item->name,
		    2,
		    item->icon_pixmap, item->icon_mask
		);
	    else if(item->icon_pixmap != NULL)
		gtk_clist_set_pixmap(
		    clist, row, column,
		    item->icon_pixmap, item->icon_mask
		);
	    else if(item->name != NULL)
		gtk_clist_set_text(
		    clist, row, column,
		    item->name
		);

	    /* Set row data, but no destroy function since the row
	     * data is shared and not suppose to be deleted when the
	     * clist row is deleted
	     */
	    gtk_clist_set_row_data(clist, row, item);
	}
}

/*
 *	Appends an item specified by id to the Stack List's target
 *	list.
 */
void StackListItemAppendTar(stack_list_struct *slist, gint id)
{
	gint i, row;
	gchar **strv;
	stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->tar_clist : NULL;
	if(clist == NULL)
	    return;

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListItemAppendTar(): slist=0x%.8x id=%i\n",
 (guint32)slist, id
);
#endif

	/* Get Stack List Item that matches the specified id */
	item = StackListMatchItemByID(slist, id);
	if(item == NULL)
	    return;

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

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

	/* Append a new item to the clist */
	row = gtk_clist_append(clist, strv);

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

#if (DEBUG_LEVEL >= 1)
g_print(
 "StackListItemAppendTar(): Appended item \"%s\" at target row %i\n",
 item->name, row
);
#endif

	/* Set new row */
	if(row > -1)
	{
	    const gint column = 0;
	    if((item->icon_pixmap != NULL) && (item->name != NULL))
		gtk_clist_set_pixtext(
		    clist, row, column,
		    item->name,
		    2,
		    item->icon_pixmap, item->icon_mask
		);
	    else if(item->icon_pixmap != NULL)
		gtk_clist_set_pixmap(
		    clist, row, column,
		    item->icon_pixmap, item->icon_mask
		);
	    else if(item->name != NULL)
		gtk_clist_set_text(
		    clist, row, column,
		    item->name
		);

	    /* Set row data, but no destroy function since the row
	     * data is shared and not suppose to be deleted when the
	     * clist row is deleted
	     */
	    gtk_clist_set_row_data(clist, row, item);
	}
}

/*
 *	Creates items on the source clist based on all items in the
 *	Stack List's main item cache.
 */
void StackListItemSetAllFromCacheSrc(stack_list_struct *slist)
{
	gint i;
	const stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->src_clist : NULL;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);

	for(i = 0; i < slist->total_items; i++)
	{
	    item = slist->item[i];
	    if(item == NULL)
		continue;

	    StackListItemAppendSrc(slist, item->id);
	}

	gtk_clist_thaw(clist);
}


/*
 *	Removes all items who's id matches the given id from the
 *	source clist.
 *
 *      If exclude_allowed_multiples is TRUE than if the item allows
 *      multiple coppies of itself then it will not be removed.
 */
void StackListItemRemoveByIDSrc(
	stack_list_struct *slist, gint id,
	gboolean exclude_allowed_multiples
)
{
	gint i;
	GtkCList *clist;
	stack_list_item_struct *item;


	if(slist == NULL)
	    return;

	clist = (GtkCList *)slist->src_clist;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);

	/* Iterate through all rows, checking each item structure's
	 * id if it matches the given id.
	 */
	for(i = 0; i < clist->rows; i++)
	{
	    item = STACK_LIST_ITEM(gtk_clist_get_row_data(
		clist, i
	    ));
	    if(item == NULL)
		continue;

	    /* Skip if we are to exclude items that allow multiples and
	     * this item allows multiple coppies of itself
	     */
	    if(exclude_allowed_multiples &&
	       item->allow_multiple
	    )
		continue;

	    /* IDs match? */
	    if(item->id == id)
	    {
		/* Remove this row */
		gtk_clist_remove(clist, i);
	    }
	}

	gtk_clist_thaw(clist);
}

/*
 *      Removes all items who's id matches the given id from the
 *      source clist.
 *
 *	If exclude_allowed_multiples is TRUE than if the item allows
 *	multiple coppies of itself then it will not be removed.
 */
void StackListItemRemoveByIDTar(
	stack_list_struct *slist, gint id,
	gboolean exclude_allowed_multiples
)
{
	gint i;
	const stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->tar_clist : NULL;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);

	/* Iterate through all rows, checking each Stack List Item's
	 * ID to see if it matches the specified ID
	 */
	for(i = 0; i < clist->rows; i++)
	{
	    item = STACK_LIST_ITEM(gtk_clist_get_row_data(
		clist, i
	    ));
	    if(item == NULL)
		continue;

	    /* Skip if we are to exclude items that allow multiples and
	     * this item allows multiple coppies of itself
	     */
	    if(exclude_allowed_multiples && item->allow_multiple)
		continue;

	    /* IDs match? */
	    if(item->id == id)
	    {
		/* Remove this row */
		gtk_clist_remove(clist, i);
		continue;
	    }
	}

	gtk_clist_thaw(clist);
}

/*
 *	Deletes all items in the Stack List's source list.
 */
void StackListItemClearSrc(stack_list_struct *slist)
{
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->src_clist : NULL;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);
}

/*
 *      Deletes all items in the Stack List's target list.
 */
void StackListItemClearTar(stack_list_struct *slist)
{
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->tar_clist : NULL;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);
}


/*
 *	Returns a list of gints, each has the value of the id of the
 *	item currently in the source list.
 *
 *	The calling function must delete the returned pointer.
 */
gint *StackListItemGetSrc(stack_list_struct *slist, gint *total)
{
	gint i;
	gint *list;
	const stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->src_clist : NULL;
	if((clist == NULL) || (total == NULL))
	    return(NULL);

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

	/* Allocate gint list */
	list = (gint *)g_malloc0((*total) * sizeof(gint));
	if(list == NULL)
	{
	    *total = 0;
	    return(NULL);
	}

	/* Iterate through each row and set each ID to the gint list */
	for(i = 0; i < clist->rows; i++)
	{
	    item = STACK_LIST_ITEM(
		gtk_clist_get_row_data(clist, i)
	    );
	    list[i] = (item != NULL) ? item->id : -1;
	}

	return(list);
}

/*
 *      Returns a dynamically allocated array of gint's, each
 *      representing the id of the item currently in the target list.
 *      The calling function needs to deallocate the returned pointer.
 *
 *      Can return NULL on error or empty list.
 */
gint *StackListItemGetTar(stack_list_struct *slist, gint *total)
{
	gint i;
	gint *list;
	stack_list_item_struct *item;
	GtkCList *clist = (slist != NULL) ?
	    (GtkCList *)slist->tar_clist : NULL;
	if((clist == NULL) || (total == NULL))
	    return(NULL);

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

	/* Allocate gint array */
	list = (gint *)g_malloc0((*total) * sizeof(gint));
	if(list == NULL)
	{
	    *total = 0;
	    return(NULL);
	}

	/* Iterate through each row and set each ID to the gint list */
	for(i = 0; i < clist->rows; i++)
	{
	    item = STACK_LIST_ITEM(
		gtk_clist_get_row_data(clist, i)
	    );
	    list[i] = (item != NULL) ? item->id : -1;
	}

	return(list);
}


/*
 *	Creates a new Stack List.
 */
stack_list_struct *StackListNew(
	GtkWidget *parent,
	const gchar *src_title,
	const gchar *tar_title
)
{
	const gint border_minor = 2;
	GtkWidget *w, *parent2, *parent3, *parent4;
	GtkCList *clist;
	stack_list_struct *slist = STACK_LIST(g_malloc0(
	    sizeof(stack_list_struct)
	));
	if(slist == NULL)
	    return(slist);

	/* Reset values */
	slist->map_state = FALSE;
	slist->freeze_count = 0;
	slist->busy_count = 0;
	slist->flags = 0;
	slist->translate_cur = gdk_cursor_new(GDK_FLEUR);
	slist->src_last_selected = -1;
	slist->tar_last_selected = -1;
	slist->item = NULL;
	slist->total_items = 0;
	slist->last_description_item = NULL;

	slist->button = 0;
	slist->drag_last_x = 0;
	slist->drag_last_y = 0;

	slist->changed_cb = NULL;
	slist->changed_data = NULL;
	slist->added_cb = NULL;
	slist->added_data = NULL;
	slist->removed_cb = NULL;
	slist->removed_data = NULL;

	/* Begin creating widgets */

	/* Create the toplevel GtkVBox */
	slist->toplevel = w = gtk_vbox_new(FALSE, border_minor);
	if(parent != NULL)
	{
	    if(GTK_IS_BOX(parent))
		gtk_box_pack_start(
		    GTK_BOX(parent), w, TRUE, TRUE, 0
		);
	    else if(GTK_IS_CONTAINER(parent))
		gtk_container_add(GTK_CONTAINER(parent), w);
	}
	parent = w;

	/* GtkHBox for the source & target lists */
	w = gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent = w;

	/* GtkScrolledWindow for the source GtkCList */
	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(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Create the source GtkCList */
	if(src_title != NULL)
	{
	    gchar *strv[1];
	    strv[0] = STRDUP(src_title);
	    slist->src_clist = w = gtk_clist_new_with_titles(
		1, strv
	    );
	    g_free(strv[0]);
	}
	else
	{
	    slist->src_clist = w = gtk_clist_new(1);
	}
	clist = GTK_CLIST(w);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(StackListCListButtonEventMapMenuCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "select_row",
	    GTK_SIGNAL_FUNC(StackListSelectRowCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "unselect_row",
	    GTK_SIGNAL_FUNC(StackListUnselectRowCB), slist
	);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_realize(w);
	gtk_clist_set_row_height(clist, STACK_LIST_ROW_SPACING);
	gtk_clist_set_column_auto_resize(clist, 0, TRUE);       
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_EXTENDED);
	gtk_clist_column_titles_passive(clist);
	gtk_widget_show(w);


	/* Island GtkVBox between lists for the arrow buttons */
	w = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;


	/* GtkVBox for the left and right arrow buttons */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* Right arrow button */
	slist->right_arrow_btn = w = gtk_button_new();
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(StackListAddCB), slist
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_OUT);
	gtk_widget_set_usize(
	    w, STACK_LIST_ARROW_WIDTH, STACK_LIST_ARROW_HEIGHT
	);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);

	/* Left arrow button */
	slist->left_arrow_btn = w = gtk_button_new();
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(StackListRemoveCB), slist
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_OUT);
	gtk_widget_set_usize(
	    w, STACK_LIST_ARROW_WIDTH, STACK_LIST_ARROW_HEIGHT
	);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);


	/* Vbox for up and down arrow buttons */
	w = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* Up arrow button */
	slist->up_arrow_btn = w = gtk_button_new();
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(StackListShiftUpCB), slist
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT);
	gtk_widget_set_usize(
	    w, STACK_LIST_ARROW_WIDTH, STACK_LIST_ARROW_HEIGHT
	);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);

	/* Down arrow button */
	slist->down_arrow_btn = w = gtk_button_new();
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(StackListShiftDownCB), slist
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Arrow */
	w = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
	gtk_widget_set_usize(
	    w, STACK_LIST_ARROW_WIDTH, STACK_LIST_ARROW_HEIGHT
	);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);




	/* GtkScrolledWindow for the target GtkCList */
	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(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Create the target GtkCList */
	if(tar_title != NULL)
	{
	    gchar *strv[1];
	    strv[0] = STRDUP(tar_title);
	    slist->tar_clist = w = gtk_clist_new_with_titles(
		1, strv
	    );
	    g_free(strv[0]);
	}
	else
	{
	    slist->tar_clist = w = gtk_clist_new(1);
	}
	clist = GTK_CLIST(w);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(StackListCListButtonEventMapMenuCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(StackListCListEventCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "select_row",
	    GTK_SIGNAL_FUNC(StackListSelectRowCB), slist
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "unselect_row",
	    GTK_SIGNAL_FUNC(StackListUnselectRowCB), slist
	);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_realize(w);
	gtk_clist_set_row_height(clist, STACK_LIST_ROW_SPACING);
	gtk_clist_set_column_auto_resize(clist, 0, TRUE);       
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_EXTENDED);
	gtk_clist_column_titles_passive(clist);
	gtk_widget_show(w);


	/* Frame for description label */
	slist->description_toplevel = w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(slist->toplevel), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent = w;
#if 0
	/* Style for background */
	rcstyle = gtk_rc_style_new();
	state = GTK_STATE_NORMAL;
	rcstyle->color_flags[state] = GTK_RC_FG | GTK_RC_BG |
	    GTK_RC_TEXT | GTK_RC_BASE;
	GDK_COLOR_SET_COEFF(
	    &rcstyle->fg[state],
	    0.0f, 0.0f, 0.0f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->bg[state],
	    1.0f, 1.0f, 0.8f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->text[state],
	    0.0f, 0.0f, 0.0f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->base[state],
	    1.0f, 1.0f, 0.8f   
	)
	state = GTK_STATE_INSENSITIVE;
	rcstyle->color_flags[state] = GTK_RC_FG | GTK_RC_BG |
	    GTK_RC_TEXT | GTK_RC_BASE;
	GDK_COLOR_SET_COEFF(
	    &rcstyle->fg[state],
	    0.25f, 0.25f, 0.25f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->bg[state],
	    1.0f, 1.0f, 0.8f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->text[state],
	    0.25f, 0.25f, 0.25f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->base[state],
	    1.0f, 1.0f, 0.8f
	)
#endif
	/* Create a GtkEventBox for the label's background */
	w = gtk_event_box_new();
	gtk_widget_set_name(w, "GtkEditable");
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent2 = w;

	w = gtk_hbox_new(FALSE, 0);
	gtk_container_border_width(GTK_CONTAINER(w), border_minor);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	parent2 = w;

	/* Description label */
	slist->description_label = w = gtk_label_new("");
	gtk_widget_set_name(w, "GtkEditable");
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


#define DO_ADD_MENU_ITEM_LABEL	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_SEP		{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}
	/* Source clist right-click menu */
	slist->src_menu = w = GUIMenuCreate();
	if(w != NULL)
	{
	    GtkAccelGroup *accelgrp = NULL;
	    GtkWidget *menu = w;
	    guint8 **icon;
	    const gchar *label;
	    guint accel_key, accel_mods;
	    void (*func_cb)(GtkWidget *w, gpointer);
	    gpointer mclient_data = slist;

	    icon = (guint8 **)icon_add_20x20_xpm;
	    label = "Add";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = StackListAddCB;
	    DO_ADD_MENU_ITEM_LABEL
	    slist->src_add_mi = w;
	}

	/* Target clist right-click menu */
	slist->tar_menu = w = GUIMenuCreate();
	if(w != NULL)
	{
	    GtkAccelGroup *accelgrp = NULL;
	    GtkWidget *menu = w;
	    guint8 **icon;
	    const gchar *label;
	    guint accel_key, accel_mods;
	    void (*func_cb)(GtkWidget *w, gpointer);
	    gpointer mclient_data = slist;

	    icon = (guint8 **)icon_remove_20x20_xpm;
	    label = "Remove";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = StackListRemoveCB;
	    DO_ADD_MENU_ITEM_LABEL
	    slist->tar_remove_mi = w;

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label = "Shift Up";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = StackListShiftUpCB;
	    DO_ADD_MENU_ITEM_LABEL
	    slist->tar_up_mi = w;

	    icon = NULL;
	    label = "Shift Down";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = StackListShiftDownCB;
	    DO_ADD_MENU_ITEM_LABEL
	    slist->tar_down_mi = w;
	}
#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_SEP

	return(slist);
}

/*
 *	Sets the Stack List to show the description.
 *
 *	If both show_src_desc and show_tar_desc are FALSE then the
 *	description label is hidden.
 */
void StackListShowDescription(
	stack_list_struct *slist, 
	gboolean show_src_desc,
	gboolean show_tar_desc
)
{
	GtkWidget *w;

	if(slist == NULL)
	    return;

	if(show_src_desc)
	    slist->flags |= STACK_LIST_SHOW_DESCRIPTION_SOURCE;
	else
	    slist->flags &= ~STACK_LIST_SHOW_DESCRIPTION_SOURCE;

	if(show_tar_desc)
	    slist->flags |= STACK_LIST_SHOW_DESCRIPTION_TARGET;
	else
	    slist->flags &= ~STACK_LIST_SHOW_DESCRIPTION_TARGET;

	w = slist->description_toplevel;
	if(w != NULL)
	{
	    if(show_src_desc || show_tar_desc)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}
}

/*
 *	Sets the Stack List to maintain the order of items on the
 *	source list.
 */
void StackListSetMaintainSourceOrder(
	stack_list_struct *slist, gboolean maintain_source_order
)
{
	if(slist == NULL)
	    return;

	if(maintain_source_order)
	    slist->flags |= STACK_LIST_MAINTAIN_SOURCE_ORDER;
	else
	    slist->flags &= ~STACK_LIST_MAINTAIN_SOURCE_ORDER;
}

/*
 *	Sets the Stack List's changed callback.
 */
void StackListSetChangedCB(
	stack_list_struct *slist,
	void (*cb)(
		stack_list_struct *,		/* Stack List */
		gpointer			/* Data */
	),
	gpointer data
)
{
	if(slist == NULL)
	    return;

	slist->changed_cb = cb;
	slist->changed_data = data;
}

/*
 *      Sets the Stack List's added callback.
 */
void StackListSetAddedCB(
	stack_list_struct *slist,
	void (*cb)(
		stack_list_struct *,		/* Stack List */
		stack_list_item_struct *,	/* Stack List Item */
		gpointer			/* Data */
	),
	gpointer data
)
{
	if(slist == NULL)
	    return;

	slist->added_cb = cb;
	slist->added_data = data;
}

/*
 *	Sets the Stack List's removed callback.
 */
void StackListSetRemovedCB(
	stack_list_struct *slist,
	void (*cb)(
		stack_list_struct *,		/* Stack List */
		stack_list_item_struct *,	/* Stack List Item */
		gpointer			/* Data */
	),
	gpointer data
)
{
	if(slist == NULL)
	    return;      

	slist->removed_cb = cb;
	slist->removed_data = data;
}
 
/*
 *	Updates widgets on the given slist to reflect the current data
 *	represented.
 */
void StackListUpdateMenus(stack_list_struct *slist)
{
	gboolean sensitive;
	GList *glist;
	GtkCList *clist;

	if(slist == NULL)
	    return;

	/* Update widgets with respect to the target clist */
	clist = (GtkCList *)slist->tar_clist;
	if(clist != NULL)
	{
	    gint tar_sel_row;
	    stack_list_item_struct *item;

	    glist = clist->selection_end;
	    tar_sel_row = (glist != NULL) ? (gint)glist->data : -1;
	    item = STACK_LIST_ITEM(gtk_clist_get_row_data(
		clist, tar_sel_row
	    ));

	    sensitive = (tar_sel_row > 0) ? TRUE : FALSE;
/*	    GTK_WIDGET_SET_SENSITIVE(slist->up_arrow_btn, sensitive) */
	    GTK_WIDGET_SET_SENSITIVE(slist->tar_up_mi, sensitive)

	    if((tar_sel_row < (clist->rows - 1)) &&
	       (tar_sel_row >= 0)
	    )
		sensitive = TRUE;
	    else
		sensitive = FALSE;
/*	    GTK_WIDGET_SET_SENSITIVE(slist->down_arrow_btn, sensitive) */
	    GTK_WIDGET_SET_SENSITIVE(slist->tar_down_mi, sensitive)

	    sensitive = (tar_sel_row > -1) ? TRUE : FALSE;
	    if(sensitive)
		sensitive = (item != NULL) ?
		    !item->stay_on_target : FALSE;
/*	    GTK_WIDGET_SET_SENSITIVE(slist->left_arrow_btn, sensitive) */
	    GTK_WIDGET_SET_SENSITIVE(slist->tar_remove_mi, sensitive)
	}

	/* Update widgets with respect to the source clist */
	clist = (GtkCList *)slist->src_clist;
	if(clist != NULL)
	{
	    gint src_sel_row = -1;

	    glist = clist->selection_end;
	    if(glist != NULL)
		src_sel_row = (gint)glist->data;

	    sensitive = (src_sel_row > -1) ? TRUE : FALSE;
/*	    GTK_WIDGET_SET_SENSITIVE(slist->right_arrow_btn, sensitive) */
	    GTK_WIDGET_SET_SENSITIVE(slist->src_add_mi, sensitive)
	}
}

/*
 *	Checks if the Stack List is mapped.
 */
gboolean StackListMapped(stack_list_struct *slist)
{
	return((slist != NULL) ? slist->map_state : FALSE);
}

/*
 *	Maps the Stack List.
 */
void StackListMap(stack_list_struct *slist)
{
	GtkWidget *w = (slist != NULL) ? slist->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_show(w);
	slist->map_state = TRUE;
}

/*
 *	Unmaps the Stack List.
 */
void StackListUnmap(stack_list_struct *slist)
{
	GtkWidget *w = (slist != NULL) ? slist->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
	slist->map_state = FALSE;
}

/*
 *	Deletes the Stack List.
 */
void StackListDelete(stack_list_struct *slist)
{
	if(slist == NULL)
	    return;

	/* Delete all cached items and clear lists */
	StackListClear(slist);

	/* Begin destroying widgets */
	GTK_WIDGET_DESTROY(slist->src_menu)
	GTK_WIDGET_DESTROY(slist->tar_menu)

	GTK_WIDGET_DESTROY(slist->src_clist)
	GTK_WIDGET_DESTROY(slist->tar_clist)

	GTK_WIDGET_DESTROY(slist->left_arrow_btn)
	GTK_WIDGET_DESTROY(slist->right_arrow_btn)
	GTK_WIDGET_DESTROY(slist->up_arrow_btn)
	GTK_WIDGET_DESTROY(slist->down_arrow_btn)

	GTK_WIDGET_DESTROY(slist->description_label)
	GTK_WIDGET_DESTROY(slist->description_toplevel)

	GTK_WIDGET_DESTROY(slist->toplevel)

	GDK_CURSOR_DESTROY(slist->translate_cur);

	g_free(slist);
}
