#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkprivate.h>
#if defined(_WIN32)
# include <gdk/gdkwin32.h>
# define HAVE_WIN32
#else
# include <gdk/gdkx.h>
# define HAVE_X
#endif

#include "../include/string.h"
#include "../include/disk.h"

#include "guiutils.h"
#include "guirgbimg.h"
#include "guifullscreen.h"
#include "cdialog.h"

#include "tlist.h"
#include "edv_types.h"
#include "edv_date.h"
#include "edv_cursor.h"
#include "edv_list_cb.h"
#include "edv_obj.h"
#include "imbr.h"
#include "imbr_tlist.h"
#include "presentation_mode.h"
#include "edv_utils_gtk.h"
#include "endeavour2.h"

#include "edv_cfg_list.h"
#include "config.h"

#include "images/icon_playerbtn_close_14x14.xpm"
#include "images/icon_playerbtn_eject_14x14.xpm"
#include "images/icon_playerbtn_slideshow_14x14.xpm"   
#include "images/icon_playerbtn_help_14x14.xpm"
#include "images/icon_playerbtn_info_14x14.xpm"
#include "images/icon_playerbtn_play_14x14.xpm"
#include "images/icon_playerbtn_playbackwards_14x14.xpm"
#include "images/icon_playerbtn_rewind_14x14.xpm"
#include "images/icon_playerbtn_rotateccw90_14x14.xpm"
#include "images/icon_playerbtn_rotatecw180_14x14.xpm"
#include "images/icon_playerbtn_rotatecw90_14x14.xpm"
#include "images/icon_playerbtn_seekend_14x14.xpm"


typedef struct _FSImgData		FSImgData;
#define FSIMG_DATA(p)		((FSImgData *)(p))


/*
 *	Rotate Positions:
 */
typedef enum {
	FSIMG_ROTATE_POSITION_NONE,
	FSIMG_ROTATE_POSITION_CW90,
	FSIMG_ROTATE_POSITION_CCW90,
	FSIMG_ROTATE_POSITION_CW180
} FSImgRotatePosition;

/*
 *	Fullscreen Image Data:
 */
struct _FSImgData {

	GtkWidget	*toplevel;	/* GTK_WINDOW_POPUP GtkWindow */
	GdkVisual	*visual;	/* Visual of toplevel */
	GdkColormap	*colormap;	/* Colormap of toplevel */
	GdkColor	color_fg,
			color_bg,
			color_shadow;
	gboolean	invert_fg;
	GdkFont		*font;
	GdkPixmap	*pixmap;	/* Pixmap for toplevel */
	GdkGC		*gc;
	gint		busy_count;
	edv_core_struct	*core;

	gint		main_loop_level;

	gboolean	hide_pointer;
	guint		hide_pointer_toid;

	gulong		slideshow_delay;
	guint		slideshow_toid;

	/* Goto List */
	GtkWidget	*clist_toplevel,
			*clist;		/* GtkCList */

	/* Navigator */
	GtkWidget	*navigator_toplevel,
			*close_btn,
			*list_btn,
			*slideshow_btn,
			*next_btns_toplevel,
			*first_btn,
			*prev_btn,
			*next_btn,
			*last_btn,
			*info_btn,
			*rot_btns_toplevel,
			*rot_cw90_btn,
			*rot_ccw90_btn,
			*rot_cw180_btn,
			*help_btn;

	/* Display Effects & Rotation */
	FSImgRotatePosition	rotate_position;

	/* Messages */
	gboolean	show_help;
	gchar		*mesg;

	/* Image Information */
	gint		info_level;
	gchar		*info_text1,
			*info_text2,
			*info_text3;
	GdkBitmap	*icon_play_mask,
			*icon_pause_mask;
	gint		img_width,		/* Original size */
			img_height;
	gint		img_shown_width,	/* Size of image shown */
			img_shown_height;

	/* Video Modes */
	GList		*vidmodes_list;

	const GdkVideoModeInfo	*orig_vidmode,
				*prev_vidmode,
				*cur_vidmode;

	gint		orig_viewport_x,
			orig_viewport_y;

	gint		orig_pointer_x,
			orig_pointer_y;

};
#define FSIMG_DATA_KEY		"FSImgData"


static void FSImgListItemDestroyCB(gpointer data);
static gint FSImgPaintCB(FSImgData *fsimg);
static gint FSImgEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FSImgListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FSImgHidePointerTOCB(gpointer data);
static gint FSImgPointerMotionCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FSImgSlideShowTOCB(gpointer data);

static void FSImgCloseCB(GtkWidget *widget, gpointer data);
static void FSImgSlideshowCB(GtkWidget *widget, gpointer data);
static void FSImgNextCB(GtkWidget *widget, gpointer data);
static void FSImgPrevCB(GtkWidget *widget, gpointer data);
static void FSImgFirstCB(GtkWidget *widget, gpointer data);
static void FSImgLastCB(GtkWidget *widget, gpointer data);
static void FSImgListCB(GtkWidget *widget, gpointer data);
static void FSImgInfoCB(GtkWidget *widget, gpointer data);
static void FSImgRotateCW90CB(GtkWidget *widget, gpointer data);
static void FSImgRotateCCW90CB(GtkWidget *widget, gpointer data);
static void FSImgRotateCW180CB(GtkWidget *widget, gpointer data);
static void FSImgHelpCB(GtkWidget *widget, gpointer data);

static edv_object_struct *FSImgCurrentObject(FSImgData *fsimg);
static void FSImgCurrentViewportSize(
	FSImgData *fsimg, gint *width_rtn, gint *height_rtn
);
static void FSImgWarpPointer(GdkWindow *window, gint x, gint y);
static void FSImgShowPointer(FSImgData *fsimg, gboolean show);
static void FSImgGrab(FSImgData *fsimg);

static void FSImgDraw(FSImgData *fsimg);
static void FSImgQueueDraw(FSImgData *fsimg);

static guint8 *FSImgCreateWinImage(
	gint win_width, gint win_height,
	const guint8 *bg_rgba,
	const guint8 *src_rgba,
	gint src_width, gint src_height,
	gint src_bpl
);
static gint FSImgSetData(
	FSImgData *fsimg,
	guint8 *rgba, gint width, gint height, gint bpl
);
static gint FSImgClear(FSImgData *fsimg, gint width, gint height);

static gint FSImgOpen(FSImgData *fsimg, const gchar *path);
static void FSImgNext(FSImgData *fsimg);
static void FSImgPrev(FSImgData *fsimg);
static void FSImgFirst(FSImgData *fsimg);
static void FSImgLast(FSImgData *fsimg);
static void FSImgRefresh(FSImgData *fsimg);
static void FSImgRotateCW90(FSImgData *fsimg);
static void FSImgRotateCCW90(FSImgData *fsimg);
static void FSImgRotateCW180(FSImgData *fsimg);

static gboolean FSImgHelpShown(FSImgData *fsimg);
static void FSImgHelpShow(FSImgData *fsimg, gboolean show);

static void FSImgMesg(FSImgData *fsimg, const gchar *mesg);

static gint FSImgInfoShown(FSImgData *fsimg);
static void FSImgInfoShow(FSImgData *fsimg, gboolean show);

static gboolean FSImgListShown(FSImgData *fsimg);
static void FSImgListShow(FSImgData *fsimg, gboolean show);
static void FSImgListGet(FSImgData *fsimg, const gchar *path);

static gboolean FSImgSlideshowActive(FSImgData *fsimg);

static gboolean FSImgNavigatorShown(FSImgData *fsimg);
static void FSImgNavigatorShow(FSImgData *fsimg, gboolean show);
static void FSImgNavigatorUpdate(FSImgData *fsimg);

static void FSImgRestore(FSImgData *fsimg);

static FSImgData *FSImgNew(edv_core_struct *core);
static void FSImgSetBusy(FSImgData *fsimg, gboolean busy);
static void FSImgDeleteRestore(FSImgData *fsimg);
static void FSImgDelete(FSImgData *fsimg);

void EDVPresentationModeEnterFromImbr(
	edv_core_struct *core,
	edv_imbr_struct *imbr
);
void EDVPresentationModeEnter(edv_core_struct *core);


#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 BTN_WIDTH		60
#define BTN_HEIGHT		20

#define NAVIGATOR_HEIGHT	26

#define HELP_MESG	{				\
 "F1",		"Help",					\
 "F2",		"List",					\
 " L",		"",					\
 "F3",		"Slideshow",				\
 "F4,",		"Cancel/Exit/Stop",			\
 " ESC,",	"",					\
 " Q,",		"",					\
 " W",		"",					\
 "F5",		"Refresh",				\
 "F6",		"Rotate Counter-Clockwise 90",		\
 "F7",		"Rotate Clockwise 90",			\
 "F8",		"Rotate Clockwise 180",			\
 "I",		"Display Image Information",		\
 "UP,",		"Previous Image",			\
 " LEFT",	"",					\
 "DOWN,",	"Next Image",				\
 " RIGHT,",	"",					\
 " ENTER,",	"",					\
 " SPACE",	"",					\
 "HOME",	"First Image",				\
 "END",		"Last Image",				\
 NULL,		NULL					\
}


/*
 *	List item destroy callback.
 */
static void FSImgListItemDestroyCB(gpointer data)
{
	EDVObjectDelete(EDV_OBJECT(data));
}

/*
 *	FSImg paint callback.
 */
static gint FSImgPaintCB(FSImgData *fsimg)
{
	FSImgDraw(fsimg);
	return(FALSE);
}

/*
 *      FSImg toplevel GtkWindow event signal callback.
 */
static gint FSImgEventCB( 
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gboolean keypress = FALSE;
	guint state;
	GdkEventConfigure *configure;
	GdkEventFocus *focus;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GdkEventCrossing *crossing;
	GtkWidget *event_widget;
	edv_core_struct *core;
	FSImgData *fsimg = FSIMG_DATA(data);
	if((widget == NULL) || (event == NULL) || (fsimg == NULL))
	    return(status);

	event_widget = gtk_get_event_widget(event);
	core = fsimg->core;
	if(core == NULL)
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_DELETE:
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    /* Switch to tbe original video mode and delete the fsimg */
	    FSImgDeleteRestore(fsimg);
	    fsimg = NULL;
	    break;

	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    /* Move viewport to align with the movement of the window */
	    gdk_video_mode_set_viewport(
		configure->x, configure->y
	    );
	    break;

	  case GDK_FOCUS_CHANGE:
	    focus = (GdkEventFocus *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    if(focus->in && !GTK_WIDGET_HAS_FOCUS(widget))
	    {
		GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
		FSImgQueueDraw(fsimg);
	    }
	    else if(!focus->in && GTK_WIDGET_HAS_FOCUS(widget))
	    {
		GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
		FSImgQueueDraw(fsimg);
	    }
	    break;

	  case GDK_KEY_PRESS:
	    keypress = TRUE;
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    state = key->state;
	    switch(key->keyval)
	    {
	      /* Display help */
	      case GDK_F1:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgHelpCB(fsimg->help_btn, fsimg);
		    status = TRUE;
		}
		break;

	      /* List */
	      case GDK_F2:
	      case GDK_l:
		if(!FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgListShow(
			    fsimg,
			    !FSImgListShown(fsimg)
			);
		    status = TRUE;
		}
		break;

	      /* Slideshow */
	      case GDK_F3:
		if(!FSImgListShown(fsimg))
		{
		    if(keypress)
		    {
			if(fsimg->slideshow_toid > 0)
			{
			    GTK_TIMEOUT_REMOVE(fsimg->slideshow_toid);
			    fsimg->slideshow_toid = 0;
			    FSImgNavigatorUpdate(fsimg);
			}
			else
			{
			    FSImgSlideshowCB(fsimg->slideshow_btn, fsimg);
			}
		    }
		    status = TRUE;
		}
		break;

	      /* Exit */
	      case GDK_F4:
	      case GDK_Escape:
	      case GDK_Cancel:
	      case GDK_q:
	      case GDK_w:
	      case GDK_x:
		if(keypress)
		{
		    FSImgCloseCB(fsimg->close_btn, fsimg);
		}
		status = TRUE;
		break;

	      /* Refresh */
	      case GDK_F5:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgRefresh(fsimg);
		    status = TRUE;
		}
		break;

	      /* Rotate Counter-Clockwise 90 */
	      case GDK_F6:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgRotateCCW90(fsimg);
		    status = TRUE;
		}
		break;

	      /* Rotate Clockwise 90 */
	      case GDK_F7:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgRotateCW90(fsimg);
		    status = TRUE;
		}
		break;

	      /* Rotate Clockwise 180 */
	      case GDK_F8:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress) 
			FSImgRotateCW180(fsimg);
		    status = TRUE;
		}
		break;

	      /* Previous */
	      case GDK_Left:
	      case GDK_Up:
	      case GDK_Page_Up:
	      case GDK_KP_Left:
	      case GDK_KP_Up:
	      case GDK_KP_Page_Up:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
		        FSImgPrev(fsimg);
		    status = TRUE;
		}
		break;

	      /* Next */
	      case GDK_Right:
	      case GDK_Down:
	      case GDK_Page_Down:
	      case GDK_KP_Right:
	      case GDK_KP_Down:
	      case GDK_KP_Page_Down:
	      case GDK_space:
	      case GDK_Return:
	      case GDK_KP_Enter:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
		        FSImgNext(fsimg);
		    status = TRUE;
		}
		break;

	      /* First */
	      case GDK_Home:
	      case GDK_KP_Home:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)   
		        FSImgFirst(fsimg);
		    status = TRUE;
		}
		break;

	      /* Last */
	      case GDK_End:
	      case GDK_KP_End:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		{
		    if(keypress)
			FSImgLast(fsimg);
		    status = TRUE;
		}
		break;

	      /* Information */
	      case GDK_i:
	      case GDK_d:
		if(!FSImgListShown(fsimg))
		{
		    if(keypress)
			FSImgInfoShow(
			    fsimg,
			    FSImgInfoShown(fsimg) + 1
			);
		    status = TRUE;
		}
		break;
	    }
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    if(!GTK_WIDGET_HAS_FOCUS(widget))
		gtk_widget_grab_focus(widget);
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		    FSImgNext(fsimg);
		break;

	      case GDK_BUTTON2:
		if(!FSImgListShown(fsimg) && !FSImgSlideshowActive(fsimg))
		    FSImgPrev(fsimg);
		break;

	      case GDK_BUTTON3:
		if(!FSImgListShown(fsimg))
		{
		    if(button->state & GDK_SHIFT_MASK)
		    {
			if(!FSImgSlideshowActive(fsimg))
			    FSImgListShow(fsimg, TRUE);
		    }
		    else
		    {
			FSImgNavigatorShow(
			    fsimg, !FSImgNavigatorShown(fsimg)
			);
		    }
		}
		break;
	    }
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    break;

	  case GDK_ENTER_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    break;

	  case GDK_LEAVE_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
	    if(event_widget != widget)
		return(gtk_widget_event(event_widget, event));
	    break;
	}

	return(status);
}

/*
 *	List event signal callback.
 */
static gint FSImgListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventButton *button;
	GtkCList *clist;
	edv_core_struct *core;
	FSImgData *fsimg = FSIMG_DATA(data);
	if((widget == NULL) || (event == NULL) || (fsimg == NULL))
	    return(status);

	clist = GTK_IS_CLIST(widget) ? GTK_CLIST(widget) : NULL;
	core = fsimg->core;
	if((clist == NULL) || (core == NULL))
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(TRUE)
	    {
		/* Need to grab the pointer and confine it to the
		 * FSImg's toplevel window since the GtkCList will grab
		 * the pointer from it whenever a "button_press_event"
		 * event signal occures
		 */
		const GdkEventMask mask = (
		    (1 << (4 + button->button)) |
		    GDK_POINTER_MOTION_HINT_MASK |
		    GDK_BUTTON_RELEASE_MASK
		);
		GtkWidget *toplevel = fsimg->toplevel;
		gdk_pointer_grab(
		    button->window, FALSE, mask,
		    toplevel->window, NULL, button->time
		);
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    if(button->window == clist->clist_window)
	    {
		GList *glist;

		switch(button->button)
		{
		  case GDK_BUTTON1:
		    /* Display selected image */
		    glist = clist->selection_end;
		    if(glist != NULL)
		    {
		        /* Get object from the selected row */
			edv_object_struct *obj = EDV_OBJECT(gtk_clist_get_row_data(
			    clist, (gint)glist->data
			));
			if(obj != NULL)
			{
			    FSImgSetBusy(fsimg, TRUE);
			    FSImgNavigatorUpdate(fsimg);

			    /* Open the image */
			    FSImgOpen(fsimg, obj->full_path);

			    /* Update information as needed */
			    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

			    /* Hide the list */
			    FSImgListShow(fsimg, FALSE);

			    FSImgSetBusy(fsimg, FALSE);
			    FSImgNavigatorUpdate(fsimg);
			}
		    }
		    break;

		  case GDK_BUTTON3:
		    /* Hide the list */
		    FSImgListShow(fsimg, FALSE);
		    break;
		}
	    }
	    status = TRUE;
	    break;
	}

	return(status);
}

/*
 *	Hide pointer timeout callback.
 */
static gint FSImgHidePointerTOCB(gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return(FALSE);

	/* Do not hide while busy or if the list is shown */
	if((fsimg->busy_count > 0) || FSImgListShown(fsimg))
	    return(TRUE);

	/* Hide pointer */
	FSImgShowPointer(fsimg, FALSE);

	fsimg->hide_pointer_toid = 0;

	return(FALSE);
}

/*
 *	Pointer motion signal callback.
 *
 *	Called on "button_press_event", "button_release_event", and
 *	"motion_notify_event" signals to schedual the hide pointer
 *	timeout callback.
 */
static gint FSImgPointerMotionCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gint delay;
	GdkEventMotion *motion;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;
	FSImgData *fsimg = FSIMG_DATA(data);
	if((widget == NULL) || (event == NULL) || (fsimg == NULL))
	    return(status);

	core = fsimg->core;
	if(core == NULL)
	    return(status);

	cfg_list = core->cfg_list;

	switch((gint)event->type)
	{
	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    if(motion->is_hint)
	    {
		gint x, y;
		GdkModifierType mask;
		gdk_window_get_pointer(
		    motion->window, &x, &y, &mask
		);
	    }
	  case GDK_BUTTON_PRESS:
	  case GDK_BUTTON_RELEASE:
	    delay = EDV_GET_I(
		EDV_CFG_PARM_PRESENTATION_HIDE_POINTER_DELAY
	    );
	    if(delay > 0)
	    {
		/* Show pointer if it is hidden */
		if(fsimg->hide_pointer)
		    FSImgShowPointer(fsimg, TRUE);

		/* Reschedual hide pointer timeout due to recent
		 * pointer motion   
		 */
		GTK_TIMEOUT_REMOVE(fsimg->hide_pointer_toid);
		fsimg->hide_pointer_toid = gtk_timeout_add(
		    (glong)(delay * 1000),
		    FSImgHidePointerTOCB, fsimg
		);
	    }
	    break;
	}

	return(status);
}

/*
 *	Slideshow timeout callback.
 */
static gint FSImgSlideShowTOCB(gpointer data)
{
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return(FALSE);

	core = fsimg->core;
	if(core == NULL)
	{
	    fsimg->slideshow_toid = 0;
	    FSImgQueueDraw(fsimg);
	    FSImgNavigatorUpdate(fsimg);
	    return(FALSE);
	}

	cfg_list = core->cfg_list;

	/* Display next image */
	FSImgNext(fsimg);

	/* At last image? */
	clist = GTK_CLIST(fsimg->clist);
	if(clist != NULL)
	{
	    GList *glist = clist->selection_end;
	    if((glist != NULL) ?
		((gint)glist->data >= (clist->rows - 1)) : TRUE
	    )
	    {
		/* Stop slideshow */
		EDVPlaySoundCompleted(fsimg->core);
		fsimg->slideshow_toid = 0;
		FSImgQueueDraw(fsimg);
		FSImgNavigatorUpdate(fsimg);
		return(FALSE);
	    }
	}

	/* Schedual next call to this function, do not just return
	 * TRUE since there may have been considerable delay while
	 * displaying thenext image
	 */
	fsimg->slideshow_toid = gtk_timeout_add(
	    MAX(fsimg->slideshow_delay, 1000l),
	    FSImgSlideShowTOCB, fsimg
	);

	return(FALSE);
}


/*
 *	Close callback.
 */
static void FSImgCloseCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	/* If displaying help then hide help */
	if(FSImgHelpShown(fsimg))
	{
	    FSImgHelpShow(fsimg, FALSE);
	}
	/* If in slideshow then stop slideshow */
	else if(FSImgSlideshowActive(fsimg))
	{
	    GTK_TIMEOUT_REMOVE(fsimg->slideshow_toid);
	    fsimg->slideshow_toid = 0;
	    FSImgQueueDraw(fsimg);
	    FSImgNavigatorUpdate(fsimg);
	}
	/* If the list is mapped then unmap it */
	else if(FSImgListShown(fsimg))
	{
	    FSImgListShow(fsimg, FALSE);
	}
	/* All else exit presentation mode */
	else
	{
	    FSImgDeleteRestore(fsimg);
	}
}

/*
 *	Slideshow callback.
 */
static void FSImgSlideshowCB(GtkWidget *widget, gpointer data)
{
	GtkCList *clist;
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

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

	/* Stop slideshow as needed */
	GTK_TIMEOUT_REMOVE(fsimg->slideshow_toid);
	fsimg->slideshow_toid = 0;

	/* If there are enough items in the list to have a slideshow
	 * then start slideshow
	 */
	if(clist->rows >= 2)
	    fsimg->slideshow_toid = gtk_timeout_add(
		MAX(fsimg->slideshow_delay, 1000l),
		FSImgSlideShowTOCB, fsimg
	    );

	FSImgQueueDraw(fsimg);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Next callback.
 */
static void FSImgNextCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgNext(fsimg);
}

/*
 *	Previous callback.
 */
static void FSImgPrevCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgPrev(fsimg);
}

/*
 *	First callback.
 */
static void FSImgFirstCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgFirst(fsimg);
}

/*
 *	Last callback.
 */
static void FSImgLastCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgLast(fsimg);
}

/*
 *	List callback.
 */
static void FSImgListCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgListShow(
	    fsimg,
	    !FSImgListShown(fsimg)
	);
}

/*
 *	Information callback.
 */
static void FSImgInfoCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgInfoShow(
	    fsimg,
	    FSImgInfoShown(fsimg) + 1
	);
}

/*
 *	Rotate clockwise 90 callback.
 */
static void FSImgRotateCW90CB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgRotateCW90(fsimg);
}

/*
 *	Rotate counter-clockwise 90 callback.
 */
static void FSImgRotateCCW90CB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgRotateCCW90(fsimg);
}

/*
 *	Rotate clockwise 180 callback.
 */
static void FSImgRotateCW180CB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgRotateCW180(fsimg);
}

/*
 *	Help callback.
 */
static void FSImgHelpCB(GtkWidget *widget, gpointer data)
{
	FSImgData *fsimg = FSIMG_DATA(data);
	if(fsimg == NULL)
	    return;

	FSImgHelpShow(fsimg, !FSImgHelpShown(fsimg));
}


/*
 *	Returns the currently selected object or the first object in
 *	the list.
 *
 *	Can return NULL on error.
 */
static edv_object_struct *FSImgCurrentObject(FSImgData *fsimg)
{
	GList *glist;
	GtkCList *clist = (fsimg != NULL) ? (GtkCList *)fsimg->clist : NULL;
	if(clist == NULL)
	    return(NULL);

	glist = clist->selection_end;

	return(EDV_OBJECT(gtk_clist_get_row_data(
	    clist,
	    (glist != NULL) ? (gint)glist->data : 0
	)));
}

/*
 *	Returns the current size of the viewport.
 */
static void FSImgCurrentViewportSize(
	FSImgData *fsimg, gint *width_rtn, gint *height_rtn
)
{
	const GdkVideoModeInfo *m;

	if(width_rtn != NULL)
	    *width_rtn = 0;
	if(height_rtn !=  NULL)
	    *height_rtn = 0;

	if(fsimg == NULL)
	    return;

	m = fsimg->cur_vidmode;
	if(m == NULL)
	    return;

	if(width_rtn != NULL)
	    *width_rtn = m->viewport_width;
	if(height_rtn !=  NULL)
	    *height_rtn = m->viewport_height;
}

/*
 *	Warps the pointer relative to the desktop.
 */
static void FSImgWarpPointer(GdkWindow *window, gint x, gint y)
{
#if defined(HAVE_X)
	GdkWindowPrivate *private = (GdkWindowPrivate *)window;
	if((private != NULL) ? (private->xdisplay == NULL) : TRUE)
	    return;

	XWarpPointer(
	    private->xdisplay,
	    None,		/* No source window */
	    private->xwindow,	/* Move relative to destination window */
	    0, 0, 0, 0,		/* No source rectangle */
	    (int)x, (int)y
	);
#else

#endif
}

/*
 *	Shows or hides the pointer.
 */
static void FSImgShowPointer(FSImgData *fsimg, gboolean show)
{
	GtkWidget *w;
	edv_core_struct *core = (fsimg != NULL) ? fsimg->core : NULL;
	if(core == NULL)
	    return;

	/* Already shown or hidden? */
	if(fsimg->hide_pointer != show)
	    return;

	/* Update show pointer marker */
	fsimg->hide_pointer = !show;

	w = fsimg->toplevel;
	if(w != NULL)
	{
	    GdkWindow *window = w->window;
	    if(window != NULL)
	    {
		GdkCursor *cursor;
		if(show)
		{
		    if(fsimg->busy_count > 0)
			cursor = EDVGetCursor(
			    core, EDV_CURSOR_CODE_BUSY
			);
		    else
			cursor = NULL;
		}
		else
		{
		    cursor = EDVGetCursor(
			core, EDV_CURSOR_CODE_INVISIBLE
		    );
		}
		gdk_window_set_cursor(window, cursor);
		gdk_flush();
	    }
	}
}

/*
 *	Grabs the FSImg's window.
 */
static void FSImgGrab(FSImgData *fsimg)
{
	GdkWindow *window;
	GtkWidget *w;

	if(fsimg == NULL)
	    return;

	w = fsimg->toplevel;
	if(w == NULL)
	    return;

	window = w->window;
	if(window == NULL)
	    return;

	/* Grab widget to receive key events */
	gtk_grab_add(w);
	gtk_widget_grab_focus(w);

	/* Grab the pointer and confine it to the window's area */
	gdk_pointer_grab(                                         
	    window,
	    TRUE,		/* Report event relative to event's window */
	    GDK_BUTTON_PRESS_MASK |
	    GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK,
	    window,		/* Confine pointer to this window */
	    NULL,		/* Cursor */
	    GDK_CURRENT_TIME
	);
}


/*
 *	Draws the FSImg.
 */
static void FSImgDraw(FSImgData *fsimg)
{
	gboolean navigator_shown;
	gint	win_width = 0, win_height = 0,
		img_shown_width, img_shown_height;
	GdkFont *font;
	GdkColor *c_fg, *c_bg, *c_shadow;
	GdkWindow *window;
	GdkPixmap *pixmap;
	GdkGC *gc;
	GtkStyle *style;
	GtkWidget *w = (fsimg != NULL) ? fsimg->toplevel : NULL;
	if(w == NULL)
	    return;

	window = w->window;
	style = gtk_widget_get_style(w);
	if((window == NULL) || (style == NULL))
	    return;

	navigator_shown = FSImgNavigatorShown(fsimg);
	img_shown_width = fsimg->img_shown_width;
	img_shown_height = fsimg->img_shown_height;

	/* Draw background? */
	pixmap = fsimg->pixmap;
	if(pixmap != NULL)
	{
	    gdk_window_get_size(pixmap, &win_width, &win_height);
	    gdk_window_copy_area(
		window, style->black_gc,
		0, 0,
		pixmap,
		0, 0,
		win_width, win_height
	    );
	}

	font = fsimg->font;
	gc = fsimg->gc;
	if((font == NULL) || (gc == NULL))
	    return;

	c_fg = &fsimg->color_fg;
	c_bg = &fsimg->color_bg;
	c_shadow = &fsimg->color_shadow;

	/* Set up GC for text drawing */
	gdk_gc_set_foreground(gc, c_fg);
	gdk_gc_set_background(gc, c_bg);
	gdk_gc_set_font(gc, font);
	gdk_gc_set_function(gc, GDK_COPY);
	gdk_gc_set_fill(gc, GDK_SOLID);
	gdk_gc_set_clip_mask(gc, NULL);
	gdk_gc_set_clip_origin(gc, 0, 0);

#define DRAW_TEXT(_d_,_f_,_b_,_x_,_y_,_s_,_l_) {	\
 gdk_gc_set_foreground(gc, c_fg);		\
 gdk_draw_text(					\
  (_d_), (_f_), gc,				\
  ((_x_) + 1) - (_b_)->lbearing,		\
  ((_y_) + 1) + (_f_)->ascent,			\
  (_s_), (_l_)					\
 );						\
}
#define DRAW_TEXT_DROP_SHADE(_d_,_f_,_b_,_x_,_y_,_s_,_l_) {	\
 gdk_gc_set_foreground(gc, c_bg);		\
 gdk_draw_text(					\
  (_d_), (_f_), gc,				\
  ((_x_) + 1) - (_b_)->lbearing,		\
  ((_y_) + 1) + (_f_)->ascent,			\
  (_s_), (_l_)					\
 );						\
 gdk_gc_set_foreground(gc, c_fg);		\
 gdk_draw_text(					\
  (_d_), (_f_), gc,				\
  (_x_) - (_b_)->lbearing,			\
  (_y_) + (_f_)->ascent,			\
  (_s_), (_l_)					\
 );						\
}

	/* Draw information text? */
	if(fsimg->info_text1 != NULL)
	{
	    const gint	border_major = 5,
			font_height = font->ascent + font->descent;
	    const gchar *s = fsimg->info_text1, *s2;
	    gint x = border_major, y = border_major, len;

	    gdk_gc_set_function(
		gc,
		fsimg->invert_fg ? GDK_INVERT : GDK_COPY
	    );

	    while(y < win_height)
	    {
		/* Get pointer to end of line */
		s2 = strchr(s, '\n');
		/* Get length of line */
		len = (s2 != NULL) ? (gint)(s2 - s) : STRLEN(s);

		/* Draw this line? */
		if(len > 0)
		{
		    GdkTextBounds b;
		    gdk_text_bounds(font, s, len, &b);
		    DRAW_TEXT_DROP_SHADE(
			window, font, &b,
			x, y,
			s, len
		    );
		}

		/* Move drawing position to next line */
		y += font_height;

		/* Is there a next line? */
		if(s2 != NULL)
		    s = s2 + 1;		/* Seek s to next line */
		else
		    break;
	    }
	}
	if(fsimg->info_text2 != NULL)
	{
	    const gint  border_major = 5,
			font_height = font->ascent + font->descent;
	    const gchar *s = fsimg->info_text2, *s2;
	    gint x, y = border_major, len;

	    gdk_gc_set_function(
		gc,
		fsimg->invert_fg ? GDK_INVERT : GDK_COPY
	    );

	    while(y < win_height)
	    {
		/* Get pointer to end of line */
		s2 = strchr(s, '\n');
		/* Get length of line */
		len = (s2 != NULL) ? (gint)(s2 - s) : STRLEN(s);

		/* Draw this line? */
		if(len > 0)
		{
		    GdkTextBounds b;
		    gdk_text_bounds(font, s, len, &b);
		    x = win_width - b.width - border_major;
		    DRAW_TEXT_DROP_SHADE(
			window, font, &b,
			x, y,
			s, len
		    );
		}

		/* Move drawing position to next line */
		y += font_height;

		/* Is there a next line? */
		if(s2 != NULL)
		    s = s2 + 1;         /* Seek s to next line */
		else
		    break;
	    }
	}    
	if(fsimg->info_text3 != NULL)
	{
	    const gint  border_major = 5,
			font_height = font->ascent + font->descent;
	    const gchar *s = fsimg->info_text3, *s2;
	    gint	x = win_width - border_major,
			y = win_height -
			    (navigator_shown ? NAVIGATOR_HEIGHT : 0) -
			    font_height - border_major - 1,
			len;

	    gdk_gc_set_function(
		gc,
		fsimg->invert_fg ? GDK_INVERT : GDK_COPY
	    );

	    while(y >= 0)
	    {
		/* Get pointer to end of line */
		s2 = strchr(s, '\n');
		/* Get length of line */
		len = (s2 != NULL) ? (gint)(s2 - s) : STRLEN(s);

		/* Draw this line? */
		if(len > 0)
		{
		    GdkTextBounds b;
		    gdk_text_bounds(font, s, len, &b);
		    x = win_width - b.width - border_major;
		    DRAW_TEXT_DROP_SHADE(
			window, font, &b,
			x, y,
			s, len
		    );
		}     

		/* Move drawing position to next line */
		y -= font_height;

		/* Is there a next line? */
		if(s2 != NULL)
		    s = s2 + 1;         /* Seek s to next line */
		else
		    break;
	    }
	}    
	if(fsimg->info_level >= 4)
	{
	    const gboolean slideshow_active = FSImgSlideshowActive(fsimg);
	    GdkBitmap *icon_mask = slideshow_active ?
		fsimg->icon_play_mask : fsimg->icon_pause_mask;

	    if(icon_mask != NULL)
	    {
		const gint border_major = 5;
		gint x, y, icon_width, icon_height;

		gdk_window_get_size(icon_mask, &icon_width, &icon_height);

		x = border_major;
		y = win_height -
		    (navigator_shown ? NAVIGATOR_HEIGHT : 0) -
			icon_height - border_major - 1;

		gdk_gc_set_function(
		    gc,
		    fsimg->invert_fg ? GDK_INVERT : GDK_COPY
		);
		gdk_gc_set_clip_mask(gc, icon_mask);
		gdk_gc_set_clip_origin(gc, x + 1, y + 1);
		gdk_gc_set_foreground(gc, c_bg);
		gdk_draw_rectangle(
		    window, gc, TRUE,
		    x + 1, y + 1, icon_width, icon_height
		);
		gdk_gc_set_clip_origin(gc, x, y);
		gdk_gc_set_foreground(gc, c_fg);
		gdk_draw_rectangle(
		    window, gc, TRUE,
		    x, y, icon_width, icon_height
		);
		gdk_gc_set_clip_mask(gc, NULL);
		gdk_gc_set_clip_origin(gc, 0, 0);
	    }
	}

	/* Draw message */
	if((fsimg->mesg != NULL) && !FSImgHelpShown(fsimg))
	{
	    const gint	border_major = 5,
			font_height = font->ascent + font->descent;
	    gint x, y, len, lines = 0, longest_line_width = 0;
	    const gchar *s = fsimg->mesg, *s2;
	    GdkTextBounds b;

	    /* Count number of lines and longest line */
	    while(TRUE)
	    {
		s2 = strchr(s, '\n');
		len = (s2 != NULL) ? (gint)(s2 - s) : STRLEN(s);
		gdk_text_bounds(font, s, len, &b);

		lines++;
		if(b.width > longest_line_width)
		    longest_line_width = b.width;

		if(s2 != NULL)
		    s = s2 + 1;
		else
		    break;
	    } 

	    /* Set upper-left starting coordinates for drawing */
	    x = MAX((win_width - longest_line_width) / 2, 0);
	    y = MAX((win_height -
		(navigator_shown ? NAVIGATOR_HEIGHT : 0) -
		(lines * font_height)) / 2, 0);

	    /* Draw subtractive "shadow" background */
	    gdk_gc_set_function(gc, GDK_AND);
	    gdk_gc_set_foreground(gc, c_shadow);
	    gdk_draw_rectangle(
		window, gc, TRUE,
		x - border_major,
		y - border_major,
		longest_line_width + (2 * border_major),
		(lines * font_height) + (2 * border_major)
	    );

	    /* Draw text */
	    gdk_gc_set_function(gc, GDK_COPY);
	    gdk_gc_set_foreground(gc, c_fg);
	    s = fsimg->mesg;
	    while(y < win_height)
	    {
		/* Get pointer to end of line */
		s2 = strchr(s, '\n');
		/* Get length of line */
		len = (s2 != NULL) ? (gint)(s2 - s) : STRLEN(s);

		/* Draw this line? */
		if(len > 0)
		{
		    gdk_text_bounds(font, s, len, &b);
		    DRAW_TEXT(
			window, font, &b,
			x, y,
			s, len
		    );
		}

		/* Move drawing position to next line */
		y += font_height;

		/* Is there a next line? */
		if(s2 != NULL)
		    s = s2 + 1;         /* Seek s to next line */
		else
		    break;
	    }
	}

	/* Draw help? */
	if(FSImgHelpShown(fsimg))
	{
	    const gint	border_major = 5,
			font_height = font->ascent + font->descent,
			column1_spacing = 10;
	    gint	i, x, y, len,
			column1_width = 0,
			lines = 0, longest_line_width = 0;
	    const gchar *s, *mesg[] = HELP_MESG;
	    GdkTextBounds b;

	    /* Count number of lines and longest line */
	    for(i = 0; mesg[i] != NULL; i += 2)
	    {
		lines++;

		gdk_string_bounds(font, mesg[i], &b);
		x = b.width + column1_spacing;
		if(x > column1_width)
		    column1_width = x;
	    }
	    for(i = 0; mesg[i] != NULL; i += 2)
	    {
		gdk_string_bounds(font, mesg[i + 1], &b);
		x = column1_width + b.width;
		if(x > longest_line_width)
		    longest_line_width = x;
	    }

	    /* Set upper-left starting coordinates for drawing */
	    x = MAX(((win_width - longest_line_width) / 2), 0);
	    y = MAX(((win_height -
		(navigator_shown ? NAVIGATOR_HEIGHT : 0) -
		(lines * font_height)) / 2), 0);

	    /* Draw subtractive "shadow" background */
	    gdk_gc_set_function(gc, GDK_AND);
	    gdk_gc_set_foreground(gc, c_shadow);
	    gdk_draw_rectangle(
		window, gc, TRUE,
		x - border_major,
		y - border_major,
		longest_line_width + (2 * border_major),
		(lines * font_height) + (2 * border_major)
	    );   

	    /* Draw text */
	    gdk_gc_set_function(gc, GDK_COPY);
	    gdk_gc_set_foreground(gc, c_fg);
	    for(i = 0; mesg[i] != NULL; i += 2)
	    {
		s = mesg[i];
		len = STRLEN(s);
		if(len > 0)
		{
		    gdk_string_bounds(font, s, &b);
		    DRAW_TEXT(
			window, font, &b,
			x, y,
			s, len
		    );
		}
		s = mesg[i + 1];
		len = STRLEN(s);
		if(len > 0)
		{
		    gdk_string_bounds(font, s, &b);
		    DRAW_TEXT(
			window, font, &b,
			x + column1_width, y,
			s, len
		    );
		}

		y += font_height;
	    }
	}

#undef DRAW_TEXT_DROP_SHADE
#undef DRAW_TEXT
}

/*
 *	Queues a draw for the FSImg.
 */
static void FSImgQueueDraw(FSImgData *fsimg)
{
	GtkWidget *w = (fsimg != NULL) ? fsimg->toplevel : NULL;
	if(w != NULL)
	    gtk_widget_queue_draw(w);
}


/*
 *	Creates an RGBA image data of the specified size containing the
 *	specified rgba image data.
 *
 *	Inputs assumed valid.
 *
 *	The calling function must delete the returned RGBA image data.
 */
static guint8 *FSImgCreateWinImage(
	gint win_width, gint win_height,
	const guint8 *bg_rgba,
	const guint8 *src_rgba,
	gint src_width, gint src_height,
	gint src_bpl
)
{
	const gint	bpp = 4,
			tar_bpl = win_width * bpp;
	guint8 *tar_rgba = ((tar_bpl * win_height) > 0) ?
	    (guint8 *)g_malloc(tar_bpl * win_height) : NULL;
	if(tar_rgba == NULL)
	    return(NULL);

	if(src_bpl <= 0)
	    src_bpl = src_width * bpp;

	/* Clear target image data with background color if specified */
	if(bg_rgba != NULL)
	{
	    guint32	*ptr = (guint32 *)tar_rgba,
			*end = ptr + (win_width * win_height);
	    while(ptr < end)
		*ptr++ = *(const guint32 *)bg_rgba;
	}

	/* Copy source image to center of target window image */
	GUIImageBufferCopyArea(
	    bpp,
	    src_rgba,
	    src_width, src_height, src_bpl,
	    tar_rgba,
	    win_width, win_height, tar_bpl,
	    (win_width / 2) - (src_width / 2),
	    (win_height / 2) - (src_height / 2),
	    TRUE			/* Blend */
	);

	return(tar_rgba);
}

/*
 *	Displays the specified RGBA image data.
 *
 *	Resizes the fsimg to display the specified RGBA image data using
 *	a suitable video mode size.
 *
 *	Recreates the pixmap and puts the specified RGBA image data on
 *	to it.
 *
 *	Switches video modes as needed.
 *
 *	Grabs the pointer.
 *
 *	Returns values:
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad value
 *	-3	Systems error
 *	-4	User aborted
 *	-5	No video mode available
 */
gint FSImgSetData(
	FSImgData *fsimg,
	guint8 *rgba,
	gint width, gint height, gint bpl
)
{
	gboolean need_change_vidmode = FALSE;
	gint win_width, win_height;
	guint8 bg_rgba[4] = { 0xff, 0xff, 0xff, 0xff };
	GList *glist;
	GdkVideoModeInfo *m_sel, *m;
	GdkRgbDither dither;
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget *w;
	const cfg_color_struct *c;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if((fsimg == NULL) || (rgba == NULL))
	    return(-1);

	core = fsimg->core;
	if(core == NULL)
	    return(-1);

	cfg_list = core->cfg_list;

	/* Get background color */
	c = EDV_GET_COLOR(EDV_CFG_PARM_PRESENTATION_COLOR_BG);
	if(c != NULL)
	{
	    bg_rgba[0] = c->r * 0xff;
	    bg_rgba[1] = c->g * 0xff;
	    bg_rgba[2] = c->b * 0xff;
	    bg_rgba[3] = c->a * 0xff;
	}

	/* Get quality */
	switch(EDV_GET_I(EDV_CFG_PARM_IMAGE_QUALITY))
	{
	  case 2:
	    dither = GDK_RGB_DITHER_MAX;
	    break;
	  case 1:
	    dither = GDK_RGB_DITHER_NORMAL;
	    break;
	  default:
	    dither = GDK_RGB_DITHER_NONE;
	    break;
	}

	w = fsimg->toplevel;
	if(w == NULL)
	    return(-1);

	window = w->window;
	style = gtk_widget_get_style(w);
	if((window == NULL) || (style == NULL))
	    return(-1);

	/* Update the image size */
	fsimg->img_width = width;
	fsimg->img_height = height;

	/* Find and nominate a new video mode with a suitable size for
	 * use with the specified image
	 */
	m_sel = NULL;
	for(glist = fsimg->vidmodes_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    m = GDK_VIDEO_MODE_INFO(glist->data);
	    if(m == NULL)
		continue;

	    /* Size too small to fit image? */
	    if((m->viewport_width < width) ||
	       (m->viewport_height < height)
	    )
		continue;

	    if(m_sel != NULL)
	    {
		/* This suitable video mode smaller than the last
		 * nominated one
		 */
		if((m->viewport_width < m_sel->viewport_width) ||
		   (m->viewport_height < m_sel->viewport_height)
		)
		    m_sel = m;
	    }
	    else
	    {
		/* No video mode nominated yet, so nominate this one */
		m_sel = m;
	    }
	}
	/* If no video mode nominated then nominatee the largest video
	 * mode that is available
	 */
	if(m_sel == NULL)
	{
	    /* Nominate largest video mode that is available */
	    for(glist = fsimg->vidmodes_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		m = GDK_VIDEO_MODE_INFO(glist->data);
		if(m == NULL)
		    continue;

		if(m_sel != NULL)
		{
		    /* This video mode larger than the last nominated
		     * video mode?
		     */
		    if((m->viewport_width > m_sel->viewport_width) ||
		       (m->viewport_height > m_sel->viewport_height)
		    )  
			m_sel = m;
		}
		else
		{
		    /* No video mode nominated yet, so nominate this one */
		    m_sel = m;
		}
	    }
	}

	/* If there is still no video mode nominated then just use the
	 * current video mode, otherwise determine if we need to change
	 * video modes
	 */
	if(m_sel == NULL)
	    m_sel = gdk_video_mode_get_current(fsimg->vidmodes_list);
	else
	    need_change_vidmode = (m_sel != gdk_video_mode_get_current(fsimg->vidmodes_list)) ?
		TRUE : FALSE;
	if(m_sel == NULL)
	    return(-5);

	/* Get values of the new video mode and record the previous
	 * video mode
	 */
	fsimg->prev_vidmode = fsimg->cur_vidmode;
	fsimg->cur_vidmode = m_sel;
	win_width = m_sel->viewport_width;
	win_height = m_sel->viewport_height;
	if((win_width <= 0) || (win_height <= 0))
	    return(-2);

	/* Need to resize image to fit? */
	if((width > win_width) || (height > win_height))
	{
	    /* Create a resized smaller image to fit within the viewport */
	    const gint bpp = 4;
	    const gfloat aspect = (gfloat)width / (gfloat)height;
	    gint	rs_width = win_height * aspect,
			rs_height = win_height,
			rs_bpl;
	    guint8 *rs_rgba, *win_data_rgba;
	    GdkPixmap *pixmap;

	    if((rs_width > win_width) && (aspect > 0.0f))
	    {
		rs_width = win_width;
		rs_height = win_width / aspect;
	    }
	    if((rs_width <= 0) || (rs_height <= 0))
		return(-2);


	    /* The size of smaller image that will fit within the
	     * window has now been calculated
	     */

	    /* Allocate RGBA image data for the resized image */
	    rs_bpl = rs_width * bpp;
	    rs_rgba = (guint8 *)g_malloc(rs_bpl * rs_height);
	    if(rs_rgba == NULL)
		return(-3);

	    /* Copy/resize the specified image data to the new resized
	     * image data
	     */
	    GUIImageBufferResize(
		bpp,
		rgba, width, height, bpl,
		rs_rgba, rs_width, rs_height, rs_bpl
	    );

	    /* Create the window's RGBA image data and put the new
	     * resized RGBA image data into it
	     */
	    win_data_rgba = FSImgCreateWinImage(
		win_width, win_height,
		bg_rgba,
		rs_rgba, rs_width, rs_height, rs_bpl
	    );

	    /* Delete the resized RGBA image data */
	    g_free(rs_rgba);

	    /* Unable to create the window's RGBA image data? */
	    if(win_data_rgba == NULL)
		return(-3);

	    /* Update the shown image size */
	    fsimg->img_shown_width = rs_width;
	    fsimg->img_shown_height = rs_height;

	    /* Recreate the window's GdkPixmap and put the window's
	     * RGBA image data on it
	     */
	    GDK_PIXMAP_UNREF(fsimg->pixmap);
	    fsimg->pixmap = pixmap = gdk_pixmap_new(
		window, win_width, win_height, -1  
	    );
	    if(pixmap != NULL)
		gdk_draw_rgb_32_image(
		    pixmap, style->black_gc,
		    0, 0, win_width, win_height,
		    dither,
		    (guchar *)win_data_rgba,
		    win_width * 4
		);

	    /* Delete the window's RGBA image data */
	    g_free(win_data_rgba);
	}
	else
	{
	    GdkPixmap *pixmap;

	    /* Create the window's RGBA image data and put the specified
	     * RGBA image data into it
	     */
	    guint8 *win_data_rgba = FSImgCreateWinImage(
		win_width, win_height,
		bg_rgba,
		rgba, width, height, bpl
	    );
	    if(win_data_rgba == NULL)
		return(-3);

	    /* Update the shown image size */
	    fsimg->img_shown_width = width;
	    fsimg->img_shown_height = height;

	    /* Recreate the window's GdkPixmap and put the window's 
	     * RGBA image data on it
	     */
	    GDK_PIXMAP_UNREF(fsimg->pixmap);
	    fsimg->pixmap = pixmap = gdk_pixmap_new(
		window, win_width, win_height, -1
	    );
	    if(pixmap != NULL)
		gdk_draw_rgb_32_image(
		    pixmap, style->black_gc,
		    0, 0, win_width, win_height,
		    dither,
		    (guchar *)win_data_rgba,
		    win_width * 4
		);

	    /* Delete the window's RGBA image data */
	    g_free(win_data_rgba);
	}

	/* Move and resize the FSImg window to display the image
	 *
	 * The widget needs to have its user position and size set for
	 * it's child widgets to configure correctly and the widget's
	 * window has to be moved and resized for the actual affect to
	 * take place for the window
	 */
	gtk_widget_set_usize(w, win_width, win_height);
	gtk_widget_set_uposition(w, 0, 0);
	gdk_window_move_resize(
	    window,
	    0, 0,
	    win_width, win_height
	);

	/* Map and raise the FSImg window */
	gtk_widget_show_raise(w);

	/* Grab the FSImg toplevel window to confine pointer and
	 * receive key events
	 */
	FSImgGrab(fsimg);

	/* Need to change video modes? */
	if(need_change_vidmode)
	{
	    /* Move pointer to center of window */
	    FSImgWarpPointer(
		window,
		win_width / 2, win_height / 2
	    );
	    /* Switch to the nominated video mode */
	    gdk_video_mode_switch(m_sel);
	}

	/* Viewport will be set when the "configure_event" signal is
	 * received
	 */

	/* Redraw since image changed */
	FSImgQueueDraw(fsimg);

	FSImgNavigatorUpdate(fsimg);

	return(0);
}

/*
 *	Clears the fsimg.
 *
 *	Resizes the fsimg with the specified size using a suitable video
 *	mode size.
 *
 *	Recreates the pixmap and clears it with just the background.
 *
 *	Switches video modes as needed.
 *
 *	Grabs the pointer.
 *
 *	Returns values: 
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad value   
 *	-3	Systems error
 *	-4	User aborted
 *	-5	No video mode available
 */
static gint FSImgClear(FSImgData *fsimg, gint width, gint height) 
{
	gboolean need_change_vidmode = FALSE;
	gint win_width, win_height;
	guint8 bg_rgba[4] = { 0xff, 0xff, 0xff, 0xff };
	GList *glist;
	const GdkVideoModeInfo *m_cur;
	GdkVideoModeInfo *m_sel, *m;
	GdkRgbDither dither;
	GdkWindow *window;
	GtkStyle *style;
	GtkWidget *w;
	const cfg_color_struct *c;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(fsimg == NULL)
	    return(-1);

	core = fsimg->core;
	if(core == NULL)
	    return(-1);

	cfg_list = core->cfg_list; 

	/* Get background color */
	c = EDV_GET_COLOR(EDV_CFG_PARM_PRESENTATION_COLOR_BG);
	if(c != NULL)
	{
	    bg_rgba[0] = c->r * 0xff;
	    bg_rgba[1] = c->g * 0xff;
	    bg_rgba[2] = c->b * 0xff;     
	    bg_rgba[3] = c->a * 0xff;
	}                             

	/* Get quality */
	switch(EDV_GET_I(EDV_CFG_PARM_IMAGE_QUALITY))
	{
	  case 2:
	    dither = GDK_RGB_DITHER_MAX;
	    break;
	  case 1:
	    dither = GDK_RGB_DITHER_NORMAL;
	    break;
	  default:
	    dither = GDK_RGB_DITHER_NONE;
	    break;
	}

	w = fsimg->toplevel;
	if(w == NULL)
	    return(-1);

	window = w->window;
	style = gtk_widget_get_style(w);
	if((window == NULL) || (style == NULL))
	    return(-1);

	/* Reset the image size and the shown image size */
	fsimg->img_width = 0;
	fsimg->img_height = 0;
	fsimg->img_shown_width = 0;
	fsimg->img_shown_height = 0;

	/* Use size of current video mode? */
	m_cur = fsimg->cur_vidmode;
	if(m_cur != NULL)
	{
	    if(width <= 0)
		width = m_cur->viewport_width;
	    if(height <= 0)
		height = m_cur->viewport_height;
	}
	if((width <= 0) || (height <= 0))
	    return(-5);

	/* Find and nominate a new video mode with a suitable size */
	m_sel = NULL;
	for(glist = fsimg->vidmodes_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    m = GDK_VIDEO_MODE_INFO(glist->data);
	    if(m == NULL)
		continue;

	    /* Size too small? */
	    if((m->viewport_width < width) ||
	       (m->viewport_height < height)
	    )
		continue;

	    if(m_sel != NULL)
	    {
		/* This suitable video mode smaller than the last
		 * nominated one?
		 */
		if((m->viewport_width < m_sel->viewport_width) ||
		   (m->viewport_height < m_sel->viewport_height)
		)
		    m_sel = m;
	    }
	    else
	    {
		/* No video mode nominated yet, so nominate this one */
		m_sel = m;
	    }
	}
	/* If no video mode nominated then nominatee the largest video
	 * mode that is available
	 */
	if(m_sel == NULL)
	{
	    for(glist = fsimg->vidmodes_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		m = GDK_VIDEO_MODE_INFO(glist->data);
		if(m == NULL)
		    continue;

		if(m_sel != NULL)
		{
		    /* This video mode larger than the last nominated
		     * video mode?
		     */
		    if((m->viewport_width > m_sel->viewport_width) ||
		       (m->viewport_height > m_sel->viewport_height)
		    )
			m_sel = m;
		}
		else
		{   
		    /* No video mode nominated yet, so nominate this one */
		    m_sel = m;
		}
	    }    
	}

	/* If there is still no video mode nominated then just use the
	 * current video mode, otherwise determine if we need to change
	 * video modes
	 */
	if(m_sel == NULL)
	    m_sel = gdk_video_mode_get_current(fsimg->vidmodes_list);
	else
	    need_change_vidmode = (m_sel != gdk_video_mode_get_current(fsimg->vidmodes_list)) ?
		TRUE : FALSE;
	if(m_sel == NULL)
	    return(-5);

	/* Get values of the new video mode and record the previous
	 * video mode
	 */
	fsimg->prev_vidmode = fsimg->cur_vidmode;
	fsimg->cur_vidmode = m_sel;
	win_width = m_sel->viewport_width;
	win_height = m_sel->viewport_height;
	if((win_width <= 0) || (win_height <= 0))
	    return(-2);

	/* Recreate the pixmap */
	if(bg_rgba != NULL)
	{
	    GdkPixmap *pixmap;
	    guint8 *rgba = (guint8 *)g_malloc(
		win_width * win_height * 4 * sizeof(guint8)
	    );
	    if(rgba != NULL)
	    {
		guint32	*ptr = (guint32 *)rgba,
			*end = ptr + (win_width * win_height);
		while(ptr < end)
		    *ptr++ = *(const guint32 *)bg_rgba;
	    }

	    /* Recreate the window's GdkPixmap and put the window's   
	     * RGBA image data on it
	     */
	    GDK_PIXMAP_UNREF(fsimg->pixmap);
	    fsimg->pixmap = pixmap = gdk_pixmap_new(
		window, win_width, win_height, -1
	    );
	    if((pixmap != NULL) && (rgba != NULL))
		gdk_draw_rgb_32_image(
		    pixmap, style->black_gc,
		    0, 0, win_width, win_height,
		    dither,
		    (guchar *)rgba,
		    win_width * 4
		);

	    /* Delete the window's RGBA image data */
	    g_free(rgba);
	}

	/* Move and resize the FSImg window to the new size
	 *
	 * The widget needs to have its user position and size set for
	 * it's child widgets to configure correctly and the widget's
	 * window has to be moved and resized for the actual affect to
	 * take place for the window
	 */
	gtk_widget_set_usize(w, win_width, win_height);
	gtk_widget_set_uposition(w, 0, 0);
	gdk_window_move_resize(
	    window,
	    0, 0,
	    win_width, win_height
	);

	/* Map and raise the FSImg window */
	gtk_widget_show_raise(w);

	/* Grab the FSImg toplevel window to confine pointer and
	 * receive key events
	 */
	FSImgGrab(fsimg);

	/* Need to change video modes? */
	if(need_change_vidmode)
	{
	    /* Move pointer to center of window */
	    FSImgWarpPointer(
		window,
		win_width / 2, win_height / 2
	    );
	    /* Switch to the nominated video mode */
	    gdk_video_mode_switch(m_sel);
	}

	/* Viewport will be set when the "configure_event" signal is
	 * received
	 */

	/* Redraw since image changed */
	FSImgQueueDraw(fsimg);

	FSImgNavigatorUpdate(fsimg);

	return(0);
}


/*
 *	Loads the specified image and switches video modes as needed.
 *
 *	Returns values: 
 *
 *	0	Success
 *	-1	General error
 *	-2	Bad value   
 *	-3	Systems error
 *	-4	User aborted
 *	-5	No video mode available
 */
static gint FSImgOpen(FSImgData *fsimg, const gchar *path)
{
	gint status, width, height, bpl, nframes;
	guint8 *rgba;
	GList *rgba_list, *delay_list;
	GdkWindow *window;
	GtkWidget *w;

	if((fsimg == NULL) || STRISEMPTY(path))
	    return(-1);

	FSImgHelpShow(fsimg, FALSE);
	FSImgMesg(fsimg, NULL);

	w = fsimg->toplevel;
	if(w == NULL)
	    return(-1);

	window = w->window;
	if(window == NULL)
	    return(-1);

	/* Open image and get the RGBA image data */
	rgba_list = EDVImbrTListLoadImageRGBA(
	    NULL, path,
	    &width, &height, &bpl, &nframes, &delay_list,
	    FALSE,              /* Do not resize for tlist thumb */
	    FALSE,              /* Allow enlarge */
	    window
	);
	if(rgba_list == NULL)
	{
	    gchar *s;
	    const gchar *name = strrchr(path, G_DIR_SEPARATOR);
	    if(name != NULL)
		name++;
	    else
		name = path;
	    FSImgClear(fsimg, -1, -1);
	    s = g_strdup_printf(
		"Unable to open image:\n\n    %s",
		name
	    );
	    FSImgMesg(fsimg, s);
	    EDVPlaySoundError(fsimg->core);
	    g_free(s);
	    return(-1);
	}

	rgba = (guint8 *)rgba_list->data;

	/* Set image data and switch video mode as needed */
	status = FSImgSetData(fsimg, rgba, width, height, bpl);
	if(status != 0)
	{
	    gchar *s;
	    switch(status)
	    {
	      case -1:
		s = STRDUP(
		    "Error occured while displaying image data"
		);
		break;
	      case -2:
		s = STRDUP(
		    "Invalid value in image data"
		);
		break;
	      case -3:
		s = STRDUP(
		    "Memory allocation error"
		);
		break;
	      case -4:
		s = STRDUP(
		    "User interrupted"
		);
		break;
	      case -5:
		s = STRDUP(
		    "No video mode available to display image data"
		);
		break;
	      default:
		s = STRDUP(
		    "Error occured while displaying image data"
		);
		break;
	    }
	    FSImgMesg(fsimg, s);
	    EDVPlaySoundError(fsimg->core);
	    FSImgClear(fsimg, -1, -1);
	    g_free(s);
	}

	/* Reset rotate position marker */
	fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;

	/* Delete the image data */
	g_list_foreach(rgba_list, (GFunc)g_free, NULL);
	g_list_free(rgba_list);
	g_list_free(delay_list);

	return(status);
}

/*
 *	Displays the next image on the FSImg.
 *
 *	Opens the next image and switches video modes as needed.
 */
static void FSImgNext(FSImgData *fsimg)
{
	gboolean image_opened = FALSE;
	gint i;
	GList *glist;
	GtkCList *clist;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	clist = GTK_CLIST(fsimg->clist);
	core = fsimg->core;
	if((clist == NULL) || (core == NULL))
	    return;

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	glist = clist->selection_end;

	/* Iterate from the next selected row until the last row is
	 * reached or an image is opened
	 */
	for(
	    i = (glist != NULL) ? MAX(((gint)glist->data + 1), 0) : 0;
	    i < clist->rows;
	    i++
	)
	{
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    if(!EDVCheckImlibImage(core, obj->name))
		continue;

	    /* Open image to the FSImg and switch video mode as
	     * needed
	     */
	    FSImgOpen(fsimg, obj->full_path);
	    image_opened = TRUE;

	    /* Select list row associated with this image */
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, i, 0);

	    /* Update information as needed */
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	    break;
	}

	/* Beep if no image was opened */
	if(!image_opened)
	    EDVPlaySoundBeep(core);

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Displays the previous image on the FSImg.
 *
 *	Opens the previous image and switches video modes as needed.
 */
static void FSImgPrev(FSImgData *fsimg)
{
	gboolean image_opened = FALSE;
	gint i;
	GList *glist;
	GtkCList *clist;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	clist = GTK_CLIST(fsimg->clist);
	core = fsimg->core;
	if((clist == NULL) || (core == NULL))
	    return;

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	glist = clist->selection_end;

	/* Iterate from the previous selected row until the first row
	 * is reached or an image is opened
	 */
	for(
	    i = (glist != NULL) ?
		MIN(((gint)glist->data - 1), (clist->rows - 1)) : 0;
	    i >= 0;
	    i--
	)
	{
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    if(!EDVCheckImlibImage(core, obj->name))
		continue;

	    /* Open image to the FSImg and switch video mode as
	     * needed
	     */
	    FSImgOpen(fsimg, obj->full_path);
	    image_opened = TRUE;

	    /* Select list row associated with this image */
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, i, 0);

	    /* Update information as needed */
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	    break;
	}

	/* Beep if no image was opened */
	if(!image_opened)
	    EDVPlaySoundBeep(core);

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Displays the first image on the FSImg.
 *
 *	Opens the first image and switches video modes as needed.
 */
static void FSImgFirst(FSImgData *fsimg)
{
	gboolean image_opened = FALSE;
	gint i;
	GtkCList *clist;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	clist = GTK_CLIST(fsimg->clist);
	core = fsimg->core;
	if((clist == NULL) || (core == NULL))
	    return;

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	/* Iterate from the first row until the last row is reached or
	 * an image is opened
	 */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    if(!EDVCheckImlibImage(core, obj->name))
		continue;

	    /* Open image to the FSImg and switch video mode as
	     * needed
	     */
	    FSImgOpen(fsimg, obj->full_path);
	    image_opened = TRUE;

	    /* Select list row associated with this image */
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, i, 0);

	    /* Update information as needed */
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	    break;
	}

	/* Beep if no image was opened */
	if(!image_opened)
	    EDVPlaySoundBeep(core);

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Displays the last image on the FSImg.
 *
 *	Opens the last image and switches video modes as needed.
 */
static void FSImgLast(FSImgData *fsimg)
{
	gboolean image_opened = FALSE;
	gint i;
	GtkCList *clist;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	clist = GTK_CLIST(fsimg->clist);
	core = fsimg->core;
	if((clist == NULL) || (core == NULL))
	    return;

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	/* Iterate from the last row until the first row is reached or
	 * an image is opened
	 */  
	for(i = clist->rows - 1; i >= 0; i--)
	{
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    if(!EDVCheckImlibImage(core, obj->name))
		continue;

	    /* Open image to the FSImg and switch video mode as
	     * needed
	     */
	    FSImgOpen(fsimg, obj->full_path);
	    image_opened = TRUE;

	    /* Select list row associated with this image */
	    gtk_clist_unselect_all(clist);
	    gtk_clist_select_row(clist, i, 0);

	    /* Update information as needed */
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	    break;
	}

	/* Beep if no image was opened */
	if(!image_opened)
	    EDVPlaySoundBeep(core);

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Refreshes and reopens the image.
 */
static void FSImgRefresh(FSImgData *fsimg)
{
	edv_object_struct *obj = FSImgCurrentObject(fsimg);
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	FSImgOpen(fsimg, obj->full_path);

	/* Update information as needed */
	FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Rotates the image clockwise by 90 degrees.
 *
 *	The image will be reopened and the video mode will be switched
 *	as needed.
 */
static void FSImgRotateCW90(FSImgData *fsimg)
{
	gint	status,
		width, height, bpl, nframes,
		rot_width, rot_height, rot_bpl;
	guint8 *rgba, *rot_rgba;
	GList *rgba_list, *delay_list;
	GdkWindow *window;
	GtkWidget *w;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL) 
	    return;

	FSImgHelpShow(fsimg, FALSE);
	FSImgMesg(fsimg, NULL);

	obj = FSImgCurrentObject(fsimg);
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	w = fsimg->toplevel;
	core = fsimg->core;
	if((w == NULL) || (core == NULL))
	    return;

	window = w->window;

	/* Handle rotation alternates */
	switch(fsimg->rotate_position)
	{
	  case FSIMG_ROTATE_POSITION_NONE:
	    break;

	  case FSIMG_ROTATE_POSITION_CW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCW180(fsimg);
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CCW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgOpen(fsimg, obj->full_path);
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CW180:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCCW90(fsimg);
	    return;
	    break;
	}

	/* Begin rotating */

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	/* Open image and get the RGBA image data */
	rgba_list = EDVImbrTListLoadImageRGBA(           
	    NULL, obj->full_path,
	    &width, &height, &bpl, &nframes, &delay_list,
	    FALSE,		/* Do not resize for tlist thumb */
	    FALSE,		/* Allow enlarge */
	    window
	);
	if(rgba_list == NULL)
	{
	    gchar *s = g_strdup_printf(
		"Unable to open image:\n\n    %s",
		obj->name
	    );
	    FSImgMesg(fsimg, s);
	    g_free(s);
	    EDVPlaySoundError(core);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	rgba = (guint8 *)rgba_list->data;

	/* Allocate rotated RGBA image data */
	rot_width = height;
	rot_height = width;
	rot_bpl = rot_width * 4;
	rot_rgba = (guint8 *)g_malloc(rot_bpl * rot_height * sizeof(guint8));
	if(rot_rgba == NULL)
	{
	    EDVPlaySoundError(core);
	    g_free(rgba);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	/* Rotate */
	GUIImageBufferRotateCW90(
	    4,
	    rgba, width, height, bpl,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Delete the image data */
	g_list_foreach(rgba_list, (GFunc)g_free, NULL);
	g_list_free(rgba_list);
	g_list_free(delay_list);

	/* Set rotated RGBA image data and switch video mode as needed */
	status = FSImgSetData(
	    fsimg,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Update rotate position marker */
	fsimg->rotate_position = FSIMG_ROTATE_POSITION_CW90;

	/* Delete the rotated RGBA image data */
	g_free(rot_rgba);

	/* Update information as needed */
	FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Rotates the image counter-clockwise by 90 degrees.
 *
 *	The image will be reopened and the video mode will be switched
 *	as needed.
 */
static void FSImgRotateCCW90(FSImgData *fsimg)
{
	gint    status,
		width, height, bpl, nframes,
		rot_width, rot_height, rot_bpl;
	guint8 *rgba, *rot_rgba;
	GList *rgba_list, *delay_list;
	GdkWindow *window;
	GtkWidget *w;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	FSImgHelpShow(fsimg, FALSE);
	FSImgMesg(fsimg, NULL);

	obj = FSImgCurrentObject(fsimg);
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	w = fsimg->toplevel;
	core = fsimg->core;
	if((w == NULL) || (core == NULL))
	    return;

	window = w->window;

	/* Handle rotation alternates */
	switch(fsimg->rotate_position)  
	{
	  case FSIMG_ROTATE_POSITION_NONE:
	    break;

	  case FSIMG_ROTATE_POSITION_CW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgOpen(fsimg, obj->full_path);
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CCW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCW180(fsimg);
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CW180:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCW90(fsimg);
	    return;
	    break;
	}

	/* Begin rotating */

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	/* Open image and get the RGBA image data */
	rgba_list = EDVImbrTListLoadImageRGBA(
	    NULL, obj->full_path,
	    &width, &height, &bpl, &nframes, &delay_list,
	    FALSE,		/* Do not resize for tlist thumb */
	    FALSE,		/* Allow enlarge */
	    window
	);
	if(rgba_list == NULL)
	{
	    gchar *s = g_strdup_printf(
		"Unable to open image:\n\n    %s",
		obj->name
	    );
	    FSImgMesg(fsimg, s);
	    g_free(s);
	    EDVPlaySoundError(core);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	rgba = (guint8 *)rgba_list->data;

	/* Allocate rotated RGBA image data */
	rot_width = height;
	rot_height = width;
	rot_bpl = rot_width * 4;
	rot_rgba = (guint8 *)g_malloc(rot_bpl * rot_height * sizeof(guint8));
	if(rot_rgba == NULL)
	{
	    EDVPlaySoundError(core);
	    g_free(rgba);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	/* Rotate */
	GUIImageBufferRotateCCW90(
	    4,
	    rgba, width, height, bpl,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Delete the image data */
	g_list_foreach(rgba_list, (GFunc)g_free, NULL);
	g_list_free(rgba_list);
	g_list_free(delay_list);

	/* Set rotated RGBA image data and switch video mode as needed */
	status = FSImgSetData(
	    fsimg,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Update rotate position marker */
	fsimg->rotate_position = FSIMG_ROTATE_POSITION_CCW90;

	/* Delete the rotated RGBA image data */
	g_free(rot_rgba);

	/* Update information as needed */
	FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Rotates the image clockwise by 180 degrees.
 *
 *	The image will be reopened and the video mode will be switched
 *	as needed.
 */
static void FSImgRotateCW180(FSImgData *fsimg)
{
	gint	status,
		width, height, bpl, nframes,
		rot_width, rot_height, rot_bpl;
	guint8 *rgba, *rot_rgba;
	GList *rgba_list, *delay_list;
	GdkWindow *window;
	GtkWidget *w;
	edv_object_struct *obj;
	edv_core_struct *core;
	if(fsimg == NULL)
	    return;

	FSImgHelpShow(fsimg, FALSE);
	FSImgMesg(fsimg, NULL);

	obj = FSImgCurrentObject(fsimg);
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	w = fsimg->toplevel;
	core = fsimg->core;
	if((w == NULL) || (core == NULL))
	    return;

	window = w->window;

	/* Handle rotation alternates */
	switch(fsimg->rotate_position)
	{
	  case FSIMG_ROTATE_POSITION_NONE:
	    break;

	  case FSIMG_ROTATE_POSITION_CW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCCW90(fsimg);
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CCW90:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgRotateCW90(fsimg);
	    return;
	    break;

	  case FSIMG_ROTATE_POSITION_CW180:
	    fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;
	    FSImgOpen(fsimg, obj->full_path);
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));
	    return;
	}

	/* Begin rotating */

	FSImgSetBusy(fsimg, TRUE);
	FSImgNavigatorUpdate(fsimg);

	/* Open image and get the RGBA image data */
	rgba_list = EDVImbrTListLoadImageRGBA(
	    NULL, obj->full_path,
	    &width, &height, &bpl, &nframes, &delay_list,
	    FALSE,		/* Do not resize for tlist thumb */
	    FALSE,		/* Allow enlarge */
	    window
	);
	if(rgba_list == NULL)
	{
	    gchar *s = g_strdup_printf(
		"Unable to open image:\n\n    %s",
		obj->name
	    );
	    FSImgMesg(fsimg, s);
	    g_free(s);
	    EDVPlaySoundError(core);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	rgba = (guint8 *)rgba_list->data;

	/* Allocate rotated RGBA image data */
	rot_width = width;
	rot_height = height;
	rot_bpl = rot_width * 4;
	rot_rgba = (guint8 *)g_malloc(rot_bpl * rot_height * sizeof(guint8));
	if(rot_rgba == NULL)
	{
	    EDVPlaySoundError(core);
	    g_free(rgba);
	    FSImgSetBusy(fsimg, FALSE);
	    FSImgNavigatorUpdate(fsimg);
	    return;
	}

	/* Rotate */
	GUIImageBufferRotateCW180(
	    4,
	    rgba, width, height, bpl,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Delete the image data */
	g_list_foreach(rgba_list, (GFunc)g_free, NULL);
	g_list_free(rgba_list);
	g_list_free(delay_list);

	/* Set rotated RGBA image data and switch video mode as needed */
	status = FSImgSetData(
	    fsimg,
	    rot_rgba, rot_width, rot_height, rot_bpl
	);

	/* Update rotate position marker */
	fsimg->rotate_position = FSIMG_ROTATE_POSITION_CW180;

	/* Delete the rotated RGBA image data */
	g_free(rot_rgba);

	/* Update information as needed */
	FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	FSImgSetBusy(fsimg, FALSE);
	FSImgNavigatorUpdate(fsimg);
}


/*
 *	Returns TRUE if help is being shown.
 */
static gboolean FSImgHelpShown(FSImgData *fsimg)
{
	return((fsimg != NULL) ? fsimg->show_help : FALSE);
}

/*
 *	Shows or hides the help.
 */
static void FSImgHelpShow(FSImgData *fsimg, gboolean show)
{
	if(fsimg == NULL)
	    return;

	if(fsimg->show_help == show)
	    return;

	fsimg->show_help = show;
	FSImgQueueDraw(fsimg);
}


/*
 *	Sets the specified message and redraws.
 */
static void FSImgMesg(FSImgData *fsimg, const gchar *mesg)
{
	if(fsimg == NULL)
	    return;

	if((fsimg->mesg == NULL) && (mesg == NULL))
	    return;

	g_free(fsimg->mesg);
	fsimg->mesg = (mesg != NULL) ? g_strdup(mesg) : NULL;

	FSImgQueueDraw(fsimg);
}


/*
 *	Returns the shown information level.
 */
static gint FSImgInfoShown(FSImgData *fsimg)
{
	return((fsimg != NULL) ? fsimg->info_level : 0);
}

/*
 *	Shows or hides the information based on the specified info
 *	level.
 */
static void FSImgInfoShow(FSImgData *fsimg, gint info_level)
{
	const gchar *date_format;
	edv_date_relativity date_relativity;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(fsimg == NULL)
	    return;

	core = fsimg->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;
	date_relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	date_format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);

	/* Specified info level has "cycled"? */
	if(info_level >= 5)
	    info_level = 0;
	else if(info_level < 0)
	    info_level = 0;

	/* Set info level */
	fsimg->info_level = info_level;

	/* Delete existing info */
	g_free(fsimg->info_text1);
	fsimg->info_text1 = NULL;
	g_free(fsimg->info_text2);
	fsimg->info_text2 = NULL;
	g_free(fsimg->info_text3);
	fsimg->info_text3 = NULL;

	/* Set new information? */
	if(info_level >= 1)
	{
	    GdkVisual *vis = fsimg->visual;
	    const GdkVideoModeInfo *cur_vidmode = fsimg->cur_vidmode;
	    GtkCList *clist = GTK_CLIST(fsimg->clist);
	    edv_object_struct *obj = FSImgCurrentObject(fsimg);

	    /* File name, image size, and file size */
	    if(obj != NULL)
	    {
		if((fsimg->img_shown_width != fsimg->img_width) ||
		   (fsimg->img_shown_height != fsimg->img_height)
		)
		    fsimg->info_text1 = g_strdup_printf(
			"%s\n%ix%i (%ix%i shown)\n%s bytes",
			obj->name,
			fsimg->img_width, fsimg->img_height,
			fsimg->img_shown_width, fsimg->img_shown_height,
			EDVGetObjectSizeStr(core, obj->size)
		    );
		else
		    fsimg->info_text1 = g_strdup_printf(
			"%s\n%ix%i\n%s bytes",
			obj->name,
			fsimg->img_width, fsimg->img_height,
			EDVGetObjectSizeStr(core, obj->size)
		    );
	    }
	    /* Date and image number */
	    if((clist != NULL) && (info_level >= 2))
	    {
		const gchar *s;
		const gulong t = obj->modify_time;
		GList *glist = clist->selection_end;
		const gint i = (glist != NULL) ? (gint)glist->data : 0;

		if(t > 0)
		    s = EDVDateFormatString(
			t, date_format, date_relativity
		    );
		else  
		    s = "";

		fsimg->info_text2 = g_strdup_printf(
		    "%s\n%i/%i",
		    s, i + 1, clist->rows
		);
	    }
	    /* Display size */
	    if((vis != NULL) && (cur_vidmode != NULL) && (info_level >= 3))
	    {
		const gchar *vis_name = NULL;
		switch(vis->type)
		{
		  case GDK_VISUAL_STATIC_GRAY:
		    vis_name = "StaticGrey";
		    break;
		  case GDK_VISUAL_GRAYSCALE:
		    vis_name = "GreyScale";
		    break;
		  case GDK_VISUAL_STATIC_COLOR:
		    vis_name = "StaticColor";
		    break;
		  case GDK_VISUAL_PSEUDO_COLOR:
		    vis_name = "PseduoColor";
		    break;
		  case GDK_VISUAL_TRUE_COLOR:
		    vis_name = "TrueColor";
		    break;
		  case GDK_VISUAL_DIRECT_COLOR:
		    vis_name = "DirectColor";
		    break;
		}
		fsimg->info_text3 = g_strdup_printf(
		    "Display: %ix%i %i bits\n%s",
		    cur_vidmode->viewport_width,
		    cur_vidmode->viewport_height,
		    vis->depth,
		    vis_name
		);
	    }
	}

	FSImgQueueDraw(fsimg);
}

/*
 *	Returns the map state of the list.
 */
static gboolean FSImgListShown(FSImgData *fsimg)
{
	GtkWidget *w = (fsimg != NULL) ? fsimg->clist_toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
}

/*
 *	Shows or hides the list.
 */
static void FSImgListShow(FSImgData *fsimg, gboolean show)
{
	GtkWidget *w = (fsimg != NULL) ? fsimg->clist_toplevel : NULL;
	if(w == NULL)
	    return;

	if(show)
	{
	    GList *glist;
	    GtkCList *clist = GTK_CLIST(fsimg->clist);

	    gtk_widget_show(w);

	    /* Move to selected row */
	    glist = (clist != NULL) ? clist->selection_end : NULL;
	    if(glist != NULL)
		gtk_clist_moveto(
		    clist,
		    (gint)glist->data, -1,	/* Row, column */
		    0.5f, 0.0f			/* Row, column */
		);
	}
	else
	{
	    gtk_widget_hide(w);

	    /* Need to grab the FSImg's toplevel window again whenever
	     * the GtkCList is unmapped because it will ungrab the
	     * pointer and loose confinement to the FSImg's toplevel
	     * window
	     */
	    FSImgGrab(fsimg);
	}

	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Gets the list of images from the specified location.
 */
static void FSImgListGet(FSImgData *fsimg, const gchar *path)
{
	const gint border_minor = 2;
	gint column, columns;
	const gchar *date_format;
	const cfg_item_struct *cfg_list;
	edv_date_relativity date_relativity;
	edv_core_struct *core;
	GtkCList *clist = (fsimg != NULL) ? GTK_CLIST(fsimg->clist) : NULL;
	if(clist == NULL)
	    return;

	columns = clist->columns;
	core = fsimg->core;
	if((columns < 1) || (core == NULL))
	    return;

	cfg_list = core->cfg_list;
	date_relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	date_format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);


	/* Begin getting list of images */

	gtk_clist_freeze(clist);

	/* Delete all existing items */
	gtk_clist_clear(clist);

	/* Set up columns */
	gtk_clist_column_titles_show(clist);
	gtk_clist_column_titles_passive(clist);
	column = 0;
	if(column < columns)
	{
	    gtk_clist_set_column_visibility(
		clist, column, TRUE
	    );
	    gtk_clist_set_column_justification(
		clist, column, GTK_JUSTIFY_LEFT
	    );
	}
	column = 1;
	if(column < columns)
	{
	    gtk_clist_set_column_visibility(
		clist, column, TRUE
	    );
	    gtk_clist_set_column_justification(
		clist, column, GTK_JUSTIFY_RIGHT
	    );
	}
	column = 2;
	if(column < columns)
	{
	    gtk_clist_set_column_visibility(
		clist, column, TRUE
	    );
	    gtk_clist_set_column_justification(
		clist, column, GTK_JUSTIFY_LEFT
	    );
	}

	/* Get list of items from the Thumbs List */
	if(!STRISEMPTY(path))
	{
	    gint names;
	    gchar **names_list = GetDirEntNames2(path, &names);

	    /* Sort image file names */
	    names_list = StringQSort(names_list, names);

	    if(names_list != NULL)
	    {
		gint i, row;
		gchar *name, *full_path, **strv;
		struct stat lstat_buf;
		GdkPixmap *pixmap_closed;
		GdkBitmap *mask_closed;
		GdkPixmap *pixmap_opened;
		GdkBitmap *mask_opened;
		GdkPixmap *pixmap_extended;
		GdkBitmap *mask_extended;
		GdkPixmap *pixmap_hidden;
		GdkBitmap *mask_hidden;
		edv_object_struct *obj;

		for(i = 0; i < names; i++)
		{
		    name = names_list[i];
		    if(name == NULL)
			continue;

		    /* Skip special directory notations */
		    if(!strcmp(name, "..") ||
		       !strcmp(name, ".")
		    )
		    {
			g_free(name);
			continue;
		    }

		    /* Skip non-images */
		    if(!EDVCheckImlibImage(core, name))
		    {
			g_free(name);
			continue;
		    }

		    /* Get full path to this object */
		    full_path = STRDUP(PrefixPaths(path, name));
		    if(full_path == NULL)
		    {
			g_free(name);
			continue;
		    }

		    /* Get this object's destination statistics */
		    if(lstat((char *)full_path, &lstat_buf))
		    {
			g_free(full_path);
			g_free(name);
			continue; 
		    }

		    /* Create a new object */
		    obj = EDVObjectNew();
		    if(obj == NULL)
		    {
			g_free(full_path);
			g_free(name);
			continue;
		    }   

		    EDVObjectSetPath(obj, full_path);
		    EDVObjectSetStat(obj, &lstat_buf);
		    EDVObjectUpdateLinkFlags(obj);

		    /* Get icon associated with this object */
		    EDVMatchObjectIcon(
			core->device, core->total_devices,
			core->mimetype, core->total_mimetypes,
			obj->type,
			obj->full_path,
			EDV_OBJECT_IS_LINK_VALID(obj),
			obj->permissions,
			0,			/* Small icons */
			&pixmap_closed, &mask_closed,
			&pixmap_opened, &mask_opened,
			&pixmap_extended, &mask_extended,
			&pixmap_hidden, &mask_hidden
		    );

		    /* Append row */
		    strv = (gchar **)g_malloc(columns * sizeof(gchar *));
		    for(column = 0; column < columns; column++)
			strv[column] = "";
		    row = gtk_clist_append(clist, strv);
		    g_free(strv);
		    if(row < 0)
		    {
			EDVObjectDelete(obj);
			g_free(full_path);
			g_free(name);
			continue;
		    }

		    /* Set row cells */
		    /* Name */
		    column = 0;
		    if(column < columns)
		    {
			if(pixmap_closed != NULL)
			    gtk_clist_set_pixtext(
				clist, row, column,
				obj->name,
				EDV_LIST_PIXMAP_TEXT_SPACING,
				pixmap_closed, mask_closed
			    );
			else
			    gtk_clist_set_text(
				clist, row, column,
				obj->name
			    );
			gtk_clist_set_shift(
			    clist, row, column, 0, 0
			);
		    }
		    /* Size */
		    column = 1;
		    if(column < columns) 
		    {
			const gchar *s = EDVGetObjectSizeStr(
			    core, obj->size
			);
			gtk_clist_set_text(
			    clist, row, column,
			    (s != NULL) ? s : ""
			);
			gtk_clist_set_shift(                                
			    clist, row, column, 0, -border_minor
			);
		    }
		    /* Date Modified */
		    column = 2;
		    if(column < columns)  
		    {
			const gchar *s;
			const gulong t = obj->modify_time;
			if(t > 0)
			    s = EDVDateFormatString(
				t, date_format, date_relativity
			    );
			else
			    s = "";
			gtk_clist_set_text(
			    clist, row, column, s
			);
		    }

		    /* Set the object as the row's data */
		    gtk_clist_set_row_data_full(
			clist, row,
			obj, FSImgListItemDestroyCB
		    );

		    g_free(full_path);
		    g_free(name);
		}

		g_free(names_list);
	    }
	}

	/* Resize columns */
	column = 0;
	if(column < columns)
	{
	    const gint width = gtk_clist_optimal_column_width(
		clist, column
	    ) + 10;
	    gtk_clist_set_column_width(clist, column, width);
	}
	column = 1;
	if(column < columns)
	{
	    const gint width = gtk_clist_optimal_column_width(
		clist, column
	    ) + 10;
	    gtk_clist_set_column_width(clist, column, width);
	}
	column = 2;
	if(column < columns)
	{
	    const gint width = gtk_clist_optimal_column_width(
		clist, column
	    ) + (2 * 2);
	    gtk_clist_set_column_width(clist, column, width);
	}


	gtk_clist_thaw(clist); 
}


/*
 *	Returns the active state of the slideshow.
 */
static gboolean FSImgSlideshowActive(FSImgData *fsimg)
{
	if(fsimg == NULL)
	    return(FALSE);
	else
	    return((fsimg->slideshow_toid != 0) ? TRUE : FALSE);
}


/*
 *	Returns the map state of the control bar.
 */
static gboolean FSImgNavigatorShown(FSImgData *fsimg)
{
	GtkWidget *w = (fsimg != NULL) ? fsimg->navigator_toplevel : NULL;
	return((w != NULL) ? GTK_WIDGET_MAPPED(w) : FALSE);
} 

/*
 *	Shows or hides the control bar.
 */
static void FSImgNavigatorShow(FSImgData *fsimg, gboolean show)
{
	GtkWidget *w = (fsimg != NULL) ? fsimg->navigator_toplevel : NULL;
	if(w == NULL)
	    return;

	if(show)
	    gtk_widget_show(w);
	else
	    gtk_widget_hide(w);

	FSImgQueueDraw(fsimg);
	FSImgNavigatorUpdate(fsimg);
}

/*
 *	Updates the control bar's button mappings and sensitivities.
 */
static void FSImgNavigatorUpdate(FSImgData *fsimg)
{
	const guint	border_major = 5,
			btn_width = BTN_WIDTH;
	gboolean busy, list_mapped, slideshow;
	gint width, height;

	if(fsimg == NULL)
	    return;

	busy = (fsimg->busy_count > 0) ? TRUE : FALSE;
	list_mapped = FSImgListShown(fsimg);
	slideshow = FSImgSlideshowActive(fsimg);

	/* Get size of the current view port */
	FSImgCurrentViewportSize(
	    fsimg, &width, &height
	);

	/* Reduce width by the borders of the control bar's frame */
	width -= (2 * 3);

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

	/* Begin mapping or unmapping buttons due to the width of the
	 * control bar
	 *
	 * Buttons layout:
	 * 
	 * [x]  [l]  [s]  [<<] {<] [>] [>>]   [i]  [rcw] [rccw] [cw180]  [?]
	 *
	 */

	/* Close */
	gtk_widget_show(fsimg->close_btn);

	/* List */
	if(width >= ((8 * btn_width) + (4 * border_major)))
	    gtk_widget_show(fsimg->list_btn);
	else
	    gtk_widget_hide(fsimg->list_btn);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->list_btn,
	    (!busy && !slideshow) ? TRUE : FALSE
	);

	/* Slideshow */
	if(width >= ((8 * btn_width) + (4 * border_major)))
	    gtk_widget_show(fsimg->slideshow_btn);
	else
	    gtk_widget_hide(fsimg->slideshow_btn);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->slideshow_btn,
	    (!busy && !slideshow && !list_mapped) ? TRUE : FALSE
	);

	/* First, Previous, Next, and Last */
	gtk_widget_show(fsimg->next_btns_toplevel);
	if(width >= ((6 * btn_width) + (2 * border_major)))
	{
	    gtk_widget_show(fsimg->first_btn);
	    gtk_widget_show(fsimg->last_btn);
	}
	else
	{
	    gtk_widget_hide(fsimg->first_btn);
	    gtk_widget_hide(fsimg->last_btn);
	}
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->first_btn,
	    (!busy && !slideshow && !list_mapped) ? TRUE : FALSE
	);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->last_btn,
	    (!busy && !slideshow && !list_mapped) ? TRUE : FALSE
	);

	gtk_widget_show(fsimg->prev_btn);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->prev_btn,
	    (!slideshow && !list_mapped) ? TRUE : FALSE
	);
	gtk_widget_show(fsimg->next_btn);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->next_btn,
	    (!slideshow && !list_mapped) ? TRUE : FALSE
	);

	/* Info */
	if(width >= ((8 * btn_width) + (4 * border_major)))
	    gtk_widget_show(fsimg->info_btn);
	else
	    gtk_widget_hide(fsimg->info_btn);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->info_btn,
	    (!busy && !slideshow && !list_mapped) ? TRUE : FALSE
	);

	/* Rotate */
	if(width >= ((11 * btn_width) + (5 * border_major)))
	    gtk_widget_show(fsimg->rot_btns_toplevel);
	else
	    gtk_widget_hide(fsimg->rot_btns_toplevel);
	GTK_WIDGET_SET_SENSITIVE(
	    fsimg->rot_btns_toplevel,
	    (!busy && !slideshow && !list_mapped) ? TRUE : FALSE
	);

	/* Help */
	gtk_widget_show(fsimg->help_btn);
}


/*
 *	Ungrabs the pointer as needed, unmaps the FSImg window, and
 *	restores the original video mode.
 */
static void FSImgRestore(FSImgData *fsimg)
{
	const GdkVideoModeInfo *m;
	GdkWindow *root = GDK_ROOT_PARENT();
	GtkWidget *w;
	if(fsimg == NULL)
	    return;

	/* Ungrab toplevel */
	w = fsimg->toplevel; 
	if(w != NULL)
	    gtk_grab_remove(w);

	/* Ungrab pointer as needed */
	if(gdk_pointer_is_grabbed())
	    gdk_pointer_ungrab(GDK_CURRENT_TIME);

	/* Unmap the fsimg */
	w = fsimg->toplevel;
	if(w != NULL)
	    gtk_widget_hide(w);

	/* Switch back to the original video mode */
	m = fsimg->orig_vidmode;
	if(gdk_video_mode_switch(m))
	{
	    /* Restore pointer position */
	    FSImgWarpPointer(
		root,
		fsimg->orig_pointer_x, fsimg->orig_pointer_y
	    );

	    /* Restore viewport position */
	    gdk_video_mode_set_viewport(
		fsimg->orig_viewport_x, fsimg->orig_viewport_y
	    );
	}
	else
	{
	    /* Failed to switch video modes, try to switch to the
	     * default video mode
	     */
	    GList *glist = fsimg->vidmodes_list;
	    while(glist != NULL)
	    {
		m = GDK_VIDEO_MODE_INFO(glist->data);
		if((m != NULL) ? (m->flags & GDK_VIDEO_MODE_DEFAULT) : FALSE)
		{
		    gdk_video_mode_switch(m);
		    break;
		}
		glist = g_list_next(glist);
	    }
	}
}

/*
 *	Creates a new FSImg.
 */
static FSImgData *FSImgNew(edv_core_struct *core)
{
	const gint	border_major = 5,
			btn_width = BTN_WIDTH,
			btn_height = BTN_HEIGHT;
	const guint8	icon_play_mask_16x16_data[] = {
		0x03, 0x00, 0x0f, 0x00, 0x3f, 0x00, 0xff, 0x00,
		0xff, 0x03, 0xff, 0x0f, 0xff, 0x3f, 0xff, 0xff,
		0xff, 0xff, 0xff, 0x3f, 0xff, 0x0f, 0xff, 0x03,
		0xff, 0x00, 0x3f, 0x00, 0x0f, 0x00, 0x03, 0x00
	},
			icon_pause_mask_16x16_data[] = {
		0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
		0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
		0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
		0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18
	};
	const gchar	*wm_name = NULL,
			*wm_class = NULL;
	gchar *clist_heading[3];
	GdkModifierType mask;
	GdkFont *font;
	GdkColormap *colormap;
	GdkWindow *window, *root = GDK_ROOT_PARENT();
	GdkVisual *vis;
	GdkGC *gc;
	GtkWidget *w, *parent, *parent2, *parent3;
	GtkCList *clist;
	const cfg_item_struct *cfg_list = NULL;
	FSImgData *fsimg = FSIMG_DATA(g_malloc0(sizeof(FSImgData)));
	if(fsimg == NULL)
	    return(NULL);

	if(core != NULL)
	{
	    cfg_list = core->cfg_list;
	    wm_name = core->wm_name;
	    wm_class = core->wm_class;
	}

	fsimg->visual = NULL;
	fsimg->colormap = NULL;
	memset(&fsimg->color_fg, 0x00, sizeof(GdkColor));
	memset(&fsimg->color_bg, 0x00, sizeof(GdkColor));
	memset(&fsimg->color_shadow, 0x00, sizeof(GdkColor));
	fsimg->invert_fg = EDV_GET_B(
	    EDV_CFG_PARM_PRESENTATION_COLOR_FG_INVERT
	);
	fsimg->font = NULL;
 	fsimg->pixmap = NULL;
	fsimg->gc = NULL;
	fsimg->busy_count = 0;
	fsimg->core = core;

	fsimg->main_loop_level = 0;

	fsimg->hide_pointer = FALSE;
	fsimg->hide_pointer_toid = 0;

	fsimg->slideshow_delay = (gulong)EDV_GET_I(
	    EDV_CFG_PARM_PRESENTATION_SLIDESHOW_DELAY) * 1000l;
	fsimg->slideshow_toid = 0;

	fsimg->rotate_position = FSIMG_ROTATE_POSITION_NONE;

	fsimg->show_help = FALSE;
	fsimg->mesg = NULL;

	fsimg->info_level = 0;
	fsimg->info_text1 = NULL;
	fsimg->info_text2 = NULL;
	fsimg->info_text3 = NULL;
	fsimg->icon_play_mask = gdk_bitmap_create_from_data(
	    root, icon_play_mask_16x16_data,
	    16, 16
	);
	fsimg->icon_pause_mask = gdk_bitmap_create_from_data(
	    root, icon_pause_mask_16x16_data,
	    16, 16
	);
	fsimg->img_width = 0;
	fsimg->img_height = 0;
	fsimg->img_shown_width = 0;
	fsimg->img_shown_height = 0;

	/* Get list of video modes */
	fsimg->vidmodes_list = gdk_video_modes_get();

	/* Record original video mode */
	fsimg->orig_vidmode = fsimg->cur_vidmode = gdk_video_mode_get_current(
	    fsimg->vidmodes_list
	);
	fsimg->prev_vidmode = NULL;

	/* Record original video mode values */
	gdk_video_mode_get_viewport(
	    &fsimg->orig_viewport_x, &fsimg->orig_viewport_y
	);
	gdk_window_get_pointer(
	    root,
	    &fsimg->orig_pointer_x, &fsimg->orig_pointer_y,
	    &mask
	);


	/* Begin creating widgets */

	fsimg->toplevel = parent = w = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_app_paintable(w, TRUE);
	if(!STRISEMPTY(wm_name) && !STRISEMPTY(wm_class))
	    gtk_window_set_wmclass(GTK_WINDOW(w), wm_name, wm_class);
	else
	    gtk_window_set_wmclass(
		GTK_WINDOW(w), "presentationmode", PROG_NAME
	    );
	gtk_window_set_policy(GTK_WINDOW(w), FALSE, FALSE, FALSE);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT | 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 |
	    GDK_POINTER_MOTION_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(  
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FSImgPointerMotionCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FSImgPointerMotionCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(FSImgPointerMotionCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect(  
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(FSImgEventCB), fsimg
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(FSImgPaintCB), (GtkObject *)fsimg
	);
	gtk_signal_connect_object(
	    GTK_OBJECT(w), "draw",
	    GTK_SIGNAL_FUNC(FSImgPaintCB), (GtkObject *)fsimg
	);
	gtk_widget_realize(w);
	window = w->window;

	/* Get Visual */
	fsimg->visual = vis = gdk_window_get_visual(window);
	if(vis != NULL)
	    gdk_visual_ref(vis);

	/* Get Colormap */
	fsimg->colormap = colormap = gdk_window_get_colormap(window);
	if(colormap != NULL)
	{
	    GdkColor *ct;
	    const cfg_color_struct *cs;

	    gdk_colormap_ref(colormap);

	    cs = EDV_GET_COLOR(EDV_CFG_PARM_PRESENTATION_COLOR_FG);
	    ct = &fsimg->color_fg;
	    if(cs != NULL)
		GDK_COLOR_SET_COEFF(ct, cs->r, cs->g, cs->b);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, ct);

	    cs = EDV_GET_COLOR(EDV_CFG_PARM_PRESENTATION_COLOR_BG);
	    ct = &fsimg->color_bg;
	    if(cs != NULL)
		GDK_COLOR_SET_COEFF(ct, cs->r, cs->g, cs->b);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, ct);

	    ct = &fsimg->color_shadow;
	    ct->red = 0x5fff;
	    ct->green = 0x5fff;
	    ct->blue = 0x5fff;
	    GDK_COLORMAP_ALLOC_COLOR(colormap, ct);          
	}

	/* Create Font */
	font = gdk_font_load(
	    EDV_GET_S(EDV_CFG_PARM_PRESENTATION_FONT)
	);
	if(font == NULL)
	    font = gdk_font_load(DEF_PRESENTATION_FONT);
	if(font == NULL)
	    font = gdk_font_load("8x16");
	fsimg->font = font;

	/* Create GC */
	fsimg->gc = gc = gdk_gc_new(window);


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


	/* Create List */
	fsimg->clist_toplevel = parent2 = w = gtk_event_box_new();
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	/* GtkScrolledWindow */
	parent3 = 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);
	/* GtkCList */
	clist_heading[0] = "Name";
	clist_heading[1] = "Size";
	clist_heading[2] = "Date Modified";
	fsimg->clist = w = gtk_clist_new_with_titles(3, clist_heading);
	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(EDVCListKeyEventCB), core
	);                                               
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(EDVCListKeyEventCB), core
	);
#if 0
/* Causes the loss of pointer grabs */
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(EDVCListButtonEventCB), core
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(EDVCListButtonEventCB), core
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(EDVCListMotionEventCB), core
	);
#endif
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FSImgListEventCB), fsimg
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FSImgListEventCB), fsimg
	);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_realize(w);
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_BROWSE);
	gtk_clist_set_row_height(clist, EDV_LIST_ROW_SPACING);
	gtk_clist_set_shadow_type(clist, GTK_SHADOW_IN);
	gtk_widget_show(w);


	/* Create control Bar */
	fsimg->navigator_toplevel = w = gtk_event_box_new();
	gtk_box_pack_end(GTK_BOX(parent), w, FALSE, FALSE, 0);
	parent = w;

	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;

	/* Hbox for buttons */
	w = gtk_hbox_new(FALSE, border_major);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent2 = w;

	/* Close Button */
	fsimg->close_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_close_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgCloseCB), fsimg
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Cierre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Fermer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Nah"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Vicino"
#elif defined(PROG_LANGUAGE_DUTCH)
"Einde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Próximo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Nær"
#else
"Close"
#endif
	);
	gtk_widget_show(w);

	/* List Button */
	fsimg->list_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_eject_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgListCB), fsimg
	);
	gtk_widget_show(w);

	/* Slideshow Button */
	fsimg->slideshow_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_slideshow_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgSlideshowCB), fsimg
	);                                           
	gtk_widget_show(w);

	/* Hbox for First, Previous, Next, and Last buttons */
	fsimg->next_btns_toplevel = w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* First Button */
	fsimg->first_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_rewind_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgFirstCB), fsimg
	);
	gtk_widget_show(w);

	/* Previous Button */
	fsimg->prev_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_playbackwards_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgPrevCB), fsimg
	);
	gtk_widget_show(w);

	/* Next Button */
	fsimg->next_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_play_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgNextCB), fsimg
	);
	gtk_widget_show(w);

	/* Last Button */
	fsimg->last_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_seekend_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgLastCB), fsimg
	);
	gtk_widget_show(w);

	/* Info Button */
	fsimg->info_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_info_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgInfoCB), fsimg
	);
	gtk_widget_show(w);

	/* Hbox for Rotate buttons */
	fsimg->rot_btns_toplevel = w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* Rotate CCW 90 Button */
	fsimg->rot_ccw90_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_rotateccw90_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgRotateCCW90CB), fsimg
	);
	gtk_widget_show(w);

	/* Rotate CW 90 Button */
	fsimg->rot_cw90_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_rotatecw90_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgRotateCW90CB), fsimg
	);
	gtk_widget_show(w);

	/* Rotate CW 180 Button */
	fsimg->rot_cw180_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_rotatecw180_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgRotateCW180CB), fsimg
	);
	gtk_widget_show(w);

	/* Help Button */  
	fsimg->help_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_playerbtn_help_14x14_xpm
	);
	gtk_widget_set_usize(w, btn_width, btn_height);
	gtk_box_pack_end(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FSImgHelpCB), fsimg
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)         
"Ayuda"
#elif defined(PROG_LANGUAGE_FRENCH)
"Aide"
#elif defined(PROG_LANGUAGE_GERMAN) 
"Hilfe"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aiuto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Hulp"
#elif defined(PROG_LANGUAGE_PORTUGUESE)   
"Ajuda"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Hjelp"
#else
"Help"
#endif
	);
	gtk_widget_show(w);


	return(fsimg);
}

/*
 *	Marks the FSImg as busy or ready.
 */
static void FSImgSetBusy(FSImgData *fsimg, gboolean busy)
{
	GdkCursor *cursor;
	GtkWidget *w;
	edv_core_struct *core = (fsimg != NULL) ? fsimg->core : NULL;
	if(core == NULL)
	    return;

	w = fsimg->toplevel;
	if(w != NULL)
	{
	    if(busy)
	    {
		/* Increase busy count */
		fsimg->busy_count++;

		/* If already busy then don't change anything */
		if(fsimg->busy_count > 1)
		    return;

		cursor = EDVGetCursor(core, EDV_CURSOR_CODE_BUSY);
	    }
	    else
	    {
		/* Reduce busy count */
		fsimg->busy_count--;
		if(fsimg->busy_count < 0)
		    fsimg->busy_count = 0;

		/* If still busy do not change anything */
		if(fsimg->busy_count > 0)
		    return;

		if(fsimg->hide_pointer)
		    cursor = EDVGetCursor(core, EDV_CURSOR_CODE_INVISIBLE);
		else
		    cursor = NULL;
	    }

	    /* Update toplevel window's cursor */
	    if(w->window != NULL)
	    {
		gdk_window_set_cursor(w->window, cursor);
		gdk_flush();
	    }
	}
}

/*
 *	Pops all main loop levels, switches to the original video mode,
 *	and deletes the FSImg.
 */
static void FSImgDeleteRestore(FSImgData *fsimg)
{
	if(fsimg == NULL)
	    return;

	/* Pop all main loop levels */
	while(fsimg->main_loop_level > 0)
	{
	    gtk_main_quit();
	    fsimg->main_loop_level--;
	}

	/* Ungrabs the pointer, unmap the fsimg window and restore the
	 * original video mode
	 */
	FSImgRestore(fsimg);

	/* Delete the fsimg */
	FSImgDelete(fsimg);
}

/*
 *	Deletes the FSImg.
 */
static void FSImgDelete(FSImgData *fsimg)
{
	GdkColormap *colormap;

	if(fsimg == NULL)
	    return;

	colormap = fsimg->colormap;

	GTK_TIMEOUT_REMOVE(fsimg->hide_pointer_toid);
	fsimg->hide_pointer_toid = 0;

	GTK_TIMEOUT_REMOVE(fsimg->slideshow_toid);
	fsimg->slideshow_toid = 0;

	g_free(fsimg->mesg);
	fsimg->mesg = NULL;

	g_free(fsimg->info_text1);
	fsimg->info_text1 = NULL;
	g_free(fsimg->info_text2);
	fsimg->info_text2 = NULL;
	g_free(fsimg->info_text3);
	fsimg->info_text3 = NULL;

	GDK_BITMAP_UNREF(fsimg->icon_play_mask);
	fsimg->icon_play_mask = NULL;
	GDK_BITMAP_UNREF(fsimg->icon_pause_mask);
	fsimg->icon_pause_mask = NULL;

	GTK_WIDGET_DESTROY(fsimg->clist);
	GTK_WIDGET_DESTROY(fsimg->clist_toplevel);
	GTK_WIDGET_DESTROY(fsimg->close_btn);
	GTK_WIDGET_DESTROY(fsimg->list_btn);
	GTK_WIDGET_DESTROY(fsimg->slideshow_btn);
	GTK_WIDGET_DESTROY(fsimg->first_btn);
	GTK_WIDGET_DESTROY(fsimg->prev_btn);
	GTK_WIDGET_DESTROY(fsimg->next_btn);
	GTK_WIDGET_DESTROY(fsimg->last_btn);
	GTK_WIDGET_DESTROY(fsimg->info_btn);
	GTK_WIDGET_DESTROY(fsimg->rot_cw90_btn);
	GTK_WIDGET_DESTROY(fsimg->rot_ccw90_btn);
	GTK_WIDGET_DESTROY(fsimg->rot_cw180_btn);
	GTK_WIDGET_DESTROY(fsimg->help_btn);
	GTK_WIDGET_DESTROY(fsimg->next_btns_toplevel);
	GTK_WIDGET_DESTROY(fsimg->rot_btns_toplevel);
	GTK_WIDGET_DESTROY(fsimg->navigator_toplevel);
	GTK_WIDGET_DESTROY(fsimg->toplevel);
	GDK_GC_UNREF(fsimg->gc);
	GDK_PIXMAP_UNREF(fsimg->pixmap);
	GDK_FONT_UNREF(fsimg->font);
	GDK_COLORMAP_FREE_COLOR(colormap, &fsimg->color_fg);
	GDK_COLORMAP_FREE_COLOR(colormap, &fsimg->color_bg);
	GDK_COLORMAP_FREE_COLOR(colormap, &fsimg->color_shadow);
	GDK_COLORMAP_UNREF(colormap);
	if(fsimg->visual != NULL)
	    gdk_visual_unref(fsimg->visual);
	gdk_video_modes_delete(fsimg->vidmodes_list);
	g_free(fsimg);
}


/*
 *	Enters presentation mode from the specified Image Browser.
 *
 *	The Image Browser's first or selected image will be displayed,
 *	video modes will be switched as needed, and the pointer will be
 *	grabbed.
 *
 *	A main loop level will be pushed and this function will block
 *	until the user exits presentation mode.
 */
void EDVPresentationModeEnterFromImbr(
	edv_core_struct *core,
	edv_imbr_struct *imbr  
)
{
	gboolean image_opened = FALSE;
	gint i, thumb_num;
	GList *glist;
	GtkWidget *toplevel;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	tlist_struct *tlist;
	edv_object_struct *obj, *obj2;
	FSImgData *fsimg;

	if((core == NULL) || (imbr == NULL))
	    return;

	toplevel = imbr->toplevel;
	tlist = imbr->tlist;
	cfg_list = core->cfg_list;
	if(tlist == NULL)
	    return;

	/* If video modes are not supported then presentation mode will
	 * not work
	 */
	if(!gdk_video_mode_is_supported())
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageWarning(
#if defined(PROG_LANGUAGE_SPANISH)
"La Conmutacio'n Video Del Modo No Sostuvo",
"Incapaz de entrar el modo de la presentacio'n, la\n\
conmutacio'n video del modo no se sostiene.\n\
\n\
Su GUI necesita sostener la conmutacio'n video del modo\n\
y usted necesita compilar este programa con apoyo video\n\
de modo.",
#elif defined(PROG_LANGUAGE_FRENCH)
"La Commutation du mode Vidéo n'est pas supportée",
"Impossible de passer en mode présentation, la\n\
commutation de mode vidéo n'est pas supportée.\n\
\n\
Votre GUI doit supporter la commutation du mode\n\
vidéo et vous devez compiler ce programme avec\n\
le support de mode vidéo.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Videomodusschaltung Ist Nicht Unterstu'tzt",
"Unfa'hig, Vorstellungsmodus einzutragen, ist\n\
videomodusschaltung nicht unterstu'tzt.\n\
\n\
Ihr GUI muss Videomodusschaltung und Sie mu'ssen\n\
kompilieren dieses programm mit videomodusstu'tze\n\
unterstu'tzen.",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Commutare Di Modo Video Non E` Sostenuto",
"Incapace per entrare il modo di presentazione,\n\
commutare di modo video non e` sostenuto.\n\
\n\
Il suo GUI ha bisogno di sostenere commutare di modo video\n\
e lei ha bisogno di compilare questo programma col sostegno\n\
di modo video.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Videomodus Schakelen Is Niet Gesteund",
"Onbekwaam om voorstelling modus binnengaan, is videomodus\n\
schakelen niet gesteund.\n\
\n\
Uw GUI moet steunen videomodus schakelen en u moet dit\n\
programma met videomodus steun verzamelen.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Modo De Vi'deo Na~o Troca E' Apoiado",
"Incapaz de entrar modo de apresentac,a~o, modo de vi'deo\n\
na~o troca e' apoiado.\n\
\n\
Seu GUI necessita apoiar modo de vi'deo trocar e necessita\n\
compilar este programa com apoio de modo de vi'deo.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"VideomodusOmkopling St?tter Ikke",
"Maktesls ga inn i framstillingsmodus,\n\
videomodusomkopling st'tter ikke.\n\
\n\
Deres GUI st'tter videomodusomkopling og de kompilerer dette\n\
programet med videomodusst'tte.",
#else
"Video Mode Switching Not Supported",
"Unable to enter presentation mode, video mode switching\n\
is not supported.\n\
\n\
Your GUI needs to support video mode switching and you need\n\
to compile this program with video mode support.",
#endif
		NULL, toplevel
	    );
	    return;
	}


	/* Create FSImg */
	fsimg = FSImgNew(core);
	if(fsimg == NULL)
	    return;

	clist = GTK_CLIST(fsimg->clist);
	if(clist == NULL)
	{
	    FSImgDelete(fsimg);
	    return;
	}

	/* Get list of images from the specified location */
	FSImgListGet(fsimg, EDVImbrCurrentLocation(imbr));

	/* Get last selected thumb or first thumb if none are selected */
	glist = tlist->selection_end;

	/* Start with the selected or first thumb and iterate until an
	 * image is opened or there are no more thumbs
	 */
	for(
	    thumb_num = (glist != NULL) ? (gint)glist->data : 0;
	    thumb_num < tlist->total_thumbs;
	    thumb_num++
	)
	{
	    obj = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));
	    if(obj == NULL)
		continue;

	    if(!EDVCheckImlibImage(core, obj->name))
		continue;

	    /* Open the image and switch video mode as needed */
	    FSImgOpen(fsimg, obj->full_path);
	    image_opened = TRUE;
	    for(i = 0; i < clist->rows; i++)
	    {
		obj2 = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
		if((obj2 != NULL) ? STRISEMPTY(obj2->full_path) : TRUE)
		    continue;

		if(!strcmp(obj->full_path, obj2->full_path))
		{
		    gtk_clist_unselect_all(clist);
		    gtk_clist_select_row(clist, i, 0);
		    break;
		}
	    }

	    /* Update information as needed */
	    FSImgInfoShow(fsimg, FSImgInfoShown(fsimg));

	    break;
	}

	/* If no image was opened then clear the fsimg */
	if(!image_opened)
	    FSImgClear(fsimg, -1, -1);


	/* Begin startup settings */

	/* Show Navigator? */
	FSImgNavigatorShow(
	    fsimg,
	    EDV_GET_B(EDV_CFG_PARM_PRESENTATION_SHOW_NAVIGATOR)
	);

	/* Hide pointer? */
	FSImgShowPointer(
	    fsimg,
	    (EDV_GET_I(EDV_CFG_PARM_PRESENTATION_HIDE_POINTER_DELAY) > 0) ?
		FALSE : TRUE
	);

	/* Slideshow? */
	if(EDV_GET_B(EDV_CFG_PARM_PRESENTATION_SLIDESHOW) &&
	   (clist->rows >= 2)
	)
	{
	    GTK_TIMEOUT_REMOVE(fsimg->slideshow_toid);
	    fsimg->slideshow_toid = gtk_timeout_add(
		MAX(fsimg->slideshow_delay, 1000l),
		FSImgSlideShowTOCB, fsimg
	    );
	}

	FSImgNavigatorUpdate(fsimg);


	/* Push main loop level and block */
	fsimg->main_loop_level++;
	gtk_main();

	/* The FSImg should have been deleted after leaving the main
	 * loop
	 */
	fsimg = NULL;
}


/*
 *	Enters presentation mode.
 *
 *	The first image at the specified location will be displayed, or
 *	if the specified location is an image then that image will be
 *	displayed.
 *
 *	Video modes will be switched as needed and the pointer will be
 *	grabbed.
 *
 *	A main loop level will be pushed and this function will block
 *	until the user exits presentation mode.
 */
void EDVPresentationModeEnter(edv_core_struct *core)
{





}
