#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#if defined(_WIN32)
# include <windows.h>
#else
# include <unistd.h>
# include <fnmatch.h>
# if defined(__SOLARIS__)
#  include <sys/mnttab.h>
#  include <sys/vfstab.h>
# elif defined(__FreeBSD__)
/* #  include <mntent.h> */
# else
#  include <mntent.h>
# endif
#endif
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

#include "guiutils.h"
#include "cdialog.h"
#include "pulist.h"
#include "fprompt.h"
#include "fb.h"
#include "config.h"


#include "images/icon_file_20x20.xpm"
#include "images/icon_file_hidden_20x20.xpm"
#include "images/icon_folder_closed_20x20.xpm"
#include "images/icon_folder_opened_20x20.xpm"
#include "images/icon_folder_parent_20x20.xpm"
#include "images/icon_folder_noaccess_20x20.xpm"
#include "images/icon_folder_home_20x20.xpm"
#include "images/icon_folder_hidden_20x20.xpm"
#include "images/icon_link2_20x20.xpm"
#include "images/icon_pipe_20x20.xpm"
#include "images/icon_socket_20x20.xpm"
#include "images/icon_device_block_20x20.xpm"
#include "images/icon_device_character_20x20.xpm"
#include "images/icon_executable_20x20.xpm"
#include "images/icon_drive_fixed_20x20.xpm"
#include "images/icon_drive_root_20x20.xpm"

#include "images/icon_fb_list_standard_20x20.xpm"
#include "images/icon_fb_list_vertical_20x20.xpm"
#include "images/icon_fb_list_vertical_details_20x20.xpm"
#include "images/icon_rename_20x20.xpm"
#include "images/icon_chmod_20x20.xpm"
#include "images/icon_reload_20x20.xpm"
#include "images/icon_select_20x20.xpm"
#include "images/icon_ok_20x20.xpm"
#include "images/icon_cancel_20x20.xpm"

#include "images/icon_trash_32x32.xpm"


typedef struct _FileBrowserIcon		FileBrowserIcon;
#define FILE_BROWSER_ICON(p)		((FileBrowserIcon *)(p))
typedef struct _FileBrowserObject	FileBrowserObject;
#define FILE_BROWSER_OBJECT(p)		((FileBrowserObject *)(p))
typedef struct _FileBrowserColumn	FileBrowserColumn;
#define FILE_BROWSER_COLUMN(p)		((FileBrowserColumn *)(p))
typedef struct _FileBrowser		FileBrowser;
#define FILE_BROWSER(p)			((FileBrowser *)(p))


/*
 *	List Formats:
 */
typedef enum {
	FB_LIST_FORMAT_STANDARD,		/* Horizontal */
	FB_LIST_FORMAT_VERTICAL,		/* Vertical */
	FB_LIST_FORMAT_VERTICAL_DETAILS		/* Vertical with details */
} FileBrowserListFormat;


/*
 *	Icon Index Values:
 *
 *	The values represent the index of each icon defined in
 *	FB_ICON_DATA_LIST.
 */
typedef enum {
	FB_ICON_FILE,				/* 0 */
	FB_ICON_FILE_HIDDEN,
	FB_ICON_FOLDER_CLOSED,
	FB_ICON_FOLDER_OPENED,
	FB_ICON_FOLDER_PARENT,
	FB_ICON_FOLDER_NOACCESS,
	FB_ICON_FOLDER_HOME,
	FB_ICON_FOLDER_HIDDEN,
	FB_ICON_LINK,
	FB_ICON_PIPE,
	FB_ICON_SOCKET,				/* 10 */
	FB_ICON_DEVICE_BLOCK,
	FB_ICON_DEVICE_CHARACTER,
	FB_ICON_EXECUTABLE,
	FB_ICON_DRIVE_FIXED,
	FB_ICON_DRIVE_ROOT			/* 15 */
} FileBrowserIconNum;


/* Utilities */
static GList *FileBrowserDNDBufParse(
	const gchar *buf, const gint len
);
static gchar *FileBrowserDNDBufFormat(
	FileBrowserObject **object, const gint total, GList *selection,
	gint *len
);
static gboolean FileBrowserObjectNameFilter(
	const gchar *name, const gchar *full_path,
	const gchar *ext
);


/* Get Drive Paths */
static GList *FileBrowserGetDrivePaths(void);


/* Busy/Ready Setting */
static void FileBrowserSetBusy(FileBrowser *fb, const gboolean busy);


/* Show Hidden Objects Setting */
static void FileBrowserSetShowHiddenObjects(
	FileBrowser *fb, const gboolean show
);


/* List Format Setting */
static void FileBrowserSetListFormat(
	FileBrowser *fb, const FileBrowserListFormat list_format
);


/* Location */
static void FileBrowserSetLocation(FileBrowser *fb, const gchar *path);
static const gchar *FileBrowserGetLocation(FileBrowser *fb);


/* Locations Popup List */
static void FileBrowserLocationsPUListUpdate(FileBrowser *fb);


/* Icons */
static FileBrowserIcon *FileBrowserGetIcon(FileBrowser *fb, const gint i);
static FileBrowserIcon *FileBrowserIconAppend(
	FileBrowser *fb, guint8 **data, const gchar *desc
);
static gint FileBrowserMatchIconNumFromPath(
	FileBrowser *fb, const gchar *full_path, const struct stat *lstat_buf
);


/* Objects */
static FileBrowserObject *FileBrowserGetObject(FileBrowser *fb, gint i);
static void FileBrowserObjectUpdateValues(
	FileBrowser *fb, FileBrowserObject *o
);
static FileBrowserObject *FileBrowserObjectAppend(
	FileBrowser *fb, const gchar *name, const gchar *full_path,
	struct stat *lstat_buf
);


/* List */
static void FileBrowserListUpdate(FileBrowser *fb, const gchar *filter);
static void FileBrowserListUpdatePositions(FileBrowser *fb);
static void FileBrowserListObjectSetDNDIcon(FileBrowser *fb, const gint i);
static GtkVisibility FileBrowserListObjectVisibility(FileBrowser *fb, const gint i);
static void FileBrowserListMoveToObject(
	FileBrowser *fb, const gint i, const gfloat coeff
);
static gint FileBrowserListSelectCoordinates(
	FileBrowser *fb, const gint x, const gint y
);
static void FileBrowserListHeaderDraw(FileBrowser *fb);
static void FileBrowserListHeaderQueueDraw(FileBrowser *fb);
static void FileBrowserListDraw(FileBrowser *fb);
static void FileBrowserListQueueDraw(FileBrowser *fb);


/* List Columns */
static FileBrowserColumn *FileBrowserListGetColumn(FileBrowser *fb, gint i);
static FileBrowserColumn *FileBrowserListColumnAppend(
	FileBrowser *fb, const gchar *label, const gint width
);
static void FileBrowserListColumnsClear(FileBrowser *fb);

static void FileBrowserEntrySetSelectedObjects(FileBrowser *fb);


/* Types List */
static void FileBrowserTypesListSetTypes(
	FileBrowser *fb,
	fb_type_struct **list, const gint total
);


/* Callbacks */
static void FileBrowserLocationsPUListItemDestroyCB(gpointer data);
static void FileBrowserTypesPUListItemDestroyCB(gpointer data);
static void FileBrowserIconDestroyCB(gpointer data);
static void FileBrowserObjectDestroyCB(gpointer data);
static void FileBrowserColumnDestroyCB(gpointer data);

static gboolean FileBrowserDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
	gpointer data
);
static void FileBrowserDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
static void FileBrowserDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
static void FileBrowserDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
);

static void FileBrowserShowHiddenObjectsToggleCB(
	GtkWidget *widget, gpointer data
);
static void FileBrowserListFormatToggleCB(
	GtkWidget *widget, gpointer data
);
static void FileBrowserGoToParentCB(GtkWidget *widget, gpointer data);
static void FileBrowserNewDirectoryCB(GtkWidget *widget, gpointer data);
static void FileBrowserRefreshCB(GtkWidget *widget, gpointer data);
static void FileBrowserOKCB(GtkWidget *widget, gpointer data);
static void FileBrowserCancelCB(GtkWidget *widget, gpointer data);

static void FileBrowserLocPUListChangedCB(
	pulistbox_struct *pulistbox, gint i, gpointer data
);

static gint FileBrowserListHeaderEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint FileBrowserListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void FileBrowserListScrollCB(
	GtkAdjustment *adj, gpointer data
);

static void FileBrowserSelectAllCB(GtkWidget *widget, gpointer data);
static void FileBrowserUnselectAllCB(GtkWidget *widget, gpointer data);
static void FileBrowserInvertSelectionCB(GtkWidget *widget, gpointer data);

static void FileBrowserRenameFPromptCB(
	gpointer data, const gchar *value
);
static void FileBrowserRenameCB(GtkWidget *widget, gpointer data);

static void FileBrowserCHModFPromptCB(
	gpointer data, const gchar *value
);
static void FileBrowserCHModCB(GtkWidget *widget, gpointer data);

static void FileBrowserDeleteCB(GtkWidget *widget, gpointer data);

static gint FileBrowserEntryCompletePathKeyCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
);
static void FileBrowserEntryEnterCB(GtkWidget *widget, gpointer data);

static void FileBrowserTypesPUListChangedCB(
	pulistbox_struct *pulistbox, gint i, gpointer data
);


/* File Browser Front Ends */
gint FileBrowserInit(void);
void FileBrowserSetStyle(GtkRcStyle *rc_style);
void FileBrowserSetTransientFor(GtkWidget *w);
gboolean FileBrowserIsQuery(void);
void FileBrowserBreakQuery(void);
GtkWidget *FileBrowserGetToplevel(void);
void FileBrowserReset(void);
gboolean FileBrowserGetResponse(
	const gchar *title,
	const gchar *ok_label, const gchar *cancel_label,
	const gchar *path,
	fb_type_struct **type, gint total_types,
	gchar ***path_rtn, gint *path_total_rtns,
	fb_type_struct **type_rtn
);
void FileBrowserMap(void);
void FileBrowserUnmap(void);
void FileBrowserShutdown(void);

const gchar *FileBrowserGetLastLocation(void);
void FileBrowserShowHiddenObjects(const gboolean show);
void FileBrowserListStandard(void);
void FileBrowserListDetailed(void);


/* File Types List */
gint FileBrowserTypeListNew(
	fb_type_struct ***list, gint *total,
	const gchar *ext,	/* Space separated list of extensions */
	const gchar *name	/* Descriptive name */
);
static void FileBrowserTypeDelete(fb_type_struct *t);
void FileBrowserDeleteTypeList(
	fb_type_struct **list, const gint total
);


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

#ifndef ISBLANK
# define ISBLANK(c)	(((c) == ' ') || ((c) == '\t'))
#endif

#define OBJISSEL(fb,n)	((((fb) != NULL) && ((n) >= 0)) ?	\
 ((g_list_find(fb->selection, (gpointer)(n)) != NULL) ? TRUE : FALSE) : FALSE)


#define FB_WIDTH		480
#define FB_HEIGHT		300

#define FB_TB_BTN_WIDTH		(20 + 5)
#define FB_TB_BTN_HEIGHT	(20 + 5)

#define FB_LOC_LIST_MAX_CHARS	25	/* Max chars for each item in the
					 * the locations list */

#define FB_LIST_ITEM_MAX_CHARS	25	/* Max chars for each item in the
					 * list */

#define FB_LIST_HEADER_WIDTH	-1
#define FB_LIST_HEADER_HEIGHT	(20 + 2)

#define FB_LIST_WIDTH		-1
#define FB_LIST_HEIGHT		-1

#define FB_LIST_ICON_WIDTH	20
#define FB_LIST_ICON_HEIGHT	20
#define FB_LIST_ICON_BORDER	2	/* Border between icon and text */

#define FB_DEFAULT_TYPE_STR	"All Files (*.*)"

#define FB_NEW_DIRECTORY_NAME	"new_directory"


/*
 *	Icons Data List:
 *
 *	The order of this list must correspond with FileBrowserIconNum.
 *
 *	Each set contains 6 gpointers, and the gpointers in the last
 *	set are all NULL.
 */
#define FB_ICON_DATA_LIST	{	\
	"file",				\
	icon_file_20x20_xpm,		\
	NULL, NULL, NULL, NULL,		\
					\
	"file/hidden",			\
	icon_file_hidden_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/closed",		\
	icon_folder_closed_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/opened",		\
	icon_folder_opened_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/parent",		\
	icon_folder_parent_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/noaccess",		\
	icon_folder_noaccess_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/home",		\
	icon_folder_home_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"directory/hidden",		\
	icon_folder_hidden_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"link",				\
	icon_link2_20x20_xpm,		\
	NULL, NULL, NULL, NULL,		\
					\
	"pipe",				\
	icon_pipe_20x20_xpm,		\
	NULL, NULL, NULL, NULL,		\
					\
	"socket",			\
	icon_socket_20x20_xpm,		\
	NULL, NULL, NULL, NULL,		\
					\
	"device/block",			\
	icon_device_block_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"device/character",		\
	icon_device_character_20x20_xpm,\
	NULL, NULL, NULL, NULL,		\
					\
	"executable",			\
	icon_executable_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"drive/fixed",			\
	icon_drive_fixed_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	"drive/root",			\
	icon_drive_root_20x20_xpm,	\
	NULL, NULL, NULL, NULL,		\
					\
	NULL, NULL,			\
	NULL, NULL, NULL, NULL		\
}


/*
 *	Icon:
 */
struct _FileBrowserIcon {

	GdkPixmap	*pixmap;
	GdkBitmap	*mask;
	gint		width, height;
	gchar		*desc;		/* Used for MIME type */

};


/*
 *	Object:
 */
struct _FileBrowserObject {

	gchar		*name,
			*full_path,
			*displayed_name;	/* Name shown on the list
						 * (may be abbriviated) */
	gint		icon_num;
	gint		x, y,			/* Position and size on list */
			width, height;

	struct stat	lstat_buf;

};


/*
 *	Column:
 *
 *	Used in the FileBrowser's list when displaying things that
 *	need columns.
 */
struct _FileBrowserColumn {

	GtkWidgetFlags	flags;
	gint		position;
	gchar		*label;
	GtkJustification	label_justify;

	/* If the member drag is TRUE then it means this column is
	 * being dragged (resized) and the member drag_position
	 * indicates the right or lower edge of this column
	 */
	gboolean	drag;
	gint		drag_position;
	gint		drag_last_drawn_position;

};


/*
 *	File Browser:
 */
struct _FileBrowser {

	GtkWidget	*toplevel;
	gint		busy_count,
			freeze_count;
	GtkAccelGroup	*accelgrp;

	GdkCursor	*cur_busy,
			*cur_column_hresize,
			*cur_translate;

	GtkWidget	*main_vbox,
			*goto_parent_btn,
			*new_directory_btn,
			*rename_btn,
			*refresh_btn,
			*show_hidden_objects_tb,
			*list_format_standard_tb,
			*list_format_vertical_tb,
			*list_format_vertical_details_tb,
			*list_header_da,	/* List's header GtkDrawingArea */
			*list_da,	/* List's GtkDrawingArea */
			*list_vsb,	/* List's vertical GtkScrollBar */
			*list_hsb,	/* List's horizontal GtkScrollBar */
 			*list_menu,	/* List's right-click GtkMenu */
			*entry,		/* Location GtkEntry */
			*ok_btn_label,
			*ok_btn,
			*cancel_btn_label,
			*cancel_btn;

	gboolean	vsb_map_state,
			hsb_map_state;

	GdkPixmap	*list_pm;

	pulistbox_struct	*loc_pulistbox,
				*types_pulistbox;

	gchar		*cur_location;

	gint		block_loop_level;
	gboolean	user_response;	/* TRUE if user clicked on OK */

	FileBrowserListFormat	list_format;

	FileBrowserColumn	**column;
	gint			total_columns;

	fb_type_struct	cur_type;	/* Current file type */
	gchar 		**selected_path;
	gint		total_selected_paths;

	FileBrowserIcon	**icon;
	gint		total_icons;

	gint		uid, euid;
	gint		gid, egid;
	gchar		*home_path;
	GList		*device_paths_list;	/* List of gchar * device paths */

	gint		focus_object;
	GList		*selection,
			*selection_end;
	FileBrowserObject	**object;
	gint		total_objects;
	gint		objects_per_row;	/* For use with
						 * FB_LIST_FORMAT_STANDARD */
	gint		last_single_select_object;
	gboolean	show_hidden_objects;

	/* Last button pressed and pointer position */
	gint		button,
			drag_last_x,
			drag_last_y;
	gulong		last_button1_release_time;	/* In ms */

};

static FileBrowser file_browser;


/*
 *	Parses the DND buffer into a list of paths.
 *
 *	Returns a GList of gchar * paths or NULL on error. The calling
 *	function must delete the list and each path.
 */
static GList *FileBrowserDNDBufParse(
	const gchar *buf, const gint len
)
{
	const gchar	*buf_ptr = buf,
			*buf_end = buf_ptr + len;
	GList *paths_list;

	if((buf_ptr == NULL) || (len <= 0))
	    return(NULL);

	/* Iterate through the buffer */
	paths_list = NULL;
	while(buf_ptr < buf_end)
	{
	    const gchar *s = buf_ptr;
	    gint s_len = STRLEN(s);

	    /* Is the protocol prefix of the string one that we want? */
	    if(g_strcasepfx(s, "file://"))
	    {
		/* Seek past protocol "file://" */
		s += STRLEN("file://");

		/* Seek past "username:password@host:port"
		 * too many programs fail to do this!
		 */
		s = (s != NULL) ?
		    (gchar *)strchr((char *)s, '/') : NULL;

		/* Able to seek to full path portion of the string? */
		if(s != NULL)
		{
		    /* Add this path to the paths list */
		    paths_list = g_list_append(
			paths_list,
			STRDUP(s)
		    );
		}
	    }

	    /* Seek to the next segment, past any '\0'
	     * deliminator(s)
	     */
	    buf_ptr += s_len;
	    while((buf_ptr < buf_end) ? (*buf_ptr == '\0') : FALSE)
		buf_ptr++;
	}

	return(paths_list);
}

/*
 *	Generates a DND buffer based on the list of objects that are
 *	selected based on the given selection.
 */
static gchar *FileBrowserDNDBufFormat(
	FileBrowserObject **object, const gint total, GList *selection,
	gint *len
)
{
	gchar *buf = NULL;
	gint i, buf_len = 0;
	GList *glist;
	FileBrowserObject *o;


	/* Iterate through the selection list */
	for(glist = selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    i = (gint)glist->data;
	    if((i >= 0) && (i < total))
		o = object[i];
	    else
		o = NULL;
	    if((o != NULL) ? (o->full_path != NULL) : FALSE)
	    {
		gchar *url = g_strdup_printf(
		    "file://%s",
		    o->full_path
		);
		gint url_len = STRLEN(url);
		gint last_buf_len = buf_len;

		/* Increase allocation of buffer for this url string
		 * which we will append to the buffer (include the
		 * '\0' character counted in buf_len)
		 */
		buf_len += url_len + 1;
		buf = (gchar *)g_realloc(buf, buf_len * sizeof(gchar));
		if(buf == NULL)
		{
		    buf_len = 0;
		    break;
		}
		memcpy(
		    buf + last_buf_len,
		    (url != NULL) ? url : "",
		    url_len + 1		/* Include the '\0' character */
		);

		g_free(url);
	    }
	}

	/* Update return value */
	if(len != NULL)
	    *len = buf_len;

	return(buf);
}


/*
 *	Checks if the object name specified by name and full_path
 *	match the extensions given in ext.
 */
static gboolean FileBrowserObjectNameFilter(
	const gchar *name, const gchar *full_path,
	const gchar *ext
)
{
	gboolean use_regex;
	gchar *st, *st_end, cur_ext[80];
	gint cur_ext_len, name_len;

	if((name == NULL) || (full_path == NULL) || (ext == NULL))
	    return(FALSE);

	name_len = STRLEN(name);

	while(ISBLANK(*ext) || (*ext == ','))
	    ext++;

	/* Iterate through each extension in ext */
	while(*ext != '\0')
	{
	    use_regex = FALSE;

	    /* Copy current extension string in ext to cur_ext and
	     * seek ext to next position
	     */
	    cur_ext_len = 0;
	    st = cur_ext;
	    st_end = cur_ext + 79;	/* Set end 1 character premature */
	    while((st < st_end) && !ISBLANK(*ext) && (*ext != ',') &&
		  (*ext != '\0')
	    )
	    {
		/* Use this opportunity to check if there are characters
		 * in the extension string to warrent the use of regex
		 */
		if((*ext == '*') || (*ext == '?'))
		    use_regex = TRUE;

		*st++ = *ext++;
		cur_ext_len++;
	    }
	    *st = '\0';

	    /* Seek ext to next extension string */
	    while(ISBLANK(*ext) || (*ext == ','))
		ext++;

	    /* Check if there is a match */
#if defined(_WIN32)
	    if(name_len >= cur_ext_len)
	    {
		if(!g_strcasecmp(
		    name + name_len - cur_ext_len,
		    cur_ext
		))
		return(TRUE);
	    }
#else
	    if(use_regex)
	    {
		/* Use regex to match */
		if(!fnmatch(cur_ext, name, 0))
		    return(TRUE);
	    }
	    else
	    {
		/* Check if cur_ext is a postfix of name */
		if(name_len >= cur_ext_len)
		{
		    if(!g_strcasecmp(
			name + name_len - cur_ext_len,
			cur_ext
		    ))
			return(TRUE);
		}
	    }
#endif
	}
	return(FALSE);
}


/*
 *	Returns a list of strings describing the drive paths.
 */
static GList *FileBrowserGetDrivePaths(void)
{
#if defined(_WIN32)
	gchar	drive_letter = 'a',
		drive_name[10];
	GList *paths_list = NULL;

	for(drive_letter = 'a'; drive_letter <= 'g'; drive_letter++)
	{
	    g_snprintf(
		drive_name, sizeof(drive_name),
		"%c:\\",
		toupper(drive_letter)
	    );
	    paths_list = g_list_append(
		paths_list,
		STRDUP(drive_name)
	    );
	}

	return(paths_list);
#elif defined(__FreeBSD__)
	return(NULL);
#else	/* UNIX */
	GList *paths_list = NULL;
#ifdef __SOLARIS__
	struct vfstab *vfs_ptr = NULL;
	int mtback;
#else
	struct mntent *mt_ptr;
#endif

	/* Open system devices list file */
#ifdef __SOLARIS__
	FILE *fp = fopen("/etc/vfstab", "rb");
#else
	FILE *fp = setmntent("/etc/fstab", "rb");
#endif
	if(fp == NULL)
	    return(NULL);

	/* Begin reading system devices list file */
#ifdef __SOLARIS__
	vfs_ptr = (struct vfstab *)g_malloc(sizeof(struct vfstab));
	mtback = getvfsent(fp, vfs_ptr);
	while(mtback != 0)
#else
	mt_ptr = getmntent(fp);
	while(mt_ptr != NULL)
#endif
	{
	    /* Get mount path as the drive path */
#ifdef __SOLARIS__
	    paths_list = g_list_append(
		paths_list,
		STRDUP(vfs_ptr->vfs_mountp)
	    );
#else
	    paths_list = g_list_append(
		paths_list,
		STRDUP(mt_ptr->mnt_dir)
	    );
#endif

	    /* Read next mount entry */
#ifdef __SOLARIS__
	    mtback = getmntent(fp, vfs_ptr);
#else
	    mt_ptr = getmntent(fp);
#endif
	}


	/* Close the system devices list file */
#ifdef __SOLARIS__
	fclose(fp);
	fp = NULL;
	vfs_ptr = NULL;
#else
	endmntent(fp);
	fp = NULL;
#endif

	return(paths_list);
#endif
}


/*
 *	Sets the file browser busy or ready.
 */
static void FileBrowserSetBusy(FileBrowser *fb, const gboolean busy)
{
	GdkCursor *cursor;
	GtkWidget *w;

	if(fb == NULL)
	    return;

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

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

		cursor = fb->cur_busy;
	    }
	    else
	    {
		/* Reduce busy count */
		fb->busy_count--;
		if(fb->busy_count < 0)
		    fb->busy_count = 0;

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

		cursor = NULL;	/* Use default cursor */
	    }

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


/*
 *	Sets the showing of hidden objects and updates the list as
 *	needed.
 */
static void FileBrowserSetShowHiddenObjects(
	FileBrowser *fb, const gboolean show
)
{
	if(fb == NULL)
	    return;

	/* Skip if there is no change */
	if(fb->show_hidden_objects == show)
	    return;

	fb->freeze_count++;

	fb->show_hidden_objects = show;
	GTK_TOGGLE_BUTTON_SET_ACTIVE(
	    fb->show_hidden_objects_tb, show
	);

	/* Update the list */
	FileBrowserListUpdate(fb, NULL);

	fb->freeze_count--;
}


/*
 *	Sets the list format and updates the list as needed.
 */
static void FileBrowserSetListFormat(
	FileBrowser *fb, const FileBrowserListFormat list_format
)
{
	GtkWidget *w, *toplevel;
	FileBrowserColumn *column;

	if(fb == NULL)
	    return;

	/* Skip if there is no change */
	if(fb->list_format == list_format)
	    return;

	fb->freeze_count++;

	toplevel = fb->toplevel;

	/* Set new list format */
	fb->list_format = list_format;

	/* Update list format toggle buttons, make sure we freeze so the
	 * toggle callback does not recurse
	 */
	switch(list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL:
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_standard_tb, FALSE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_tb, TRUE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_details_tb, FALSE
	    );
	    break;
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_standard_tb, FALSE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_tb, FALSE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_details_tb, TRUE
	    );
	    break;
	  case FB_LIST_FORMAT_STANDARD:
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_standard_tb, TRUE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_tb, FALSE
	    );
	    GTK_TOGGLE_BUTTON_SET_ACTIVE(
		fb->list_format_vertical_details_tb, FALSE
	    );
	    break;
	}

	/* Begin updating things to recognize the new list format */

	/* Update list columns */
	FileBrowserListColumnsClear(fb);
	switch(list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	    /* Set up list columns */
	    /* Name */
	    column = FileBrowserListColumnAppend(
		fb, "Name", 145
	    );
	    /* Size */
	    column = FileBrowserListColumnAppend(
		fb, "Size", 70
	    );
	    column->label_justify = GTK_JUSTIFY_RIGHT;
	    /* Permissions */
	    column = FileBrowserListColumnAppend(
		fb, "Permissions", 78
	    );
	    /* Last Modified */
	    column = FileBrowserListColumnAppend(
		fb, "Last Modified", 160
	    );

	    /* Show list header */
	    w = fb->list_header_da;
	    if(w != NULL)
		gtk_widget_show(w);
	    break;

	  case FB_LIST_FORMAT_VERTICAL:
	  case FB_LIST_FORMAT_STANDARD:
	    /* Hide list header */
	    w = fb->list_header_da;
	    if(w != NULL)
		gtk_widget_hide(w);
	    break;
	}
	gtk_widget_queue_resize(toplevel);


	/* Update the list */
	FileBrowserListUpdate(fb, NULL);

	fb->freeze_count--;
}

/*
 *	Sets the new location and updates the locations popup list.
 *
 *	The path specifies the full path to the new location. If
 *	path does not lead to a directory then its parent will
 *	be used instead. If the specified location and the current
 *	location are the same then the objects lists will not be
 *	updated.
 */
static void FileBrowserSetLocation(FileBrowser *fb, const gchar *path)
{
	gchar *new_location, *new_name;
	GtkWidget *w;

	if((fb == NULL) || (path == NULL))
	    return;

	if(path == fb->cur_location)
	    return;

	/* Make a copy of the given path which will be modified and
	 * checked before (if) it is finally accepted
	 */
	new_location = STRDUP(path);
	new_name = NULL;


	/* Special notation checks */

	/* Current location? */
	if(!strcmp(new_location, "."))
	{
	    g_free(new_location);
#ifdef _WIN32
	    new_location = (fb->cur_location != NULL) ?
		STRDUP(fb->cur_location) : STRDUP("C:");
#else
	    new_location = (fb->cur_location != NULL) ?
		STRDUP(fb->cur_location) : STRDUP("/");
#endif
	}

	/* Parent? */
	if(!strcmp(new_location, ".."))
	{
	    g_free(new_location);
#ifdef _WIN32
	    new_location = (fb->cur_location != NULL) ?
		g_dirname(fb->cur_location) : STRDUP("C:");
#else
	    new_location = (fb->cur_location != NULL) ?
		g_dirname(fb->cur_location) : STRDUP("/");
#endif
	}

	/* Home directory prefix? */
	if(*new_location == '~')
	{
	    /* Prefix the home directory to the new location path */
	    const gchar *home = fb->home_path;
	    gchar *s = g_strdup_printf(
		"%s%s",
		(home != NULL) ? home : "",
		new_location + 1
	    );
	    g_free(new_location);
	    new_location = s;
	}

	/* At this point we need to check if the new location is an
	 * absolute path, if it is not then the current working dir
	 * must be prefixed to it
	 */
	if(!g_path_is_absolute(new_location))
	{
	    gchar *s, *cwd = STRDUP(g_get_current_dir());
	    s = STRDUP(PrefixPaths(cwd, new_location));
	    g_free(new_location);
	    new_location = s;
	    g_free(cwd);
	}

	/* Make sure that the new location is a directory, if it is
	 * not then use its parent
	 */
	if(!ISPATHDIR(new_location))
	{
	    gchar *parent = g_dirname(new_location);
	    const gchar *name = strrchr(new_location, G_DIR_SEPARATOR);

	    /* Record that we have an object name */
	    g_free(new_name);
	    new_name = (name != NULL) ? STRDUP(name + 1) : NULL;

	    /* Record parent directory */
	    g_free(new_location);
	    new_location = parent;
	}

	/* Strip tailing deliminator */
	if(new_location != NULL)
	{
#ifdef _WIN32
	    gchar *s = strrchr(new_location, G_DIR_SEPARATOR);
	    gint prefix_len = STRLEN("x:\\");
#else
	    gchar *s = strrchr(new_location, G_DIR_SEPARATOR);
	    gint prefix_len = STRLEN("/");
#endif
	    while(s >= (new_location + prefix_len))
	    {
		if((s[0] == G_DIR_SEPARATOR) &&
		   (s[1] == '\0')
		)
		    *s = '\0';
		else
		    break;
		s--;
	    }
	}

	/* No change in the location? */
	if((fb->cur_location != NULL) ?
	    !strcmp(fb->cur_location, new_location) : FALSE
	)
	{
	    /* No change in location, but check if we still need to
	     * update the lists if the lists were reset/cleared
	     */
	    if(fb->total_objects > 0)
	    {
		/* Lists were not cleared, so no need to update */
		g_free(new_location);
		g_free(new_name);
		return;
	    }
	}

	/* Accept and set the new location */
	g_free(fb->cur_location);
	fb->cur_location = new_location;
	new_location = NULL;

	/* Update file name entry */
	w = fb->entry;
	if(w != NULL)
	{
	    gtk_entry_set_text(
		GTK_ENTRY(w),
		(!STRISEMPTY(new_name)) ? new_name : ""
	    );
	    gtk_entry_set_position(GTK_ENTRY(w), -1);
	}

	/* Get the listing of the objects at the new location */
	FileBrowserListUpdate(fb, NULL);

	/* Update the locations popup list */
	FileBrowserLocationsPUListUpdate(fb);

	g_free(new_location);
	g_free(new_name);
}

/*
 *	Returns the current location, never returns NULL.
 */
static const gchar *FileBrowserGetLocation(FileBrowser *fb)
{
#ifdef _WIN32
	return((fb != NULL) ? fb->cur_location : "C:\\");
#else
	return((fb != NULL) ? fb->cur_location : "/");
#endif
}


/*
 *	DND "drag_motion" signal callback.
 */
static gboolean FileBrowserDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
	gpointer data
)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if((dc == NULL) || (fb == NULL))
	    return(FALSE);

	if(dc->actions & GDK_ACTION_COPY)
	    gdk_drag_status(dc, GDK_ACTION_COPY, t);
	else
	    gdk_drag_status(dc, 0, t);

	return(TRUE);
}

/*
 *	DND "drag_data_received" signal callback.
 */
static void FileBrowserDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if((widget == NULL) || (dc == NULL) || (fb == NULL))
	    return;

	if(selection_data == NULL)
	    return;

	/* Make sure the DND info is one that we want */
	if((info == 0) || (info == 1) || (info == 2))
	{
	    /* Handle by destination widget */
	    /* List */
	    if(widget == fb->list_da)
	    {
		/* When dropping to the list, first check if there is
		 * exactly one object and if so then check if it leads
		 * to a directory, in which case we will set the new
		 * location.  Otherwise we just set it to the file
		 * name entry.
		 */
		GList *paths_list = FileBrowserDNDBufParse(
		    (const gchar *)selection_data->data,
		    selection_data->length
		);
		const gint npaths = g_list_length(paths_list);
		const gchar *new_location = (npaths == 1) ?
		    (gchar *)paths_list->data : NULL;

		if((new_location != NULL) ? ISPATHDIR(new_location) : FALSE)
		{
		    /* Exactly one object dropped and it leads to a
		     * directory so go to the new location
		     */
		    FileBrowserSetLocation(fb, new_location);
		}
		else
		{
		    /* Not one object or the one object does not lead to
		     * a directory, so just set the dropped objects to
		     * the file name entry
		     */
		    gchar *s, *s2;
		    const gchar *path;
		    GList *glist;
		    GtkEntry *entry = GTK_ENTRY(fb->entry);

		    s = NULL;
		    for(glist = paths_list;
			glist != NULL;
			glist = g_list_next(glist)
		    )
		    {
			path = (const gchar *)glist->data;
			if(path == NULL)
			    continue;

			if(s != NULL)
			    s2 = g_strconcat(
				s,
				",",
				path,
				NULL
			    );
			else
			    s2 = STRDUP(path);
			g_free(s);
			s = s2;
		    }
		    gtk_entry_set_text(entry, s);
		    g_free(s);
		    gtk_entry_set_position(entry, -1);
		}
		if(paths_list != NULL)
		{
		    g_list_foreach(paths_list, (GFunc)g_free, NULL);
		    g_list_free(paths_list);
		}
	    }
	    /* File name entry */
	    else if(widget == fb->entry)
	    {
		gchar *s, *s2;
		const gchar *path;
		GtkEntry *entry = GTK_ENTRY(widget);
		GList	*glist,
			*paths_list = FileBrowserDNDBufParse(
		    (const gchar *)selection_data->data,
		    selection_data->length
		);

		s = NULL;
		for(glist = paths_list;
		    glist != NULL;
		    glist = g_list_next(glist)
		)
		{
		    path = (const gchar *)glist->data;
		    if(path == NULL)
			continue;

		    if(s != NULL)
			s2 = g_strconcat(
			    s,
			    ",",
			    path,
			    NULL
			);
		    else
			s2 = STRDUP(path);
		    g_free(s);
		    s = s2;
		}
		if(paths_list != NULL)
		{
		    g_list_foreach(paths_list, (GFunc)g_free, NULL);
		    g_list_free(paths_list);
		}

		gtk_entry_set_text(entry, s);
		g_free(s);
		gtk_entry_set_position(entry, -1);
	    }
	}
}

/*
 *	DND "drag_data_get" signal callback.
 */
static void FileBrowserDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	FileBrowser *fb = FILE_BROWSER(data);
	if((widget == NULL) || (fb == NULL))
	    return;

	/* Make sure the DND info is one that we support */
	if((info == 0) || (info == 1) || (info == 2))
	{
	    /* Handle by destination widget */
	    /* List */
	    if(widget == fb->list_da)
	    {
		gint buf_len;
		gchar *buf = FileBrowserDNDBufFormat(
		    fb->object, fb->total_objects, fb->selection,
		    &buf_len
		);
		if(buf != NULL)
		{
		    /* Send out DND data */
		    gtk_selection_data_set(
			selection_data,
			GDK_SELECTION_TYPE_STRING,
			8,	/* 8 bits per character */
			buf, buf_len
		    );
		    data_sent = TRUE;
		    g_free(buf);
		}
	    }
	}

	/* Failed to send out DND data? */
	if(!data_sent)
	{
	    /* Send a response indicating error */
	    const gchar *s = "Error";
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,		/* 8 bits per character */
		s,
		STRLEN(s) + 1	/* Include the '\0' character */
	    );
	    data_sent = TRUE;
	}
}

/*
 *	DND "drag_data_delete" signal callback.
 */
static void FileBrowserDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if((widget == NULL) || (fb == NULL))
	    return;

	/* Typical this means that some objects have been deleted
	 * so we need to refresh.
	 */
	FileBrowserRefreshCB(fb->refresh_btn, fb);
}

/*
 *	Show hidden objects GtkToggleButton "toggled" signal callback.
 */
static void FileBrowserShowHiddenObjectsToggleCB(
	GtkWidget *widget, gpointer data
)
{
	GtkToggleButton *tb = GTK_TOGGLE_BUTTON(widget);
	FileBrowser *fb = FILE_BROWSER(data);
	if((tb == NULL) || (fb == NULL))
	    return;

	if(fb->freeze_count > 0)
	    return;

	fb->freeze_count++;

	if(fb->show_hidden_objects == gtk_toggle_button_get_active(tb))
	{
	    fb->freeze_count--;
	    return;
	}

	fb->show_hidden_objects = tb->active;

	/* Update the listing */
	FileBrowserListUpdate(fb, NULL);

	fb->freeze_count--;
}

/*
 *	List format GtkToggleButton "toggled" signal callback.
 */
static void FileBrowserListFormatToggleCB(
	GtkWidget *widget, gpointer data
)
{
	FileBrowserListFormat list_format = FB_LIST_FORMAT_STANDARD;
	GtkToggleButton *tb = GTK_TOGGLE_BUTTON(widget);
	FileBrowser *fb = FILE_BROWSER(data);
	if((tb == NULL) || (fb == NULL))
	    return;

	if(fb->freeze_count > 0)
	    return;

	fb->freeze_count++;

	/* Do not let toggle button become untoggled when toggling
	 * was not frozen
	 */
	if(!tb->active)
	{
	    gtk_toggle_button_set_active(tb, TRUE);
	    fb->freeze_count--;
	    return;
	}

	/* See which toggle button this signal was for so we know
	 * what list format to set
	 */
	/* Standard */
	if(widget == fb->list_format_standard_tb)
	    list_format = FB_LIST_FORMAT_STANDARD;
	/* Vertical */
	else if(widget == fb->list_format_vertical_tb)
	    list_format = FB_LIST_FORMAT_VERTICAL;
	/* Vertical with details */
	else if(widget == fb->list_format_vertical_details_tb)
	    list_format = FB_LIST_FORMAT_VERTICAL_DETAILS;

	/* Set new list format and reget listing */
	FileBrowserSetListFormat(fb, list_format);

	fb->freeze_count--;
}

/*
 *	Go to parent callback.
 */
static void FileBrowserGoToParentCB(GtkWidget *widget, gpointer data)
{
	const gchar *cur_location;
	gchar *parent;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	/* Get current location, then get its parent */
	cur_location = FileBrowserGetLocation(fb);
	parent = (cur_location != NULL) ?
	    g_dirname(cur_location) : NULL;

	/* Go to the parent of the current location */
	if(parent != NULL)
	    FileBrowserSetLocation(fb, parent);

	g_free(parent);
}

/*
 *	New Directory callback.
 */
static void FileBrowserNewDirectoryCB(GtkWidget *widget, gpointer data)
{
	mode_t m;
	gchar *name, *full_path;
	const gchar *cur_location;
	GtkWidget *toplevel;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_MKDIR_FAILED	"Cree Guía Nueva Fallada"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_MKDIR_FAILED	"Créer Le Nouvel Annuaire Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_MKDIR_FAILED	"Schaffen Sie Neues Versagten Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_MKDIR_FAILED	"Creare L'Elenco Nuovo Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_MKDIR_FAILED	"Creëer Nieuwe Gids Verzuimde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_MKDIR_FAILED	"Crie Novo Guia Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_MKDIR_FAILED	"Skap New Directory Failed"
#else
# define TITLE_MKDIR_FAILED	"Create New Directory Failed"
#endif

#define MESSAGE_MKDIR_FAILED(s)		{	\
 CDialogSetTransientFor(toplevel);		\
 CDialogGetResponse(				\
  TITLE_MKDIR_FAILED,				\
  (s), NULL,					\
  CDIALOG_ICON_WARNING,				\
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK	\
 );						\
 CDialogSetTransientFor(NULL);			\
}

	toplevel = fb->toplevel;
	cur_location = FileBrowserGetLocation(fb);
	if(cur_location == NULL)
	    return;

	FileBrowserSetBusy(fb, TRUE);

	/* Generate new name and full path */
	name = STRDUP(FB_NEW_DIRECTORY_NAME);
	full_path = STRDUP(PrefixPaths(cur_location, name));

#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
	m =	S_IRUSR | S_IWUSR | S_IXUSR |
		S_IRGRP | S_IWGRP | S_IXGRP |
		S_IROTH | S_IWOTH | S_IXOTH;
#else
	m = 0;
#endif
	/* Create new directory */
#ifdef _WIN32
	if(mkdir(full_path))
#else
	if(mkdir(full_path, m))
#endif
	{
	    gint i, error_code;
	    gboolean created = FALSE;

	    /* Failed to create, try creating it under a different
	     * name.
	     */
	    for(i = 2; i < 100; i++)
	    {
		/* Regenerate new name and full path */
		g_free(name);
		g_free(full_path);
		name = g_strdup_printf(
		    "%s%i",
		    FB_NEW_DIRECTORY_NAME, i
		);
		full_path = STRDUP(PrefixPaths(cur_location, name));

		/* Try to create new directory, if success then
		 * break out of the loop.
		 */
#ifdef _WIN32
		if(!mkdir(full_path))
#else
		if(!mkdir(full_path, m))
#endif
		{
		    created = TRUE;
		    break;
		}

		/* Error creating directory and it was not because it
		 * already exists?
		 */
		error_code = (gint)errno;
		if(error_code != EEXIST)
		{
		    gchar *s = g_strdup_printf(
			"%s.",
			g_strerror(error_code)
		    );
		    MESSAGE_MKDIR_FAILED(s);
		    g_free(s);
		    break;
		}
	    }
	    /* Failed to create new directory? */
	    if(!created)
	    {
		g_free(name);
		g_free(full_path);
		FileBrowserSetBusy(fb, FALSE);
		return;
	    }
	}

	/* Update the list */
	FileBrowserListUpdate(fb, NULL);

	/* Select the newly created directory */
	if(name != NULL)
	{
	    gint i;
	    const FileBrowserObject *o;
	    for(i = 0; i < fb->total_objects; i++)
	    {
		o = fb->object[i];
		if((o != NULL) ? (o->name == NULL) : TRUE)
		    continue;

		if(!strcmp(o->name, name))
		{
		    /* Select this object */
		    fb->focus_object = i;
		    fb->selection = g_list_append(
			fb->selection, (gpointer)i
		    );
		    fb->selection_end = g_list_last(fb->selection);

		    /* Scroll to this object */
		    FileBrowserListMoveToObject(fb, i, 0.5f);

		    break;
		}
	    }
	}
	FileBrowserListQueueDraw(fb);

	/* Update selected objects on entry */
	FileBrowserEntrySetSelectedObjects(fb);

	FileBrowserSetBusy(fb, FALSE);

	g_free(name);
	g_free(full_path);
#undef MESSAGE_MKDIR_FAILED
#undef TITLE_MKDIR_FAILED
}

/*
 *	Refresh callback.
 */
static void FileBrowserRefreshCB(GtkWidget *widget, gpointer data)
{
	gint last_scroll_x = 0, last_scroll_y = 0;
	GtkWidget *w;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	FileBrowserSetBusy(fb, TRUE);

	/* Record the last scroll positions */
	w = fb->list_hsb;
	if(w != NULL)
	    last_scroll_x = (gint)GTK_ADJUSTMENT_GET_VALUE(
		GTK_RANGE(w)->adjustment
	    );
	w = fb->list_vsb;
	if(w != NULL)
	    last_scroll_y = (gint)GTK_ADJUSTMENT_GET_VALUE(
		GTK_RANGE(w)->adjustment  
	    );


	/* Begin refreshing */

	/* Reget the locations list */
	FileBrowserLocationsPUListUpdate(fb);

	/* Reget the objects list */
	FileBrowserListUpdate(fb, NULL);

	/* Restore the GtkScrollBar positions */
	w = fb->list_hsb;
	if(w != NULL)
	{
	    GtkRange *range = GTK_RANGE(w);
	    GtkAdjustment *adj = range->adjustment;
	    if(adj != NULL)
		gtk_adjustment_set_value(
		    adj,
		    CLIP(
			(gfloat)last_scroll_x,
			adj->lower,
			MAX(adj->upper - adj->page_size, adj->lower)
		    )
		);
	}
	w = fb->list_vsb;
	if(w != NULL)
	{
	    GtkRange *range = GTK_RANGE(w);
	    GtkAdjustment *adj = range->adjustment;
	    if(adj != NULL)
		gtk_adjustment_set_value(
		    adj,
		    CLIP(
			(gfloat)last_scroll_y,
			adj->lower,
			MAX(adj->upper - adj->page_size, adj->lower)
		    )
		);
	}

	FileBrowserSetBusy(fb, FALSE);
}


/*
 *	Updates the locations popup list based on the current
 *	location.
 */
static void FileBrowserLocationsPUListUpdate(FileBrowser *fb)
{
	struct stat stat_buf;
	const gchar *cur_location;
	pulist_struct *pulist;
	pulistbox_struct *pulistbox;
	const FileBrowserIcon *icon;
	GtkDestroyNotify destroy_cb = FileBrowserLocationsPUListItemDestroyCB;

	if(fb == NULL)
	    return;

	pulistbox = fb->loc_pulistbox;
	pulist = PUListBoxGetPUList(pulistbox);
	if(pulist == NULL)
	    return;

	FileBrowserSetBusy(fb, TRUE);

	/* Delete all the existing items */
	PUListClear(pulist);

#define GET_ICON(p)	{		\
 if(stat((p), &stat_buf))		\
  icon = NULL;				\
 else					\
  icon = FileBrowserGetIcon(		\
   fb,					\
   FileBrowserMatchIconNumFromPath(	\
    fb, (p), &stat_buf			\
   )					\
  );					\
}

/* Takes string s and shortens it, allocating a new string s2
 * that is a shortened (as needed) version of s.  The string s2
 * must be deleted
 */
#define ALLOC_STRING_SHORTENED(_s_) {	\
 if((_s_) != NULL) {			\
  const gint len = STRLEN(_s_),		\
	     max_characters = FB_LOC_LIST_MAX_CHARS;\
					\
  /* Length of _s_ is too long? */	\
  if(len > max_characters)		\
   s2 = g_strdup_printf(		\
    "...%s",				\
    &(_s_)[len - max_characters + 3]	\
   );					\
  else					\
   s2 = STRDUP(_s_);			\
 } else {				\
  s2 = NULL;				\
 }					\
}

	/* Get the current location and set the items for it as
	 * each of its parents
	 */
	cur_location = FileBrowserGetLocation(fb);
	if(cur_location != NULL)
	{
	    gint i, icon_num;
	    gchar	*s = STRDUP(cur_location),
			*sd = strrchr(s, G_DIR_SEPARATOR),
			*s2;

	    /* Current location */
	    if(stat(s, &stat_buf))
		icon_num = -1;
	    else
		icon_num = FileBrowserMatchIconNumFromPath(
		    fb, s, &stat_buf
		);
	    if(icon_num == FB_ICON_FOLDER_CLOSED)
		icon_num = FB_ICON_FOLDER_OPENED;
	    icon = FileBrowserGetIcon(fb, icon_num);
	    ALLOC_STRING_SHORTENED(s);
	    if(icon != NULL)
		i = PUListAddItemPixText(
		    pulist, s2, icon->pixmap, icon->mask
		);
	    else
		i = PUListAddItem(pulist, s2);
	    g_free(s2);
	    PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);

	    /* Parent locations */
#ifdef _WIN32
	    while(sd > (s + 3))
#else
	    while(sd > s)
#endif
	    {
		*sd = '\0';

		GET_ICON(s);
		ALLOC_STRING_SHORTENED(s);
		if(icon != NULL)
		    i = PUListAddItemPixText(
			pulist, s2, icon->pixmap, icon->mask
		    );
		else
		    i = PUListAddItem(pulist, s2);
		g_free(s2);
		PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);

		sd = strrchr(s, G_DIR_SEPARATOR);
	    }

	    /* Toplevel */
#ifdef _WIN32
	    /* On Win32, use s as is from the above parent fetching loop */
	    sd = strchr(s, G_DIR_SEPARATOR);
	    if((sd != NULL) ? (*(sd + 1) != '\0') : FALSE)
	    {
		*(sd + 1) = '\0';
		GET_ICON(s);
		ALLOC_STRING_SHORTENED(s);
		if(icon != NULL)
		    i = PUListAddItemPixText(
			pulist, s2, icon->pixmap, icon->mask
		    );
		else
		    i = PUListAddItem(pulist, s2);
		g_free(s2);
		PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
	    }
	    g_free(s);
#else
	    g_free(s);
	    s = STRDUP("/");
	    GET_ICON(s);
	    ALLOC_STRING_SHORTENED(s);
	    if(icon != NULL)
		i = PUListAddItemPixText(
		    pulist, s2, icon->pixmap, icon->mask
		);
	    else
		i = PUListAddItem(pulist, s2);
	    g_free(s2);
	    PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
	    g_free(s);
#endif
	}

#ifndef _WIN32
	/* Home */
	if(fb->home_path != NULL)
	{
	    const gchar *s = fb->home_path;
	    gint i;
	    gchar *s2;
	    GET_ICON(s);
	    ALLOC_STRING_SHORTENED(s);
	    if(icon != NULL)
		i = PUListAddItemPixText(
		    pulist, s2, icon->pixmap, icon->mask
		);
	    else
		i = PUListAddItem(pulist, s2);
	    g_free(s2);
	    PUListSetItemDataFull(pulist, i, STRDUP(s), destroy_cb);
	}
#endif

	/* Drives */
	if(fb->device_paths_list != NULL)
	{
	    gint i;
	    const gchar *path;
	    gchar *s2;
	    GList *glist;

	    for(glist = fb->device_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		path = (const gchar *)glist->data;
		if(path == NULL)
		    continue;

		/* Ignore the toplevel path */
		if(!g_strcasecmp((const char *)path, "/"))
		    continue;

#ifndef _WIN32
		/* Ignore drives that do not have absolute paths */
		if(!g_path_is_absolute(path))
		    continue;
#endif

#if defined(_WIN32)
		icon = FileBrowserGetIcon(fb, FB_ICON_DRIVE_FIXED);
#else
		GET_ICON(path);
#endif
		ALLOC_STRING_SHORTENED(path);
		if(icon != NULL)
		    i = PUListAddItemPixText(
			pulist, s2, icon->pixmap, icon->mask
		    );
		else
		    i = PUListAddItem(
			pulist, s2
		    );
		g_free(s2);
		PUListSetItemDataFull(pulist, i, STRDUP(path), destroy_cb);
	    }
	}

/* Other things to be added to the popup list, like mounted drives? */


	/* Make sure that no more than 10 lines are visible */
	PUListBoxSetLinesVisible(
	    pulistbox,
	    MIN(PUListGetTotalItems(pulist), 10)
	);

	/* Select the first item */
	PUListBoxSelect(pulistbox, 0);


	FileBrowserSetBusy(fb, FALSE);

#undef GET_ICON
#undef ALLOC_STRING_SHORTENED
}


/*
 *	Returns the icon at index i or NULL on error.
 */
static FileBrowserIcon *FileBrowserGetIcon(FileBrowser *fb, const gint i)
{
	if(fb == NULL)
	    return(NULL);

	if((i < 0) || (i >= fb->total_icons))
	    return(NULL);
	else
	    return(fb->icon[i]);
}

/*
 *	Appends an icon to the list.
 */
static FileBrowserIcon *FileBrowserIconAppend(
	FileBrowser *fb, guint8 **data, const gchar *desc
)
{
	gint i;
	FileBrowserIcon *icon;

	if((fb == NULL) || (data == NULL))
	    return(NULL);

	i = MAX(fb->total_icons, 0);
	fb->total_icons = i + 1;
	fb->icon = (FileBrowserIcon **)g_realloc(
	    fb->icon, fb->total_icons * sizeof(FileBrowserIcon *)
	);
	if(fb->icon == NULL)
	{
	    fb->total_icons = 0;
	    return(NULL);
	}

	fb->icon[i] = icon = FILE_BROWSER_ICON(g_malloc0(
	    sizeof(FileBrowserIcon)
	));
	if(icon != NULL)
	{
	    icon->pixmap = GDK_PIXMAP_NEW_FROM_XPM_DATA(&icon->mask, data);
	    gdk_window_get_size(icon->pixmap, &icon->width, &icon->height);
	    icon->desc = STRDUP(desc);
	}
	return(icon);
}


/*
 *	Returns the icon index appropriate for the object specified by
 *	full_path and lstat_buf.
 */
static gint FileBrowserMatchIconNumFromPath(
	FileBrowser *fb, const gchar *full_path, const struct stat *lstat_buf
)
{
	gint uid, gid;
	const gchar *ext, *name;
	mode_t m;

	if(fb == NULL)
	    return(FB_ICON_FILE);

	if(full_path != NULL)
	{
	    const gchar *s;
	    GList *glist;

	    name = g_basename(full_path);

#ifndef _WIN32
	    /* Home directory? */
	    s = fb->home_path;
	    if((s != NULL) ? !strcmp(s, full_path) : FALSE)
		return(FB_ICON_FOLDER_HOME);

	    /* Toplevel? */
	    if(!g_strcasecmp(full_path, "/"))
		return(FB_ICON_DRIVE_ROOT);
#endif
	    /* Drive path? */
	    for(glist = fb->device_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		s = (const gchar *)glist->data;
		if(s == NULL)
		    continue;

		if(!strcmp(s, full_path))
		    return(FB_ICON_DRIVE_FIXED);
	    }

	    /* Get the extension (if any) */
	    ext = (const gchar *)strrchr((const char *)full_path, '.');
	}
	else
	{
	    name = NULL;
	    ext = NULL;
	}

	/* Get object's statistics */
	if(lstat_buf != NULL)
	{
	    m = lstat_buf->st_mode;
	    uid = lstat_buf->st_uid;
	    gid = lstat_buf->st_gid;
	}
	else
	{
	    m = 0;
	    uid = 0;
	    gid = 0;
	}

	/* Directory? */
#ifdef S_ISDIR
	if(S_ISDIR(m))
#else
	if(FALSE)
#endif
	{
#if defined(_WIN32)
	    return(FB_ICON_FOLDER_CLOSED);
#else
	    /* Accessable? */
	    if(fb->euid != 0)
	    {
		int accessable_icon = FB_ICON_FOLDER_CLOSED;

		/* Hidden? */
		if((name != NULL) ? (*name == '.') : FALSE)
		{
		    if((name[2] != '\0') && (name[2] != '.'))
			accessable_icon = FB_ICON_FOLDER_HIDDEN;
		}

		/* For non-root process id's we should check if the
		 * process or the process' group owns the object and
		 * respectively see if the object (the directory) is
		 * executable (accessable) and return the appropriate
		 * icon number.
		 */
		/* This process owns object? */
		if(fb->euid == uid)
		    return((m & S_IXUSR) ?
			accessable_icon : FB_ICON_FOLDER_NOACCESS
		    );
		/* This process' group id owns object? */
		else if(fb->egid == gid)
		    return((m & S_IXGRP) ?
			accessable_icon : FB_ICON_FOLDER_NOACCESS
		    );
		/* Anonymous */
		else
		    return((m & S_IXOTH) ?
			accessable_icon : FB_ICON_FOLDER_NOACCESS
		    );
	    }
	    else
	    {
		/* Root always owns the object, so check if owner has
		 * access
		 */
		if(!(m & S_IXUSR))
		    return(FB_ICON_FOLDER_NOACCESS);

		/* Hidden? */
		if((name != NULL) ? (*name == '.') : FALSE)
		{
		    if((name[2] != '\0') && (name[2] != '.'))
			return(FB_ICON_FOLDER_HIDDEN);
		}

		return(FB_ICON_FOLDER_CLOSED);
	    }
#endif
	}

#if defined(_WIN32)
	if(ext != NULL)
	{
	    if(!g_strcasecmp(ext, ".exe") ||
	       !g_strcasecmp(ext, ".com") ||
	       !g_strcasecmp(ext, ".bat")
	    )
		return(FB_ICON_EXECUTABLE);
	    else
		return(FB_ICON_FILE);
	}
	else
	{
	    return(FB_ICON_FILE);
	}
#else
#ifdef S_ISLNK
	if(S_ISLNK(m))
	    return(FB_ICON_LINK);
#endif
#ifdef S_ISFIFO
	if(S_ISFIFO(m))
	    return(FB_ICON_PIPE);
#endif
#ifdef S_ISSOCK
	if(S_ISSOCK(m))
	    return(FB_ICON_SOCKET);
#endif
#ifdef S_ISBLK
	if(S_ISBLK(m))
	    return(FB_ICON_DEVICE_BLOCK);
#endif
#ifdef S_ISCHR
	if(S_ISCHR(m))
	    return(FB_ICON_DEVICE_CHARACTER);
#endif
#if defined(S_IXUSR) && defined(S_IXGRP) && defined(S_IXOTH)
	if((m & S_IXUSR) || (m & S_IXGRP) || (m & S_IXOTH))
	    return(FB_ICON_EXECUTABLE);
#endif

	/* Hidden? */
	if((name != NULL) ? (*name == '.') : FALSE)
	{
	    if((name[2] != '\0') && (name[2] != '.'))
		return(FB_ICON_FILE_HIDDEN);
	}

	return(FB_ICON_FILE);
#endif
}


/*
 *	Returns the object at index i or NULL on error.
 */
static FileBrowserObject *FileBrowserGetObject(FileBrowser *fb, gint i)
{
	if(fb == NULL)
	    return(NULL);

	if((i < 0) || (i >= fb->total_objects))
	    return(NULL);
	else
	    return(fb->object[i]);
}

/*
 *	Updates the Object's values.
 *
 *	The name and lstat_buf must be updated prior to this call since
 *	the information used to update the other values are based on
 *	them.
 *
 *	The updated values are; icon_num, width, and height.
 */
static void FileBrowserObjectUpdateValues(
	FileBrowser *fb, FileBrowserObject *o
)
{
	gint icon_num;
	GdkFont *font;
	GtkStyle *style;
	GtkWidget *w;
	FileBrowserIcon *icon;

	if((fb == NULL) || (o == NULL))
	    return;

	/* Get the list widget's style and font */
	w = fb->list_da;
	style = (w != NULL) ? gtk_widget_get_style(w) : NULL;
	font = (style != NULL) ? style->font : NULL;

	/* Get icon number based on the object's path and statistics */
	o->icon_num = icon_num = FileBrowserMatchIconNumFromPath(
	    fb, o->full_path, &o->lstat_buf
	);

	/* Get icon */
	icon = FileBrowserGetIcon(fb, icon_num);


	/* Set displayed_name from name, abbreviate it as needed */
	if(o->name != NULL)
	{
	    const gchar *name = o->name;
	    const gint	len = STRLEN(name),
			max_chars = FB_LIST_ITEM_MAX_CHARS;

	    g_free(o->displayed_name);
	    if((len > max_chars) && (len > 3))
		o->displayed_name = g_strdup_printf(
		    "...%s",
		    name + (len - max_chars + 3)
		);
	    else
		o->displayed_name = STRDUP(name);
	}
	else
	{
	    g_free(o->displayed_name);
	    o->displayed_name = STRDUP("");
	}


	/* Update size */
	if((o->displayed_name != NULL) && (font != NULL))
	{
	    const gchar *name = o->displayed_name;
	    const gint font_height = font->ascent + font->descent;
	    GdkTextBounds b;
	    gdk_string_bounds(font, name, &b);
	    if(icon != NULL)
	    {
		o->width = MAX(
		    icon->width + FB_LIST_ICON_BORDER + 2 + b.width,
		    1
		);
		o->height = MAX(
		    icon->height,
		    font_height
		);
	    }
	    else
	    {
		o->width = MAX(
		    b.width,
		    1
		);
		o->height = MAX(
		    font_height,
		    1
		);
	    }
	}
	else
	{
	    if(icon != NULL)
	    {
		o->width = icon->width;
		o->height = icon->height;
	    }
	    else
	    {
		o->width = 1;
		o->height = 1;
	    }
	}
}

/*
 *	Appends an object to the list.
 *
 *	Returns the new object or NULL on error.  The calling function
 *	needs to calculate the position.
 */
static FileBrowserObject *FileBrowserObjectAppend(
	FileBrowser *fb, const gchar *name, const gchar *full_path,
	struct stat *lstat_buf
)
{
	gint i;
	FileBrowserObject *o;

	if((fb == NULL) || (full_path == NULL))
	    return(NULL);

	/* Allocate one more pointer for the array */
	i = MAX(fb->total_objects, 0);
	fb->total_objects = i + 1;
	fb->object = (FileBrowserObject **)g_realloc(
	    fb->object, fb->total_objects * sizeof(FileBrowserObject *)
	);
	if(fb->object == NULL)
	{
	    fb->total_objects = 0;
	    return(NULL);
	}

	/* Create a new Object */
	fb->object[i] = o = FILE_BROWSER_OBJECT(g_malloc0(
	    sizeof(FileBrowserObject)
	));
	if(o != NULL)
	{
	    o->name = STRDUP(name);
	    o->full_path = STRDUP(full_path);
	    if(lstat_buf != NULL)
		memcpy(&o->lstat_buf, lstat_buf, sizeof(struct stat));
	    else
		memset(&o->lstat_buf, 0x00, sizeof(struct stat));

	    /* Update values */
	    FileBrowserObjectUpdateValues(fb, o);
	}

	return(o);
}

/*
 *	Updates the list.
 *
 *	Regets the list of objects from the current location and
 *	updates the list's scroll bar values
 */
static void FileBrowserListUpdate(FileBrowser *fb, const gchar *filter)
{
	gboolean	match_all_files,
			show_hidden_objects;
	gint i;
	const gchar *cur_location, *ext;

	if(fb == NULL)
	    return;

	FileBrowserSetBusy(fb, TRUE);

	show_hidden_objects = fb->show_hidden_objects;

	/* Get the extension from the filter (if specified) or from
	 * the current file type
	 */
	ext = (filter != NULL) ? filter : fb->cur_type.ext;
	if(ext != NULL)
	    match_all_files = (!strcmp(ext, "*.*") || !strcmp(ext, "*")) ? TRUE : FALSE;
	else
	    match_all_files = TRUE;

	/* Get listing of contents in current location */

	/* Delete current objects list and the selection */

	/* Unselect all */
	fb->focus_object = -1;
	g_list_free(fb->selection);
	fb->selection = fb->selection_end = NULL;

	/* Delete all objects */
	for(i = 0; i < fb->total_objects; i++)
	    FileBrowserObjectDestroyCB(fb->object[i]);
	g_free(fb->object);
	fb->object = NULL;
	fb->total_objects = 0;	

	/* Reset objects per row */
	fb->objects_per_row = 0;


	/* Get the current location */
	cur_location = FileBrowserGetLocation(fb);
	if(cur_location != NULL)
	{
	    /* Get the listing of objects in the current location */
	    gint nobjs;
	    gchar **names_list = GetDirEntNames2(cur_location, &nobjs);
	    if(names_list != NULL)
	    {
		struct stat stat_buf;
		const gchar *name;
		gchar *full_path;

		StringQSort(names_list, nobjs);

		/* Iterate through the names list and get only the
		 * directories
		 */
		for(i = 0; i < nobjs; i++)
		{
		    name = names_list[i];
		    if(name == NULL)
			continue;

		    /* Skip special directory notations */
		    if(!strcmp(name, ".") ||
		       !strcmp(name, "..")
		    )
		    {
			g_free(names_list[i]);
			names_list[i] = NULL;
			continue;
		    }

		    /* Skip hidden objects? */
		    if(!show_hidden_objects)
		    {
			/* Does the name start with a '.' character? */
			if(*name == '.')
			{
			    g_free(names_list[i]);
			    names_list[i] = NULL;
			    continue;
			}
		    }

		    /* Get the full path to the object */
		    full_path = STRDUP(PrefixPaths(cur_location, name));
		    if(full_path == NULL)
		    {
			g_free(names_list[i]);
			names_list[i] = NULL;
			continue;
		    }

		    /* Get this object's destination stats */
		    if(stat(full_path, &stat_buf))
		    {
			g_free(full_path);
			/* Do not delete this object from the names list */
			continue;
		    }

		    /* Skip this object if it's destination is not
		     * a directory
		     */
		    if(!S_ISDIR(stat_buf.st_mode))
		    {
			g_free(full_path);
			/* Do not delete this object from the names list */
			continue;
		    }

		    /* Get this directory's local stats */
#if !defined(_WIN32)
		    if(lstat(full_path, &stat_buf))
			memset(&stat_buf, 0x00, sizeof(struct stat));
#endif

		    /* Append this directory and its stats to the list */
		    FileBrowserObjectAppend(
			fb, name, full_path, &stat_buf
		    );

		    g_free(full_path);

		    /* Delete this directory from the names list */
		    g_free(names_list[i]);
		    names_list[i] = NULL;
		}

		/* Add all the other objects */
		for(i = 0; i < nobjs; i++)
		{
		    name = names_list[i];
		    if(name == NULL)
			continue;

		    /* Get the full path to the object */
		    full_path = STRDUP(PrefixPaths(cur_location, name));
		    if(full_path == NULL)
		    {
			g_free(names_list[i]);
			names_list[i] = NULL;
			continue;
		    }

		    /* Filter this object's name */
		    if(!match_all_files)
		    {
			if(!FileBrowserObjectNameFilter(
			    name, full_path, ext
			))
			{
			    g_free(full_path);
			    g_free(names_list[i]);
			    names_list[i] = NULL;
			    continue;
			}
		    }

		    /* Get this object's local stats */
#if defined(_WIN32)
		    if(stat(full_path, &stat_buf))
			memset(&stat_buf, 0x00, sizeof(struct stat));
#else
		    if(lstat(full_path, &stat_buf))
			memset(&stat_buf, 0x00, sizeof(struct stat));
#endif

		    /* Append this object and its stats to the list */
		    FileBrowserObjectAppend(
			fb, name, full_path, &stat_buf
		    );

		    g_free(full_path);

		    /* Delete this object from the names list */
		    g_free(names_list[i]);
		    names_list[i] = NULL;
		}

		/* Delete the names list */
		g_free(names_list);
	    }
	}

	/* Set the list's object positions and update the scroll
	 * bar values
	 */
	FileBrowserListUpdatePositions(fb);

	FileBrowserSetBusy(fb, FALSE);
}

/*
 *	Updates the list's object positions and scroll bar values.
 *
 *	This may produce a change in the list window's size due to
 *	the mapping or unmapping of the scroll bar, in which case
 *	a configure_event will be queued
 */
static void FileBrowserListUpdatePositions(FileBrowser *fb)
{
	const gint      border_major = 5,
			border_minor = 2,
			border_x = border_minor,
			border_y = border_minor;
	gboolean	need_resize = FALSE;
	gint	i, width, height,
		total_objects, objects_per_row,
		cur_x = border_x,
		cur_y = border_y,
		longest_width = 0,
		last_longest_width = longest_width,
		list_x_max = 0, list_y_max = 0,
		list_x_inc = 0, list_x_page_inc = 0,
		list_y_inc = 0, list_y_page_inc = 0;
	GdkWindow *window;
	GtkWidget *w;
	FileBrowserListFormat list_format;
	FileBrowserObject *o = NULL;

	if(fb == NULL)
	    return;

	list_format = fb->list_format;
	w = fb->list_da;
	if(w == NULL)
	    return;

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

	/* Get the size of the list */
	gdk_window_get_size(window, &width, &height);

	/* Update the object positions and get values for the scroll
	 * bar
	 */
	switch(list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    total_objects = fb->total_objects;
	    for(i = 0; i < total_objects; i++)
	    {
	        o = fb->object[i];
	        if(o == NULL)
		    continue;

		if(longest_width < o->width)
		    last_longest_width = longest_width = o->width;

		o->x = cur_x;
		o->y = cur_y;
		cur_y += o->height + 1;
	    }
	    break;
	  case FB_LIST_FORMAT_STANDARD:
	    total_objects = fb->total_objects;
	    objects_per_row = 0;
	    for(i = 0; i < total_objects; i++)
	    {
	        o = fb->object[i];
	        if(o == NULL)
		    continue;

		if(longest_width < o->width)
		    last_longest_width = longest_width = o->width;

		o->x = cur_x;
		o->y = cur_y;
		cur_y += o->height + 1;
		objects_per_row++;

		/* Update the objects per row if this object's y
		 * position has exceeded the list's height
		 */
		if((cur_y + o->height) > height)
		{
		    if(objects_per_row > fb->objects_per_row)
			fb->objects_per_row = objects_per_row;
		    objects_per_row = 0;
		    cur_y = border_y;
		    cur_x += longest_width + border_major;
		    longest_width = 0;
		}
	    }
	    break;
	}

	/* Use the last object to set the list's bounds, these
	 * bounds will be used in updating of the scroll bar's values
	 */
	if(o != NULL)
	{
	    list_x_max = o->x + last_longest_width + border_x;
	    list_y_max = o->y + o->height + border_y;
	    list_x_inc = (gint)(width * 0.25f);
	    list_x_page_inc = (gint)(width * 0.5f);
	    list_y_inc = o->height;
	    list_y_page_inc = (gint)(height * 0.5f);
	}

	/* Show/hide the scroll bars */
	switch(list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    w = fb->list_hsb;
	    if(w != NULL)
	    {
		if(fb->hsb_map_state)
		{
		    gtk_widget_hide(w);
		    fb->hsb_map_state = FALSE;
		    need_resize = TRUE;
		}
	    }
	    w = fb->list_vsb;
	    if(w != NULL)
	    {
		GtkRange *range = GTK_RANGE(w);
		GtkAdjustment *adj = range->adjustment;
		if(adj != NULL)
		{
		    adj->lower = 0.0f;
		    adj->upper = (gfloat)list_y_max;
		    adj->value = adj->lower;
		    adj->step_increment = (gfloat)list_y_inc;
		    adj->page_increment = (gfloat)list_y_page_inc;
		    adj->page_size = (gfloat)height;
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "changed"
		    );
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "value_changed"
		    );

		    /* If content size is larger than visible size then
		     * map the scrollbar, otherwise unmap the scrollbar
		     * since it would not be needed.
		     */
		    if((adj->upper - adj->lower) > adj->page_size)
		    {
			if(!fb->vsb_map_state)
			{
			    gtk_widget_show(w);
			    fb->vsb_map_state = TRUE;
			    need_resize = TRUE;
			}
		    }
		    else
		    {
			if(fb->vsb_map_state)
			{
			    gtk_widget_hide(w);
			    fb->vsb_map_state = FALSE;
			    need_resize = TRUE;
			}
		    } 
		}
	    }
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    w = fb->list_vsb;
	    if(w != NULL)
	    {
		if(fb->vsb_map_state)
		{
		    gtk_widget_hide(w);
		    fb->vsb_map_state = FALSE;
		    need_resize = TRUE;
		}
	    }
	    w = fb->list_hsb;
	    if(w != NULL)
	    {
		GtkRange *range = GTK_RANGE(w);
		GtkAdjustment *adj = range->adjustment;
		if(adj != NULL)
		{
		    adj->lower = 0.0f;
		    adj->upper = (gfloat)list_x_max;
		    adj->value = adj->lower;
		    adj->step_increment = (gfloat)list_x_inc;
		    adj->page_increment = (gfloat)list_x_page_inc;
		    adj->page_size = (gfloat)width;
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "changed"
		    );
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "value_changed"
		    );
		    /* If content size is larger than visible size then
		     * map the scrollbar, otherwise unmap the scrollbar
		     * since it would not be needed
		     */
		    if((adj->upper - adj->lower) > adj->page_size)
		    {
			if(!fb->hsb_map_state)
			{
			    gtk_widget_show(w);
			    fb->hsb_map_state = TRUE;
			    need_resize = TRUE;
			}
		    }
		    else
		    {
			if(fb->hsb_map_state)
			{
			    gtk_widget_hide(w);
			    fb->hsb_map_state = FALSE;
			    need_resize = TRUE;
			}
		    }
		}
	    }
	    break;
	}

	/* Need to resize due to widgets being mapped or unmapped? */
	if(need_resize)
	    gtk_widget_queue_resize(fb->toplevel);
}

/*
 *	Sets the DND icon for the object in the list.
 *
 *	The i specifies the object's index.
 */
static void FileBrowserListObjectSetDNDIcon(FileBrowser *fb, const gint i)
{
	FileBrowserIcon *icon;
	FileBrowserObject *o = FileBrowserGetObject(fb, i);
	if(fb == NULL)
	    return;

	/* Get object's icon (if any) */
	icon = FileBrowserGetIcon(fb, o->icon_num);
	if(icon == NULL)
	    return;

	/* Set new DND icon if it has a pixmap */
	if(icon->pixmap != NULL)
	    GUIDNDSetDragIcon(
		icon->pixmap, icon->mask,
		icon->width / 2, icon->height / 2
	    );
}

/*
 *	Returns the visibility of the object in the list.
 *
 *	The i specifies the object's index.
 *
 *	Returns one of GTK_VISIBILITY_*.
 */
static GtkVisibility FileBrowserListObjectVisibility(
	FileBrowser *fb, const gint i
)
{
	gint scroll_x = 0, scroll_y = 0;
	gint x, y, width, height;
	GdkWindow *window;
	GtkWidget *w;
	FileBrowserObject *o;

	if(fb == NULL)
	    return(GTK_VISIBILITY_NONE);

	/* Get list's GdkWindow and its size, making sure it exists and
	 * its size is positive
	 */
	w = fb->list_da;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return(GTK_VISIBILITY_NONE);
	gdk_window_get_size(window, &width, &height);
	if((width <= 0) || (height <= 0))
	    return(GTK_VISIBILITY_NONE);

	/* Get object */
	o = FileBrowserGetObject(fb, i);
	if(o == NULL)
	    return(GTK_VISIBILITY_NONE);

	/* Get current scroll position */
	if(fb->list_hsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_hsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
	}
	if(fb->list_vsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_vsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
	}

	/* Check visibility by list display format */
	switch(fb->list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    y = o->y - scroll_y;
	    if(((y + o->height) <= 0) || (y >= height))
		return(GTK_VISIBILITY_NONE);
	    else if((y < 0) || ((y + o->height) > height))
		return(GTK_VISIBILITY_PARTIAL);
	    else
		return(GTK_VISIBILITY_FULL);
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    x = o->x - scroll_x;
	    if(((x + o->width) <= 0) || (x >= width))
		return(GTK_VISIBILITY_NONE);
	    else if((x < 0) || ((x + o->width) > width))
		return(GTK_VISIBILITY_PARTIAL);
	    else
		return(GTK_VISIBILITY_FULL);
	    break;
	}

	return(GTK_VISIBILITY_NONE);
}

/*
 *	Scrolls to the object in the list.
 *
 *	The i specifies the object's index.
 *
 *	The coefficient specifies the offset within the list window.
 */
static void FileBrowserListMoveToObject(
	FileBrowser *fb, const gint i, const gfloat coeff
)
{
	gint x, y, width, height;
	GdkWindow *window;
	GtkWidget *w;
	FileBrowserObject *o;

	if(fb == NULL)
	    return;

	/* Get list's GdkWindow and its size, make sure that it exists
	 * and its size is positive
	 */
	w = fb->list_da;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;
	gdk_window_get_size(window, &width, &height);
	if((width <= 0) || (height <= 0))
	    return;

	/* Get object */
	o = FileBrowserGetObject(fb, i);
	if(o == NULL)
	    return;

	/* Move to object depending on list display format */
	switch(fb->list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    y = o->y + (gint)(o->height * coeff);
	    y -= (gint)(height * CLIP(coeff, 0.0f, 1.0f));
	    if(fb->list_vsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_vsb);
		GtkAdjustment *adj = range->adjustment;
		if(adj != NULL)
		    gtk_adjustment_set_value(
			adj,
			CLIP(
			    y,
			    adj->lower,
			    MAX(adj->upper - adj->page_size, adj->lower)
			)
		    );
	    }
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    x = o->x + (gint)(o->width * coeff);
	    x -= (gint)(width * CLIP(coeff, 0.0f, 1.0f));
	    if(fb->list_hsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_hsb);
		GtkAdjustment *adj = range->adjustment;
		if(adj != NULL)
		    gtk_adjustment_set_value(
			adj,
			CLIP(
			    x,
			    adj->lower,
			    MAX(adj->upper - adj->page_size, adj->lower)
			)
		    );
	    }
	    break;
	}
}

/*
 *	Gets the object at the coordinates within the list.
 *
 *	The x and y specifies the coordinates in the list window.
 *
 *	Returns the object index or -1 on failed match.
 */
static gint FileBrowserListSelectCoordinates(
	FileBrowser *fb, const gint x, const gint y
)
{
	gint scroll_x = 0, scroll_y = 0;

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

	/* Get scroll position */
	if(fb->list_hsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_hsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
	}
	if(fb->list_vsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_vsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
	}

	/* Find object by list display format */
	switch(fb->list_format)
	{
	    gint i, cx, cy;
	    FileBrowserObject *o;

	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    for(i = 0; i < fb->total_objects; i++)
	    {
		o = fb->object[i];
		if(o == NULL)
		    continue;

		if((o->width <= 0) || (o->height <= 0))
		    continue;

		/* Calculate object position with scrolled adjust */
		cx = o->x;
		cy = o->y - scroll_y;

		/* In bounds? */
		if((x >= cx) && (x < (cx + o->width)) &&
		   (y >= cy) && (y < (cy + o->height))
		)
		    return(i);
	    }
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    for(i = 0; i < fb->total_objects; i++)
	    {
		o = fb->object[i];
		if(o == NULL)
		    continue;

		if((o->width <= 0) || (o->height <= 0))
		    continue;

		/* Calculate object position with scrolled adjust */
		cx = o->x - scroll_x;
		cy = o->y;

		/* In bounds? */
		if((x >= cx) && (x < (cx + o->width)) &&
		   (y >= cy) && (y < (cy + o->height))
		)
		    return(i);
	    }
	    break;
	}

	return(-1);
}

/*
 *	Redraws the File Browser's list header.
 */
static void FileBrowserListHeaderDraw(FileBrowser *fb)
{
	gint width, height, font_height;
	GdkFont *font;
	GdkDrawable *drawable;
	GdkWindow *window;
	GdkGC *gc;
	GtkStateType state;
	GtkStyle *style;
	GtkWidget *w = (fb != NULL) ? fb->list_header_da : NULL;
	if(w == NULL)
	    return;

	if(!GTK_WIDGET_VISIBLE(w))
	    return;

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

	drawable = (GdkDrawable *)fb->list_pm;
	if(drawable == NULL)
	    drawable = window;

	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	gdk_window_get_size(window, &width, &height);
	if((style == NULL) || (width <= 0) || (height <= 0))
	    return;

	font = style->font;
	if(font == NULL)
	    return;

	font_height = font->ascent + font->descent;


	/* Draw background */
	gc = style->bg_gc[state];
#if 0
	bg_pixmap = style->bg_pixmap[state];
	if(bg_pixmap != NULL)
	{
	    gdk_gc_set_fill(gc, GDK_TILED);
	    gdk_gc_set_tile(gc, bg_pixmap);
	    gdk_gc_set_ts_origin(gc, 0, 0);
	}
	else
	{
	    gdk_gc_set_fill(gc, GDK_SOLID);
	    gdk_gc_set_foreground(gc, &style->bg[state]);
	}
#endif
	gdk_draw_rectangle(
	    drawable, gc, TRUE,
	    0, 0, width, height
	);
#if 0
	gdk_gc_set_fill(gc, GDK_SOLID);
	gdk_gc_set_tile(gc, NULL);
#endif


	/* Draw frame around entire header */
	gtk_draw_box(
	    style, drawable, state,
	    GTK_SHADOW_OUT,
	    0, 0, width, height
	);

	/* Any column headings to draw? */
	if(fb->total_columns > 0)
	{
	    gint i, last_column_position = 0;
	    GdkRectangle rect;
	    FileBrowserColumn *column;
	    GdkGC *gc_text;

	    /* Draw each column heading */
	    for(i = 0; i < fb->total_columns; i++)
	    {
		column = fb->column[i];
		if(column == NULL)
		    continue;

		rect.x = last_column_position;
		rect.y = 0;
		rect.width = MAX(column->position - last_column_position, 0);
		rect.height = height;

		gtk_draw_box(
		    style, drawable, state,
		    GTK_SHADOW_OUT,
		    rect.x, rect.y, rect.width, rect.height
		);

		gc_text = (column->flags & GTK_SENSITIVE) ?
		    style->text_gc[state] : style->text_gc[GTK_STATE_INSENSITIVE];

		if((column->label != NULL) ? (*column->label != '\0') : FALSE)
		{
		    const gchar *label = column->label;
		    GdkTextBounds b;
		    gdk_string_bounds(font, label, &b);
		    gdk_gc_set_clip_origin(gc_text, 0, 0);
		    gdk_gc_set_clip_rectangle(gc_text, &rect);
		    switch(column->label_justify)
		    {
		      case GTK_JUSTIFY_FILL:
		      case GTK_JUSTIFY_CENTER:
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + ((rect.width - b.width) / 2)) -
				b.lbearing,
			    (rect.y + ((rect.height - font_height) / 2)) +
				font->ascent,
			    label
			);
			break;
		      case GTK_JUSTIFY_RIGHT:
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + rect.width - b.width - 5) -
				b.lbearing,
			    (rect.y + ((rect.height - font_height) / 2)) +
				font->ascent,
			    label
			);
			break;
		      case GTK_JUSTIFY_LEFT:
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + 5) - b.lbearing,
			    (rect.y + ((rect.height - font_height) / 2)) +
				font->ascent,
			    label
			);
			break;
		    }
		    gdk_gc_set_clip_rectangle(gc_text, NULL);
		}

		if((column->flags & GTK_SENSITIVE) &&
		   (column->flags & GTK_HAS_FOCUS) &&
		   GTK_WIDGET_HAS_FOCUS(w) &&
		   GTK_WIDGET_SENSITIVE(w)
		)
		    gdk_draw_rectangle(
			drawable, style->fg_gc[state], FALSE,
			rect.x, rect.y, rect.width - 1, rect.height - 1
		    );

		last_column_position = column->position;
	    }
	}

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

/*
 *	Queues a redraw of the File Browser's list header.
 */
static void FileBrowserListHeaderQueueDraw(FileBrowser *fb)
{
	GtkWidget *w = (fb != NULL) ? fb->list_header_da : NULL;
	if(w != NULL)
	    gtk_widget_queue_draw(w);
}

/*
 *	Redraws the File Browser's list.
 */
static void FileBrowserListDraw(FileBrowser *fb)
{
	gint width, height, font_height;
	gint scroll_x = 0, scroll_y = 0;
	GdkFont *font;
	GdkDrawable *drawable;
	GdkWindow *window;
	GdkGC *gc;
	GtkStateType state;
	GtkStyle *style;
	GtkWidget *w = (fb != NULL) ? fb->list_da : NULL;
	if(w == NULL)
	    return;

	/* Do not draw if the list is not visible */
	if(!GTK_WIDGET_VISIBLE(w))
	    return;

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

	drawable = (GdkDrawable *)fb->list_pm;
	if(drawable == NULL)
	    drawable = window;

	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	gdk_window_get_size(window, &width, &height);
	if((style == NULL) || (width <= 0) || (height <= 0))
	    return;

	font = style->font;
	if(font == NULL)
	    return;

	font_height = font->ascent + font->descent;


	/* Draw the background */
	gc = style->base_gc[state];
	gdk_draw_rectangle(
	    drawable, gc, TRUE,
	    0, 0, width, height
	);
#if 0
	if(style->bg_pixmap[state] != NULL)
	    gtk_style_apply_default_background(
		style, drawable, FALSE, state,
		NULL,
		0, 0, width, height
	    );
#endif

	/* Get the scroll position */
	if(fb->list_hsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_hsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_x = (gint)((adj != NULL) ? adj->value : 0.0f);
	}
	if(fb->list_vsb != NULL)
	{
	    GtkRange *range = GTK_RANGE(fb->list_vsb);
	    GtkAdjustment *adj = range->adjustment;
	    scroll_y = (gint)((adj != NULL) ? adj->value : 0.0f);
	}

	/* Begin drawing objects by list display format */
	switch(fb->list_format)
	{
	    gboolean o_is_selected;
	    GdkTextBounds b;
	    gint i, x, y, icon_width;
	    GdkGC *gc_text;
	    const FileBrowserObject *o;
	    const FileBrowserIcon *icon;

	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	    /* Make sure we have enough columns to draw for this
	     * list display format
	     */
	    if(fb->total_columns >= 4)
	    {
		mode_t m;
		const struct stat *lstat_buf;
		GdkRectangle rect;
		const FileBrowserColumn *column[4];

		/* Get the pointers to each column */
		for(i = 0; i < 4; i++)
		    column[i] = fb->column[i];

		/* Draw each object */
		for(i = 0; i < fb->total_objects; i++)
		{
		    o = fb->object[i];
		    if(o == NULL)
			continue;

		    if((o->width <= 0) || (o->height <= 0))
			continue;

		    x = o->x;
		    y = o->y - scroll_y;

		    /* Is this object completely off the y axis? */
		    if(((y + o->height) < 0) || (y >= height))
			continue;

		    /* Get this object's values */
		    lstat_buf = &o->lstat_buf;
		    m = lstat_buf->st_mode;

		    /* Get calculate this object's boundary
		     * rectangle
		     */
		    rect.x = x - 0;
		    rect.y = y;
		    rect.width = MIN(o->width, column[0]->position - 0);
		    rect.height = o->height;

		    /* Is this object selected? */
		    o_is_selected = OBJISSEL(fb, i);
		    if(o_is_selected)
		    {
			/* Get the selection graphic contexts and
			 * draw the selection background
			 */
			gc = style->fg_gc[GTK_STATE_SELECTED];
			gc_text = style->text_gc[GTK_STATE_SELECTED];
			gdk_draw_rectangle(
			    drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
			    rect.x, rect.y, rect.width, rect.height
			);
		    }
		    else
		    {
			/* Get the graphic contexts for drawing
			 * this object
			 */
			gc = style->fg_gc[state];
			gc_text = style->text_gc[state];
		    }

		    /* Draw the icon */
		    icon = FileBrowserGetIcon(fb, o->icon_num);
		    if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
		    {
			GdkBitmap *mask = icon->mask;
			GdkPixmap *pixmap = icon->pixmap;
			gdk_gc_set_clip_origin(gc, x, y);
			gdk_gc_set_clip_mask(gc, mask);
			gdk_draw_pixmap(
			    drawable, gc, pixmap,
			    0, 0, x, y, icon->width, icon->height
			);
			gdk_gc_set_clip_mask(gc, NULL);
			icon_width = icon->width;
		    }
		    else
		    {
			icon_width = 0;
		    }

		    /* Set up the clip rectangle for drawing of the
		     * object's name cell
		     */
		    gdk_gc_set_clip_origin(gc, 0, 0);
		    gdk_gc_set_clip_rectangle(gc, &rect);
		    gdk_gc_set_clip_origin(gc_text, 0, 0);
		    gdk_gc_set_clip_rectangle(gc_text, &rect);

		    /* Draw the name */
		    if(o->displayed_name != NULL)
		    {
			const gchar *s = o->displayed_name;
			gdk_string_bounds(font, s, &b);
			gdk_draw_string(
			    drawable, font, gc_text,
			    (x + icon_width + FB_LIST_ICON_BORDER) -
				b.lbearing,
			    (y + ((o->height - font_height) / 2)) +
				font->ascent,
			    s
			);
		    }
		    else
		    {
			/* No name, but we still need to have font extents
			 * for use with drawing the subsequent cells below
			 */
			gdk_string_bounds(font, "X", &b);
		    }

		    /* If the object was selected it means we drew the
		     * icon and name with the selected gc's. We should
		     * now restore the gc's and use the current state
		     * gc's for drawing subsequent values
		     */
		    if(o_is_selected)
		    {
			gdk_gc_set_clip_rectangle(gc, NULL);
			gdk_gc_set_clip_rectangle(gc_text, NULL);
			gc = style->fg_gc[state];
			gc_text = style->text_gc[state];
		    }

		    /* Draw the size */
		    if(S_ISREG(m))
		    {
			gchar s[80];
			g_snprintf(
			    s, sizeof(s),
			    "%ld",
			    lstat_buf->st_size
			);
			gdk_string_bounds(font, s, &b);
			rect.x = column[0]->position;
			rect.width = column[1]->position - column[0]->position;
			if(rect.width > 0)
			    gdk_gc_set_clip_rectangle(gc_text, &rect);
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + rect.width - b.width - 2) -
				b.lbearing,
			    (y + (o->height / 2) -
				((font->ascent + font->descent) / 2)) +
				font->ascent,
			    s
			);
		    }

		    /* Draw the permissions */
#ifdef S_ISLNK
		    if(!S_ISLNK(m))
#else
		    if(TRUE)
#endif
		    {
			gchar s[80];
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
			g_snprintf(
			    s, sizeof(s),
			    "%c%c%c%c%c%c%c%c%c",
			    (m & S_IRUSR) ? 'r' : '-',
			    (m & S_IWUSR) ? 'w' : '-',
			    (m & S_ISUID) ? 'S' :
				((m & S_IXUSR) ? 'x' : '-'),
			    (m & S_IRGRP) ? 'r' : '-',
			    (m & S_IWGRP) ? 'w' : '-',
			    (m & S_ISGID) ? 'G' :
				((m & S_IXGRP) ? 'x' : '-'),
			    (m & S_IROTH) ? 'r' : '-',
			    (m & S_IWOTH) ? 'w' : '-',
			    (m & S_ISVTX) ? 'T' :
				((m & S_IXOTH) ? 'x' : '-')
			);
#else
			strcpy(s, "rwxrwxrwx");
#endif
			gdk_string_bounds(font, s, &b);
			rect.x = column[1]->position;
			rect.width = column[2]->position - column[1]->position;
			if(rect.width > 0)
			    gdk_gc_set_clip_rectangle(gc_text, &rect);
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + 2) - b.lbearing,
			    (y + (o->height / 2) -
				((font->ascent + font->descent) / 2)) +
				font->ascent,
			    s
			);
		    }

		    /* Draw the modified time */
		    if(S_ISREG(m))
		    {
			time_t mtime = lstat_buf->st_mtime;
			gchar *s = STRDUP((mtime > 0) ? ctime(&mtime) : "*undefined*");
			gchar *s2 = strchr(s, '\n');
			if(s2 == NULL)
			    s2 = strchr(s, '\r');
			if(s2 != NULL)
			    *s2 = '\0';
			gdk_string_bounds(font, s, &b);
			rect.x = column[2]->position;
			rect.width = column[3]->position - column[2]->position;
			if(rect.width > 0)
			    gdk_gc_set_clip_rectangle(gc_text, &rect);
			gdk_draw_string(
			    drawable, font, gc_text,
			    (rect.x + 2) - b.lbearing,
			    (y + (o->height / 2) -
				((font->ascent + font->descent) / 2)) +
				font->ascent,
			    s
			);
			g_free(s);
		    }

		    /* Restore the graphic context */
		    gdk_gc_set_clip_rectangle(gc_text, NULL);
		    gdk_gc_set_clip_rectangle(gc, NULL);

		    /* Draw the focus rectangle around this object? */
		    if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
		    {
			GdkGCValues gcv;
			gdk_gc_get_values(gc, &gcv);
			gdk_gc_set_function(gc, GDK_INVERT);
			rect.x = x - 0;
			rect.width = MIN(o->width, column[0]->position - 0);
			gdk_draw_rectangle(
			    drawable, gc, FALSE,
			    rect.x, rect.y,
			    rect.width - 1, rect.height - 1
			);
			gdk_gc_set_function(gc, gcv.function);
		    }
		}
	    }
	    break;

	  case FB_LIST_FORMAT_VERTICAL:
	    for(i = 0; i < fb->total_objects; i++)
	    {
		o = fb->object[i];
		if(o == NULL)
		    continue;

		if((o->width <= 0) || (o->height <= 0))
		    continue;

		x = o->x;
		y = o->y - scroll_y;

		/* Is this object completely off the y axis? */
		if(((y + o->height) < 0) || (y >= height))
		    continue;

		/* Is object selected? */
		o_is_selected = OBJISSEL(fb, i);
		if(o_is_selected)
		{
		    /* Get the selection graphic contexts and draw
		     * the selection background
		     */
		    gc = style->fg_gc[GTK_STATE_SELECTED];
		    gc_text = style->text_gc[GTK_STATE_SELECTED];
		    gdk_draw_rectangle(
			drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
			x, y, o->width, o->height
		    );
		}
		else
		{
		    gc = style->fg_gc[state];
		    gc_text = style->text_gc[state];
		}

		/* Draw the icon */
		icon = FileBrowserGetIcon(fb, o->icon_num);
		if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
		{
		    GdkBitmap *mask = icon->mask;
		    GdkPixmap *pixmap = icon->pixmap;
		    gdk_gc_set_clip_origin(gc, x, y);
		    gdk_gc_set_clip_mask(gc, mask);
		    gdk_draw_pixmap(
			drawable, gc, pixmap,
			0, 0, x, y, icon->width, icon->height
		    );
		    gdk_gc_set_clip_mask(gc, NULL);
		    icon_width = icon->width;
		}
		else
		{
		    icon_width = 0;
		}

		/* Draw the name */
		if(o->displayed_name != NULL)
		{
		    const gchar *s = o->displayed_name;
		    gdk_string_bounds(font, s, &b);
		    gdk_draw_string(
			drawable, font, gc_text,
			(x + icon_width + FB_LIST_ICON_BORDER) -
			    b.lbearing,
			(y + ((o->height - font_height) / 2)) +
			    font->ascent,
			s
		    );
		}

		/* Draw the focus rectangle around this object? */
		if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
		{
		    GdkGCValues gcv;
		    gdk_gc_get_values(gc, &gcv);
		    gdk_gc_set_function(gc, GDK_INVERT);
		    gdk_draw_rectangle(
			drawable, gc, FALSE,
			x, y, o->width - 1, o->height - 1
		    );
		    gdk_gc_set_function(gc, gcv.function);
		}
	    }
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    /* Draw each object */
	    for(i = 0; i < fb->total_objects; i++)
	    {
		o = fb->object[i];
		if(o == NULL)
		    continue;

		if((o->width <= 0) || (o->height <= 0))
		    continue;

		x = o->x - scroll_x;
		y = o->y;

		/* Is this object completely off the x axis? */
		if(((x + o->width) < 0) || (x >= width))
		    continue;

		/* Is this object selected? */
		o_is_selected = OBJISSEL(fb, i);
		if(o_is_selected)
		{
		    /* Get the selection graphic contexts and draw
		     * the selection background
		     */
		    gc = style->fg_gc[GTK_STATE_SELECTED];
		    gc_text = style->text_gc[GTK_STATE_SELECTED];
		    gdk_draw_rectangle(
			drawable, style->bg_gc[GTK_STATE_SELECTED], TRUE,
			x, y, o->width, o->height
		    );
		}
		else
		{
		    gc = style->fg_gc[state];
		    gc_text = style->text_gc[state];
		}

		/* Draw the icon */
		icon = FileBrowserGetIcon(fb, o->icon_num);
		if((icon != NULL) ? (icon->pixmap != NULL) : FALSE)
		{
		    GdkBitmap *mask = icon->mask;
		    GdkPixmap *pixmap = icon->pixmap;
		    gdk_gc_set_clip_origin(gc, x, y);
		    gdk_gc_set_clip_mask(gc, mask);
		    gdk_draw_pixmap(
			drawable, gc, pixmap,
			0, 0, x, y, icon->width, icon->height
		    );
		    gdk_gc_set_clip_mask(gc, NULL);
		    icon_width = icon->width;
		}
		else
		{
		    icon_width = 0;
		}

		/* Draw the name */
		if(o->displayed_name != NULL)
		{
		    const gchar *s = o->displayed_name;
		    gdk_string_bounds(font, s, &b);
		    gdk_draw_string(
			drawable, font, gc_text,
			(x + icon_width + FB_LIST_ICON_BORDER) -
			    b.lbearing,
			(y + ((o->height - font_height) / 2)) +
			    font->ascent,
			s
		    );
		}

		/* Draw the focus rectangle around this object? */
		if((i == fb->focus_object) && GTK_WIDGET_HAS_FOCUS(w))
		{
		    GdkGCValues gcv;
		    gdk_gc_get_values(gc, &gcv);
		    gdk_gc_set_function(gc, GDK_INVERT);
		    gdk_draw_rectangle(
			drawable, gc, FALSE,
			x, y, o->width - 1, o->height - 1
		    );
		    gdk_gc_set_function(gc, gcv.function);
		}
	    }
	    break;
	}

#if 0
	/* Draw focus rectangle if widget is in focus */
	if(GTK_WIDGET_HAS_FOCUS(w) && (state != GTK_STATE_INSENSITIVE))
#if defined(_WIN32)
	    gdk_draw_rectangle(
		drawable, style->fg_gc[state], FALSE,
		0, 0, width - 1, height - 1
	    );
#else
	    gtk_draw_focus(
		style, drawable,
		0, 0, width - 1, height - 1
	    );
#endif
#endif

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

/*
 *	Queues a redraw of the File Browser's list.
 */
static void FileBrowserListQueueDraw(FileBrowser *fb)
{
	GtkWidget *w = (fb != NULL) ? fb->list_da : NULL;
	if(w != NULL)
	    gtk_widget_queue_draw(w);
}


/*
 *	Returns the list column at index i.
 */
static FileBrowserColumn *FileBrowserListGetColumn(FileBrowser *fb, gint i)
{
	if(fb == NULL)
	    return(NULL);
	if((i < 0) || (i >= fb->total_columns))
	    return(NULL);
	else
	    return(fb->column[i]);
}

/*
 *	Appends a new list column.
 */
static FileBrowserColumn *FileBrowserListColumnAppend(
	FileBrowser *fb, const gchar *label, const gint width
)
{
	gint i;
	FileBrowserColumn *column, *column_prev;

	if(fb == NULL)
	    return(NULL);

	i = fb->total_columns;
	fb->total_columns = i + 1;
	fb->column = (FileBrowserColumn **)g_realloc(
	    fb->column, fb->total_columns * sizeof(FileBrowserColumn *)
	);
	if(fb->column == NULL)
	{
	    fb->total_columns = 0;
	    return(NULL);
	}

	fb->column[i] = column = FILE_BROWSER_COLUMN(g_malloc0(
	    sizeof(FileBrowserColumn)
	));
	if(column == NULL)
	    return(NULL);

	column->label = STRDUP(label);
	column_prev = FileBrowserListGetColumn(fb, i - 1);
	column->position = ((column_prev != NULL) ?
	    column_prev->position : 0) + width;
	column->label_justify = GTK_JUSTIFY_LEFT;
	column->flags = GTK_SENSITIVE | GTK_CAN_FOCUS |
			GTK_CAN_DEFAULT;

	column->drag = FALSE;
	column->drag_position = column->position;
	column->drag_last_drawn_position = column->drag_position;

	return(column);
}

/*
 *	Deletes all list columns.
 */
static void FileBrowserListColumnsClear(FileBrowser *fb)
{
	gint i;

	if(fb == NULL)
	    return;

	for(i = 0; i < fb->total_columns; i++)
	    FileBrowserColumnDestroyCB(fb->column[i]);
	g_free(fb->column);
	fb->column = NULL;
	fb->total_columns = 0;
}


/*
 *	Updates the entry with the selected objects, if there are no
 *	selected objects then the entry is blanked.
 */
static void FileBrowserEntrySetSelectedObjects(FileBrowser *fb)
{
	gchar *s, *s2;
	GList *glist;
	GtkEntry *entry;
	const FileBrowserObject *o;

	if(fb == NULL)
	    return;

	entry = GTK_ENTRY(fb->entry);

	s = NULL;
	for(glist = fb->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    o = FileBrowserGetObject(fb, (gint)glist->data);
	    if(o == NULL)
		continue;

	    if(o->name == NULL)
		continue;

	    if(s != NULL)
		s2 = g_strconcat(
		    s,
		    ",",
		    o->name,
		    NULL
		);
	    else
		s2 = STRDUP(o->name);
	    g_free(s);
	    s = s2;
	}

	gtk_entry_set_text(entry, (s != NULL) ? s : "");
	g_free(s);
	gtk_entry_set_position(entry, -1);
}


/*
 *	Sets the types list.
 *
 *	The list and total specifies the new types list.
 */
static void FileBrowserTypesListSetTypes(
	FileBrowser *fb,
	fb_type_struct **list, const gint total
)
{
	gint i, n;
	gchar *label;
	pulist_struct *pulist;
	pulistbox_struct *pulistbox;
	fb_type_struct *t, *t2;

	if(fb == NULL)
	    return;

	pulistbox = fb->types_pulistbox;
	pulist = PUListBoxGetPUList(pulistbox);
	if(pulist == NULL)
	    return;

	fb->freeze_count++;

	/* Delete all the types in the list */
	PUListClear(pulist);

	/* Set the new types list */
	for(i = 0; i < total; i++) 
	{
	    t = list[i];
	    if(t == NULL)
		continue;

	    if(t->ext == NULL)
		continue;

	    t2 = FB_TYPE(g_malloc0(sizeof(fb_type_struct)));
	    if(t2 == NULL)
		continue;

	    t2->ext = STRDUP(t->ext);
	    t2->name = STRDUP(t->name);

	    if(STRISEMPTY(t2->name))
		label = STRDUP(t2->ext);
	    else
		label = g_strdup_printf(
		    "%s (%s)", t2->name, t2->ext
		);
	    n = PUListAddItem(pulist, label);
	    g_free(label);
	    if(n < 0)
	    {
		FileBrowserTypeDelete(t2);
		continue;
	    }

	    PUListSetItemDataFull(
		pulist, n,
		t2, FileBrowserTypesPUListItemDestroyCB
	    );
	}

	/* Make sure that no more than 10 items are visible */
	n = PUListGetTotalItems(pulist);
	PUListBoxSetLinesVisible(
	    fb->types_pulistbox,
	    CLIP(n, 1, 10)
	);

	/* Select the first item */
	PUListBoxSelect(pulistbox, 0);
	FileBrowserTypesPUListChangedCB(pulistbox, 0, fb);

	fb->freeze_count--;
}


/*
 *	Locations popup list item destroy callback.
 */
static void FileBrowserLocationsPUListItemDestroyCB(gpointer data)
{
#if 0
	gchar *full_path = (gchar *)data;
	if(full_path == NULL)
	    return;
#endif
	g_free(data);
}

/*
 *	Types popup list item destroy callback.
 */
static void FileBrowserTypesPUListItemDestroyCB(gpointer data)
{
	FileBrowserTypeDelete(FB_TYPE(data));
}

/*
 *	File browser icon destroy callback.
 */
static void FileBrowserIconDestroyCB(gpointer data)
{
	FileBrowserIcon *icon = FILE_BROWSER_ICON(data);
	if(icon == NULL)
	    return;

	GDK_PIXMAP_UNREF(icon->pixmap);
	GDK_BITMAP_UNREF(icon->mask);
	g_free(icon->desc);
	g_free(icon);
}

/*
 *	File browser object destroy callback.
 */
static void FileBrowserObjectDestroyCB(gpointer data)
{
	FileBrowserObject *o = FILE_BROWSER_OBJECT(data);
	if(o == NULL)
	    return;

	g_free(o->displayed_name);
	g_free(o->full_path);
	g_free(o->name);
	g_free(o);
}

/*
 *	File browser list column destroy callback.
 */
static void FileBrowserColumnDestroyCB(gpointer data)
{
	FileBrowserColumn *column = FILE_BROWSER_COLUMN(data);
	if(column == NULL)
	    return;

	g_free(column->label);
	g_free(column);
}

/*
 *	File browser ok button signal callback.
 */
static void FileBrowserOKCB(GtkWidget *widget, gpointer data)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	if(FPromptIsQuery())
	{
	    FPromptBreakQuery();
	}
	else if(PUListIsQuery(PUListBoxGetPUList(fb->loc_pulistbox)))
	{
	    PUListBreakQuery(PUListBoxGetPUList(fb->loc_pulistbox));
	}
	else if(PUListIsQuery(PUListBoxGetPUList(fb->types_pulistbox)))
	{
	    PUListBreakQuery(PUListBoxGetPUList(fb->types_pulistbox));
	}
	else if(CDialogIsQuery())
	{

	}
	else
	{
	    gint i;
	    GtkWidget *w;

	    /* Mark that user response was OK */
	    fb->user_response = TRUE;

	    /* Delete previous list of selected paths */
	    for(i = 0; i < fb->total_selected_paths; i++)
		g_free(fb->selected_path[i]);
	    g_free(fb->selected_path);
	    fb->selected_path = NULL;
	    fb->total_selected_paths = 0;

	    /* Get value in entry and explode it to the list of
	     * return paths
	     */
	    w = fb->entry;
	    if(w != NULL)
	    {
		const gchar *s = gtk_entry_get_text(GTK_ENTRY(w));
		if(!STRISEMPTY(s))
		{
		    /* Explode the entry's string value at all ','
		     * characters and then prefix the current location
		     * to each exploded string
		     */
		    gint i, n;
		    const gchar *cur_location = FileBrowserGetLocation(fb);
		    gchar	*name,
				**names_list = g_strsplit(s, ",", -1);
		    for(i = 0; names_list[i] != NULL; i++)
		    {
			name = names_list[i];
/* Do not strip the spaces in the object's name since spaces may,
 * in fact, be part of the object's name
 */
/*			name = g_strstrip(name); */

			/* Append a new path to the selected_path
			 * array, the new path will be either name
			 * (if name is an absolute path) or the
			 * cur_location prefixed to name (if name is
			 * not an absolute path).
			 */
			n = MAX(fb->total_selected_paths, 0);
			fb->total_selected_paths = n + 1;
			fb->selected_path = (gchar **)g_realloc(
			    fb->selected_path,
			    fb->total_selected_paths * sizeof(gchar *)
			);
			if(fb->selected_path != NULL)
			{
			    /* If name is an absolute path then copy
			     * it to the selected_path list
			     *
			     * Otherwise copy the cur_location prefixed
			     * to name
			     */
			    if(g_path_is_absolute(name))
				fb->selected_path[n] = STRDUP(name);
			    else
				fb->selected_path[n] = STRDUP(PrefixPaths(
				    cur_location, name
				));
			}
			else
			{
			    fb->total_selected_paths = 0;
			}

			g_free(name);
		    }
		    g_free(names_list);
	        }
	        else
	        {
		    /* File name entry is empty, so set the current
		     * location as the return path.
		     */
		    const gchar *cur_location = FileBrowserGetLocation(fb);
		    gint n = MAX(fb->total_selected_paths, 0);
		    fb->total_selected_paths = n + 1;
		    fb->selected_path = (gchar **)g_realloc(
		        fb->selected_path,
		        fb->total_selected_paths * sizeof(gchar *)
		    );
		    if(fb->selected_path == NULL)
		    {
			fb->total_selected_paths = 0;
		    }
		    else
		    {
		        fb->selected_path[n] = STRDUP(cur_location);
		    }
	        }
	    }

	    /* Unmap */
	    FileBrowserUnmap();

	    /* Break out of blocking loop */
	    if(fb->block_loop_level > 0)
	    {
		gtk_main_quit();
		fb->block_loop_level--;
	    }
	}
}

/*
 *	File browser cancel button signal callback.
 */
static void FileBrowserCancelCB(GtkWidget *widget, gpointer data)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	if(FPromptIsQuery())
	{
	    FPromptBreakQuery();
	}
	else if(PUListIsQuery(PUListBoxGetPUList(fb->loc_pulistbox)))
	{
	    PUListBreakQuery(PUListBoxGetPUList(fb->loc_pulistbox));
	}
	else if(PUListIsQuery(PUListBoxGetPUList(fb->types_pulistbox)))
	{
	    PUListBreakQuery(PUListBoxGetPUList(fb->types_pulistbox));
	}
	else if(CDialogIsQuery())
	{

	}
	else
	{
	    /* Unmap */
	    FileBrowserUnmap();

	    /* Break out of blocking loop */
	    if(fb->block_loop_level > 0)
	    {
		gtk_main_quit();
		fb->block_loop_level--;
	    }
	}
}

/*
 *	Toplevel GtkWindow "delete_event" signal callback.
 */
static gint FileBrowserDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	FileBrowserCancelCB(widget, data);
	return(TRUE);
}


/*
 *	Locations popup list "changed" signal callback.
 */
static void FileBrowserLocPUListChangedCB(
	pulistbox_struct *pulistbox, gint i, gpointer data
)
{
	const gchar *location;
	pulist_struct *pulist;
	FileBrowser *fb = FILE_BROWSER(data);
	if((pulistbox == NULL) || (fb == NULL))
	    return;

	if(fb->freeze_count > 0)
	    return;

	pulist = PUListBoxGetPUList(pulistbox);
	if(pulist == NULL)
	    return;

	location = (const gchar *)PUListGetItemData(
	    pulist, i
	);
	if(location == NULL)
	    return;

	fb->freeze_count++;

	FileBrowserSetLocation(fb, location);

	fb->freeze_count--;
}


/*
 *	List header event signal callback.
 */
static gint FileBrowserListHeaderEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	GdkEventConfigure *configure;
	GdkEventExpose *expose;
	GdkEventFocus *focus;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GtkWidget *w;
	FileBrowser *fb = FILE_BROWSER(data);
	if((event == NULL) || (fb == NULL))
	    return(status);

	w = fb->list_header_da;
	if(w == NULL)
	    return(status);

	/* List widget must also be valid */
	if(fb->list_da == NULL)
	    return(status);

#define DRAW_DRAG_LINE(x)					\
{ if(column != NULL) {						\
 gint line_p = (gint)(x);					\
 GdkWindow *window1, *window2;					\
 GdkGC *gc;							\
 GtkStyle *style;						\
								\
 window1 = w->window;						\
 window2 = fb->list_da->window;					\
 style = gtk_widget_get_style(w);				\
 gc = (style != NULL) ?						\
  style->fg_gc[GTK_WIDGET_STATE(w)] : NULL;			\
								\
 if((window1 != NULL) && (window2 != NULL) && (gc != NULL)) {	\
  GdkGCValues gcv;						\
  gint width, height;						\
								\
  gdk_gc_get_values(gc, &gcv);					\
  gdk_gc_set_function(gc, GDK_INVERT);				\
								\
  /* Draw drag line on window1 */				\
  gdk_window_get_size(window1, &width, &height);		\
  gdk_draw_line(window1, gc, line_p, 0, line_p, height);	\
								\
  /* Draw drag line on window2 */				\
  gdk_window_get_size(window2, &width, &height);		\
  gdk_draw_line(window2, gc, line_p, 0, line_p, height);	\
								\
  gdk_gc_set_function(gc, gcv.function);			\
 }								\
} }

	switch((gint)event->type)
	{
	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    status = TRUE;
	    break;

	  case GDK_EXPOSE:
	    expose = (GdkEventExpose *)event;
	    FileBrowserListHeaderDraw(fb);
	    status = TRUE;
	    break;

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

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(!GTK_WIDGET_HAS_FOCUS(w))
		gtk_widget_grab_focus(w);
	    if(fb->total_columns > 0)
	    {
		gint i, cp, tolor = 3;
		gint p = (gint)button->x;
		FileBrowserColumn *column;

#define COLUMN_POSITION(p)	(((p) != NULL) ? (p)->position : 0)

		/* Iterate through all columns to update focus and
		 * reset drag state.
		 */
		for(i = 0; i < fb->total_columns; i++)
		{
		    column = fb->column[i];
		    if(column == NULL)
			continue;

		    /* Get left edge column position */
		    cp = ((i - 1) >= 0) ?
			COLUMN_POSITION(fb->column[i - 1]) : 0;

		    if((p >= (cp + tolor)) && (p < (column->position - tolor)))
			column->flags |= GTK_HAS_FOCUS;
		    else
			column->flags &= ~GTK_HAS_FOCUS;

		    column->drag = FALSE;
		}
		FileBrowserListHeaderQueueDraw(fb);

		/* Iterate through all columns, checking for one
		 * where the pointer is over its resizing area.
		 */
		for(i = fb->total_columns - 1; i >= 0; i--)
		{
		    column = fb->column[i];
		    if(column == NULL)
			continue;

		    cp = column->position;
		    if((p >= (cp - tolor)) && (p < (cp + (2 * tolor))))
		    {
			/* Update column drag values */
			column->drag = TRUE;
			column->drag_position = cp;

			/* Draw drag line on list header and list */
			DRAW_DRAG_LINE(cp);
			column->drag_last_drawn_position =
			    column->drag_position;

			break;
		    }
		}
#undef COLUMN_POSITION
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    if(fb->total_columns > 0)
	    {
		gint i, pos_shift_delta = 0;
		FileBrowserColumn *column;

		/* Iterate through all columns, checking for one that
		 * is being dragged and set that new column's
		 * position
		 */
		for(i = 0; i < fb->total_columns; i++)
		{
		    column = fb->column[i];
		    if(column == NULL)
			continue;

		    /* This column being dragged? */
		    if(column->drag)
		    {
			column->drag = FALSE;
			pos_shift_delta = column->drag_position -
			    column->position;
			column->position = column->drag_position;
		    }
		    else
		    {
			column->position += pos_shift_delta;
		    }
		}

		/* Redraw the list header and the list */
		FileBrowserListHeaderQueueDraw(fb);
		FileBrowserListQueueDraw(fb);
	    }
	    status = TRUE;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    if(fb->total_columns > 0)
	    {
		gint i, tolor = 3, left_column_pos = 0;
		GdkCursor *cursor = NULL;
		gint p = (gint)motion->x;
		FileBrowserColumn *column;


		/* Iterate through all columns and check if one is
		 * being dragged (resized) in which case it will be
		 * handled accordingly.  Also if a pointer has
		 * moved into the dragging area of a column then
		 * the new cursor will be specified.
		 */
		for(i = 0; i < fb->total_columns; i++)
		{
		    column = fb->column[i];
		    if(column == NULL)
			continue;

		    /* This column being dragged? */
		    if(column->drag)
		    {
			column->drag_position = CLIP(
			    p, left_column_pos, w->allocation.width
			);

			/* Draw drag line on list header and list */
			DRAW_DRAG_LINE(column->drag_last_drawn_position);
			DRAW_DRAG_LINE(column->drag_position);
			column->drag_last_drawn_position =
			    column->drag_position;

			/* Update cursor just in case */
			cursor = fb->cur_column_hresize;

			/* No need to handle other columns after
			 * this one since it should be the only
			 * being dragged.
			 */
			break;
		    }
		    else
		    {
			/* Column not being dragged, check if the
			 * pointer has moved into the dragging
			 * area of this column.
			 */
			gint cp = column->position;
			if((p >= (cp - tolor)) && (p < (cp + (2 * tolor))))
			{
			    cursor = fb->cur_column_hresize;
			}

			left_column_pos = column->position;
		    }
		}
		gdk_window_set_cursor(w->window, cursor);
		gdk_flush();
	    }
	    status = TRUE;
	    break;

	  case GDK_LEAVE_NOTIFY:
	    gdk_window_set_cursor(w->window, NULL);
	    gdk_flush();
	    status = TRUE;
	    break;
	}

#undef DRAW_DRAG_LINE

	return(status);
}

/*
 *	List GtkDrawingArea event signal callback.
 */
static gint FileBrowserListEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	gboolean key_press;
	GdkEventConfigure *configure;
	GdkEventExpose *expose;
	GdkEventFocus *focus;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GtkWidget *w;
	FileBrowser *fb = FILE_BROWSER(data);
	if((event == NULL) || (fb == NULL))
	    return(status);

	w = fb->list_da;
	if(w == NULL)
	    return(status);

	switch((gint)event->type)
	{
	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    /* Recreate the list's GdkPixmap buffer */
	    GDK_PIXMAP_UNREF(fb->list_pm);
	    if((configure->width > 0) && (configure->height > 0))
		fb->list_pm = gdk_pixmap_new(
		    configure->window,
		    configure->width, configure->height,
		    -1
		);
	    else
		fb->list_pm = NULL;
	    /* Update the list's object positions */
	    FileBrowserListUpdatePositions(fb);
	    status = TRUE;
	    break;

	  case GDK_EXPOSE:
	    expose = (GdkEventExpose *)event;
	    FileBrowserListDraw(fb);
	    status = TRUE;
	    break;

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

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    key_press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
#define STOP_KEY_SIGNAL_EMIT	{		\
 if(widget != NULL)				\
  gtk_signal_emit_stop_by_name(			\
   GTK_OBJECT(widget),				\
   key_press ?					\
    "key_press_event" : "key_release_event"	\
  );						\
}
	    /* First handle key event by list format */
	    switch(fb->list_format)
	    {
	      /* Vertical List Format */
	      case FB_LIST_FORMAT_VERTICAL_DETAILS:
	      case FB_LIST_FORMAT_VERTICAL:
		switch(key->keyval)
		{
		  case GDK_Up:		/* Change Focus Up */
		  case GDK_KP_Up:
		  case GDK_Left:
		  case GDK_KP_Left:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->value - adj->step_increment,
					adj->lower
				    )
				);
			}
			else if(fb->focus_object > 0)
			{
			    gint	n = fb->focus_object,
					i = n - 1;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				if(!OBJISSEL(fb, n))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)n
				    );
				if(!OBJISSEL(fb, i))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)i
				    );
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT		    
		    status = TRUE;
		    break;

		  case GDK_Down:	/* Change Focus Down */
		  case GDK_KP_Down:
		  case GDK_Right:
		  case GDK_KP_Right:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MIN(
					adj->value + adj->step_increment,
					MAX(adj->upper - adj->page_size, adj->lower)
				    )
				);
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    gint	n = fb->focus_object,
					i = n + 1;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				if(!OBJISSEL(fb, n))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)n
				    );
				if(!OBJISSEL(fb, i))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)i
				    );
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		    break;

		  case GDK_Page_Up:	/* Page Focus Up */
		  case GDK_KP_Page_Up:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->value - adj->page_increment,
					adj->lower
				    )
			        );
			}
			else
			{
			    const FileBrowserObject *o = (fb->total_objects > 0) ?
				fb->object[0] : NULL;
			    gint i, n = fb->focus_object,
				row_height = (o != NULL) ?
				    (o->height + 1) : 0;
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;

			    if((row_height > 0) && (adj != NULL))
			    {
				fb->focus_object = i = MAX(
				    fb->focus_object -
				    (gint)(adj->page_increment / row_height),
				    0
				);

				/* Select? */
				if((key->state & GDK_SHIFT_MASK) &&
				   (fb->total_objects > 0)
				)
				{
				    gint j;
				    for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
				    {
					if(!OBJISSEL(fb, j))
					    fb->selection = g_list_append(
						fb->selection, (gpointer)j
					    );
				    }
				    fb->selection_end = g_list_last(fb->selection);
				    FileBrowserEntrySetSelectedObjects(fb);
				}

				/* Scroll if focus object is not fully visible */
				if(FileBrowserListObjectVisibility(fb, i) !=
				    GTK_VISIBILITY_FULL
				)
				    FileBrowserListMoveToObject(fb, i, 0.0f);
				else
				    FileBrowserListQueueDraw(fb);
			    }
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_Page_Down:	/* Page Focus Down */
		  case GDK_KP_Page_Down:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MIN(
					adj->value + adj->page_increment,
					MAX(adj->upper - adj->page_size, adj->lower)
				    )
			        );
			}
			else
			{
			    const FileBrowserObject *o = (fb->total_objects > 0) ?
				fb->object[0] : NULL;
			    gint i, n = fb->focus_object,
				row_height = (o != NULL) ?
				    (o->height + 1) : 0;
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;

			    if((row_height > 0) && (adj != NULL))
			    {
				fb->focus_object = i = MIN(
				    fb->focus_object +
				    (gint)(adj->page_increment / row_height),
				    fb->total_objects - 1
				);

				/* Select? */
				if((key->state & GDK_SHIFT_MASK) &&
				   (fb->total_objects > 0)
				)
				{
				    gint j;
				    for(j = MAX(n, 0); j <= i; j++)
				    {
					if(!OBJISSEL(fb, j))
					    fb->selection = g_list_append(
						fb->selection, (gpointer)j
					    );
				    }
				    fb->selection_end = g_list_last(fb->selection);
				    FileBrowserEntrySetSelectedObjects(fb);
				}

				/* Scroll if focus object is not fully visible */
				if(FileBrowserListObjectVisibility(fb, i) !=
				    GTK_VISIBILITY_FULL
				)
				    FileBrowserListMoveToObject(fb, i, 1.0f);
				else
				    FileBrowserListQueueDraw(fb);
			    }
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_Home:	/* Focus Beginning */
		  case GDK_KP_Home:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    adj->lower
				);
			}
			else if(fb->focus_object > 0)
			{
			    gint	i = 0,
					n = fb->focus_object;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_End:		/* Focus End */
		  case GDK_KP_End:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_vsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->upper - adj->page_size,
					adj->lower
				    )
			        );
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    gint	i = MAX(fb->total_objects - 1, 0),
					n = fb->focus_object;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MAX(n, 0); j <= i; j++)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 1.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;
		}
		break;

	      /* Horizontal List Format */
	      case FB_LIST_FORMAT_STANDARD:
		switch(key->keyval)
		{
		  case GDK_Up:		/* Focus Up */
		  case GDK_KP_Up:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    /* No vertical scrolling */
			}
			else if(fb->focus_object > 0)
			{
			    const gint	n = fb->focus_object,
					i = n - 1;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				if(!OBJISSEL(fb, n))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)n
				    );
				if(!OBJISSEL(fb, i))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)i
				    );
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		    break;

		  case GDK_Down:	/* Focus Down */
		  case GDK_KP_Down:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    /* No vertical scrolling */
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    const gint	n = MAX(fb->focus_object, 0),
					i = n + 1;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				if(!OBJISSEL(fb, n))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)n
				    );
				if(!OBJISSEL(fb, i))
				    fb->selection = g_list_append(
					fb->selection, (gpointer)i
				    );
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		    break;

		  case GDK_Left:	/* Focus Left */
		  case GDK_KP_Left:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->value - adj->step_increment,
					adj->lower
				    )
				);
			}
			else if(fb->focus_object > 0)
			{
			    const gint	n = fb->focus_object,
					i = MAX(
			fb->focus_object - fb->objects_per_row, 0
					);

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
			        GTK_VISIBILITY_FULL
			    )
			        FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
			        FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		    break;

		  case GDK_Right:	/* Focus Right */
		  case GDK_KP_Right:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
				range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MIN(
					adj->value + adj->step_increment,
					MAX(adj->upper - adj->page_size, adj->lower)
				    )
				);
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    const gint	n = MAX(fb->focus_object, 0),
					i = MIN(
					    n + fb->objects_per_row,
					    fb->total_objects - 1
					);

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = n; j <= i; j++)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
			        GTK_VISIBILITY_FULL
			    )
			        FileBrowserListMoveToObject(fb, i, 0.5f);
			    else
			        FileBrowserListQueueDraw(fb);
			}
		    }
		    STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		    break;

		  case GDK_Page_Up:	/* Page Focus Left */
		  case GDK_KP_Page_Up:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->value - adj->page_increment,
					adj->lower
				    )
			        );
			}
			else if(fb->focus_object > 0)
			{
			    gint        n = fb->focus_object,
					i = MAX(
			fb->focus_object - fb->objects_per_row, 0
					);

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_Page_Down:	/* Page Focus Right */
		  case GDK_KP_Page_Down:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MIN(
					adj->value + adj->page_increment,
					MAX(adj->upper - adj->page_size, adj->lower)
				    )
			        );
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    gint        n = fb->focus_object,
					i = MIN(
			    fb->focus_object + fb->objects_per_row,
			    fb->total_objects - 1
					);

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MAX(n, 0); j <= i; j++)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 1.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_Home:	/* Focus Beginning */
		  case GDK_KP_Home:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    adj->lower
				);
			}
			else if(fb->focus_object > 0)
			{
			    gint        i = 0,
					n = fb->focus_object;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MIN(n, fb->total_objects - 1); j >= i; j--)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 0.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;

		  case GDK_End:		/* Focus End */
		  case GDK_KP_End:
		    if(key_press)
		    {
			/* Scroll only? */
			if(key->state & GDK_CONTROL_MASK)
			{
			    GtkRange *range = (GtkRange *)fb->list_hsb;
			    GtkAdjustment *adj = (range != NULL) ?
			        range->adjustment : NULL;
			    if(adj != NULL)
				gtk_adjustment_set_value(
				    adj,
				    MAX(
					adj->upper - adj->page_size,
					adj->lower
				    )
			        );
			}
			else if(fb->focus_object < (fb->total_objects - 1))
			{
			    gint        i = MAX(fb->total_objects - 1, 0),
					n = fb->focus_object;

			    fb->focus_object = i;

			    /* Select? */
			    if((key->state & GDK_SHIFT_MASK) &&
			       (fb->total_objects > 0)
			    )
			    {
				gint j;
				for(j = MAX(n, 0); j <= i; j++)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				}
				fb->selection_end = g_list_last(fb->selection);
				FileBrowserEntrySetSelectedObjects(fb);
			    }

			    /* Scroll if focus object is not fully visible */
			    if(FileBrowserListObjectVisibility(fb, i) !=
				GTK_VISIBILITY_FULL
			    )
				FileBrowserListMoveToObject(fb, i, 1.0f);
			    else
				FileBrowserListQueueDraw(fb);
			}
		    }
		    status = TRUE;
		    break;
		}
		break;
	    }
	    /* If the key event was not handled above then we
	     * handle it generally independent of the list
	     * format here
	     */
	    if(!status)
	    {
		switch(key->keyval)
		{
		  case GDK_Return:	/* Enter */
		  case GDK_KP_Enter:
		  case GDK_3270_Enter:
		  case GDK_ISO_Enter:
		    if(key_press)
			FileBrowserEntryEnterCB(fb->entry, fb);
		    status = TRUE;
		    break;

		  case GDK_space:	/* Toggle Select */
		    if(key_press)
		    {
			gint i = fb->focus_object;
			if(OBJISSEL(fb, i))
			    fb->selection = g_list_remove(
				fb->selection, (gpointer)i
			    );
			else
			    fb->selection = g_list_append(
				fb->selection, (gpointer)i
			    );
			fb->selection_end = g_list_last(fb->selection);
			FileBrowserListQueueDraw(fb);
			FileBrowserEntrySetSelectedObjects(fb);
		    }
		    status = TRUE;
		    break;

		  case GDK_BackSpace:
		    if(key_press)
			FileBrowserGoToParentCB(NULL, fb);
		    status = TRUE;
		    break;

		  case GDK_F5:	/* Refresh */
		    if(key_press)
			FileBrowserRefreshCB(NULL, fb);
		    status = TRUE;
		    break;

		  case GDK_Insert:	/* New Directory */
#if 0
/* Some other accelerator seems to catch this even with this widget in
 * focus so we don't need to respond to it.
 */
		    if(key_press)
			FileBrowserNewDirectoryCB(NULL, fb);
#endif
		    status = TRUE;
		    break;

		  case GDK_F2:		/* Rename */
		    if(key_press)
			FileBrowserRenameCB(NULL, fb);
		    status = TRUE;
		    break;

		  case GDK_Delete:	/* Delete */
		    if(key_press)
			FileBrowserDeleteCB(NULL, fb);
		    status = TRUE;
		    break;
#if 0
/* Some other accelerator seems to catch this even with this widget in
 * focus so we don't need to respond to it.
 */
		  case 'a':		/* Select All */
		    if(key_press && (key->state & GDK_CONTROL_MASK))
			FileBrowserSelectAllCB(NULL, fb);
		    status = TRUE;
		    break;

		  case 'u':		/* Unelect All */
		    if(key_press && (key->state & GDK_CONTROL_MASK))
			FileBrowserUnselectAllCB(NULL, fb);
		    status = TRUE;
		    break;

		  case 'i':		/* Invert Selection */
		    if(key_press && (key->state & GDK_CONTROL_MASK))
			FileBrowserInvertSelectionCB(NULL, fb);
		    status = TRUE;
		    break;
#endif
		}
	    }
#undef STOP_KEY_SIGNAL_EMIT
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    /* Unmap the floating prompt as needed */
	    if(FPromptIsQuery())
		FPromptBreakQuery();
	    /* Grab focus as needed */
	    if(!GTK_WIDGET_HAS_FOCUS(w))
		gtk_widget_grab_focus(w);
	    /* Handle by button number */
	    switch(button->button)
	    {
	      case GDK_BUTTON1:		/* Select */
		/* Select one item at a time? */
		if(button->state & GDK_CONTROL_MASK)
		{
		    const gint i = FileBrowserListSelectCoordinates(
			fb, (gint)button->x, (gint)button->y
		    );
		    if(i > -1)
		    {
			/* If new object is already selected then unselect
			 * it, otherwise add it to the selection
			 */
			fb->last_single_select_object = -1;
			fb->focus_object = i;
			if(OBJISSEL(fb, i))
			{
			    fb->selection = g_list_remove(
				fb->selection, (gpointer)i
			    );
			    fb->selection_end = g_list_last(fb->selection);
			}
			else
			{
			    fb->selection = g_list_append(
				fb->selection, (gpointer)i
			    );
			    fb->selection_end = g_list_last(fb->selection);

			    /* Update DND icon based on the newly selected
			     * object.
			     */
			    FileBrowserListObjectSetDNDIcon(fb, i);
			}

			/* If matched object is not fully visibile then
			 * scroll so that it is in the center of the
			 * list.
			 */
			if(FileBrowserListObjectVisibility(fb, i) !=
			    GTK_VISIBILITY_FULL
			)
			    FileBrowserListMoveToObject(fb, i, 0.5f);
		    }
		}
		/* Select a group of items */
		else if(button->state & GDK_SHIFT_MASK)
		{
		    const gint i = FileBrowserListSelectCoordinates(
			fb, (gint)button->x, (gint)button->y
		    );
		    if(i > -1)
		    {
			/* Select all objects between the last selected
			 * object and the current one
			 */
			fb->last_single_select_object = -1;
			fb->focus_object = i;
			if(fb->selection_end != NULL)
			{
			    const gint n = (gint)fb->selection_end->data;
			    if(i > n)
			    {
				gint j = n + 1;
				while(j <= i)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				    j++;
				}
				fb->selection_end = g_list_last(fb->selection);
			    }
			    else if(i < n)
			    {
				gint j = n - 1;
				while(j >= i)
				{
				    if(!OBJISSEL(fb, j))
					fb->selection = g_list_append(
					    fb->selection, (gpointer)j
					);
				    j--;
				}
				fb->selection_end = g_list_last(fb->selection);
			    }
			}
			else
			{
			    /* No previously selected object, so just
			     * select this one
			     */
			    fb->selection = g_list_append(
				fb->selection, (gpointer)i
			    );
			    fb->selection_end = g_list_last(fb->selection);
			}

			/* Update the DND icon based on the newly
			 * selected object
			 */
			FileBrowserListObjectSetDNDIcon(fb, i);

			/* If matched object is not fully visibile
			 * then scroll so that it is in the center
			 * of the list
			 */
			if(FileBrowserListObjectVisibility(fb, i) !=
			    GTK_VISIBILITY_FULL
			)
			    FileBrowserListMoveToObject(fb, i, 0.5f);
		    }
		}
		/* Single select */
		else
		{
		    const gint i = FileBrowserListSelectCoordinates(
			fb, (gint)button->x, (gint)button->y
		    );
		    if(i > -1)
		    {
			/* If the last single selected object (if any)
			 * is the same as the newly selected object
			 * then update last_single_select_object
			 */
			if((g_list_length(fb->selection) == 1) ?
			    ((gint)fb->selection->data == i) : FALSE
			)
			    fb->last_single_select_object = i;
			else
			    fb->last_single_select_object = -1;

			/* Unselect all */
			fb->focus_object = -1;
			g_list_free(fb->selection);
			fb->selection = fb->selection_end = NULL;

			/* Select this object */
			fb->focus_object = i;
			fb->selection = g_list_append(
			    fb->selection, (gpointer)i
			);
			fb->selection_end = g_list_last(fb->selection);

			/* Update DND icon based on the newly selected
			 * object
			 */
			FileBrowserListObjectSetDNDIcon(fb, i);

			/* If matched object is not fully visibile then
			 * scroll so that it is in the center of the
			 * list
			 */
			if(FileBrowserListObjectVisibility(fb, i) !=
			    GTK_VISIBILITY_FULL
			)
			    FileBrowserListMoveToObject(fb, i, 0.5f);
		    }
		    else
		    {
			/* Unselect all */
			fb->focus_object = -1;
			g_list_free(fb->selection);
			fb->selection = fb->selection_end = NULL;
		    }
		}
		/* Update entry with the new list of selected
		 * objects
		 */
		FileBrowserEntrySetSelectedObjects(fb);
		break;

	      case GDK_BUTTON2:
		/* Start scrolling */
		gdk_pointer_grab(
		    button->window,
		    FALSE,
		    GDK_BUTTON_PRESS_MASK |
			GDK_BUTTON_RELEASE_MASK |
			GDK_POINTER_MOTION_MASK |
			GDK_POINTER_MOTION_HINT_MASK,
		    NULL,
		    fb->cur_translate,
		    button->time
		);
		break;

	      case GDK_BUTTON3:
		/* Right-click menu */
		if(fb->list_menu != NULL)
		{
		    GtkMenu *menu = GTK_MENU(fb->list_menu);
		    gint i = FileBrowserListSelectCoordinates(
			fb, (gint)button->x, (gint)button->y
		    );
		    /* If ctrl or shift modifier keys are held then do
		     * not modify selection before mapping of menu
		     */
		    if((button->state & GDK_CONTROL_MASK) ||
		       (button->state & GDK_SHIFT_MASK)
		    )
		    {
			/* Do not modify selection, just update focus
			 * object
			 */
			fb->focus_object = i;
		    }
		    else if(i > -1)
		    {
			/* Unselect all objects */
			fb->focus_object = -1;
			if(fb->selection != NULL)
			    g_list_free(fb->selection);
			fb->selection = fb->selection_end = NULL;

			/* Select this object */
			fb->focus_object = i;
			fb->selection = g_list_append(
			    fb->selection, (gpointer)i
			);
			fb->selection_end = g_list_last(fb->selection);

			/* Update DND icon based on the newly selected
			 * object
			 */
			FileBrowserListObjectSetDNDIcon(fb, i);
		    }

		    /* Map menu */
		    gtk_menu_popup(
			menu, NULL, NULL,
			NULL, NULL,
			button->button, button->time
		    );
		}
		break;

	      case GDK_BUTTON4:
		/* Scroll left? */
		if(fb->list_format == FB_LIST_FORMAT_STANDARD)
		{
		    GtkRange *range = (GtkRange *)fb->list_hsb;
		    GtkAdjustment *adj = (range != NULL) ?
			range->adjustment : NULL;
		    if(adj != NULL)
		    {
			const gfloat inc = MAX(
			    (0.25f * adj->page_size),
			    adj->step_increment
			);
			gtk_adjustment_set_value(
			    adj,
			    MAX(
				adj->value - inc,
				adj->lower
			    )
			);
		    }
		}
		/* Scroll up */
		else
		{
		    GtkRange *range = (GtkRange *)fb->list_vsb;
		    GtkAdjustment *adj = (range != NULL) ?
			range->adjustment : NULL;
		    if(adj != NULL)
		    {
			const gfloat inc = MAX(
			    (0.25f * adj->page_size),
			    adj->step_increment
			);
			gtk_adjustment_set_value(
			    adj,
			    MAX(
				adj->value - inc,
				adj->lower
			    )
			);
		    }
		}
		break;

	      case GDK_BUTTON5:
		/* Scroll Right? */
		if(fb->list_format == FB_LIST_FORMAT_STANDARD)
		{
		    GtkRange *range = (GtkRange *)fb->list_hsb;
		    GtkAdjustment *adj = (range != NULL) ?
			range->adjustment : NULL;
		    if(adj != NULL)
		    {
			const gfloat inc = MAX(
			    (0.25f * adj->page_size),
			    adj->step_increment
			);
			gtk_adjustment_set_value(
			    adj,
			    MIN(
				adj->value + inc,
				MAX(adj->upper - adj->page_size, adj->lower)
			    )
			);
		    }
		}
		/* Scroll down */
		else
		{
		    GtkRange *range = (GtkRange *)fb->list_vsb;
		    GtkAdjustment *adj = (range != NULL) ?
			range->adjustment : NULL;
		    if(adj != NULL)
		    {
			const gfloat inc = MAX(
			    (0.25f * adj->page_size),
			    adj->step_increment
			);
			gtk_adjustment_set_value(
			    adj,
			    MIN(
				adj->value + inc,
				MAX(adj->upper - adj->page_size, adj->lower)
			    )
			);
		    }
		}
		break;
	    }
	    fb->button = button->button;
	    fb->drag_last_x = (gint)button->x;
	    fb->drag_last_y = (gint)button->y;
	    FileBrowserListQueueDraw(fb);
	    status = TRUE;
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    /* Handle by button number */
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		if(!(button->state & GDK_CONTROL_MASK) &&
		   !(button->state & GDK_SHIFT_MASK)
		)
		{
		    gulong	double_click_int = 800,
				last_time = fb->last_button1_release_time,
				cur_time = (gulong)button->time,
				dt = (
			(last_time > 0) && (cur_time > last_time)
				) ? (cur_time - last_time) : 0l;
#if 0
		    /* Slow double click? */
		    if((dt > double_click_int) &&
		       (dt <= (2 * double_click_int))
		       (dt != 0)
		    )
		    {
			/* Last single selected object did not change? */
			if(fb->last_single_select_object > -1)
			{
			    /* Call rename callback */
			    FileBrowserRenameCB(NULL, fb);
			}
		    }
#endif
		    /* Double click? */
		    if((dt <= double_click_int) && (dt != 0))
		    {
			/* Last single selected object did not change? */
			if(fb->last_single_select_object > -1)
			{
			    /* Call entry enter callback */
			    FileBrowserEntryEnterCB(fb->entry, fb);
			}
		    }
		}
		fb->last_button1_release_time = (gulong)button->time;
		break;

	      case GDK_BUTTON2:
		/* Stop scrolling as needed */
		if(gdk_pointer_is_grabbed())
		    gdk_pointer_ungrab(button->time);
		break;
	    }
	    fb->button = 0;
	    status = TRUE;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
#define STOP_SIGNAL_EMIT	{			\
 gtk_signal_emit_stop_by_name(				\
  GTK_OBJECT(widget), "motion_notify_event"		\
 );							\
}
	    switch(fb->button)
	    {
		gint dx, dy;
		GtkAdjustment *adj;
		GtkRange *range;

	      case GDK_BUTTON1:
		if(motion->is_hint)
		{
		    gint x, y;
		    GdkModifierType mask;
		    gdk_window_get_pointer(
			motion->window, &x, &y, &mask
		    );
		}
		break;

	      case GDK_BUTTON2:
		if(motion->is_hint)
		{
		    gint x, y;
		    GdkModifierType mask;
		    gdk_window_get_pointer(
			motion->window, &x, &y, &mask
		    );
		    dx = x - fb->drag_last_x;
		    dy = y - fb->drag_last_y;
		    fb->drag_last_x = x;
		    fb->drag_last_y = y;
		}
		else
		{
		    dx = (gint)(motion->x - fb->drag_last_x);
		    dy = (gint)(motion->y - fb->drag_last_y);
		    fb->drag_last_x = (gint)motion->x;
		    fb->drag_last_y = (gint)motion->y;
		}

		range = (GtkRange *)fb->list_hsb;
		adj = (range != NULL) ? range->adjustment : NULL;
		if((adj != NULL) && (dx != 0) &&
		   (fb->list_format == FB_LIST_FORMAT_STANDARD)
		)
		{
		    if((adj->upper - adj->page_size) > adj->lower)
			gtk_adjustment_set_value(
			    adj,
			    CLIP(
				adj->value - dx,
				adj->lower, adj->upper - adj->page_size
			    )
			);
		}

		range = (GtkRange *)fb->list_vsb;
		adj = (range != NULL) ? range->adjustment : NULL;
		if((adj != NULL) && (dy != 0) &&
		   ((fb->list_format == FB_LIST_FORMAT_VERTICAL) ||
		    (fb->list_format == FB_LIST_FORMAT_VERTICAL_DETAILS)
		   )
		)
		{
		    if((adj->upper - adj->page_size) > adj->lower)
			gtk_adjustment_set_value(
			    adj,
			    CLIP(
				adj->value - dy,
				adj->lower, adj->upper - adj->page_size
			    )
			);
		}
		STOP_SIGNAL_EMIT
		status = TRUE;
		break;
	    }
#undef STOP_SIGNAL_EMIT
	    break;
	}

	return(status);
}

/*
 *	List scrollbar GtkAdjustment "value_changed" signal callback.
 */
static void FileBrowserListScrollCB(
	GtkAdjustment *adj, gpointer data
)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	FileBrowserListQueueDraw(fb);
}

/*
 *	Select all callback.
 */
static void FileBrowserSelectAllCB(GtkWidget *widget, gpointer data)
{
	gint i;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	if(fb->selection != NULL)
	    g_list_free(fb->selection);
	fb->selection = NULL;
	for(i = 0; i < fb->total_objects; i++)
	    fb->selection = g_list_append(
		fb->selection, (gpointer)i
	    );
	fb->selection_end = g_list_last(fb->selection);
	FileBrowserListQueueDraw(fb);
	FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *	Unselect all callback.
 */
static void FileBrowserUnselectAllCB(GtkWidget *widget, gpointer data)
{
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	if(fb->selection != NULL)
	    g_list_free(fb->selection);
	fb->selection = fb->selection_end = NULL;
	FileBrowserListQueueDraw(fb);
	FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *	Invert selection callback.
 */
static void FileBrowserInvertSelectionCB(GtkWidget *widget, gpointer data)
{
	gint i;
	GList *glist = NULL;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	for(i = 0; i < fb->total_objects; i++)
	{
	    if(!OBJISSEL(fb, i))
		glist = g_list_append(glist, (gpointer)i);
	}
	if(fb->selection != NULL)
	    g_list_free(fb->selection);
	fb->selection = glist;
	fb->selection_end = g_list_last(fb->selection);
	FileBrowserListQueueDraw(fb);
	FileBrowserEntrySetSelectedObjects(fb);
}

/*
 *	Used by FileBrowserRenameCB() as the apply callback for
 *	the floating prompt.
 */
static void FileBrowserRenameFPromptCB(
	gpointer data, const gchar *value
)
{
	gint i;
	GList *glist;
	FileBrowserObject *o;
	FileBrowser *fb = FILE_BROWSER(data);
	if((fb == NULL) || STRISEMPTY(value))
	    return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_RENAME_FAILED     "Reagrupe Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_RENAME_FAILED     "Renommer Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_RENAME_FAILED     "Benennen Sie Versagt Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_RENAME_FAILED     "Rinominare Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_RENAME_FAILED     "Herdoop Geverzuimenene"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_RENAME_FAILED     "O Rename Fracassou"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_RENAME_FAILED     "Ombenevn Failed"
#else
# define TITLE_RENAME_FAILED     "Rename Failed"
#endif

#define MESSAGE_RENAME_FAILED(s)	{	\
 CDialogSetTransientFor(fb->toplevel);		\
 CDialogGetResponse(				\
  TITLE_RENAME_FAILED,				\
  (s), NULL,					\
  CDIALOG_ICON_WARNING,				\
  CDIALOG_BTNFLAG_OK,				\
  CDIALOG_BTNFLAG_OK				\
 );						\
 CDialogSetTransientFor(NULL);			\
}

	/* No directory deliminators may exist in the new value */
	if(strchr(value, G_DIR_SEPARATOR) != NULL)
	{
	    gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"El nombre nuevo \"%s\" contiene deliminators de\n\
guía de '%c' que no se permiten en un nombre de objeto."
#elif defined(PROG_LANGUAGE_FRENCH)
"Le nouveau \"%s\" de nom contient deliminators d'annuaire\n\
de '%c' qui ne sont pas permis dans un nom de l'objet."
#elif defined(PROG_LANGUAGE_GERMAN)
"Der neu \"%s\" enthält '%c' Verzeichnis deliminators, das\n\
im Namen eines Objekts nicht erlaubt wird."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Lo \"%s\" di nome nuovo contiene il deliminators\n\
di elenco di '%c' che non sono lasciati in un nome dell'oggetto."
#elif defined(PROG_LANGUAGE_DUTCH)
"De nieuwe naam \"%s\" bevat '%c' gids deliminators,\n\
die welk in de naam van een voorwerp niet toegestaan is."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O novo \"%s\" de nome contem deliminators de guia\n\
de '%c' que nao são permitidos num nome do objeto."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Den nye navne \"%s\" inneholder '%c' katalog deliminators\n\
som ikke tillater i et objekts navn."
#else
"The new name \"%s\" contains '%c' directory\n\
deliminators which are not allowed in an object's name"
#endif
		,
		value, G_DIR_SEPARATOR
	    );
	    MESSAGE_RENAME_FAILED(s);
	    g_free(s);
	    return;
	}

	/* Get last selected object */
	glist = fb->selection_end;
	i = (glist != NULL) ? (gint)glist->data : -1;
	o = FileBrowserGetObject(fb, i);
	if((o != NULL) ? (o->full_path != NULL) : FALSE)
	{
	    const gchar *cur_location = FileBrowserGetLocation(fb);
	    gchar	*old_full_path = STRDUP(o->full_path),
			*new_full_path = STRDUP(
			    PrefixPaths(cur_location, value)
			);
	    struct stat lstat_buf;

	    /* New name and old name the same? */
	    if(!strcmp(o->name, value))
	    {

	    }
	    /* Make that the new name does not refer to an object that
	     * already exists
	     */
#ifdef _WIN32
	    else if(stat(new_full_path, &lstat_buf) == 0)
#else
	    else if(lstat(new_full_path, &lstat_buf) == 0)
#endif
	    {
		gchar *s = g_strdup_printf(
"An object by the name of \"%s\" already exists.",
		    value
		);
		MESSAGE_RENAME_FAILED(s);
		g_free(s);
	    }
	    /* Rename */
	    else if(rename(old_full_path, new_full_path))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strdup_printf(
		    "%s.",
		    g_strerror(error_code)
		);
		MESSAGE_RENAME_FAILED(s);
		g_free(s);
	    }
	    else
	    {
		/* Rename successful */

		/* Update the list */
		FileBrowserListUpdate(fb, NULL);

		/* Reselect object */
		for(i = 0; i < fb->total_objects; i++)
		{
		    o = fb->object[i];
		    if((o != NULL) ? (o->full_path == NULL) : TRUE)
			continue;

		    if(!strcmp(o->full_path, new_full_path))
		    {
			/* Select this object */
			fb->focus_object = i;
			fb->selection = g_list_append(
			    fb->selection, (gpointer)i
			);
			fb->selection_end = g_list_last(fb->selection);

			/* Scroll to this object */
			FileBrowserListMoveToObject(fb, i, 0.5f);

			break;
		    }
		}
		FileBrowserListQueueDraw(fb);

		/* Update selected objects on entry */
		FileBrowserEntrySetSelectedObjects(fb);
	    }

	    g_free(old_full_path);
	    g_free(new_full_path);
	}

#undef MESSAGE_RENAME_FAILED
#undef TITLE_RENAME_FAILED
}

/*
 *	Rename callback.
 */
static void FileBrowserRenameCB(GtkWidget *widget, gpointer data)
{
	gint i, x, y, width, height;
	const GList *glist;
	const FileBrowserObject *o;
	FileBrowser *fb = FILE_BROWSER(data);
	if((fb == NULL) || FPromptIsQuery())
	    return;

	/* Get last selected object */
	glist = fb->selection_end;
	i = (glist != NULL) ? (gint)glist->data : -1;
	o = FileBrowserGetObject(fb, i);
	if(o == NULL)
	    return;

	/* Get fprompt geometry based on list display format */
	x = o->x;
	y = o->y;
	width = MAX(o->width + 4, 100);
	height = -1;
	switch(fb->list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    if(fb->list_vsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_vsb);
		GtkAdjustment *adj = range->adjustment;
		y -= (gint)((adj != NULL) ? adj->value : 0.0f);
	    }
	    break;
	  case FB_LIST_FORMAT_STANDARD:
	    if(fb->list_hsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_hsb);
		GtkAdjustment *adj = range->adjustment;
		x -= (gint)((adj != NULL) ? adj->value : 0.0f);
	    }
	    break;
	}

	/* Get root window relative coordinates of the object for
	 * placement of the floating prompt.
	 */
	if(fb->list_da != NULL)
	{
	    GtkWidget *w = fb->list_da;
	    GdkWindow *window = w->window;
	    if(window != NULL)
	    {
		gint rx, ry;
		gdk_window_get_deskrelative_origin(
		    window, &rx, &ry
		);
		x += rx;
		y += ry;
	    }
	}
	/* At this point x and y should now be the root window
	 * relative position for the floating prompt
	 */
	FPromptSetTransientFor(fb->toplevel);
	FPromptSetPosition(x - 2, y - 2);
	FPromptMapQuery(
	    NULL,			/* No label */
	    o->name,
	    NULL,			/* No tooltip */
	    FPROMPT_MAP_NO_MOVE,	/* Map code */
	    width, height,		/* Width and height */
	    0,				/* No buttons */
	    fb,				/* Data */
	    NULL,			/* No browse callback */
	    FileBrowserRenameFPromptCB,
	    NULL			/* No cancel callback */
	);
}

/*
 *	CHMod fprompt callback.
 */
static void FileBrowserCHModFPromptCB(
	gpointer data, const gchar *value
)
{
	gint i;
	GList *glist;
	FileBrowserObject *o;
	FileBrowser *fb = FILE_BROWSER(data);
	if((fb == NULL) || STRISEMPTY(value))
	    return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_CHMOD_FAILED     "Cambie El Modo Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_CHMOD_FAILED     "Changer Le Mode A échoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_CHMOD_FAILED     "Ändern Sie Versagten Modus"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_CHMOD_FAILED     "Cambiare Il Modo Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_CHMOD_FAILED     "Verandeer Modus Verzuimde"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_CHMOD_FAILED     "Mude Modo Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_CHMOD_FAILED     "Forandr Sviktet Modus"
#else
# define TITLE_CHMOD_FAILED     "Change Mode Failed"
#endif

#define MESSAGE_CHMOD_FAILED(s)		{	\
 CDialogSetTransientFor(fb->toplevel);		\
 CDialogGetResponse(				\
  TITLE_CHMOD_FAILED,				\
  (s), NULL,					\
  CDIALOG_ICON_WARNING,				\
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK	\
 );						\
 CDialogSetTransientFor(NULL);			\
}

	/* Get last selected object */
	glist = fb->selection_end;
	i = (glist != NULL) ? (gint)glist->data : -1;
	o = FileBrowserGetObject(fb, i);
	if((o != NULL) ? (o->full_path != NULL) : FALSE)
	{
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
	    mode_t m = 0;
	    const gchar *s = value;

	    /* Begin parsing value to obtain new mode value */
	    while(ISBLANK(*s))
		s++;

	    /* User */
	    /* Read */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'r')
		    m |= S_IRUSR;
		s++;
	    }
	    /* Write */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'w')
		    m |= S_IWUSR;
		s++;
	    }
	    /* Execute */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'x')
		    m |= S_IXUSR;
		else if(tolower(*s) == 's')
		    m |= S_ISUID;
		s++;
	    }
	    /* Group */
	    /* Read */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'r')
		    m |= S_IRGRP;
		s++;
	    }
	    /* Write */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'w')
		    m |= S_IWGRP;
		s++;
	    }
	    /* Execute */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'x')
		    m |= S_IXGRP;
		else if(tolower(*s) == 'g')
		    m |= S_ISGID;
		s++;
	    }
	    /* Other */
	    /* Read */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'r')
		    m |= S_IROTH;
		s++;
	    }
	    /* Write */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'w')
		    m |= S_IWOTH;
		s++;
	    }
	    /* Execute */
	    if(*s != '\0')
	    {
		if(tolower(*s) == 'x')
		    m |= S_IXOTH;
		else if(tolower(*s) == 't')
		    m |= S_ISVTX;
		s++;
	    }

	    /* CHMod */
	    if(chmod(o->full_path, m))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strdup_printf(
		    "%s.",
		    g_strerror(error_code)
		);
		MESSAGE_CHMOD_FAILED(s);
		g_free(s);
	    }
	    else
	    {
		/* Statistics */
#ifdef _WIN32
		if(stat(o->full_path, &o->lstat_buf))
#else
		if(lstat(o->full_path, &o->lstat_buf))
#endif
		    memset(&o->lstat_buf, 0x00, sizeof(struct stat));

		/* Update values */
		FileBrowserObjectUpdateValues(fb, o);
	    }
#endif
	}

	/* Need to redraw and update the value in the entry
	 * since one of the selected object's names may have
	 * changed
	 */
	FileBrowserListQueueDraw(fb);
	FileBrowserEntrySetSelectedObjects(fb);

#undef MESSAGE_RENAME_FAILED
#undef TITLE_RENAME_FAILED
}

/*
 *	CHMod callback.
 */
static void FileBrowserCHModCB(GtkWidget *widget, gpointer data)
{
	gint i, x, y, width, height;
	mode_t m;
	gchar *value;
	const GList *glist;
	const FileBrowserObject *o;
	const FileBrowserColumn *column, *column2;
	FileBrowser *fb = FILE_BROWSER(data);
	if((fb == NULL) || FPromptIsQuery())
	    return;

	/* Get last selected object */
	glist = fb->selection_end;
	i = (glist != NULL) ? (gint)glist->data : -1;
	o = FileBrowserGetObject(fb, i);
	if(o == NULL)
	    return;

	m = o->lstat_buf.st_mode;

#ifdef S_ISLNK
	/* Cannot chmod links */
	if(S_ISLNK(m))
	{
	    CDialogSetTransientFor(fb->toplevel);
	    CDialogGetResponse(
"Change Mode Failed",
"You cannot change the mode of symbolic link objects",
		NULL,
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}
#endif

#if !defined(S_IRUSR) || !defined(S_IWUSR) || !defined(S_IXUSR)
	/* Since permissions not supported we cannot chmod */
	if(TRUE)
	{
	    CDialogSetTransientFor(fb->toplevel);
	    CDialogGetResponse(
"Change Mode Failed",
"Changing the mode of objects is not supported on this system.",
		NULL,
		CDIALOG_ICON_WARNING,
		CDIALOG_BTNFLAG_OK,
		CDIALOG_BTNFLAG_OK
	    );
	    CDialogSetTransientFor(NULL);
	    return;
	}
#else
	/* Format current value */
	value = g_strdup_printf(
	    "%c%c%c%c%c%c%c%c%c",
	    (m & S_IRUSR) ? 'r' : '-',
	    (m & S_IWUSR) ? 'w' : '-',
	    (m & S_ISUID) ? 'S' :
		((m & S_IXUSR) ? 'x' : '-'),
	    (m & S_IRGRP) ? 'r' : '-',
	    (m & S_IWGRP) ? 'w' : '-',
	    (m & S_ISGID) ? 'G' :
		((m & S_IXGRP) ? 'x' : '-'),
	    (m & S_IROTH) ? 'r' : '-',
	    (m & S_IWOTH) ? 'w' : '-',
	    (m & S_ISVTX) ? 'T' :
		((m & S_IXOTH) ? 'x' : '-')
	);
#endif

	/* Get fprompt geometry based on list display format */
	x = o->x;
	y = o->y;
	width = MAX(o->width + 4, 80);
	height = -1;
	switch(fb->list_format)
	{
	  case FB_LIST_FORMAT_VERTICAL_DETAILS:
	  case FB_LIST_FORMAT_VERTICAL:
	    if(fb->list_vsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_vsb);
		GtkAdjustment *adj = range->adjustment;
		y -= (gint)((adj != NULL) ? adj->value : 0.0f);
	    }
	    column = FileBrowserListGetColumn(fb, 1);
	    if(column != NULL)
		x += column->position;
	    column2 = FileBrowserListGetColumn(fb, 2);
	    if((column != NULL) && (column2 != NULL))
		width = MAX(column2->position - column->position + 4, 80);
	    break;

	  case FB_LIST_FORMAT_STANDARD:
	    if(fb->list_hsb != NULL)
	    {
		GtkRange *range = GTK_RANGE(fb->list_hsb);
		GtkAdjustment *adj = range->adjustment;
		x -= (gint)((adj != NULL) ? adj->value : 0.0f);
	    }
	    break;
	}
	/* Get root window relative coordinates of the object for
	 * placement of the floating prompt
	 */
	if(fb->list_da != NULL)
	{
	    GtkWidget *w = fb->list_da;
	    GdkWindow *window = w->window;
	    if(window != NULL)
	    {
		gint rx, ry;
		gdk_window_get_deskrelative_origin(
		    window, &rx, &ry
		);
		x += rx;
		y += ry;
	    }
	}
	/* At this point x and y should now be the root window
	 * relative position for the floating prompt
	 */
	FPromptSetTransientFor(fb->toplevel);
	FPromptSetPosition(x - 2, y - 2);
	FPromptMapQuery(
	    NULL,			/* No label */
	    value,
	    NULL,			/* No tooltip */
	    FPROMPT_MAP_NO_MOVE,	/* Map code */
	    width, height,		/* Width and height */
	    0,				/* No buttons */
	    fb,				/* Data */
	    NULL,			/* No browse callback */
	    FileBrowserCHModFPromptCB,
	    NULL                        /* No cancel callback */
	);

	g_free(value);
}

/*
 *	List object delete callback.
 */
static void FileBrowserDeleteCB(GtkWidget *widget, gpointer data)
{
	gint objects_deleted = 0;
	gint i, response;
	gchar *msg;
	GList *glist;
	FileBrowserObject *o;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

#if defined(PROG_LANGUAGE_SPANISH)
# define TITLE_DELETE_FAILED	"Borre Fallado"
#elif defined(PROG_LANGUAGE_FRENCH)
# define TITLE_DELETE_FAILED	"Effacer Echoué"
#elif defined(PROG_LANGUAGE_GERMAN)
# define TITLE_DELETE_FAILED	"Löschen Sie Versagt"
#elif defined(PROG_LANGUAGE_ITALIAN)
# define TITLE_DELETE_FAILED	"Cancellare Fallito"
#elif defined(PROG_LANGUAGE_DUTCH)
# define TITLE_DELETE_FAILED	"Schrap Geverzuimenene"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
# define TITLE_DELETE_FAILED	"Anule Fracassado"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
# define TITLE_DELETE_FAILED	"Stryk Failed"
#else
# define TITLE_DELETE_FAILED	"Delete Failed"
#endif

#define MESSAGE_DELETE_FAILED(s)	{	\
 CDialogSetTransientFor(fb->toplevel);		\
 CDialogGetResponse(				\
  TITLE_DELETE_FAILED,				\
  (s), NULL,					\
  CDIALOG_ICON_WARNING,				\
  CDIALOG_BTNFLAG_OK, CDIALOG_BTNFLAG_OK	\
 );						\
 CDialogSetTransientFor(NULL);			\
}

	if(CDialogIsQuery())
	    return;

	/* Get selection */
	glist = fb->selection;
	if(glist == NULL)
	    return;

	FileBrowserSetBusy(fb, TRUE);

	/* Format confirmation message, if only one object is selected
	 * then confirm with its name. Otherwise confirm the number of
	 * objects to be deleted.
	 */
	i = g_list_length(glist);
	if(i == 1)
	{
	    i = (gint)glist->data;
	    o = FileBrowserGetObject(fb, i);
	    if((o != NULL) ? (o->name != NULL) : FALSE)
		msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"¿Usted está seguro que usted quiere borrar \"%s\"?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Etes-vous sûr que vous voulez effacer \"%s\"?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Sind sie sicher sie \"%s\" wollen löschen?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Lei sono sicuro che lei vuole cancellare \"%s\"?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bent u zeker u \"%s\" wil schrappen?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Estão seguro quer anular \"%s\"?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Er De sikker De stryker \"%s\"?"
#else
"Are you sure you want to delete \"%s\"?"
#endif
		    , o->name
		);
	    else
		msg = STRDUP(
"Are you sure you want to delete this (unnamed) object?"
		);
	}
	else
	{
	    msg = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"¿Borra %i objetos escogidos?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Efface %i objets choisis?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen Sie %i ausgewählte Objekte?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancella %i oggetti scelti?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap %i geselecteerde voorwerpen?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule %i objetos selecionados?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk %i valgte ut objekt?"
#else
"Delete %i selected objects?"
#endif
		, i
	    );
	}

	/* Confirm delete */
	CDialogSetTransientFor(fb->toplevel);
	response = CDialogGetResponseIconData(
#if defined(PROG_LANGUAGE_SPANISH)
"Confirme Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Confirmer Effacer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Bestätigen Sie Löscht"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Confermare Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bevestiig Schrappet"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Confirme Anula"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Bekreft Delete"
#else
"Confirm Delete"
#endif
	    , msg, NULL,
	    (guint8 **)icon_trash_32x32_xpm,
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
	    CDIALOG_BTNFLAG_NO
	);
	CDialogSetTransientFor(NULL);
	g_free(msg);
	if(response != CDIALOG_RESPONSE_YES)
	{
	    FileBrowserSetBusy(fb, FALSE);
	    return;
	}

	/* Iterate through selected objects */
	while(glist != NULL)
	{
	    i = (gint)glist->data;
	    o = FileBrowserGetObject(fb, i);
	    if((o != NULL) ? (o->full_path != NULL) : FALSE)
	    {
		const gchar *full_path = o->full_path;
		if(ISLPATHDIR(full_path))
		{
		    /* Delete directory */
		    if(rmdir(full_path))
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strdup_printf(
			    "%s.",
			    g_strerror(error_code)
			);
			MESSAGE_DELETE_FAILED(s);
			g_free(s);
		    }
		    else
		    {
			objects_deleted++;
		    }
		}
		else
		{
		    /* Delete object */
		    if(unlink((const char *)full_path))
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strdup_printf(
			    "%s.",
			    g_strerror(error_code)
			);
			MESSAGE_DELETE_FAILED(s);
			g_free(s);
		    }
		    else
		    {
			objects_deleted++;
		    }
		}
	    }

	    glist = g_list_next(glist);
	}

	/* Need to refresh everything due to delete */
	FileBrowserRefreshCB(fb->refresh_btn, fb);

	FileBrowserSetBusy(fb, FALSE);

#undef MESSAGE_DELETE_FAILED
#undef TITLE_DELETE_FAILED
}


/*
 *	Entry complete path "key_press_event" or "key_release_event"
 *	signal callback.
 */
static gint FileBrowserEntryCompletePathKeyCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
	gint status = FALSE;
	gboolean press;
	GtkEntry *entry = GTK_ENTRY(widget);
	FileBrowser *fb = FILE_BROWSER(data);
	if((entry == NULL) || (key == NULL) || (fb == NULL))
	    return(status);

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

#define SIGNAL_EMIT_STOP	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
  press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}

	switch(key->keyval)
	{
	  case GDK_Tab:
	    /* Skip this if the shift or ctrl keys are held */
	    if((key->state & GDK_CONTROL_MASK) ||
	       (key->state & GDK_SHIFT_MASK)
	    )
		return(status);

	    if(press)
	    {
		gchar *s = STRDUP(gtk_entry_get_text(entry));
		if(s != NULL)
		{
		    gint status;

		    /* Because the entry is usually blank or not an
		     * absolute path, the path must first be made into
		     * an absolute path before completing it
		     */
		    if(!ISPATHABSOLUTE(s))
		    {
			if(STRISEMPTY(s))
			{
			    g_free(s);
			    s = STRDUP(FileBrowserGetLocation(fb));
			}
			else
			{
			    gchar *s2 = STRDUP(PrefixPaths(
				FileBrowserGetLocation(fb), s
			    ));
			    g_free(s);
			    s = s2;
			}
		    }

		    s = CompletePath(s, &status);
		    gtk_entry_set_text(entry, (s != NULL) ? s : "");
		    gtk_entry_set_position(entry, -1);
		    g_free(s);

		    switch(status)
		    {
		      case COMPLETE_PATH_NONE:
		      case COMPLETE_PATH_AMBIGUOUS:
			gdk_beep();
			break;
		    }
		}
	    }
	    SIGNAL_EMIT_STOP
	    status = TRUE;
	    break;
	}

#undef SIGNAL_EMIT_STOP

	return(status);
}

/*
 *	Entry enter callback.
 *
 *	This function is slightly different from the OK callback
 *	since it will switch to a directory if the single value
 *	is a directory.
 *
 *	This function will call the OK callback if there are
 *	multiple values specified (when it sees a ',' deliminator)
 *	or the single object is specified and (if it actually exists)
 *	does not lead to a directory.
 */
static void FileBrowserEntryEnterCB(GtkWidget *widget, gpointer data)
{
	gchar *s;
	GtkWidget *w;
	FileBrowser *fb = FILE_BROWSER(data);
	if(fb == NULL)
	    return;

	w = fb->entry;
	if(w == NULL)
	    return;

	/* Get file name entry value */
	s = gtk_entry_get_text(GTK_ENTRY(w));
	if((s != NULL) ? (*s == '\0') : TRUE)
	    return;

	s = STRDUP(s);	/* Make a copy of s */

	/* Filter specified? */
	if((strchr(s, '*') != NULL) || (strchr(s, '?') != NULL))
	{
	    /* Reget the listing with the newly specified filter */
	    FileBrowserListUpdate(fb, s);
	}
	/* Multiple objects specified? */
	else if(strchr(s, ','))
	{
	    /* Multiple objects selected, just call the OK callback */
	    FileBrowserOKCB(fb->ok_btn, fb);
	}
	/* Current directory specified? */
	else if(!strcmp(s, "."))
	{
	    /* Do nothing */
	}
	/* Parent directory specified? */
	else if(!strcmp(s, ".."))
	{
	    /* Go to parent location */
	    const gchar *cur_location = FileBrowserGetLocation(fb);
	    gchar *parent = (cur_location != NULL) ?
		g_dirname(cur_location) : NULL;
	    FileBrowserSetLocation(fb, parent);
	    g_free(parent);
	}
	/* Home directory specified? */
	else if(*s == '~')
	{
	    /* Go to home directory */
	    FileBrowserSetLocation(fb, s);
	}
	else
	{
	    /* All else assume single object specified, it may or may
	     * not actually exist
	     */
	    gchar *full_path;
	    struct stat stat_buf;

	    /* Check if the object specified is specified as a full
	     * path and if it is not then prefix the current location
	     * to it
	     */
	    if(g_path_is_absolute(s))
	    {
		full_path = STRDUP(s);
	    }
	    else
	    {
		const gchar *cur_location = FileBrowserGetLocation(fb);
		full_path = STRDUP(PrefixPaths(cur_location, s));
	    }

	    /* Check if the object specified by full_path exists and if
	     * it leads to a directory
	     */
	    if(stat(full_path, &stat_buf))
	    {
		/* Cannot stat object (note that it may exist locally
		 * as a symbolic link), the user may want to save as a
		 * new object, so go ahead and call the OK callback on
		 * this
		 */
		FileBrowserOKCB(fb->ok_btn, fb);
	    }
	    else
	    {
		/* If the object specified by full_path is a directory
		 * then go to that directory, otherwise call the OK
		 * callback
		 */
#ifdef S_ISDIR
		if(S_ISDIR(stat_buf.st_mode))
#else
		if(FALSE)
#endif
		    FileBrowserSetLocation(fb, full_path);
		else
		    FileBrowserOKCB(fb->ok_btn, fb);
	    }

	    g_free(full_path);
	}

	g_free(s);
}

/*
 *	Types popup list box "changed" signal callback.
 */
static void FileBrowserTypesPUListChangedCB(
	pulistbox_struct *pulistbox, gint i, gpointer data
)
{
	pulist_struct *pulist;
	fb_type_struct *t_sel, *t;
	FileBrowser *fb = FILE_BROWSER(data);
	if((pulistbox == NULL) || (fb == NULL))
	    return;

	pulist = PUListBoxGetPUList(pulistbox);
	if(pulist == NULL)
	    return;

	t_sel = FB_TYPE(PUListGetItemData(pulist, i));
	if(t_sel == NULL)
	    return;

	t = &fb->cur_type;

	g_free(t->ext);
	t->ext = STRDUP(t_sel->ext);

	g_free(t->name);
	t->name = STRDUP(t_sel->name);

	/* Update the listings only if not frozen */
	if(fb->freeze_count <= 0)
	{
	    fb->freeze_count++;

	    /* Update the list */
	    FileBrowserListUpdate(fb, NULL);

	    fb->freeze_count--;
	}
}


/*
 *	Initializes the file browser.
 */
gint FileBrowserInit(void)
{
	const gint	border_major = 5,
			border_minor = 2;
	gint i;
	GdkWindow *window;
	GtkAdjustment *adj;
	GtkAccelGroup *accelgrp;
	GtkWidget *w, *parent, *parent2, *parent3, *parent4;
	pulist_struct *pulist;
	pulistbox_struct *pulistbox;
	FileBrowser *fb = &file_browser;

	/* Reset values */
	fb->busy_count = 0;
	fb->freeze_count = 0;
	fb->accelgrp = accelgrp = gtk_accel_group_new();
	fb->cur_busy = gdk_cursor_new(GDK_WATCH);
	fb->cur_column_hresize = gdk_cursor_new(GDK_SB_H_DOUBLE_ARROW);
	fb->cur_translate = gdk_cursor_new(GDK_FLEUR);
	fb->list_pm = NULL;
	fb->vsb_map_state = FALSE;
	fb->hsb_map_state = FALSE;
	fb->cur_location = NULL;
	fb->block_loop_level = 0;
	fb->user_response = FALSE;
	fb->list_format = FB_LIST_FORMAT_STANDARD;
	fb->column = NULL;
	fb->total_columns = 0;
	memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));
	fb->selected_path = NULL;
	fb->total_selected_paths = 0;
	fb->icon = NULL;
	fb->total_icons = 0;
#ifdef _WIN32
	fb->uid = 0;
	fb->euid = 0;
	fb->gid = 0;
	fb->egid = 0;
#else
	fb->uid = getuid();
	fb->euid = geteuid();
	fb->gid = getgid();
	fb->egid = getegid();
#endif
#ifdef _WIN32
	fb->home_path = STRDUP("C:\\");
#else
	fb->home_path = STRDUP(g_getenv("HOME"));
#endif
	fb->device_paths_list = NULL;
	fb->focus_object = -1;
	fb->selection = fb->selection_end = NULL;
	fb->object = NULL;
	fb->total_objects = 0;
	fb->objects_per_row = 0;
	fb->last_single_select_object = -1;
	fb->show_hidden_objects = TRUE;
	fb->button = 0;
	fb->drag_last_x = 0;
	fb->drag_last_y = 0;
	fb->last_button1_release_time = 0l;

	/* Toplevel */
	fb->toplevel = w = gtk_window_new(GTK_WINDOW_DIALOG);
	gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, TRUE);
	gtk_widget_set_usize(w, FB_WIDTH, FB_HEIGHT);
	gtk_window_set_title(GTK_WINDOW(w), "Select File");
#ifdef PROG_NAME
	gtk_window_set_wmclass(
	    GTK_WINDOW(w), "fileselection", PROG_NAME
	);
#endif
	gtk_widget_realize(w);
	window = w->window;
	if(window != NULL)
	{
	    GdkGeometry geo;

	    gdk_window_set_decorations(
		window,
		GDK_DECOR_BORDER | GDK_DECOR_TITLE
	    );
	    gdk_window_set_functions(
		window,
		GDK_FUNC_MOVE | GDK_FUNC_CLOSE
	    );

	    geo.min_width = 100;
	    geo.min_height = 70;
	    geo.max_width = gdk_screen_width() - 10;
	    geo.max_height = gdk_screen_height() - 10;
	    geo.base_width = 0;
	    geo.base_height = 0;
	    geo.width_inc = 1;
	    geo.height_inc = 1;
	    geo.min_aspect = 1.3f;
	    geo.max_aspect = 1.3f;
	    gdk_window_set_geometry_hints(
		window, &geo,
		GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE |
		GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC
	    );
	}
	gtk_widget_add_events(
	    w,
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "delete_event",
	    GTK_SIGNAL_FUNC(FileBrowserDeleteEventCB), fb
	);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
	parent = w;

	/* Main vbox */
	fb->main_vbox = w = gtk_vbox_new(FALSE, border_major);
	gtk_container_border_width(GTK_CONTAINER(w), border_major);
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);
	parent = w;


	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;


	/* GtkHBox for the location label and locations popup list */
	w = gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent3 = w;

	w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Ubicación:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Emplacement:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ort:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Posizione:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Plaats:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Localidade:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Plassering:"
#else
"Location:"
#endif
	);
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Locations popup list box */
	fb->loc_pulistbox = pulistbox = PUListBoxNew(
	    parent3,
	    -1, 20 + (2 * 3)
	);
	PUListBoxSetChangedCB(
	    pulistbox,
	    FileBrowserLocPUListChangedCB, fb
	);
	PUListBoxMap(pulistbox);


	/* Go To Parent */
	fb->goto_parent_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_folder_parent_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserGoToParentCB), fb
	);
#if 0
	gtk_accel_group_add(
	    accelgrp, GDK_BackSpace, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
#endif
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"El Padre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Le Parent"
#elif defined(PROG_LANGUAGE_GERMAN)
"Elternteil"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Genitore"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ouder"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Pai"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Mor Eller Far"
#else
"Parent"
#endif
	);
	gtk_widget_show(w);

	/* New Directory */
	fb->new_directory_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_folder_closed_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserNewDirectoryCB), fb
	);
#if 0
	gtk_accel_group_add(
	    accelgrp, GDK_Insert, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
#endif
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"La Guía Nueva"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nouvel Annuaire"
#elif defined(PROG_LANGUAGE_GERMAN)
"Neues Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Elenco Nuovo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Nieuwe Gids"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Novo Guia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ny Directory"
#else
"New Directory"
#endif
	);
	gtk_widget_show(w);

	/* Rename */
	fb->rename_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_rename_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserRenameCB), fb
	);
#if 0
	gtk_accel_group_add(
	    accelgrp, GDK_F2, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
#endif
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Reagrupe"
#elif defined(PROG_LANGUAGE_FRENCH)
"Renommer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Benennen Sie Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinominare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Herdoop"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Nome"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ombenevn"
#else
"Rename"
#endif
	);
	gtk_widget_show(w);

	/* Refresh */
	fb->refresh_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_reload_20x20_xpm
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserRefreshCB), fb
	);
#if 0
	gtk_accel_group_add(
	    accelgrp, GDK_F5, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
#endif
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Refresque"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rafraîchir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erfrischen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinfrescare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verfris"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refresque-se"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forfrisk"
#else
"Refresh"
#endif
	);
	gtk_widget_show(w);

	/* Show hidden objects */
	fb->show_hidden_objects_tb = w = GUIToggleButtonPixmap(
	    (guint8 **)icon_file_hidden_20x20_xpm
	);
	gtk_toggle_button_set_active(
	    GTK_TOGGLE_BUTTON(w), fb->show_hidden_objects
	);
	gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "toggled",
	    GTK_SIGNAL_FUNC(FileBrowserShowHiddenObjectsToggleCB), fb
	);
	GUISetWidgetTip(w,
"Show Hidden Objects"
	);
	gtk_widget_show(w);

	/* Hbox for list format buttons */
	w = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;

	fb->list_format_standard_tb = w = GUIToggleButtonPixmap(
	    (guint8 **)icon_fb_list_standard_20x20_xpm
	);
	gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "toggled",
	    GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar De Estándar"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer De Norme"
#elif defined(PROG_LANGUAGE_GERMAN)
"Standard Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Di Norma"
#elif defined(PROG_LANGUAGE_DUTCH)
"Standaard Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Padrão"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Normal Listing"
#else
"Standard Listing"
#endif
	);
	gtk_widget_show(w);

	fb->list_format_vertical_tb = w = GUIToggleButtonPixmap(
	    (guint8 **)icon_fb_list_vertical_20x20_xpm
	);
	gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "toggled",
	    GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar Detallado"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer Détaillé"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ausführliches Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Dettagliato"
#elif defined(PROG_LANGUAGE_DUTCH)
"Gedetailleerd Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Detailed"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Detaljert Listing"
#else
"Detailed Listing"
#endif
	);
/* Don't offer the use of the vertical list anymore */
/*	gtk_widget_show(w); */

	fb->list_format_vertical_details_tb = w = GUIToggleButtonPixmap(
	    (guint8 **)icon_fb_list_vertical_details_20x20_xpm
	);
	gtk_widget_set_usize(w, FB_TB_BTN_WIDTH, FB_TB_BTN_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "toggled",
	    GTK_SIGNAL_FUNC(FileBrowserListFormatToggleCB), fb
	);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Listar Detallado"
#elif defined(PROG_LANGUAGE_FRENCH)
"Enumérer Détaillé"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ausführliches Aufführen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Elencare Dettagliato"
#elif defined(PROG_LANGUAGE_DUTCH)
"Gedetailleerd Opsommen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Alistamento De Detailed"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Detaljert Listing"
#else
"Detailed Listing"
#endif
	);
	gtk_widget_show(w);



	/* Table for list */
	w = gtk_table_new(2, 2, FALSE);
	gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Frame and vbox with in for parenting the list header and
	 * the list.
	 */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 0, 1,
	    GTK_FILL | GTK_SHRINK | GTK_EXPAND,
	    GTK_FILL | GTK_SHRINK | GTK_EXPAND,
	    0, 0
	);
	gtk_widget_show(w);
	parent3 = w;
	w = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);
	parent3 = w;


	/* List column headers GtkDrawingArea */
	fb->list_header_da = w = gtk_drawing_area_new();
	gtk_widget_set_usize(w, FB_LIST_HEADER_WIDTH, FB_LIST_HEADER_HEIGHT);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(FileBrowserListHeaderEventCB), fb
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* List GtkDrawingArea */
	fb->list_da = w = gtk_drawing_area_new();
	gtk_widget_set_usize(w, FB_LIST_WIDTH, FB_LIST_HEIGHT);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK |
	    GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_in_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "focus_out_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(FileBrowserListEventCB), fb
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	if(w != NULL)
	{
	    const GtkTargetEntry dnd_tar_types[] = {
{GUI_TARGET_NAME_TEXT_PLAIN,	0,		0},
{GUI_TARGET_NAME_TEXT_URI_LIST,	0,		1},
{GUI_TARGET_NAME_STRING,	0,		2}
	    };
	    const GtkTargetEntry dnd_src_types[] = {
{GUI_TARGET_NAME_TEXT_PLAIN,	0,		0},
{GUI_TARGET_NAME_TEXT_URI_LIST,	0,		1},
{GUI_TARGET_NAME_STRING,	0,		2}
	    };
	    GUIDNDSetTar(
		w,
		dnd_tar_types,
		sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
		GDK_ACTION_COPY,		/* Actions */
		GDK_ACTION_COPY,		/* Default action if same */
		GDK_ACTION_COPY,		/* Default action */
		FileBrowserDragDataReceivedCB,
		fb
	    );
	    gtk_signal_connect_after(
		GTK_OBJECT(w), "drag_motion",
		GTK_SIGNAL_FUNC(FileBrowserDragMotionCB), fb
	    );
	    GUIDNDSetSrc(
		w,
		dnd_src_types,
		sizeof(dnd_src_types) / sizeof(GtkTargetEntry),
		GDK_ACTION_COPY | GDK_ACTION_MOVE |
		    GDK_ACTION_LINK,		/* Actions */
		GDK_BUTTON1_MASK,		/* Buttons */
		NULL,
		FileBrowserDragDataGetCB,
		FileBrowserDragDataDeleteCB,
		NULL,
		fb
	    );
	}

	/* List vertical scrollbar */
	adj = (GtkAdjustment *)gtk_adjustment_new(
	    0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
	);
	fb->list_vsb = w = gtk_vscrollbar_new(adj);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    1, 2, 0, 1,
	    0,
	    GTK_FILL | GTK_SHRINK | GTK_EXPAND,
	    0, 0
	);
	gtk_signal_connect(
	    GTK_OBJECT(adj), "value_changed",
	    GTK_SIGNAL_FUNC(FileBrowserListScrollCB), fb
	);

	/* List's horizontal GtkScrollBar */
	adj = (GtkAdjustment *)gtk_adjustment_new(
	    0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
	);
	fb->list_hsb = w = gtk_hscrollbar_new(adj);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 1, 2,
	    GTK_FILL | GTK_SHRINK | GTK_EXPAND,
	    0,
	    0, 0
	);
	gtk_signal_connect(
	    GTK_OBJECT(adj), "value_changed",
	    GTK_SIGNAL_FUNC(FileBrowserListScrollCB), fb
	);

	/* List right-click menu */
	if(TRUE)
	{
	    GtkWidget *menu = (GtkWidget *)GUIMenuCreate();
	    guint8 **icon;
	    const gchar *label;
	    guint accel_key, accel_mods;
	    gpointer mclient_data = fb;
	    void (*func_cb)(GtkWidget *w, gpointer);

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

	    icon = (guint8 **)icon_select_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Selecto"
#elif defined(PROG_LANGUAGE_FRENCH)
"Choisir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erlesen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scegliere"
#elif defined(PROG_LANGUAGE_DUTCH)
"Uitgezocht"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Selecione"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Utvalgt"
#else
"Select"
#endif
	    ;
	    accel_key = GDK_Return;
	    accel_mods = 0;
	    func_cb = FileBrowserEntryEnterCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->select_mi = w; */

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_folder_parent_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"El Padre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Le Parent"
#elif defined(PROG_LANGUAGE_GERMAN)
"Elternteil"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Genitore"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ouder"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Pai"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Mor Eller Far"
#else
"Parent"
#endif
	    ;
	    accel_key = GDK_BackSpace;
	    accel_mods = 0;
	    func_cb = FileBrowserGoToParentCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->goto_parent_mi = w; */

	    icon = (guint8 **)icon_reload_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Refresque"
#elif defined(PROG_LANGUAGE_FRENCH)
"Rafraîchir"
#elif defined(PROG_LANGUAGE_GERMAN)
"Erfrischen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinfrescare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verfris"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refresque-se"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forfrisk"
#else
"Refresh"
#endif
	    ;
	    accel_key = GDK_F5;
	    accel_mods = 0;
	    func_cb = FileBrowserRefreshCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->refresh_mi = w; */

	    DO_ADD_MENU_SEP

	    icon = (guint8 **)icon_folder_closed_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"La Guía Nueva"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nouvel Annuaire"
#elif defined(PROG_LANGUAGE_GERMAN)
"Neues Verzeichnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Elenco Nuovo"
#elif defined(PROG_LANGUAGE_DUTCH)
"Nieuwe Gids"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Novo Guia"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ny Directory"
#else
"New Directory"
#endif
	    ;
	    accel_key = GDK_Insert;
	    accel_mods = 0;
	    func_cb = FileBrowserNewDirectoryCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->new_directory_mi = w; */

	    icon = (guint8 **)icon_rename_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Reagrupe"
#elif defined(PROG_LANGUAGE_FRENCH)
"Renommer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Benennen Sie Um"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Rinominare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Herdoop"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Nome"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Ombenevn"
#else
"Rename"
#endif
	    ;
	    accel_key = GDK_F2;
	    accel_mods = 0;
	    func_cb = FileBrowserRenameCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->rename_mi = w; */

	    icon = (guint8 **)icon_chmod_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Cambie Los Permisos"
#elif defined(PROG_LANGUAGE_FRENCH)
"Changer Des Permissions"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ändern Sie Erlaubnis"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cambiare I Permessi"
#elif defined(PROG_LANGUAGE_DUTCH)
"Verandeer Toestemmingen"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mude Permissões"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Forandr Permissions"
#else
"Change Permissions"
#endif
	    ;
	    accel_key = GDK_F9;
	    accel_mods = 0;
	    func_cb = FileBrowserCHModCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->chmod_mi = w; */

	    icon = (guint8 **)icon_cancel_20x20_xpm;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Borre"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen Sie"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrap"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anule"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryk"
#else
"Delete"
#endif
	    ;
	    accel_key = GDK_Delete;
	    accel_mods = 0;
	    func_cb = FileBrowserDeleteCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->delete_mi = w; */

	    DO_ADD_MENU_SEP

	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Escoja Todo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Choisir Tout"
#elif defined(PROG_LANGUAGE_GERMAN)
"Wählen Sie Alle Aus"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scegliere Tutto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Selecteer Alle"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Selecione Todo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Utvalgt All"
#else
"Select All"
#endif
	    ;
	    accel_key = 'a';
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = FileBrowserSelectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->select_all_mi = w; */

	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Rechace Todo"
#elif defined(PROG_LANGUAGE_FRENCH)
"Déssélectionner Tout"
#elif defined(PROG_LANGUAGE_GERMAN)
"Entmarkieren Sie Alle"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Deselezionare Tutto"
#elif defined(PROG_LANGUAGE_DUTCH)
"Heldere Keuze"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"O Deselect Todo"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Klar Selection"
#else
"Unselect All"
#endif
	    ;
	    accel_key = 'u';
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = FileBrowserUnselectAllCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->unselect_all_mi = w; */

	    icon = NULL;
	    label =
#if defined(PROG_LANGUAGE_SPANISH)
"Invierta La Selección"
#elif defined(PROG_LANGUAGE_FRENCH)
"Inverser La Sélection"
#elif defined(PROG_LANGUAGE_GERMAN)
"Invertieren Sie Auswahl"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Invertire La Selezione"
#elif defined(PROG_LANGUAGE_DUTCH)
"Keer Keuze Om"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Inverta Seleção"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Inverter Selection"
#else
"Invert Selection"
#endif
	    ;
	    accel_key = 'i';
	    accel_mods = GDK_CONTROL_MASK;
	    func_cb = FileBrowserInvertSelectionCB;
	    DO_ADD_MENU_ITEM_LABEL
/*	    fb->invert_selection_mi = w; */



	    fb->list_menu = menu;

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_SEP
	}


	w = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);


	w = gtk_hbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* GtkVBox for the name and type */
	w = gtk_vbox_new(FALSE, border_major);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent3 = w;

	/* GtkHBox for the name */
	w = gtk_hbox_new(FALSE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Archive Nombre:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Nom De Fichier:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Dateiname:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Schedare Nome:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Archiveer Naam:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquive Nome:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Arkiver Name:"
#else
"File Name:"
#endif
	);
	gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_RIGHT);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	fb->entry = w = gtk_entry_new();
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(FileBrowserEntryCompletePathKeyCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(FileBrowserEntryCompletePathKeyCB), fb
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "activate",
	    GTK_SIGNAL_FUNC(FileBrowserEntryEnterCB), fb
	);
	gtk_widget_show(w);
	GUISetWidgetTip(w,
#if defined(PROG_LANGUAGE_SPANISH)
"Entre el nombre del objeto, usted puede especificar más\
 que un objeto (separa cada nombre con un ',' el carácter)"
#elif defined(PROG_LANGUAGE_FRENCH)
"Entrer le nom de l'objet, vous pouvez spécifier plus\
 qu'un objet (sépare chaque nom avec un ',' le caractère)"
#elif defined(PROG_LANGUAGE_GERMAN)
"Tragen Sie den Namen des Objekts, Sie können angeben\
 mehr als ein Objekt ein (trennen Sie jeden Namen mit einem ',' charakter)"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Entrare il nome dell'oggetto, lei può specificare più\
 di un oggetto (separa ogni nome con un ',' il carattere)"
#elif defined(PROG_LANGUAGE_DUTCH)
"Ga de naam van het voorwerp, u zou kunnen specificeren\
 meer than een voorwerp binnen (scheid elk naam met een ',' teken)"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Entre o nome do objeto, você pode especificar mais de\
 um objeto (separa cada nome com um ',' caráter)"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Gå inn i navnet av objektet, De spesifiserer mere enn\
 et objekt (separere hver navn med et ',' karakter)"
#else
"Enter the name of the object, you may specify more than\
 one object (separate each name with a ',' character)"
#endif
	);
	if(w != NULL)
	{
	    const GtkTargetEntry dnd_tar_types[] = {
{GUI_TARGET_NAME_TEXT_PLAIN,	0,		0},
{GUI_TARGET_NAME_TEXT_URI_LIST,	0,		1},
{GUI_TARGET_NAME_STRING,	0,		2}
	    };
	    GUIDNDSetTar(
		w,
		dnd_tar_types,
		sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
		GDK_ACTION_COPY,		/* Actions */
		GDK_ACTION_COPY,		/* Default action if same */
		GDK_ACTION_COPY,		/* Default action */
		FileBrowserDragDataReceivedCB,
		fb
	    );
	    gtk_signal_connect_after(
		GTK_OBJECT(w), "drag_motion",
		GTK_SIGNAL_FUNC(FileBrowserDragMotionCB), fb
	    );
	}

	/* GtkHBox for the type */
	w = gtk_hbox_new(FALSE, border_minor);	
	gtk_box_pack_start(GTK_BOX(parent3), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent4 = w;

	w = gtk_label_new(
#if defined(PROG_LANGUAGE_SPANISH)
"Tipo Archivo:"
#elif defined(PROG_LANGUAGE_FRENCH)
"Type De Fichier:"
#elif defined(PROG_LANGUAGE_GERMAN)
"Legt Typ Ab:"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Tipo Di File:"
#elif defined(PROG_LANGUAGE_DUTCH)
"Archiveer Type:"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquivo Tipo:"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Typen Arkivet:"
#else
"File Type:"
#endif
	);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Types popup list box */
	fb->types_pulistbox = pulistbox = PUListBoxNew(
	    parent4,
	    -1, -1
	);
	PUListBoxSetChangedCB(
	    pulistbox,
	    FileBrowserTypesPUListChangedCB, fb
	);
	PUListBoxMap(pulistbox);
	pulist = PUListBoxGetPUList(pulistbox);
	fb->freeze_count++;
	i = PUListAddItem(pulist, FB_DEFAULT_TYPE_STR);
	if(i > -1)
	{
	    fb_type_struct *t = FB_TYPE(g_malloc0(sizeof(fb_type_struct)));
	    if(t != NULL)
	    {
		t->ext = STRDUP("*.*");
		t->name = STRDUP("All Files");
		PUListSetItemDataFull(
		    pulist, i,
		    t, FileBrowserTypesPUListItemDestroyCB
		);
	    }
	    PUListBoxSetLinesVisible(pulistbox, 1);
	    PUListBoxSelect(pulistbox, i);
	    FileBrowserTypesPUListChangedCB(pulistbox, i, fb);
	}
	fb->freeze_count--;

	/* Vbox for ok and cancel buttons */
	w = gtk_vbox_new(TRUE, border_minor);
	gtk_box_pack_start(GTK_BOX(parent2), w, FALSE, FALSE, 0);
	gtk_widget_show(w);
	parent3 = w;


	/* OK button */
	fb->ok_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_ok_20x20_xpm, "OK", &fb->ok_btn_label
	);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_widget_set_usize(
	    w,
	    GUI_BUTTON_HLABEL_WIDTH_DEF,
	    GUI_BUTTON_HLABEL_HEIGHT_DEF
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserOKCB), fb
	);
#if 0
	gtk_accel_group_add(
	    accelgrp, GDK_Return, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_accel_group_add(
	    accelgrp, GDK_3270_Enter, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_accel_group_add(
	    accelgrp, GDK_KP_Enter, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_accel_group_add(
	    accelgrp, GDK_ISO_Enter, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
#endif
	gtk_widget_show(w);

	/* Cancel button */
	fb->cancel_btn = w = GUIButtonPixmapLabelH(
	    (guint8 **)icon_cancel_20x20_xpm, "Cancel", &fb->cancel_btn_label
	);
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
	gtk_widget_set_usize(
	    w,
	    GUI_BUTTON_HLABEL_WIDTH_DEF,
	    GUI_BUTTON_HLABEL_HEIGHT_DEF
	);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(FileBrowserCancelCB), fb
	);
	gtk_accel_group_add(
	    accelgrp, GDK_Escape, 0, GTK_ACCEL_VISIBLE,
	    GTK_OBJECT(w), "clicked"
	);
	gtk_widget_show(w);


	/* Load the icons, the order is important because the index
	 * must match the FB_ICON_* values as indices
	 */
	if(fb->toplevel != NULL)
	{
	    gint i = 0;
	    gpointer icon_list[] = FB_ICON_DATA_LIST;
	    while(icon_list[i] != NULL)
	    {
		FileBrowserIconAppend(
		    fb,
		    (guint8 **)icon_list[i + 1],	/* XPM data */
		    (const gchar *)icon_list[i + 0]	/* Description */
		);
		i += 6;
	    }
	}

	/* Force the setting of the default list format and get
	 * listing
	 */
	fb->list_format = -1;
	FileBrowserSetListFormat(fb, FB_LIST_FORMAT_STANDARD);

	return(0);
}

/*
 *	Sets the File Browser's style.
 */
void FileBrowserSetStyle(GtkRcStyle *rc_style)
{
	GtkWidget *w;
	FileBrowser *fb = &file_browser;

	w = fb->toplevel;
	if(w != NULL)
	{
	    if(rc_style != NULL)
	    {
		gtk_widget_modify_style(w, rc_style);
	    }
	    else
	    {
		rc_style = gtk_rc_style_new();
		gtk_widget_modify_style_recursive(w, rc_style);
		GTK_RC_STYLE_UNREF(rc_style)
	    }
	}
}

/*
 *	Sets the File Browser to be a transient for the given
 *	GtkWindow w.
 *
 *	If w is NULL then transient for will be unset.
 */
void FileBrowserSetTransientFor(GtkWidget *w)
{
	FileBrowser *fb = &file_browser;
	if(fb->toplevel != NULL)
	{
	    GtkWidget *toplevel = fb->toplevel;

	    if(w != NULL)
	    {
		if(!GTK_IS_WINDOW(w))
		    return;

/* Since the file browser itself has popup windows that may need to be
 * set modal, we cannot set the file browser itself modal.
		gtk_window_set_modal(
		    GTK_WINDOW(toplevel), TRUE
		);
 */
		gtk_window_set_transient_for(
		    GTK_WINDOW(toplevel), GTK_WINDOW(w)
		);
	    }
	    else
	    {
/*
		gtk_window_set_modal(
		    GTK_WINDOW(toplevel), FALSE
		);
 */
		gtk_window_set_transient_for(
		    GTK_WINDOW(toplevel), NULL
		);
	    }
	}
}

/*
 *	Returns TRUE if currently blocking for query.
 */
gboolean FileBrowserIsQuery(void) 
{
	FileBrowser *fb = &file_browser;

	if(fb->block_loop_level > 0)
	    return(TRUE);
	else
	    return(FALSE);
}

/*
 *	Ends query if any and returns a not available response.
 */
void FileBrowserBreakQuery(void)
{
	FileBrowser *fb = &file_browser;

	fb->user_response = FALSE;

	/* Break out of an additional blocking loops */
	while(fb->block_loop_level > 0)
	{
	    gtk_main_quit();
	    fb->block_loop_level--;
	}
	fb->block_loop_level = 0;
}

/*
 *	Returns the File Browser's toplevel GtkWidget.
 */
GtkWidget *FileBrowserGetToplevel(void)
{
	FileBrowser *fb = &file_browser;
	return(fb->toplevel);
}

/*
 *	Clears the lists and unset the location.
 */
void FileBrowserReset(void)
{
	FileBrowser *fb = &file_browser;
	gint i;
	GtkEntry *entry;

	fb->freeze_count++;

#if 0
/* Do not reset the current location */
	/* Unset the current location */
	g_free(fb->cur_location);
	fb->cur_location = NULL;
#endif

	/* Unset the file name entry */
	entry = (GtkEntry *)fb->entry;
	if(entry != NULL)
	    gtk_entry_set_text(entry, "");


	/* Clear the locations popup list */
	PUListClear(PUListBoxGetPUList(fb->loc_pulistbox));

	/* Clear the types popup list */
	PUListClear(PUListBoxGetPUList(fb->types_pulistbox));


	/* Clear the objects listing */

	/* Unselect all */
	fb->focus_object = -1;
	g_list_free(fb->selection);
	fb->selection = fb->selection_end = NULL;

	/* Delete all the objects */
	for(i = 0; i < fb->total_objects; i++)
	    FileBrowserObjectDestroyCB(fb->object[i]);
	g_free(fb->object);
	fb->object = NULL;
	fb->total_objects = 0;

	/* Reset the objects per row */
	fb->objects_per_row = 0;

	fb->freeze_count--;
}

/*
 *	Maps the file browser and sets up the inital values.
 *
 *	Returns TRUE if a path was selected or FALSE if user canceled.
 *
 *	For most values that are set NULL, the value is left unchanged.
 *	All given values are coppied.
 *
 *	If type is set to NULL however, then the type list on the file
 *	browser will be left empty.
 *
 *	All returned pointer values should be considered statically
 *	allocated. The returned pointer for type_rtn may point to
 *	a structure in the input type list.
 */
gboolean FileBrowserGetResponse(
	const gchar *title,
	const gchar *ok_label, const gchar *cancel_label,
	const gchar *path,
	fb_type_struct **type, gint total_types,
	gchar ***path_rtn, gint *path_total_rtns,
	fb_type_struct **type_rtn
)
{
	GtkWidget *w, *toplevel;
	FileBrowser *fb = &file_browser;

#define RESET_RETURNS	{	\
 if(path_rtn != NULL)		\
  *path_rtn = NULL;		\
 if(path_total_rtns != NULL)	\
  *path_total_rtns = 0;		\
				\
 if(type_rtn != NULL)		\
  *type_rtn = NULL;		\
}

	/* Do not handle response if already waiting for a response,
	 * return with a not available response code
	 */
	if(fb->block_loop_level > 0)
	{
	    RESET_RETURNS
	    return(FALSE);
	}


	/* Reset values */
	fb->user_response = FALSE;

	g_free(fb->cur_type.name);
	g_free(fb->cur_type.ext);
	memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));


	/* Reset the returns */
	RESET_RETURNS


	toplevel = fb->toplevel;

	/* Reget the drive paths */
	if(fb->device_paths_list != NULL)
	{
	    g_list_foreach(fb->device_paths_list, (GFunc)g_free, NULL);
	    g_list_free(fb->device_paths_list);
	}
	fb->device_paths_list = FileBrowserGetDrivePaths();

	/* Set title */
	if(title != NULL)
	    gtk_window_set_title(GTK_WINDOW(toplevel), title);

	/* Set OK Button label */
	w = fb->ok_btn_label;
	if((ok_label != NULL) && (w != NULL))
	    gtk_label_set_text(GTK_LABEL(w), ok_label);

	/* Set Cancel Button label */
	w = fb->cancel_btn_label;
	if((cancel_label != NULL) && (w != NULL))
	    gtk_label_set_text(GTK_LABEL(w), cancel_label);

	/* Set the types list */
	if((type != NULL) && (total_types > 0))
	    FileBrowserTypesListSetTypes(
		fb,
		type, total_types
	    );


	FileBrowserSetBusy(fb, TRUE);

	/* Map File Browser
	 *
	 * This needs to be done now in order to allow the proper
	 * realizing of sizes before other things can be updated
	 */
	FileBrowserMap();
#if 0
/* This is not needed any more since object positions are updated when
 * a "configure_event" signal is received
 */
	while(gtk_events_pending() > 0)
	    gtk_main_iteration();
#endif

	/* No initial path specified? */
	if(STRISEMPTY(path))
	{
	    /* If no initial path was specified then we need to
	     * check if the list was previous cleared (reset), in
	     * which case we need to reget the listing
	     */
	    if(fb->total_objects <= 0)
	    {
		FileBrowserListUpdate(fb, NULL);
		FileBrowserLocationsPUListUpdate(fb);
	    }
	}
	else
	{
	    /* Set the initial location */
	    FileBrowserSetLocation(fb, path);
	}

	/* If no location was set then set the initial location as
	 * the home directory
	 */
	if(STRISEMPTY(fb->cur_location))
	    FileBrowserSetLocation(fb, "~");

	/* If there is a first object in the list then set it in focus */
	if(fb->total_objects > 0)
	{
	    if(fb->focus_object < 0)
	    {
		fb->focus_object = 0;
		gtk_widget_queue_draw(fb->list_da);
	    }
	}

	FileBrowserSetBusy(fb, FALSE);

	/* Block until user response */
	fb->block_loop_level++;
	gtk_main();

	/* Unmap the File Browser just in case it was not unmapped
	 * from any of the callbacks
	 */
	FileBrowserUnmap();

	/* Break out of an additional blocking loops */
	while(fb->block_loop_level > 0)
	{
	    gtk_main_quit();
	    fb->block_loop_level--;
	}
	fb->block_loop_level = 0;


	/* Begin setting returns */

	/* Response path returns */
	if(path_rtn != NULL)
	    *path_rtn = fb->selected_path;
	if(path_total_rtns != NULL)
	    *path_total_rtns = fb->total_selected_paths;

	/* File type */
	if(type_rtn != NULL)
	    *type_rtn = &fb->cur_type;

	return(fb->user_response);
#undef RESET_RETURNS
}


/*
 *	Maps the File Browser.
 */
void FileBrowserMap(void)
{
	GtkWidget *w;
	FileBrowser *fb = &file_browser;

	gtk_widget_show_raise(fb->toplevel);

	w = fb->list_da;
	gtk_widget_grab_focus(w);
}

/*
 *	Unmaps the File Browser.
 */
void FileBrowserUnmap(void)
{
	FileBrowser *fb = &file_browser;
	gtk_widget_hide(fb->toplevel);
}

/*
 *	Shuts down the File Browser.
 */
void FileBrowserShutdown(void)
{
	gint i;
	FileBrowser *fb = &file_browser;

	/* Delete the current file type */
	g_free(fb->cur_type.name);
	g_free(fb->cur_type.ext);
	memset(&fb->cur_type, 0x00, sizeof(fb_type_struct));

	/* Delete the sSelected paths */
	for(i = 0; i < fb->total_selected_paths; i++)
	    g_free(fb->selected_path[i]);
	g_free(fb->selected_path);
	fb->selected_path = NULL;
	fb->total_selected_paths = 0;

	/* Delete the selected objects list */
	fb->focus_object = -1;
	g_list_free(fb->selection);
	fb->selection = fb->selection_end = NULL;

	/* Delete the objects */
	for(i = 0; i < fb->total_objects; i++)
	    FileBrowserObjectDestroyCB(fb->object[i]);
	g_free(fb->object);
	fb->object = NULL;
	fb->total_objects = 0;
	fb->objects_per_row = 0;

	/* Delete the icons */
	for(i = 0; i < fb->total_icons; i++)
	    FileBrowserIconDestroyCB(fb->icon[i]);
	g_free(fb->icon);
	fb->icon = NULL;
	fb->total_icons = 0;

	/* Delete the list columns */
	FileBrowserListColumnsClear(fb);

	/* Delete the device paths list */
	if(fb->device_paths_list != NULL)
	{
	    g_list_foreach(fb->device_paths_list, (GFunc)g_free, NULL);
	    g_list_free(fb->device_paths_list);
	}


	/* Break out of any additional blocking loops */
	while(fb->block_loop_level > 0)
	{
	    gtk_main_quit();
	    fb->block_loop_level--;
	}
	fb->block_loop_level = 0;


	fb->freeze_count++;

	/* Delete the dialog */
	gtk_widget_destroy(fb->list_menu);
	PUListBoxDelete(fb->loc_pulistbox);
	PUListBoxDelete(fb->types_pulistbox);
	gtk_widget_destroy(fb->toplevel);
	gtk_accel_group_unref(fb->accelgrp);

	GDK_PIXMAP_UNREF(fb->list_pm);

	GDK_CURSOR_DESTROY(fb->cur_busy);
	GDK_CURSOR_DESTROY(fb->cur_column_hresize);
	GDK_CURSOR_DESTROY(fb->cur_translate);

	g_free(fb->home_path);
	g_free(fb->cur_location);

	fb->freeze_count--;

	memset(fb, 0x00, sizeof(FileBrowser));
}


/*
 *	Gets the last location.
 *
 *	If no previous location was set then NULL will be returned.
 */
const gchar *FileBrowserGetLastLocation(void)
{
	return(FileBrowserGetLocation(&file_browser));
}

/*
 *	Show hidden objects.
 */
void FileBrowserShowHiddenObjects(const gboolean show)
{
	FileBrowser *fb = &file_browser;
	FileBrowserSetShowHiddenObjects(fb, show);
}

/*
 *	Sets the list format to standard.
 */
void FileBrowserListStandard(void)
{
	FileBrowser *fb = &file_browser;
	FileBrowserSetListFormat(fb, FB_LIST_FORMAT_STANDARD);
}

/*
 *	Sets the list format to detailed.
 */
void FileBrowserListDetailed(void)
{
	FileBrowser *fb = &file_browser;
	FileBrowserSetListFormat(fb, FB_LIST_FORMAT_VERTICAL_DETAILS);
}


/*
 *	Convience function to allocate a new file browser file extension
 *	type structure and append it to the given list. The given list
 *	will be modified and the index number of the newly allocated
 *	structure will be returned.
 *
 *	Can return -1 on error.
 */
gint FileBrowserTypeListNew(
	fb_type_struct ***list, gint *total,
	const gchar *ext,	/* Space separated list of extensions */
	const gchar *name	/* Descriptive name */
)
{
	gint n;
	fb_type_struct *fb_type_ptr;


	if((list == NULL) || (total == NULL))
	    return(-1);

	n = MAX(*total, 0);
	*total = n + 1;

	/* Allocate more pointers */
	*list = (fb_type_struct **)g_realloc(
	    *list,
	    (*total) * sizeof(fb_type_struct *)
	);
	if(*list == NULL)
	{
	    *total = 0;
	    return(-1);
	}

	/* Allocate new structure */
	fb_type_ptr = (fb_type_struct *)g_malloc0(sizeof(fb_type_struct));
	(*list)[n] = fb_type_ptr;
	if(fb_type_ptr == NULL)
	{
	    *total = n;
	    return(-1);
	}

	/* Set values */
	fb_type_ptr->ext = STRDUP(ext);
	fb_type_ptr->name = STRDUP(name);

	return(n);
}

/*
 *	Deletes the type.
 */
static void FileBrowserTypeDelete(fb_type_struct *t)
{
	if(t == NULL)
	    return;

	g_free(t->ext);
	g_free(t->name);
	g_free(t);
}

/*
 *	Deletes the types list.
 */
void FileBrowserDeleteTypeList(
	fb_type_struct **list, const gint total 
)
{
	gint i;

	for(i = 0; i < total; i++)
	    FileBrowserTypeDelete(list[i]);

	g_free(list);
}
