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

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

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

#include "images/icon_ok_20x20.xpm"
#include "images/icon_cancel_20x20.xpm"
#include "images/icon_browse_20x20.xpm"


/*
 *	Floating Prompt:
 */
typedef struct _FPromptData		FPromptData;
struct _FPromptData {

	GtkWidget       *toplevel,	/* Popup GtkWindow */
			*main_hbox,
			*label,
			*entry,
			*browse_btn,
			*ok_btn,
			*cancel_btn,
			*shadow;	/* Popup GtkWindow */
	GdkPixmap	*shadow_pm;

	gint		x, y,		/* Geometry of toplevel */
			width, height;
	fprompt_shadow	shadow_style;

	gpointer	client_data;
	gchar	*(*browse_cb)(
		gpointer,		/* Data */
		const gchar *		/* Value */
	);
	void	(*apply_cb)(
		gpointer,		/* Data */
		const gchar *		/* Value */
	);
	void	(*cancel_cb)(
		gpointer		/* Data */
	);

};
#define FPROMPT_DATA(p)	((FPromptData *)(p))


static FPromptData		*fprompt = NULL;


static gint FPromptEntryEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FPromptEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void FPromptBrowseCB(GtkWidget *widget, gpointer data);
static void FPromptOKCB(GtkWidget *widget, gpointer data);
static void FPromptCancelCB(GtkWidget *widget, gpointer data);

static gint FPromptShadowPaintCB(FPromptData *p);
static void FPromptShadowDraw(FPromptData *p);
static void FPromptShadowConfigure(
	FPromptData *p, gint x, gint y, gint width, gint height
);

gint FPromptInit(void);
void FPromptSetTransientFor(GtkWidget *w);
gboolean FPromptIsQuery(void);
void FPromptBreakQuery(void);
void FPromptSetPosition(gint x, gint y);
void FPromptSetShadow(fprompt_shadow shadow);
gint FPromptMapQuery(
	const gchar *label,
	const gchar *value,
	const gchar *tooltip_message,
	fprompt_map_code map_code,	/* One of FPROMPT_MAP_* */
	gint width, gint height,
	fprompt_flags flags,		/* Any of FPROMPT_FLAG_* */
	gpointer client_data,
	gchar *(*browse_cb)(gpointer, const gchar *),
	void (*apply_cb)(gpointer, const gchar *),
	void (*cancel_cb)(gpointer)
);
void FPromptMap(void);
void FPromptUnmap(void);
void FPromptShutdown(void);


#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 FPROMPT_SHADOW_OFFSET_X		5
#define FPROMPT_SHADOW_OFFSET_Y		5


/*
 *	Floating Prompt GtkEntry event signal callback.
 */
static gint FPromptEntryEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gboolean press;
	GdkEventKey *key;
	GdkEventButton *button;
	GtkWidget *w;
	FPromptData *p = FPROMPT_DATA(data);
	if((widget == NULL) || (event == NULL) || (p == NULL))
	    return(status);

	if(!FPromptIsQuery())
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_FOCUS_CHANGE:
	    /* Focus events are never sent */
	    break;

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
	    switch(key->keyval)
	    {
	      case GDK_Escape:
		if(press)
		{
		    /* Break query on escape, then return immediately
		     * so that the widget is not grabbed any more
		     */
		    FPromptBreakQuery();
		    status = TRUE;
		    return(status);
		}
		status = TRUE;
		break;

	      case GDK_Return:
	      case GDK_KP_Enter:
	      case GDK_ISO_Enter:
	      case GDK_3270_Enter:
		if(press)
		{
		    /* Call ok callback and return immediately so that
		     * the widget is not grabbed any more
		     */
		    FPromptOKCB(NULL, data);
		    status = TRUE;
		    return(status);
		}
		status = TRUE;
		break;
	    }
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    w = p->toplevel;
	    if(w != NULL)
	    {
		gint x, y;
		GdkModifierType mask;
		GdkWindow *window = w->window;

		/* Clicked outside of the toplevel window? Check if the
		 * event GdkWindow differs from the toplevel GdkWindow
		 */
		gdk_window_get_pointer(window, &x, &y, &mask);
		if((x < 0) || (x >= p->width) ||
		   (y < 0) || (y >= p->height) 
		)
		{
		    /* Since a button event occured outside of the
		     * Floating Prompt's window we need to break query
		     * and return immediately so that the widget is not
		     * grabbed any more
		     */
		    FPromptBreakQuery();
		    status = TRUE;
		    return(status);
		}  
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    status = TRUE;
	    break;
	}

	/* Need to regrab the widget, this is so that the next event
	 * such as a "key_press_event" is sent to this widget
	 */
	w = p->entry;
	if((w != NULL) && (w != gtk_grab_get_current()))
	    gtk_grab_add(w);

	return(status);
}

/*
 *	Toplevel GtkWindow "delete_event" signal callback.
 */
static gint FPromptEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventConfigure *configure;
	FPromptData *p = FPROMPT_DATA(data);
	if((widget == NULL) || (event == NULL) || (p == NULL))
	    return(status);

	if(!FPromptIsQuery())
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_DELETE:
	    FPromptCancelCB(widget, data);
	    status = TRUE;
	    break;

	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    /* Update toplevel geometry */
	    p->x = configure->x;
	    p->y = configure->y;
	    p->width = configure->width;
	    p->height = configure->height;
	    /* Update shadow */
	    FPromptShadowConfigure(
		p,
		configure->x, configure->y,
		configure->width, configure->height
	    );
	    status = TRUE;
	    break;
	}

	return(status);
}

/*
 *	Browse callback.
 */
static void FPromptBrowseCB(GtkWidget *widget, gpointer data)
{
	FPromptData *p = FPROMPT_DATA(data);
	if((widget == NULL) || (p == NULL))
	    return;  

	/* Browse callback set? */
	if(p->browse_cb != NULL)
	{
	    GtkWidget *w = p->entry;

	    /* Get current value */
	    gchar *v = STRDUP((w != NULL) ?
		gtk_entry_get_text(GTK_ENTRY(w)) : NULL
	    );

	    /* Call browse callback and get a statically allocated
	     * return string
	     */
	    const gchar *rtn_v = p->browse_cb(
		p->client_data,		/* Data */
		v			/* Value */
	    );
	    g_free(v);

	    /* Set new value obtained from the browse callback */
	    if((w != NULL) && (rtn_v != NULL))
		gtk_entry_set_text(GTK_ENTRY(w), rtn_v);
	}
}

/*
 *	OK callback.
 */
static void FPromptOKCB(GtkWidget *widget, gpointer data)
{
	FPromptData *p = FPROMPT_DATA(data);
	if(p == NULL)
	    return;

	/* Apply callback set? */
	if(p->apply_cb != NULL)
	{
	    GtkWidget *w = p->entry;
	    gchar *v = STRDUP((w != NULL) ?
		gtk_entry_get_text(GTK_ENTRY(w)) : NULL
	    );
	    if(v != NULL)
	    {
		/* Call apply callback */
		p->apply_cb(
		    p->client_data,	/* Data */
		    v			/* Value */
		);
		g_free(v);
	    }
	    p->apply_cb = NULL;
	    p->client_data = NULL;
	}

	/* Reset values */
	p->client_data = NULL;
	p->browse_cb = NULL; 
	p->apply_cb = NULL;
	p->cancel_cb = NULL;

	FPromptUnmap();
}

/*
 *	Cancel callback.
 */
static void FPromptCancelCB(GtkWidget *widget, gpointer data)
{
	FPromptData *p = FPROMPT_DATA(data);
	if(p == NULL)
	    return;

	/* Cancel callback set? */
	if(p->cancel_cb != NULL)
	{
	    /* Call cancel callback */
	    p->cancel_cb(p->client_data);
	}

	/* Reset values */
	p->client_data = NULL;
	p->browse_cb = NULL; 
	p->apply_cb = NULL;
	p->cancel_cb = NULL;

	FPromptUnmap();
}


/*
 *	Floading Prompt Shadow paint signal callback.
 */
static gint FPromptShadowPaintCB(FPromptData *p)
{
	FPromptShadowDraw(p);
	return(FALSE);
}

/*
 *	Draws the Floading Prompt Shadow.
 */
static void FPromptShadowDraw(FPromptData *p)
{
	gint width, height;
	GdkPixmap *pixmap;
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget *w = (p != NULL) ? p->shadow : NULL;
	if(w == NULL)
	    return;

	window = w->window;
	pixmap = p->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 Floating Prompt's Shadow to the new geometry.
 *
 *	The shadow cast offset will be applied to the specified
 *	position.
 */
static void FPromptShadowConfigure(
	FPromptData *p, gint x, gint y, gint width, gint height
)
{
	GtkWidget *w = (p != NULL) ? p->shadow : NULL;
	if(w == NULL)
	    return;

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

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


/*
 *	Initializes the Floating Prompt.
 */
gint FPromptInit(void)
{
	const gint	border_minor = 2;
	GdkWindow *window;
	GtkWidget *w, *parent;
	FPromptData *p;

	/* Already initialized */
	if(fprompt != NULL)
	    return(0);

	/* Allocate new Floating Prompt */
	p = FPROMPT_DATA(g_malloc0(sizeof(FPromptData)));
	if(p == NULL)
	     return(-3);

	/* Reset values */
	p->shadow_pm = NULL;
	p->x = 0;
	p->y = 0;
	p->width = FPROMPT_DEF_WIDTH;
	p->height = FPROMPT_DEF_HEIGHT;
	p->shadow_style = FPROMPT_SHADOW_SHADED;
	p->client_data = NULL;
	p->browse_cb = NULL;
	p->apply_cb = NULL;
	p->cancel_cb = NULL;

	/* Begin creating widgets */

	/* Toplevel */
	p->toplevel = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_window_set_policy(
	    GTK_WINDOW(w), TRUE, TRUE, TRUE
	);
	gtk_widget_set_uposition(w, p->x, p->y);
	gtk_widget_set_usize(w, p->width, p->height);
	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);
	}
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(FPromptEventCB), p
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(FPromptEventCB), p
	);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	parent = w;

	/* Main outer frame */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	/* Main hbox */
	p->main_hbox = w = gtk_hbox_new(FALSE, border_minor);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;

	/* Label */
	p->label = w = gtk_label_new("Value:");
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Entry */
	p->entry = w = gtk_entry_new();
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT | GTK_CAN_FOCUS);
	/* When connecting signals it is important to connect certain
	 * signals after in order for the widget to to be grabbed
	 * again in the event callback function
	 */
	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 | GDK_POINTER_MOTION_HINT_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(FPromptEntryEventCB), p  
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(FPromptEntryEventCB), p
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FPromptEntryEventCB), p
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FPromptEntryEventCB), p
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(FPromptEventCB), p
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(FPromptEventCB), p
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(FPromptEventCB), p
	);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);

	/* Browse button */
	p->browse_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_browse_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FPromptBrowseCB), p
	);

	/* OK button */
	p->ok_btn = w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)icon_ok_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FPromptOKCB), p
	);

	/* Cancel Button */
	p->cancel_btn = w = (GtkWidget *)GUIButtonPixmap(
	    (guint8 **)icon_cancel_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FPromptCancelCB), p
	);


	/* Shadow */
	p->shadow = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_uposition(w, p->x, p->y);
	gtk_widget_set_usize(w, p->width, p->height);
	gtk_widget_set_app_paintable(w, TRUE);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "expose_event",  
	    GTK_SIGNAL_FUNC(FPromptShadowPaintCB), (GtkObject *)p
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "draw",
	    GTK_SIGNAL_FUNC(FPromptShadowPaintCB), (GtkObject *)p
	);
	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);
	}


	fprompt = p;

	return(0);
}

/*
 *	Sets Floating Prompt as a transient for the given window.
 *
 *	This function has no affect since the Floating Prompt's
 *	GtkWindow is a GTK_WINDOW_POPUP and its entry grabs input,
 *	so there is no need to set it modal or transient for.
 */
void FPromptSetTransientFor(GtkWidget *w)
{

}

/*
 *	Returns TRUE if currently blocking for query.
 */
gboolean FPromptIsQuery(void)
{
	FPromptData *p = fprompt;
	GtkWidget *w = (p != NULL) ? p->toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
}

/*
 *	Ends query if any and returns a not available response.
 */
void FPromptBreakQuery(void)
{
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	/* If still in query then call the cancel callback to report
	 * cancel, reset values, and unmap the Floating Prompt
	 */
	if(FPromptIsQuery())
	    FPromptCancelCB(NULL, p);

	/* Reset values */
	p->client_data = NULL;
	p->browse_cb = NULL;
	p->apply_cb = NULL;
	p->cancel_cb = NULL;
}

/*
 *	Moves the Floating Prompt to the specified coordinates the
 *	next time it is mapped.
 */
void FPromptSetPosition(gint x, gint y)
{
	GtkWidget *toplevel, *shadow;
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	toplevel = p->toplevel;
	shadow = p->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);

	p->x = x;
	p->y = y;
}

/*
 *	Sets the Floating Prompt's shadow style.
 */
void FPromptSetShadow(fprompt_shadow shadow)
{
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	p->shadow_style = shadow;
}


/*
 *	Maps the Floating Prompt and sets it up with the given values.
 *
 *	Does not block.
 *
 *	Returns 0 on success or non-zero on error.
 */
gint FPromptMapQuery(
	const gchar *label,		/* Hides label if NULL */
	const gchar *value,
	const gchar *tooltip_message,
	fprompt_map_code map_code,	/* One of FPROMPT_MAP_* */
	gint width, gint height,
	fprompt_flags flags,		/* Any of FPROMPT_FLAG_* */
	gpointer client_data,
	gchar *(*browse_cb)(gpointer, const gchar *),
	void (*apply_cb)(gpointer, const gchar *),
	void (*cancel_cb)(gpointer)
)
{
	GtkWidget *w, *toplevel, *shadow;
	FPromptData *p = fprompt;
	if(p == NULL)
	    return(-1);

	toplevel = p->toplevel;
	shadow = p->shadow;

	/* Set new label */
	w = p->label;
	if(w != NULL)
	{
	    if(label == NULL)
	    {
		gtk_widget_hide(w);
	    }
	    else
	    {
		gtk_label_set_text(GTK_LABEL(w), label);
		gtk_widget_show(w);
	    }
	}

	/* Set new entry value */
	w = p->entry;
	if(w != NULL)
	{
	    if(value != NULL)
	    {
		gint len = STRLEN(value);
		gtk_entry_set_text(GTK_ENTRY(w), value);
		if(flags & FPROMPT_FLAG_SELECT_VALUE)
		    gtk_entry_select_region(GTK_ENTRY(w), 0, len);
		gtk_entry_set_position(GTK_ENTRY(w), len);
	    }
	    else
	    {
		/* Leave value it was */
	    }

	    /* Set new tooltip message */
	    GUISetWidgetTip(w, tooltip_message);
	}

	/* Show/hide buttons */
	/* Browse */
	w = p->browse_btn;
	if(w != NULL)
	{
	    if(flags & FPROMPT_FLAG_BROWSE_BTN)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);	    
	}
	/* OK */
	w = p->ok_btn;
	if(w != NULL)
	{
	    if(flags & FPROMPT_FLAG_OK_BTN)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}
	/* Cancel */
	w = p->cancel_btn;
	if(w != NULL)
	{
	    if(flags & FPROMPT_FLAG_CANCEL_BTN)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}


	/* Update toplevel & shadow */
	if((toplevel != NULL) && (shadow != NULL))
	{
	    gint x = 0, y = 0, lwidth, lheight;
	    GdkWindow	*window,
			*root = (toplevel->window != NULL) ?
		gdk_window_get_parent(toplevel->window) : NULL;

	    /* Resize of toplevel & shadow */
	    p->width = (width > 0) ? width : FPROMPT_DEF_WIDTH;
	    p->height = (height > 0) ? height : FPROMPT_DEF_HEIGHT;

	    gtk_widget_set_usize(toplevel, width, height);
	    gdk_window_get_size(toplevel->window, &lwidth, &lheight);
	    gdk_window_resize(
		toplevel->window,
		(width > 0) ? width : lwidth,
		(height > 0) ? height : lheight
	    );

	    gtk_widget_set_usize(shadow, width, height);
	    gdk_window_resize(shadow->window, 5, 5);

	    /* Move the toplevel's GdkWindow, calculate x and y
	     * position relative to parent (root) GdkWindow by the
	     * specified map code
	     */
	    switch(map_code)
	    {
	      case FPROMPT_MAP_TO_POINTER_WINDOW:
		window = gdk_window_at_pointer(&x, &y);
		if(window != NULL)
		{
		    gint wwidth, wheight;
		    gdk_window_get_root_position(window, &x, &y);
		    gdk_window_get_size(window, &wwidth, &wheight);
		    x += (wwidth / 2) - (p->width / 2);
		    y += (wheight / 2) - (p->height / 2);
		    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);
		}
		break;

	      case FPROMPT_MAP_TO_POINTER:
		if(root != NULL)
		{
		    gdk_window_get_pointer(root, &x, &y, 0);
		    x -= (p->width / 2);
		    y -= (p->height / 2);
		}
		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);
		break;

	      case FPROMPT_MAP_NO_MOVE:
		break;
	    }

	    /* Create shadow pixmap */
	    if(root != NULL)
	    {
		GtkWidget *w = p->shadow;
		GdkWindow *window = w->window;
		GdkPixmap *pixmap = GDK_PIXMAP_NEW(p->width, p->height);
		GtkStyle *style = gtk_widget_get_style(w);

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

		if(pixmap != NULL)
		{
		    gint mask_width = 8, mask_height = 2;
		    guint8 bm_data_8x2[] = { 0x55, 0xaa };
		    GdkColor *c, shadow_color;
		    GdkColormap *colormap = gdk_window_get_colormap(window);
		    GdkGC *gc;
		    GdkBitmap *mask;

		    switch(p->shadow_style)
		    {
		      case FPROMPT_SHADOW_NONE:
			break;

		      case FPROMPT_SHADOW_DITHERED:
			gc = GDK_GC_NEW();

			gdk_gc_set_subwindow(gc, GDK_INCLUDE_INFERIORS);
			gdk_window_copy_area(
			    pixmap, gc,
			    0, 0,
			    root,
			    p->x + FPROMPT_SHADOW_OFFSET_X,
			    p->y + FPROMPT_SHADOW_OFFSET_Y,
			    p->width, p->height
		        );
			gdk_gc_set_subwindow(gc, GDK_CLIP_BY_CHILDREN);

			mask = gdk_bitmap_create_from_data(
			    root, (const gchar *)bm_data_8x2,
			    mask_width, mask_height
			);
			if(mask != NULL)
			{
			    gint x, y;
			    gdk_gc_set_clip_mask(gc, mask);
			    gdk_gc_set_foreground(gc, &style->black);
			    for(y = 0; y < p->height; y += mask_height)
			    {
			        for(x = 0; x < p->width; x += mask_width)
			        {
				    gdk_gc_set_clip_origin(gc, x, y);
				    gdk_draw_rectangle(
				        pixmap, gc, TRUE,
				        x, y, mask_width, mask_height
				    );
			        }
			    }
			    gdk_gc_set_clip_mask(gc, NULL);
			}

			GDK_GC_UNREF(gc);
			GDK_BITMAP_UNREF(mask);
			break;

		      case FPROMPT_SHADOW_SOLID:
			gdk_draw_rectangle(
			    pixmap, style->black_gc, TRUE,
			    0, 0, p->width, p->height
			);
			break;

		      case FPROMPT_SHADOW_SHADED:
			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,
			    p->x + FPROMPT_SHADOW_OFFSET_X,
			    p->y + FPROMPT_SHADOW_OFFSET_Y,
			    p->width, p->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, p->width, p->height
			);
			gdk_gc_set_function(gc, GDK_COPY);

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

	/* Set callbacks */
	p->client_data = client_data;
	p->browse_cb = browse_cb;
	p->apply_cb = apply_cb;
	p->cancel_cb = cancel_cb;

	/* Map Floating Prompt */
	FPromptMap();

	return(0);
}

/*
 *	Maps the Floating Prompt.
 */
void FPromptMap(void)
{
	GtkWidget *w;
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	if(p->shadow_style != FPROMPT_SHADOW_NONE)
	    gtk_widget_show_raise(p->shadow);
	gtk_widget_show_raise(p->toplevel);

	w = p->entry;
	if(w != NULL)
	{
	    gtk_widget_grab_focus(w);
	    gtk_widget_grab_default(w);
	    if(w != gtk_grab_get_current())
		gtk_grab_add(w);
	    GTK_WIDGET_SET_FLAGS(w, GTK_HAS_FOCUS);
	}
}

/*
 *	Unmaps the Floating Prompt.
 */
void FPromptUnmap(void)
{
	GtkWidget *w;
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	w = p->entry;
	if((w != NULL) && (w == gtk_grab_get_current()))
	    gtk_grab_remove(w);

	p->x = 0;
	p->y = 0;
	p->width = 0;
	p->height = 0;

	gtk_widget_hide(p->toplevel);
	gtk_widget_hide(p->shadow);

	GDK_PIXMAP_UNREF(p->shadow_pm)
	p->shadow_pm = NULL;
}

/*
 *	Shuts down the Floating Prompt.
 */
void FPromptShutdown(void)
{
	FPromptData *p = fprompt;
	if(p == NULL)
	    return;

	GTK_WIDGET_DESTROY(p->shadow)
	GTK_WIDGET_DESTROY(p->cancel_btn)
	GTK_WIDGET_DESTROY(p->ok_btn)
	GTK_WIDGET_DESTROY(p->browse_btn)
	GTK_WIDGET_DESTROY(p->entry)
	GTK_WIDGET_DESTROY(p->label)
	GTK_WIDGET_DESTROY(p->main_hbox)
	GTK_WIDGET_DESTROY(p->toplevel)

	GDK_PIXMAP_UNREF(p->shadow_pm)

	fprompt = NULL;
	g_free(p);
}
