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

#include "guiutils.h"
#include "pulist.h"


/* Popup List Callbacks */
static gint PUListDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint PUListConfigureEventCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
);
static gint PUListKeyPressEventCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
);
static gint PUListButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
static gint PUListMotionNotifyEventCB(
	GtkWidget *widget, GdkEventMotion *motion, gpointer data
);

static gint PUListShadowPaintCB(pulist_struct *list);
static void PUListShadowDraw(pulist_struct *list);
static void PUListShadowConfigure(
	pulist_struct *list, gint x, gint y, gint width, gint height
);

static void PUListCListDoDragSetUp(pulist_struct *list);
static void PUListCListDoDragCleanUp(pulist_struct *list);


/* Map Button Callbacks */
static gint PUListMapButtonExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
);


/* Popup List Box Callbacks */
static gint PUListBoxEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void PUListBoxMapCB(GtkWidget *widget, gpointer data);
static void PUListBoxDraw(pulistbox_struct *box);


/* Popup List */
gint PUListFindItemFromValue(pulist_struct *list, const gchar *value);
gpointer PUListGetDataFromValue(
	pulist_struct *list, const gchar *value
);
GtkWidget *PUListGetToplevel(pulist_struct *list);
GtkWidget *PUListGetCList(pulist_struct *list);

gint PUListAddItem(
	pulist_struct *list, const gchar *value
);
gint PUListAddItemPixText(
	pulist_struct *list, const gchar *value,
	GdkPixmap *pixmap, GdkBitmap *mask
);
void PUListClear(pulist_struct *list);

void PUListSetItemText(
	pulist_struct *list, gint i,
	const gchar *value
);
void PUListSetItemPixText(
	pulist_struct *list, gint i,
	const gchar *value,
	GdkPixmap *pixmap, GdkBitmap *mask
);
void PUListSetItemData(
	pulist_struct *list, gint i,
	gpointer data
);
void PUListSetItemDataFull(
	pulist_struct *list, gint i,
	gpointer data, GtkDestroyNotify destroy_cb
);

void PUListGetItemText(
	pulist_struct *list, gint i,
	gchar **value 
);
void PUListGetItemPixText(
	pulist_struct *list, gint i,
	gchar **value,
	GdkPixmap **pixmap, GdkBitmap **mask 
);
gpointer PUListGetItemData(pulist_struct *list, gint i);

gint PUListGetSelectedLast(pulist_struct *list);
void PUListSelect(pulist_struct *list, gint i);
void PUListUnselectAll(pulist_struct *list);

gboolean PUListIsQuery(pulist_struct *list);
void PUListBreakQuery(pulist_struct *list);
const gchar *PUListMapQuery(
	pulist_struct *list,
	const gchar *value,		/* Initial value */
	gint lines_visible,		/* Can be -1 for default */
	pulist_relative relative,	/* One of PULIST_RELATIVE_* */
	GtkWidget *rel_widget,		/* Map relative to this widget */
	GtkWidget *map_widget		/* Widget that mapped this list */
);

pulist_struct *PUListNew(void);
void PUListDelete(pulist_struct *list);


/* Map Button */
GtkWidget *PUListNewMapButton(
	void (*map_cb)(GtkWidget *, gpointer),
	gpointer client_data
);
GtkWidget *PUListNewMapButtonArrow(
	GtkArrowType arrow_type, GtkShadowType shadow_type,
	void (*map_cb)(GtkWidget *, gpointer),
	gpointer client_data
);


/* Popup List Box */
GtkWidget *PUListBoxGetToplevel(pulistbox_struct *box);
pulist_struct *PUListBoxGetPUList(pulistbox_struct *box);

pulistbox_struct *PUListBoxNew(
	GtkWidget *parent,
	gint width, gint height
);
void PUListBoxSetChangedCB(
	pulistbox_struct *box,
	void (*func)(
		pulistbox_struct *,	/* Popup List Box */
		gint,			/* Item */
		gpointer		/* Data */
	),
	gpointer data
);
void PUListBoxMap(pulistbox_struct *box);
void PUListBoxUnmap(pulistbox_struct *box);
void PUListBoxDelete(pulistbox_struct *box);


#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 POPUP_LIST_DEF_WIDTH		320
#define POPUP_LIST_DEF_HEIGHT		200

#define POPUP_LIST_ROW_SPACING		20

#define POPUP_LIST_MAP_BTN_WIDTH	17
#define POPUP_LIST_MAP_BTN_HEIGHT	17

#define POPUP_LIST_SHADOW_OFFSET_X         5 
#define POPUP_LIST_SHADOW_OFFSET_Y         5


/* Timeout interval in milliseconds, this is effectivly the scrolling
 * interval of the clist when button is first pressed to map the popup
 * list and then dragged over the clist without releaseing the button.
 */
#define POPUP_LIST_TIMEOUT_INT		80


/*
 *	Popup List toplevel GtkWindow "delete_event" signal callback.
 */
static gint PUListDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	pulist_struct *list = (pulist_struct *)data;
	if(list == NULL)
	    return(FALSE);

	while(list->gtk_block_level > 0)
	{
	    list->gtk_block_level--;
	    gtk_main_quit();
	}

	return(TRUE);
}

/*
 *	Popup List toplevel GtkWindow "configure_event" signal callback.
 */
static gint PUListConfigureEventCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
)
{
	pulist_struct *list = (pulist_struct *)data;
	if((configure == NULL) || (list == NULL))
	    return(FALSE);

	/* Update shadow geometry */
	PUListShadowConfigure(
	    list,
	    configure->x, configure->y,
	    configure->width, configure->height
	);

	return(TRUE);
}

/*
 *	Popup List "key_press_event" and "key_release_event" signal
 *      callback.
 */
static gint PUListKeyPressEventCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	gboolean press;
	guint keyval, state;
	pulist_struct *list = (pulist_struct *)data;
	if((widget == NULL) || (key == NULL) || (list == NULL))
	    return(status);

#define DO_BREAK_GTK_MAIN_LOOP	{	\
 while(list->gtk_block_level > 0) {	\
  list->gtk_block_level--;		\
  gtk_main_quit();			\
 }					\
}

#define DO_ADJ_CLAMP_EMIT	{		\
 if(adj->value > (adj->upper - adj->page_size))	\
  adj->value = adj->upper - adj->page_size;	\
 if(adj->value < adj->lower)			\
  adj->value = adj->lower;			\
 gtk_signal_emit_by_name(			\
  GTK_OBJECT(adj), "value_changed"		\
 );						\
}

/* Emits a signal stop for the key event */
#define DO_STOP_KEY_SIGNAL_EMIT	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
  press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}

	etype = key->type;
	press = (etype == GDK_KEY_PRESS) ? TRUE : FALSE;
	keyval = key->keyval;
	state = key->state;

	/* Note: Only "key_press_events" seem to be reported and not
	 * "key_release_events", so always check if press is TRUE
	 */
	if(widget == list->clist)
	{
	    GtkCList *clist = GTK_CLIST(widget);

	    /* Handle by key value */
	    switch(keyval)
	    {
	      case GDK_space:
	      case GDK_Return:
	      case GDK_KP_Enter:
	      case GDK_ISO_Enter:
	      case GDK_3270_Enter:
		if(press)
		{
		    /* Activate selected item */
		    GList *glist = clist->selection_end;
		    gint row = (glist != NULL) ? (gint)glist->data : -1;
		    if((row >= 0) && (row < clist->rows))
		    {
			gchar *text = NULL;
			guint8 spacing;
			GdkPixmap *pixmap;
			GdkBitmap *mask;

			switch(gtk_clist_get_cell_type(clist, row, 0))
			{
			  case GTK_CELL_TEXT:
			    gtk_clist_get_text(clist, row, 0, &text);
			    break;
			  case GTK_CELL_PIXTEXT:
			    gtk_clist_get_pixtext(
				clist, row, 0, &text,
				&spacing, &pixmap, &mask
			    );
			    break;
			  case GTK_CELL_PIXMAP:
			  case GTK_CELL_WIDGET:
			  case GTK_CELL_EMPTY:
			    break;
			}
			if(!STRISEMPTY(text))
			{
			    g_free(list->last_value);
			    list->last_value = STRDUP(text);
			}
		    }
		    /* Break query */
		    DO_BREAK_GTK_MAIN_LOOP
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Escape:
		if(press)
		{
		    /* Break query */
		    DO_BREAK_GTK_MAIN_LOOP
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;
#if 0
	      case GDK_Page_Up:
	      case GDK_KP_Page_Up:
		if(press)
		{
		    /* Get adjustment and scroll up one page */
		    GtkAdjustment *adj = clist->vadjustment;
		    if(adj != NULL)
		    {
			adj->value -= adj->page_increment;
			DO_ADJ_CLAMP_EMIT
		    }
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Page_Down:
	      case GDK_KP_Page_Down:
		if(press)
		{
		    /* Get adjustment and scroll down one page */
		    GtkAdjustment *adj = clist->vadjustment;
		    if((adj != NULL) && press)
		    {
			adj->value += adj->page_increment;
			DO_ADJ_CLAMP_EMIT
		    }
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Home:
	      case GDK_KP_Home:
		if(press)
		{
		    /* Get adjustment and scroll all the way up */
		    GtkAdjustment *adj = clist->vadjustment;
		    if(adj != NULL)
		    {
			adj->value = adj->lower;
			DO_ADJ_CLAMP_EMIT
		    }
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_End:
	      case GDK_KP_End:
		if(press)
		{
		    /* Get adjustment and scroll all the way up */
		    GtkAdjustment *adj = clist->vadjustment;
		    if(adj != NULL)
		    {
			adj->value = adj->upper - adj->page_size;
			DO_ADJ_CLAMP_EMIT
		    }
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;
#endif
	    }
	}

	return(status);

#undef DO_BREAK_GTK_MAIN_LOOP
#undef DO_ADJ_CLAMP_EMIT
#undef DO_STOP_KEY_SIGNAL_EMIT
}

/*
 *	Popup List "button_press_event" or "button_release_event"
 *	signal callback.
 */
static gint PUListButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	pulist_struct *list = PULIST(data);
	if((widget == NULL) || (button == NULL) || (list == NULL))
	    return(status);

#define DO_BREAK_GTK_MAIN_LOOP	{	\
 while(list->gtk_block_level > 0) {	\
  list->gtk_block_level--;		\
  gtk_main_quit();			\
} }

#define DO_RESTORE_MAP_WIDGET	{	\
 GtkWidget *w = list->map_widget;	\
 if(w != NULL) {			\
  /* Handle restoring by widget type */	\
  if(GTK_IS_BUTTON(w)) {		\
   /* It is a button, make it go into	\
    * its released state		\
    */					\
   GtkButton *button = GTK_BUTTON(w);	\
   button->in_button = 1;		\
   button->button_down = 1;		\
   gtk_signal_emit_by_name(GTK_OBJECT(w), "released"); \
   gtk_signal_emit_by_name(GTK_OBJECT(w), "leave"); \
  }					\
 }					\
}

	/* See which widget this event is for */
	if(widget == list->clist)
	{
	    gint x, y;
	    GtkCList *clist = GTK_CLIST(widget);

	    /* Get the widget the event occured over */
	    GtkWidget *w = gtk_get_event_widget((GdkEvent *)button);

	    switch((gint)button->type)
	    {
	      case GDK_BUTTON_PRESS:
		x = (gint)button->x;
		y = (gint)button->y;
		/* Button pressed outside of the clist? */
		if(w != widget)
		{
		    /* Clicked on one of the other GtkWidgets
		     * belonging to the Popup List?
		     */
		    if((w == list->vscrollbar) ||
		       (w == list->hscrollbar) ||
		       (w == list->scrolled_window) ||
		       (w == list->toplevel)
		    )
		    {
			/* Forward event to that GtkWidget */
			gtk_widget_event(w, (GdkEvent *)button);
		    }
		    else
		    {
			/* All else assume pressed in some other widget
			 *
			 * Break out of the block loop and restore the
			 * map widget
			 */
			DO_BREAK_GTK_MAIN_LOOP
			DO_RESTORE_MAP_WIDGET
		    }
		}
		/* If button 1 was pressed then mark the initial button
		 * press as sent (regardless of if this event is
		 * synthetic or not)
		 */
		if(button->button == 1)
		{
		    if(!list->initial_list_button_press_sent)
			list->initial_list_button_press_sent = TRUE;
		}
		status = TRUE;
		break;

	      case GDK_BUTTON_RELEASE:
		x = (gint)button->x;
		y = (gint)button->y;
		switch(button->button)
		{
		  case 1:
		    /* Button released inside the clist? */
		    if((w == widget) &&
		       (x >= 0) && (x < widget->allocation.width) &&
		       (y >= 0) && (y < widget->allocation.height)
		    )
		    {
			/* Button was released inside the clist,
			 * meaning we now have a matched item
			 */
			GList *glist = clist->selection_end;
			gint row = (glist != NULL) ? (gint)glist->data : -1;
			if((row >= 0) && (row < clist->rows))
			{
			    gchar *text = NULL;
			    guint8 spacing;
			    GdkPixmap *pixmap;
			    GdkBitmap *mask;
			    switch(gtk_clist_get_cell_type(clist, row, 0))
			    {
			      case GTK_CELL_TEXT:
				gtk_clist_get_text(clist, row, 0, &text);
			        break;
			      case GTK_CELL_PIXTEXT:
			        gtk_clist_get_pixtext(
				    clist, row, 0, &text,
				    &spacing, &pixmap, &mask
			        );
			        break;
			      case GTK_CELL_PIXMAP:
			      case GTK_CELL_WIDGET:
			      case GTK_CELL_EMPTY:
			        break;
			    }
			    /* If we got a value then update the
			     * last recorded value on the Popup List
			     */
			    if(!STRISEMPTY(text))
			    {
				g_free(list->last_value);
				list->last_value = STRDUP(text);
			    }
		        }

			/* Break query */
			DO_BREAK_GTK_MAIN_LOOP
		    }
		    else
		    {
			/* Button was released outside of the clist */

			/* Released over one of the other GtkWidgets
			 * belonging to the Popup List?
			 */
			if((w == list->vscrollbar) ||
			   (w == list->hscrollbar) ||
			   (w == list->scrolled_window) ||
			   (w == list->toplevel)
			)
			{
			    /* Forward event to that GtkWidget */
			    gtk_widget_event(w, (GdkEvent *)button);
			}

			/* Grab the clist again, since releasing the
			 * button outside the clist would have
			 * ungrabbed it and not not unmap it
			 */
			if(widget != gtk_grab_get_current())
			    gtk_grab_add(widget);
		    }
		    break;
		}
		/* Restore the map widget on any button release */
		DO_RESTORE_MAP_WIDGET
		status = TRUE;
		break;
	    }
	}

	return(status);

#undef DO_RESTORE_MAP_WIDGET
#undef DO_BREAK_GTK_MAIN_LOOP
}

/*
 *	Popup List "motion_notify_event" signal callback.
 */
static gint PUListMotionNotifyEventCB(
	GtkWidget *widget, GdkEventMotion *motion, gpointer data
)
{
	gint status = FALSE;
	pulist_struct *list = (pulist_struct *)data;
	if((widget == NULL) || (motion == NULL) || (list == NULL))
	    return(status);

	/* See which widget this event is for */
	if(widget == list->clist)
	{
	    GtkCList *clist = GTK_CLIST(widget);

	    /* Check if the widget that the event occured over was
	     * the clist
	     */
	    GtkWidget *w = gtk_get_event_widget((GdkEvent *)motion);
	    if(widget == w)
	    {
		/* Send a "button_press_event" signal to the
		 * GtkCList if the event was not sent yet and
		 * button 1 is held
		 *
		 * This is so that the GtkCList catches the pointer
		 * when it enters it
		 */
		if(!list->initial_list_button_press_sent)
		{
		    gint x, y;
		    GdkModifierType mask;
		    GdkWindow *window = clist->clist_window;
		    GdkEvent ev;
		    GdkEventButton *button = (GdkEventButton *)&ev;

		    gdk_window_get_pointer(window, &x, &y, &mask);

		    button->type = GDK_BUTTON_PRESS;
		    button->window = window;
		    button->send_event = TRUE;
		    button->time = motion->time;
		    button->x = motion->x;
		    button->y = motion->y;
		    button->pressure = 1.0;
		    button->xtilt = 0.0;
		    button->ytilt = 0.0;
		    button->button = 1;
		    button->state = mask;
		    button->source = 0;
		    button->deviceid = 0;
		    button->x_root = 0;
		    button->y_root = 0;

		    if(mask & GDK_BUTTON1_MASK)
			gtk_widget_event(w, &ev);

		    status = TRUE;
		}
	    }
	    else
	    {
		/* Moved over one of the other GtkWidgets belonging
		 * to the Popup List?
		 */
		if((w == list->vscrollbar) ||
		   (w == list->hscrollbar) ||
		   (w == list->scrolled_window) ||
		   (w == list->toplevel)
		)
		{
		    /* Forward event to that GtkWidget */
		    gtk_widget_event(w, (GdkEvent *)motion);
		}
	    }
	}

	return(status);
}


/*
 *	Popup List's Shadow paint signal callback.
 */
static gint PUListShadowPaintCB(pulist_struct *list)
{
	PUListShadowDraw(list);
	return(FALSE);
}

/*
 *	Draws the Popup List's Shadow.
 */
static void PUListShadowDraw(pulist_struct *list)
{
	gint width, height;
	GdkPixmap *pixmap;
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget *w = (list != NULL) ? list->shadow : NULL;
	if(w == NULL)
	    return;

	window = w->window;
	pixmap = list->shadow_pm;
	if((window == NULL) || (pixmap == NULL))
	    return;

	gdk_window_get_size(pixmap, &width, &height);
	style = gtk_widget_get_style(w);

	gdk_draw_pixmap(
	    window, style->white_gc, pixmap,
	    0, 0, 0, 0, width, height
	);                           
}

/*
 *	Configures the Popup List's Shadow to the new geometry.
 *
 *	The shadow cast offset will be applied to the specified
 *	position.
 */
static void PUListShadowConfigure(                
	pulist_struct *list, gint x, gint y, gint width, gint height
)
{
	GtkWidget *w = (list != NULL) ? list->shadow : NULL;
	if(w == NULL)
	    return;

	if((width <= 0) || (height <= 0))
	    return;

	/* Update shadow geometry */
	gtk_widget_set_uposition(
	    w,
	    x + POPUP_LIST_SHADOW_OFFSET_X,
	    y + POPUP_LIST_SHADOW_OFFSET_Y
	);
	gtk_widget_set_usize(
	    w, width, height
	);
	gtk_widget_queue_resize(w);
}


/*
 *	Sets up the Popup List's GtkCList for the start of the drag.
 */
static void PUListCListDoDragSetUp(pulist_struct *list)
{
	GtkWidget *w = (list != NULL) ? list->clist : NULL;
	if(w == NULL)
	    return;

	gtk_widget_grab_focus(w);
	gtk_widget_grab_default(w);
	if(w != gtk_grab_get_current())
	    gtk_grab_add(w);
}

/*
 *	Removes all grabs from the Popup List's GtkCList, marking the
 *	end of the drag.
 */
static void PUListCListDoDragCleanUp(pulist_struct *list)
{
	GtkWidget *w = (list != NULL) ? list->clist : NULL;
	if(w == NULL)
	    return;

	gtk_grab_remove(w);
}


/*
 *	Map Button GtkDrawingArea "expose" event signal callback.
 */
static gint PUListMapButtonExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
	gint status = FALSE;
	gint y, y_inc, width, height;
	GtkStateType state;
	GdkWindow *window;
	GdkDrawable *drawable;
	GdkGC *gc;
	GtkWidget *button;
	GtkStyle *style;
	if(widget == NULL)
	    return(status);

	/* Get parent of given GtkDrawingArea widget, which should
	 * be a GtkButton
	 */
	button = widget->parent;
	if(button == NULL)
	    return(status);

	state = GTK_WIDGET_STATE(widget);
	window = widget->window;
	style = gtk_widget_get_style(button);
	if((window == NULL) || (style == NULL))
	    return(status);

	drawable = window;

	gdk_window_get_size(drawable, &width, &height);
	if((width <= 0) || (height <= 0))
	    return(status);

	/* Begin drawing */

	/* Background */
	gtk_style_apply_default_background(
	    style, drawable, FALSE, state,
	    NULL,
	    0, 0, width, height
	);

	/* Details */
	y_inc = 5;
	for(y = (gint)(height * 0.5f) - 1;
	    y >= 0;
	    y -= y_inc
	)
	{
	    gc = style->light_gc[state];
	    gdk_draw_line(
		drawable, gc,
		0, y + 0, width, y + 0
	    );
	    gc = style->dark_gc[state];
	    gdk_draw_line(
		drawable, gc,
		0, y + 1, width, y + 1
	    );
#if 0
	    gc = style->black_gc;
	    gdk_draw_line(
		drawable, gc,
		0, y + 1, width, y + 1
	    );
#endif
	}
	for(y = (gint)(height * 0.5) - 1 + y_inc;
	    y < height;
	    y += y_inc
	)
	{
	    gc = style->light_gc[state];
	    gdk_draw_line(
		drawable, gc,
		0, y + 0, width, y + 0
	    );
	    gc = style->dark_gc[state];
	    gdk_draw_line(
		drawable, gc,
		0, y + 1, width, y + 1
	    );
#if 0
	    gc = style->black_gc;
	    gdk_draw_line(
		drawable, gc,
		0, y + 1, width, y + 1
	    );
#endif
	}

	/* Send drawable to window if drawable is not the window */
	if(drawable != window)
	    gdk_draw_pixmap(
		window, style->fg_gc[state], drawable,
		0, 0, 0, 0, width, height
	    );

	status = TRUE;

	return(status);
}


/*
 *	Popup List Box GtkDrawingArea event signal callback.
 */
static gint PUListBoxEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gboolean key_press;
	GdkEventFocus *focus;
	GdkEventKey *key;
	GdkEventButton *button;
	pulistbox_struct *box = PULISTBOX(data);
	if((widget == NULL) || (event == NULL) || (box == NULL))
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_EXPOSE:
	    PUListBoxDraw(box);
	    status = TRUE;
	    break;

	  case GDK_FOCUS_CHANGE:
	    focus = (GdkEventFocus *)event;
	    if(focus->in && !GTK_WIDGET_HAS_FOCUS(widget))
	    {
		GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
		gtk_widget_queue_draw(widget);
		status = TRUE;
	    }
	    else if(!focus->in && GTK_WIDGET_HAS_FOCUS(widget))
	    {
		GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
		gtk_widget_queue_draw(widget);
		status = TRUE;
	    }
	    break;

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    key_press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
#define STOP_KEY_SIGNAL_EMIT    {               \
if(widget != NULL)                              \
 gtk_signal_emit_stop_by_name(                  \
  GTK_OBJECT(widget),                           \
  key_press ?                                   \
   "key_press_event" : "key_release_event"      \
 );                                             \
}
	    switch(key->keyval)
	    {
	      case GDK_Return:
	      case GDK_KP_Enter:
	      case GDK_space:
		if(key_press)
		    PUListBoxMapCB(box->map_btn, box);
		STOP_KEY_SIGNAL_EMIT
		status = TRUE; 
		break;

	      case GDK_Up:
	      case GDK_KP_Up:
		if(key_press)
		{
		    pulist_struct *list = PUListBoxGetPUList(box);
		    GtkCList *clist = (GtkCList *)PUListGetCList(list);
		    if(clist != NULL)
		    {
			GList *glist = clist->selection_end;
			gint row = (glist != NULL) ? (gint)glist->data : -1;
			if(row < 0)
			    row = 0;
			else 
			    row--;
			if((row >= 0) && (row < clist->rows))
			{
			    gtk_clist_unselect_all(clist);
			    gtk_clist_select_row(clist, row, 0);

			    gtk_widget_queue_draw(widget);

			    /* Report new value */
			    if(box->changed_cb != NULL)
				box->changed_cb(
				    box,		/* Popup List Box */
				    row,		/* Item */
				    box->changed_data	/* Data */
				);
			}
		    }
		}
		STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Down:
	      case GDK_KP_Down:
		if(key_press)
		{
		    pulist_struct *list = PUListBoxGetPUList(box);
		    GtkCList *clist = (GtkCList *)PUListGetCList(list);
		    if(clist != NULL)
		    {
			GList *glist = clist->selection_end;
			gint row = (glist != NULL) ? (gint)glist->data : -1;
			if(row < 0)
			    row = 0;
			else
			    row++;
			if((row >= 0) && (row < clist->rows))
			{
			    gtk_clist_unselect_all(clist);
			    gtk_clist_select_row(clist, row, 0);

			    gtk_widget_queue_draw(widget);

			    /* Report new value */
			    if(box->changed_cb != NULL)
				box->changed_cb(
				    box,		/* Popup List Box */
				    row,		/* Item */
				    box->changed_data	/* Data */
				);
			}
		    }
		}
		STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;
	    }
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(!GTK_WIDGET_HAS_FOCUS(widget))
		gtk_widget_grab_focus(widget);
	    if(button->button == 1)
		PUListBoxMapCB(box->map_btn, box);
	    status = TRUE;
	    break;
	}

	return(status);
}

/*
 *	Popup List Box map callback.
 */
static void PUListBoxMapCB(GtkWidget *widget, gpointer data)
{
	const gchar *v;
	pulist_struct *list;
	pulistbox_struct *box = PULISTBOX(data);
	if((widget == NULL) || (box == NULL))
	    return;

	list = PUListBoxGetPUList(box);
	if(list == NULL)
	    return;

	/* Map Popup List and wait for user response */
	v = PUListMapQuery(
	    list,
	    NULL,		/* Initial value */
	    -1,			/* Lines visible */
	    PULIST_RELATIVE_BELOW,
	    box->toplevel,
	    widget
	);

	/* Got new value? */
	if(v != NULL)
	{
	    gint i = PUListFindItemFromValue(list, v);

	    gtk_widget_queue_draw(box->da);

	    /* Report new value */
	    if(box->changed_cb != NULL)
		box->changed_cb(
		    box,		/* Popup List Box */
		    i,			/* Item */
		    box->changed_data	/* Data */
		);
	}
}

/*
 *	Redraws the Popup List Box.
 */
static void PUListBoxDraw(pulistbox_struct *box)
{
	const gint	frame_border = 2;
	gint width, height, font_height;
	GdkFont *font;
	GdkDrawable *drawable;
	GdkWindow *window;
	GtkStateType state;
	GtkStyle *style;
	pulist_struct *list;
	GtkWidget *w = (box != NULL) ? box->da : NULL;
	if(w == NULL)
	    return;

	window = w->window;
	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	list = PUListBoxGetPUList(box);
	if((window == NULL) || (style == NULL) || (list == NULL))
	    return;

	gdk_window_get_size(window, &width, &height);
	font = style->font;
	if((font == NULL) || (width <= 0) || (height <= 0))
	    return;
		   
	font_height = font->ascent + font->descent;

	drawable = (GdkDrawable *)window;

	/* Draw background */
	gdk_draw_rectangle(
	    drawable, style->base_gc[state], TRUE,
	    0, 0, width, height
	);

	/* Draw value */
	if(list->clist != NULL)
	{
	    /* Get selected row or first row */
	    GdkGC *gc = style->text_gc[state];
	    GtkCList *clist = GTK_CLIST(list->clist);
	    GList *glist = clist->selection_end;
	    gint row = (glist != NULL) ? (gint)glist->data : 0;

	    if((row >= 0) && (row < clist->rows))
	    {
		gint x = frame_border, y;
		gchar *text = NULL;
		guint8 spacing = 0;
		GdkPixmap *pixmap = NULL;
		GdkBitmap *mask = NULL;

		/* Get icon and text */
		switch(gtk_clist_get_cell_type(clist, row, 0))
		{
		  case GTK_CELL_TEXT:
		    gtk_clist_get_text(clist, row, 0, &text);
		    break;
		  case GTK_CELL_PIXTEXT:
		    gtk_clist_get_pixtext(
			clist, row, 0, &text,
			&spacing, &pixmap, &mask
		    );
		    break;
		  case GTK_CELL_PIXMAP:
		    gtk_clist_get_pixmap(
			clist, row, 0, &pixmap, &mask
		    );
		    break;
		  case GTK_CELL_WIDGET:
		  case GTK_CELL_EMPTY:
		    break;
		}
		/* Draw icon? */
		if(pixmap != NULL)
		{
		    gint pm_width, pm_height;
		    gdk_window_get_size(pixmap, &pm_width, &pm_height);
		    y = (height - pm_height) / 2;
		    gdk_gc_set_clip_mask(gc, mask);
		    gdk_gc_set_clip_origin(gc, x, y);
		    gdk_draw_pixmap(
			drawable, gc, pixmap,
			0, 0, x, y, pm_width, pm_height
		    );
		    gdk_gc_set_clip_mask(gc, NULL);
		    x += pm_width + (gint)spacing;
		}
		/* Draw text? */
		if(text != NULL)
		{
		    GdkTextBounds b;
		    gdk_string_bounds(font, text, &b);
		    gdk_draw_string(
			drawable, font, gc,
			x + b.lbearing,
			((height - font_height) / 2) +
			    font->ascent,
			text
		    );
		}
	    }
	}

	/* Draw frame */
	gtk_draw_shadow(
	    style, drawable, state, GTK_SHADOW_IN,
	    0, 0, width - 1, height - 1
	);

	/* Draw focus if widget is focused */
	if(GTK_WIDGET_HAS_FOCUS(w) && GTK_WIDGET_SENSITIVE(w))
	    gtk_draw_focus(
		style, drawable,
		0, 0, width - 1, height - 1
	    );

	/* Send drawable to window if drawable is not the window */
	if(drawable != window)                                     
	    gdk_draw_pixmap(
		window, style->fg_gc[state], drawable,
		0, 0, 0, 0, width, height
	    );
}

/*
 *	Returns the Popup List item index that matches the specified
 *	text.
 */
gint PUListFindItemFromValue(pulist_struct *list, const gchar *value)
{
	gint i;
	gchar *text;
	guint8 spacing;
	GdkPixmap *pixmap;
	GdkBitmap *mask;
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if((clist == NULL) || (value == NULL))
	    return(-1);

	for(i = 0; i < clist->rows; i++)
	{
	    text = NULL;
	    switch(gtk_clist_get_cell_type(clist, i, 0))
	    {
	      case GTK_CELL_TEXT:
		gtk_clist_get_text(
		    clist, i, 0, &text
		);
		break;
	      case GTK_CELL_PIXTEXT:
		gtk_clist_get_pixtext(
		    clist, i, 0, &text,
		    &spacing, &pixmap, &mask
		);
		break;
	      case GTK_CELL_PIXMAP:
	      case GTK_CELL_WIDGET:
	      case GTK_CELL_EMPTY:
		break;
	    }
	    if(text != NULL)
	    {
		if((text == value) || !strcmp(text, value))
		    return(i);
	    }
	}
	 
	return(-1);
}

/*
 *	Returns the Popup List item's data that matches the
 *	specified text.
 */
gpointer PUListGetDataFromValue(
	pulist_struct *list, const gchar *value
)
{
	gint i;
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if((clist == NULL) || (value == NULL))
	    return(NULL);

	i = PUListFindItemFromValue(list, value);
	return((i >= 0) ?
	    gtk_clist_get_row_data(clist, i) : NULL
	);
}

/*
 *	Returns the Popup List's toplevel GtkWindow.
 */
GtkWidget *PUListGetToplevel(pulist_struct *list)
{
	return((list != NULL) ? list->toplevel : NULL);
}

/*
 *	Returns the Popup List's GtkCList.
 */
GtkWidget *PUListGetCList(pulist_struct *list)
{
	return((list != NULL) ? list->clist : NULL);
}


/*
 *	Adds a new item to Popup List with the specified text.
 */
gint PUListAddItem(
	pulist_struct *list, const gchar *value
)
{
	return(PUListAddItemPixText(list, value, NULL, NULL));
}

/*
 *	Adds a new item to Popup List with the specified icon & text.
 */
gint PUListAddItemPixText(
	pulist_struct *list, const gchar *value,
	GdkPixmap *pixmap, GdkBitmap *mask
)
{
	gint i;
	gchar **strv;
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if((clist != NULL) ? (clist->columns <= 0) : TRUE)
	    return(-1);

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

	/* Append a new row */
	i = gtk_clist_append(clist, strv);

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

	if(i < 0)
	    return(-1);

	PUListSetItemPixText(list, i, value, pixmap, mask);

	return(i);
}

/*
 *	Deletes all items in the Popup List.
 */
void PUListClear(pulist_struct *list)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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


/*
 *	Sets the Popup List item's text.
 */
void PUListSetItemText(
	pulist_struct *list, gint i,
	const gchar *value
)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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

	gtk_clist_set_text(
	    clist, i, 0,
	    (value != NULL) ? value : ""
	);
}

/*
 *	Sets the Popup List item's icon & text.
 */
void PUListSetItemPixText(
	pulist_struct *list, gint i,
	const gchar *value,
	GdkPixmap *pixmap, GdkBitmap *mask
)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;      

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

	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, i, 0,
		(value != NULL) ? value : "",
		2, pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, i, 0,
		(value != NULL) ? value : ""  
	    );   
}

/*
 *	Sets the Popup List item's data.
 */
void PUListSetItemData(
	pulist_struct *list, gint i,
	gpointer data
)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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

	gtk_clist_set_row_data(clist, i, data);
}

/*
 *	Sets the Popup List item's data & destroy callback.
 */
void PUListSetItemDataFull(
	pulist_struct *list, gint i,
	gpointer data, GtkDestroyNotify destroy_cb
)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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

	if(destroy_cb != NULL)
	    gtk_clist_set_row_data_full(clist, i, data, destroy_cb);
	else
	    gtk_clist_set_row_data(clist, i, data);
}


/*
 *	Gets the Popup List item's text.
 */
void PUListGetItemText(
	pulist_struct *list, gint i,
	gchar **value
)
{
	PUListGetItemPixText(list, i, value, NULL, NULL);
}

/*
 *	Gets the Popup List item's icon & text.
 */
void PUListGetItemPixText(
	pulist_struct *list, gint i,
	gchar **value,
	GdkPixmap **pixmap, GdkBitmap **mask
)
{
	gchar *ltext = NULL;
	guint8 lspacing = 0;
	GdkPixmap *lpixmap = NULL;
	GdkBitmap *lmask = NULL;
	GtkCList *clist;

	if(value != NULL)
	    *value = NULL;
	if(pixmap != NULL)
	    *pixmap = NULL;
	if(mask != NULL)
	    *mask = NULL;

	clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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

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

	if(value != NULL)
	    *value = ltext;
	if(pixmap != NULL)
	    *pixmap = lpixmap;
	if(mask != NULL)
	    *mask = lmask;
}

/*
 *	Gets the Popup List item's data.
 */
gpointer PUListGetItemData(pulist_struct *list, gint i)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)                        
	    return(NULL);

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

	return(gtk_clist_get_row_data(clist, i));
}


/*
 *	Returns the last selected item index on the Popup List.
 */
gint PUListGetSelectedLast(pulist_struct *list)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	GList *glist = (clist != NULL) ? clist->selection_end : NULL;
	return((glist != NULL) ? (gint)glist->data : -1);
}

/*
 *	Selects the Popup List item specified by row.
 */
void PUListSelect(pulist_struct *list, gint i)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

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

	gtk_clist_freeze(clist);

	gtk_clist_unselect_all(clist);
	gtk_clist_select_row(clist, i, 0);

	if(gtk_clist_row_is_visible(clist, i) !=
	    GTK_VISIBILITY_FULL
	)
	    gtk_clist_moveto(
		clist,
		i, -1,	/* Row, column */
		0.5f, 0.0f	/* Row, column */
	    );

	gtk_clist_thaw(clist);
}

/*
 *	Unselects all items in the Popup List.
 */
void PUListUnselectAll(pulist_struct *list)
{
	GtkCList *clist = (GtkCList *)PUListGetCList(list);
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);
	gtk_clist_unselect_all(clist);
	gtk_clist_thaw(clist);
}


/*
 *	Checks if the Popup List is querying (if it is mapped).
 */
gboolean PUListIsQuery(pulist_struct *list)
{
	GtkWidget *w = (list != NULL) ? list->toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
}

/*
 *	Breaks the Popup List query and unmaps it.
 */
void PUListBreakQuery(pulist_struct *list)
{
	if(!PUListIsQuery(list))
	    return;

	while(list->gtk_block_level > 0)
	{
	    list->gtk_block_level--;
	    gtk_main_quit();
	}
}

/*
 *	Maps the Popup List and blocks for user input.
 *
 *	The Popup List will be mapped relative to the rel_widget.
 *
 *	Returns the selected value, the returned pointer must not be
 *	modified or deleted.
 *
 *	If the user clicks outside of the Popup List or the escape
 *	key is pressed then NULL will be returned.
 */
const gchar *PUListMapQuery(
	pulist_struct *list,
	const gchar *value,		/* Initial value */
	gint lines_visible,		/* Can be -1 for default */
	pulist_relative relative,	/* One of PULIST_RELATIVE_* */
	GtkWidget *rel_widget,		/* Map relative to this widget */
	GtkWidget *map_widget		/* Widget that mapped this list */
)
{
	gint sel_item = -1;
	gint	x, y,
		width = POPUP_LIST_DEF_WIDTH,
		height = POPUP_LIST_DEF_HEIGHT;
	gint root_width, root_height;
	GdkWindow *root;
	GtkWidget *w, *toplevel, *shadow;


	if(list == NULL)
	    return(NULL);

	/* Get toplevel and check if it is already mapped */
	toplevel = w = list->toplevel;
	if((w != NULL) ? GTK_WIDGET_MAPPED(w) : TRUE)
	    return(NULL);

	/* Get root window */
	root = gdk_window_get_parent(toplevel->window);

	/* Get shadow */
	shadow = list->shadow;

	/* Reset values */
	g_free(list->last_value);
	list->last_value = NULL;
	list->initial_list_button_press_sent = FALSE;

	/* Get root window geometry */
	gdk_window_get_size(root, &root_width, &root_height);

	/* Calculate size of list based on the size of the relative
	 * widget
	 */
	w = rel_widget;
	if(w != NULL)
	    width = w->allocation.width;
	height = ((lines_visible < 0) ? 10 : lines_visible) *
	    POPUP_LIST_ROW_SPACING;

	/* If map widget is given, then we need to restore its state
	 * in preparation to mapping of the popup list.
	 */
	list->map_widget = w = map_widget;
	if(w != NULL)
	{
	    /* Remove grab from map widget as needed */
	    gtk_grab_remove(w);

	    /* Handle additional restoring by widget type */
	    if(GTK_IS_BUTTON(w))
	    {
		GtkButton *button = GTK_BUTTON(w);
		button->in_button = 1;
		button->button_down = 1;
	    }
	}

	/* If a value has been specified, then select the item that
	 * matches the specified value
	 */
	w = list->clist;
	if((w != NULL) && (value != NULL))
	{
	    sel_item = PUListFindItemFromValue(list, value);
	    if(sel_item > -1)
	    {
		GtkCList *clist = GTK_CLIST(w);
		gtk_clist_select_row(clist, sel_item, 0);
	    }
	}

	/* Move and resize the toplevel & shadow */
	if((toplevel != NULL) && (shadow != NULL) && (root != NULL))
	{
	    /* Relative widget specified? */
	    w = rel_widget;
	    if((w != NULL) ? (w->window != NULL) : FALSE)
	    {
		GdkWindow *window = w->window;
		GtkAllocation *alloc = &w->allocation;

		/* Get coordinates of the relative widget with
		 * respect to the desktop
		 */
		gdk_window_get_root_position(window, &x, &y);
		if(GTK_WIDGET_NO_WINDOW(w))
		{
		    x += alloc->x;
		    y += alloc->y;
		}
		/* Adjust coordinates to be appropriate to the 
		 * specified map relativity
		 */
		switch(relative)
		{
		  case PULIST_RELATIVE_CENTER:
		    y = y - (height / 2) + (alloc->height / 2);
		    break;
		  case PULIST_RELATIVE_UP:
		    y = y - height + alloc->height;
		    break;     
		  case PULIST_RELATIVE_DOWN:
		    break;
		  case PULIST_RELATIVE_ABOVE:
		    y = y - height;
		    break;
		  case PULIST_RELATIVE_BELOW:
		    y = y + alloc->height;
		    break;
	        }
	    }
	    else
	    {
		/* Relative widget not specified, so get the
		 * pointer's coordinates
		 */
		gint px = 0, py = 0;
		GdkModifierType mask;

		gdk_window_get_pointer(root, &px, &py, &mask);
		x = px - (width / 2);
		y = py - (height / 2);
	    }
	    /* Clip coordinates with respect to the desktop */
	    if(x > (root_width - width))
		x = root_width - width;
	    if(x < 0)
		x = 0;
	    if(y > (root_height - height))
		y = root_height - height;
	    if(y < 0)
		y = 0;

	    /* Move toplevel & shadow */
	    gtk_widget_set_uposition(toplevel, x, y);
	    gdk_window_move(toplevel->window, x, y);
	    gtk_widget_set_uposition(shadow, x, y);
	    gdk_window_move(shadow->window, x, y);

	    /* Resize toplevel & shadow */
	    gtk_widget_set_usize(toplevel, width, height);
	    gdk_window_resize(toplevel->window, width, height);
	    gtk_widget_set_usize(shadow, width, height);
	    gdk_window_resize(shadow->window, width, height);

	    /* Notify toplevel to resize */
/*	    gtk_widget_queue_resize(toplevel); */
	}

	/* Recreate shadow pixmap */
	if(root != NULL)
	{
	    GdkPixmap *pixmap = GDK_PIXMAP_NEW(width, height);

	    GDK_PIXMAP_UNREF(list->shadow_pm)
	    list->shadow_pm = pixmap;

	    if(pixmap != NULL)   
	    {
		GdkWindow *window = shadow->window;
		GdkColor *c, shadow_color;
		GdkColormap *colormap = gdk_window_get_colormap(window);
		GdkGC *gc = GDK_GC_NEW();

		c = &shadow_color;
		c->red = 0x5fff;
		c->green = 0x5fff;
		c->blue = 0x5fff;
		GDK_COLORMAP_ALLOC_COLOR(colormap, c);

		gdk_gc_set_subwindow(gc, GDK_INCLUDE_INFERIORS);
		gdk_window_copy_area(
		    pixmap, gc,
		    0, 0,
		    root,
		    x + POPUP_LIST_SHADOW_OFFSET_X,
		    y + POPUP_LIST_SHADOW_OFFSET_Y,
		    width, height
		);
		gdk_gc_set_subwindow(gc, GDK_CLIP_BY_CHILDREN);

		gdk_gc_set_function(gc, GDK_AND);
		gdk_gc_set_foreground(gc, c);
		gdk_draw_rectangle(
		    pixmap, gc, TRUE,
		    0, 0, width, height
		);
		gdk_gc_set_function(gc, GDK_COPY);

		GDK_GC_UNREF(gc);
		GDK_COLORMAP_FREE_COLOR(colormap, c);
	    }
	}


	/* Map toplevel */
	gtk_widget_show_raise(shadow);
	gtk_widget_show_raise(toplevel);

	/* Set up the clist in preperation for dragged selecting */
	PUListCListDoDragSetUp(list);

	/* Move list scroll to the selected item (if any) */
	if((list->clist != NULL) && (sel_item > -1))
	    gtk_clist_moveto(
		GTK_CLIST(list->clist),
		sel_item, -1, 0.5f, 0.0f
	    ); 


	/* Wait for user response by pushing a GTK block loop */
	if(list->gtk_block_level < 0)
	    list->gtk_block_level = 0;
	list->gtk_block_level++;
	gtk_main();

	/* Broke out of GTK block loop */

	/* Remove grabs from clist and do clean up after dragged
	 * selecting
	 */
	PUListCListDoDragCleanUp(list);

	/* Unmap */
	gtk_widget_hide(toplevel);
	gtk_widget_hide(shadow);
	GDK_PIXMAP_UNREF(list->shadow_pm)
	list->shadow_pm = NULL;

#if 0
	/* Restore map widget */
	w = list->map_widget;
	if(w != NULL)
	{
	    /* Handle additional state restoring by widget type */
	    if(GTK_IS_BUTTON(w))
	    {
/* TODO */

	    }
	}
#endif

	/* Unset map widget */
	list->map_widget = NULL;

	return(list->last_value);
}


/*
 *	Creates a new Popup List.
 */
pulist_struct *PUListNew(void)
{
	GdkWindow *window;
	GtkWidget *w, *parent, *parent2;
	GtkCList *clist;
	pulist_struct *list = (pulist_struct *)g_malloc0(
	    sizeof(pulist_struct)
	);
	if(list == NULL)
	    return(list);

	/* Reset values */
	list->shadow_pm = NULL;
	list->map_widget = NULL;
	list->gtk_block_level = 0;
	list->last_value = NULL;
	list->initial_list_button_press_sent = FALSE;

	/* Create toplevel */
	list->toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
	);
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    /* No decorations */
	    gdk_window_set_decorations(window, 0);
	    /* No functions */
	    gdk_window_set_functions(window, 0);
	}
	gtk_widget_add_events(
	    w,                                  
	    GDK_STRUCTURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(PUListDeleteEventCB), list
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(PUListConfigureEventCB), list
	);
	parent = w;

	/* Main vbox */
	list->main_vbox = w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Scrolled window for clist */
	list->scrolled_window = w = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(
	    GTK_SCROLLED_WINDOW(w),
	    GTK_POLICY_AUTOMATIC,
	    GTK_POLICY_AUTOMATIC
	);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_show(w);
	list->vscrollbar = GTK_SCROLLED_WINDOW(w)->vscrollbar;
	list->hscrollbar = GTK_SCROLLED_WINDOW(w)->hscrollbar;
	parent2 = w;

	/* CList */
	list->clist = w = gtk_clist_new(1);
	clist = GTK_CLIST(w);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT | GTK_CAN_FOCUS);
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_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(PUListKeyPressEventCB), list
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(PUListKeyPressEventCB), list
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(PUListButtonPressEventCB), list
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(PUListButtonPressEventCB), list
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(PUListMotionNotifyEventCB), list
	);
	gtk_container_add(GTK_CONTAINER(parent2), w);
	gtk_widget_realize(w);
	gtk_clist_set_shadow_type(clist, GTK_SHADOW_IN);
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
	gtk_clist_set_column_width(clist, 0, 10);
	gtk_clist_set_row_height(clist, POPUP_LIST_ROW_SPACING);
	gtk_widget_show(w);


	/* Shadow */
	list->shadow = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_app_paintable(w, TRUE);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(PUListShadowPaintCB), (GtkObject *)list
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "draw",
	    GTK_SIGNAL_FUNC(PUListShadowPaintCB), (GtkObject *)list
	);
	gtk_window_set_policy(
	    GTK_WINDOW(w), TRUE, TRUE, TRUE
	);
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    /* No decorations */
	    gdk_window_set_decorations(window, 0);
	    /* No functions */
	    gdk_window_set_functions(window, 0);

	    gdk_window_set_override_redirect(window, TRUE);
	}


	return(list);
}

/*
 *	Deletes the Popup List.
 */
void PUListDelete(pulist_struct *list)
{
	if(list == NULL)
	    return;

	/* Break out of any remaining GTK main loop levels */
	while(list->gtk_block_level > 0)
	{
	    list->gtk_block_level--;
	    gtk_main_quit();
	}

	/* Begin destroying widgets */
	GTK_WIDGET_DESTROY(list->shadow)
	GTK_WIDGET_DESTROY(list->clist)
	GTK_WIDGET_DESTROY(list->scrolled_window)
	GTK_WIDGET_DESTROY(list->main_vbox)
	GTK_WIDGET_DESTROY(list->toplevel)

	GDK_PIXMAP_UNREF(list->shadow_pm)

	g_free(list->last_value);

	g_free(list);
}


/*
 *	Creates a new GtkButton that is to appear as a popup list
 *	map button.
 */
GtkWidget *PUListNewMapButton(
	void (*map_cb)(GtkWidget *, gpointer),
	gpointer client_data
)
{
	GtkWidget *w, *button;


	/* Create new button */
	button = w = gtk_button_new();
	if(button == NULL)
	    return(button);

	/* Set standard fixed size for button */
	gtk_widget_set_usize(
	    w,
	    POPUP_LIST_MAP_BTN_WIDTH,
	    POPUP_LIST_MAP_BTN_HEIGHT
	);
	/* Set map callback function as "pressed" signal as needed */
	if(map_cb != NULL)
	    gtk_signal_connect_after(
		GTK_OBJECT(w), "pressed",
		GTK_SIGNAL_FUNC(map_cb), client_data
	    );


	/* Create drawing area */
	w = gtk_drawing_area_new();
	gtk_widget_add_events(w, GDK_EXPOSURE_MASK);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(PUListMapButtonExposeCB), NULL
	);
	gtk_container_add(GTK_CONTAINER(button), w);
	gtk_widget_show(w);


	return(button);
}

/*
 *      Creates a new GtkButton that is to appear as a popup list
 *      map button with an arrow.
 */
GtkWidget *PUListNewMapButtonArrow(
	GtkArrowType arrow_type, GtkShadowType shadow_type,
	void (*map_cb)(GtkWidget *, gpointer),
	gpointer client_data
)
{
	GtkWidget *w, *button;


	/* Create new button */
	button = w = gtk_button_new();
	if(button == NULL)
	    return(button);

	/* Set standard fixed size for button */
	gtk_widget_set_usize(
	    w,
	    POPUP_LIST_MAP_BTN_WIDTH,
	    POPUP_LIST_MAP_BTN_HEIGHT
	);
	/* Set map callback function as "pressed" signal as needed */
	if(map_cb != NULL)
	    gtk_signal_connect_after(
		GTK_OBJECT(w), "pressed",
		GTK_SIGNAL_FUNC(map_cb), client_data
	    );

	/* Create arrow */
	w = gtk_arrow_new(arrow_type, shadow_type);
	gtk_container_add(GTK_CONTAINER(button), w);
	gtk_widget_show(w);

	return(button);
}


/*
 *	Returns the Popup List Box's toplevel widget.
 */
GtkWidget *PUListBoxGetToplevel(pulistbox_struct *box)
{
	return((box != NULL) ? box->toplevel : NULL);
}

/*
 *	Returns the Popup List Box's Popup List.
 */
pulist_struct *PUListBoxGetPUList(pulistbox_struct *box)
{
	return((box != NULL) ? box->pulist : NULL);
}


/*
 *	Creates a new Popup List Box.
 */
pulistbox_struct *PUListBoxNew(
	GtkWidget *parent,
	gint width, gint height
)
{
	GtkWidget *w;
	pulistbox_struct *box = PULISTBOX(g_malloc0(
	    sizeof(pulistbox_struct)
	));
	if(box == NULL)
	    return(NULL);

	/* Toplevel hbox */
	box->toplevel = w = gtk_hbox_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,
		    (width <= 0) ? TRUE : FALSE,
		    (width <= 0) ? TRUE : FALSE,   
		    0
		);   
	    else
		gtk_container_add(
		    GTK_CONTAINER(parent), w
		);
	}
	parent = w;

	/* GtkDrawingArea */
	box->da = w = gtk_drawing_area_new();
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS);
	gtk_widget_add_events(                 
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);   
	gtk_signal_connect(                                      
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",             
	    GTK_SIGNAL_FUNC(PUListBoxEventCB), box
	);
	if(height <= 0)
	    gtk_widget_set_usize(w, -1, 20 + (2 * 2));
	gtk_box_pack_start(
	    GTK_BOX(parent), w,
	    (width <= 0) ? TRUE : FALSE,
	    (width <= 0) ? TRUE : FALSE, 
	    0
	);
	gtk_widget_show(w);

	/* Map button */
	box->map_btn = w = PUListNewMapButtonArrow(
	    GTK_ARROW_DOWN, GTK_SHADOW_OUT,
	    PUListBoxMapCB, box
	);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	/* Popup List */
	box->pulist = PUListNew();

	return(box);
}

/*
 *	Sets the Popup List Box's changed callback.
 */
void PUListBoxSetChangedCB(
	pulistbox_struct *box,
	void (*func)(
		pulistbox_struct *,	/* Popup List Box */
		gint,			/* Item */         
		gpointer		/* Data */ 
	),   
	gpointer data 
)
{
	if(box == NULL)
	    return;

	box->changed_cb = func;
	box->changed_data = data;
}

/*
 *	Maps the Popup List Box.
 */
void PUListBoxMap(pulistbox_struct *box)
{
	GtkWidget *w = (box != NULL) ? box->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_show(w);
}

/*
 *	Unmaps the Popup List Box.
 */
void PUListBoxUnmap(pulistbox_struct *box)
{
	GtkWidget *w = (box != NULL) ? box->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_hide(w);
}

/*
 *	Deletes the Popup List Box.
 */
void PUListBoxDelete(pulistbox_struct *box)
{
	if(box == NULL)
	    return;

	PUListDelete(box->pulist);
	GTK_WIDGET_DESTROY(box->da);
	GTK_WIDGET_DESTROY(box->map_btn);
	GTK_WIDGET_DESTROY(box->toplevel);
	g_free(box);
}
