#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if defined(HAVE_REGEX)
# include <regex.h>
#else
# include <fnmatch.h>
#endif
#ifdef HAVE_LIBGIF
# include "gif.h"
#endif
#ifdef HAVE_IMLIB
# include <Imlib.h>
#endif
#include <gtk/gtk.h>

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

#include "guiutils.h"
#include "guirgbimg.h"
#include "cdialog.h"
#include "progressdialog.h"
#include "fprompt.h"

#include "cfg.h"
#include "edv_types.h"
#include "edv_date.h"
#include "edv_id.h"
#include "edv_obj.h"
#include "edv_obj_op.h"
#include "edv_mime_type.h"
#include "imbr.h"
#include "imbr_cb.h"
#include "imbr_tlist.h"
#include "endeavour2.h"
#include "edv_open.h"
#include "edv_cb.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"

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


static edv_object_struct *EDVImbrNewErrorObject(
	const gchar *path
);

/* Callbacks */
static void EDVImbrTListFPromptCancelCB(gpointer data);

/* Image Loading */
static guint8 *EDVImbrTListConvertRGBToRGBA(
	const guint8 *rgb, const guint8 *alpha,
	const gint width, const gint height,
	const gint rgb_bpl, const gint alpha_bpl
);
static guint8 *EDVImbrTListLoadImageRGBATarga(
	const gchar *path,
	gint *width, gint *height, gint *bpl,
	GdkWindow *window			/* Reference window */
);
GList *EDVImbrTListLoadImageRGBA(
	tlist_struct *tlist, const gchar *path,
	gint *width, gint *height, gint *bpl,
	gint *nframes, GList **delay_list,
	const gboolean resize_for_thumb,	/* Resize for tlist thumb */
	const gboolean no_enlarge,		/* No enlarge if smaller
						 * than req size */
	GdkWindow *window			/* Reference window */
);

static void EDVImbrTListSetThumbName(
	edv_core_struct *core, edv_imbr_struct *imbr,
	tlist_struct *tlist, edv_object_struct *obj,
	const gint thumb_num
);

static gint EDVImbrTListAppendObject(
	edv_core_struct *core, edv_imbr_struct *imbr,
	tlist_struct *tlist,
	edv_object_struct *obj
);

/* Finding */
gint EDVImbrTListFindThumbByIndex(
	edv_imbr_struct *imbr,
	const gulong device_index, const gulong index
);
gint EDVImbrTListFindThumbByPath(
	edv_imbr_struct *imbr, const gchar *path
);

/* Selection */
GList *EDVImbrTListGetSelectedPaths(edv_imbr_struct *imbr);

/* Realize Listing */
void EDVImbrTListRealizeListing(edv_imbr_struct *imbr);

/* Get Listing */
void EDVImbrTListGetListing(
	edv_imbr_struct *imbr, const gchar *path,
	const gboolean update_status_bar
);
void EDVImbrTlistClear(edv_imbr_struct *imbr);

/* Thumb Image Load Iteration */
gint EDVImbrTListLoadIterate(
	edv_imbr_struct *imbr,
	const gboolean update_status_bar,
	const gboolean no_enlarge
);

/* Opening */
void EDVImbrTListOpen(
	edv_imbr_struct *imbr, const gint thumb_num,
	const guint state			/* Key modifiers */
);
void EDVImbrTListOpenWith(
	edv_imbr_struct *imbr, const gint thumb_num
);

/* Renaming */
static void EDVImbrTListFPromptRenameApplyCB(
	gpointer data, const gchar *value
);
void EDVImbrTListDoFPromptRename(
	edv_imbr_struct *imbr, const gint thumb_num
);

/* Object Callbacks */
void EDVImbrTListObjectAddedNotify(
	edv_imbr_struct *imbr, const gchar *path,
	const struct stat *lstat_buf
);
void EDVImbrTListObjectModifiedNotify(
	edv_imbr_struct *imbr, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
);
void EDVImbrTListObjectRemovedNotify(
	edv_imbr_struct *imbr, const gchar *path
);

/* Mount Callbacks */
void EDVImbrTListMountNotify(
	edv_imbr_struct *imbr, edv_device_struct *dev,
	gboolean is_mounted
);


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


/*
 *	Creates a new error object.
 *
 *	The path specifies the full path to the error object.
 */
static edv_object_struct *EDVImbrNewErrorObject(
	const gchar *path
)
{
	edv_object_struct *obj = EDVObjectNew();
	if(obj == NULL)
	    return(NULL);

	obj->type = EDV_OBJECT_TYPE_ERROR;
	EDVObjectSetPath(obj, path);
	EDVObjectUpdateLinkFlags(obj);

	return(obj);
}


/*
 *	All purpose fprompt cancel callback.
 */
static void EDVImbrTListFPromptCancelCB(gpointer data)
{
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return;

	g_free(cb_data);
}


/*
 *	Converts the specified RGB and Alpha image data to RGBA.
 */
static guint8 *EDVImbrTListConvertRGBToRGBA(
	const guint8 *rgb, const guint8 *alpha,
	const gint width, const gint height,
	const gint rgb_bpl, const gint alpha_bpl
)
{
	gint	rgba_bpl,
		_rgb_bpl = rgb_bpl,
		_alpha_bpl = alpha_bpl;
	guint8 *rgba;

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

	/* Calculate bytes per line for all buffers */
	if(_rgb_bpl <= 0)
	    _rgb_bpl = width * 3;
	if(_alpha_bpl <= 0)
	    _alpha_bpl = width * 1;
	rgba_bpl = width * 4;

	/* Allocate RGBA data */
	rgba = (guint8 *)g_malloc(rgba_bpl * height);
	if(rgba == NULL)
	    return(NULL);

	/* Alpha channel available? */
	if(alpha != NULL)
	{
	    guint8 *rgba_line, *rgba_line_end, *rgba_ptr, *rgba_end;
	    const guint8 *rgb_line, *rgb_ptr, *alpha_line, *alpha_ptr;

	    for(rgba_line	= rgba,
		rgba_line_end	= rgba_line + (rgba_bpl * height),
		rgb_line	= rgb,
		alpha_line	= alpha;
		rgba_line < rgba_line_end;
		rgba_line	+= rgba_bpl,
		rgb_line	+= _rgb_bpl,
		alpha_line	+= _alpha_bpl
	    )
	    {
		rgba_ptr	= rgba_line;
		rgba_end	= rgba_ptr + (width * 4);
		rgb_ptr		= rgb_line;
		alpha_ptr	= alpha_line;

		while(rgba_ptr < rgba_end)
		{
		    *rgba_ptr++ = *rgb_ptr++;
		    *rgba_ptr++ = *rgb_ptr++;
		    *rgba_ptr++ = *rgb_ptr++;
		    *rgba_ptr++ = *alpha_ptr++;
		}
	    }
	}
	else
	{
	    guint8 *rgba_line, *rgba_line_end, *rgba_ptr, *rgba_end;
	    const guint8 *rgb_line, *rgb_ptr;

	    for(rgba_line       = rgba,
		rgba_line_end   = rgba_line + (rgba_bpl * height),
		rgb_line        = rgb;
		rgba_line < rgba_line_end;
		rgba_line       += rgba_bpl,
		rgb_line        += _rgb_bpl
	    )
	    {
		rgba_ptr        = rgba_line;
		rgba_end        = rgba_ptr + (width * 4);
		rgb_ptr         = rgb_line;

		while(rgba_ptr < rgba_end)
		{
		    *rgba_ptr++ = rgb_ptr[0];
		    *rgba_ptr++ = rgb_ptr[1];
		    *rgba_ptr++ = rgb_ptr[2];
		    *rgba_ptr++ = (
			(rgb_ptr[0] == 0xff) &&
			(rgb_ptr[1] == 0x00) &&
			(rgb_ptr[2] == 0xff)
		    ) ?
			0x00 : 0xff;

		    rgb_ptr += 3;
		}
	    }
	}

	return(rgba);
}


/*
 *	Called by EDVImbrTListLoadImageRGBA() to load a targa image.
 */
static guint8 *EDVImbrTListLoadImageRGBATarga(
	const gchar *path,
	gint *width, gint *height, gint *bpl,
	GdkWindow *window       /* Reference window */
)
{
	return(TgaReadFromFileFastRGBA(
	    path, width, height,
	    0xff000000	/* Transparent pixel rgba */
	));
}

/* 
 *	Opens the image specified by path and returns the RGBA image
 *	data and its geometry.
 *
 *	The specified Thumbs List can be NULL if resize_for_thumb is
 *	FALSE.
 *
 *	Returns the RGBA image data or NULL on error.
 */
GList *EDVImbrTListLoadImageRGBA(
	tlist_struct *tlist, const gchar *path,
	gint *width, gint *height, gint *bpl,
	gint *nframes, GList **delay_list,
	const gboolean resize_for_thumb,	/* Resize for tlist thumb */
	const gboolean no_enlarge,		/* No enlarge if smaller
						 * than req size */
 	GdkWindow *window			/* Reference window */
)
{
#ifdef HAVE_IMLIB
	const gchar *name;
	gint swidth, sheight, twidth, theight;
	GList *rgba_list = NULL;
	ImlibImage *imlib_image;

	/* Reset returns */
	if(width != NULL)
	    *width = 0;
	if(height != NULL)
	    *height = 0;
	if(bpl != NULL)
	    *bpl = 0;
	if(nframes != NULL)
	    *nframes = 0;
	if(delay_list != NULL)
	    *delay_list = NULL;

	if(STRISEMPTY(path))
	    return(NULL);

	name = g_basename(path);

	/* Begin checking extension to see if we should use a native
	 * image format loader instead of Imlib, since native loaders
	 * may be more efficient or provide more features
	 */

	/* Targa */
	if(EDVIsExtension(name, ".tga .targa"))
	{
	    guint8 *rgba = EDVImbrTListLoadImageRGBATarga(
		path, width, height, bpl, window
	    );
	    rgba_list = g_list_append(rgba_list, rgba);
	    if(delay_list != NULL)
	        *delay_list = g_list_append(
		    *delay_list, (gpointer)0l
		);
	    if(nframes != NULL)
		*nframes = g_list_length(rgba_list);
	}
#ifdef HAVE_LIBGIF
	else if(EDVIsExtension(name, ".gif"))
	{
	    gint	bpp = 0,
			x = 0, y = 0, base_width = 0, base_height = 0,
			user_aborted = 0;
	    gchar	*creator = NULL,
			*title = NULL,
			*author = NULL,
			*comments = NULL;
	    guint8 **rgba = NULL, bg_color[4];
	    gulong *delay = NULL;
	    ImgGIFReadRGBA(
		path,
		width, height, &bpp, bpl,
		&rgba, &delay, nframes,
		bg_color,		/* 4 bytes RGBA */
		&x, &y, &base_width, &base_height,
		&creator, &title, &author, &comments,
		0xff,			/* Default alpha value */
		NULL, NULL,
		&user_aborted
	    );
	    if(*nframes > 0)
	    {
		gint i;
		for(i = 0; i < *nframes; i++)
		{
		    rgba_list = g_list_append(rgba_list, rgba[i]);
		    rgba[i] = NULL;

		    if((delay_list != NULL) && (delay != NULL))
		    {
			const guint d = (guint)delay[i];
			*delay_list = g_list_append(
			    *delay_list,
			    (gpointer)d
			);
		    }
		}
	    }
	    g_free(rgba);
	    g_free(delay);
	    g_free(creator);
	    g_free(title);
	    g_free(author);
	    g_free(comments);
	}
#endif	/* HAVE_LIBGIF */
/* Add other image types that we should load alternatively (without
 * Imlib) here and make sure they return() the RGBA data pointer and do
 * not continue
 */
	/* Loaded image with native loaders? */
	if(rgba_list != NULL)
	    return(rgba_list);


	/* Load Imlib image */
	imlib_image = (imlib_handle != NULL) ?
	    Imlib_load_image(imlib_handle, (char *)path) : NULL;
	if(imlib_image == NULL)
	    return(NULL);

	/* Need to realize changes */
	Imlib_changed_image(imlib_handle, imlib_image);

	/* Get size of image */
	swidth = imlib_image->rgb_width;
	sheight = imlib_image->rgb_height;

	/* Resize the image for a thumb on the Thumbs List? */
	if(resize_for_thumb && (tlist != NULL))
	{
	    TListQueryThumbPixmapSize(
		tlist, swidth, sheight, &twidth, &theight
	    );
	    /* Check if we do not want to enlarge if the image is
	     * smaller than the thumb
	     */
	    if(no_enlarge)
	    {
		if(swidth < twidth)
		    twidth = swidth;
		if(sheight < theight)
		    theight = sheight;
	    }
	}
	else
	{
	    twidth = swidth;
	    theight = sheight;
	}
	/* At this point swidth and sheight are the size of the image
	 * and twidth and theight are the size of the image needed to
	 * fit on a thumb (if resize_for_thumb was TRUE)
	 */

	if((swidth <= 0) || (sheight <= 0) ||
	   (twidth <= 0) || (theight <= 0)
	)
	{
	    Imlib_destroy_image(imlib_handle, imlib_image);
	    return(NULL);
	}

	/* Need to resize? */
	if((swidth != twidth) || (sheight != theight))
	{
	    gint	t_rgb_bpl = twidth * 3,
			t_alpha_bpl = twidth * 1;
	    guint8 *rgba;

	    /* Allocate resized image buffers */
	    guint8	*t_rgb = (imlib_image->rgb_data != NULL) ?
		(guint8 *)g_malloc(t_rgb_bpl * theight) : NULL,
			*t_alpha = (imlib_image->alpha_data != NULL) ?
		(guint8 *)g_malloc(t_alpha_bpl * theight) : NULL;

	    if(t_rgb != NULL)
		GUIImageBufferResize(
		    3,
		    imlib_image->rgb_data,
		    swidth, sheight, -1,
		    t_rgb,
		    twidth, theight, -1
		);
	    if(t_alpha != NULL)
		GUIImageBufferResize(
		    1,
		    imlib_image->alpha_data,
		    swidth, sheight, -1,
		    t_alpha,
		    twidth, theight, -1
		);

	    /* Create RGBA image data from the resized thumb RGBA
	     * image data
	     */
	    rgba = EDVImbrTListConvertRGBToRGBA(
		t_rgb, t_alpha,
		twidth, theight,
		-1, -1		/* Autocalculate bpl values */
	    );
	    rgba_list = g_list_append(rgba_list, rgba);

	    g_free(t_rgb);
	    g_free(t_alpha);

	    /* Update image size */
	    swidth = twidth;
	    sheight = theight;
	}
	else
	{
	    /* Create RGBA data from the Imlib's RGB and Alpha data */
	    guint8 *rgba = EDVImbrTListConvertRGBToRGBA(
		imlib_image->rgb_data, imlib_image->alpha_data,
		swidth, sheight,
		-1, -1		/* Autocalculate bpl values */
	    );
	    rgba_list = g_list_append(rgba_list, rgba);
	}

	/* Destroy Imlib image */
	Imlib_destroy_image(imlib_handle, imlib_image);

	/* Set delay for only frame as 0 ms */
	if(delay_list != NULL)
	    *delay_list = g_list_append(
		*delay_list, (gpointer)0l
	    );

	/* Update returns */
	if(width != NULL)
	    *width = swidth;
	if(height != NULL)
	    *height = sheight;
	if(bpl != NULL)
	    *bpl = swidth * 4;
	if(nframes != NULL)
	    *nframes = g_list_length(rgba_list);

	return(rgba_list);
#else
	return(NULL);
#endif	/* HAVE_IMLIB */
}



/*
 *	Sets the text for the specified Thumb.
 */
static void EDVImbrTListSetThumbName(
	edv_core_struct *core, edv_imbr_struct *imbr,
	tlist_struct *tlist, edv_object_struct *obj,
	const gint thumb_num
)
{
	edv_object_type type = obj->type;
	const gchar *name = obj->name;
	GdkColor *text_color_fg = NULL;

	if(name == NULL)
	    name = "(null)";

	/* Check if this not an image file supported by Imlib */
	if(!EDVCheckImlibImage(core, name) ||
	   (EDV_OBJECT_IS_LINK(obj) && !EDV_OBJECT_IS_LINK_VALID(obj))
	)
	{
	    /* Probably not an image, so set icon */
	    GdkPixmap *pixmap, *pixmap_ext, *pixmap_hid;
	    GdkBitmap *mask, *mask_ext, *mask_hid;

	    /* Match icon with MIME Type */
	    EDVMatchObjectIcon(
		core->device, core->total_devices,
		core->mimetype, core->total_mimetypes,
		type,
		obj->full_path,
		EDV_OBJECT_IS_LINK_VALID(obj),
		obj->permissions,
		1,			/* Medium icons */
		&pixmap, &mask,
		NULL, NULL,
		&pixmap_ext, &mask_ext,
		&pixmap_hid, &mask_hid
	    );
	    /* Check if an alternate icon state should be used */
	    switch(type)
	    {
	      case EDV_OBJECT_TYPE_UNKNOWN:
		break;
	      case EDV_OBJECT_TYPE_FILE:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    pixmap = pixmap_hid;
		    mask = mask_hid;
		}
		break;
	      case EDV_OBJECT_TYPE_DIRECTORY:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    if(pixmap_hid != NULL)
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;
		    }
		}
		/* Special directory notations? */
		if(!strcmp(name, ".."))
		{
		    if(imbr->folder_parent_pixmap != NULL)
		    {
			pixmap = imbr->folder_parent_pixmap;
			mask = imbr->folder_parent_mask;
		    }
		}
#if 0
		/* Home directory? */
		if((!STRISEMPTY(obj->full_path) &&
		    !STRISEMPTY(core->home_dir)) ?
		    !strcmp(obj->full_path, core->home_dir) : FALSE
		)
		{
		    if(imbr->folder_home_pixmap != NULL)
		    {
			pixmap = imbr->folder_home_pixmap;
			mask = imbr->folder_home_mask;
		    }
		}
#endif
		/* Directory not accessable? */
		if(!EDVIsObjectAccessable(core, obj))
		{
		    pixmap = imbr->folder_noaccess_pixmap;
		    mask = imbr->folder_noaccess_mask;
		    if(text_color_fg == NULL)
		    {
			const GdkColor cs = EDV_GDKCOLOR_NO_ACCESS_FG;
			GdkColor *ct = (GdkColor *)g_malloc(
			    sizeof(GdkColor)
			);
			if(ct != NULL)
			    memcpy(ct, &cs, sizeof(GdkColor));
			text_color_fg = ct;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_LINK:
		/* Dangling? */
		if(!EDV_OBJECT_IS_LINK_VALID(obj))
		{
		    if(pixmap_ext != NULL)
		    {
			pixmap = pixmap_ext;
			mask = mask_ext;
		    }
		    if(text_color_fg == NULL)
		    {
			const GdkColor cs = EDV_GDKCOLOR_DANGLING_LINK_FG;
			GdkColor *ct = (GdkColor *)g_malloc(
			    sizeof(GdkColor)
			);
			if(ct != NULL)
			    memcpy(ct, &cs, sizeof(GdkColor));
			text_color_fg = ct;
		    }
		}
		/* Infinately recursive? */
		if(EDV_OBJECT_IS_LINK_TAR_GRAND_PARENT(obj))
		{
		    if(text_color_fg == NULL)
		    {
			const GdkColor cs = EDV_GDKCOLOR_RECURSIVE_LINK_FG;
			GdkColor *ct = (GdkColor *)g_malloc(
			    sizeof(GdkColor)
			);
			if(ct != NULL)
			    memcpy(ct, &cs, sizeof(GdkColor));
			text_color_fg = ct;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    if(pixmap_hid != NULL)
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    if(pixmap_hid != NULL)
		    {
		        pixmap = pixmap_hid;
		        mask = mask_hid;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_FIFO:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    if(pixmap_hid != NULL)
		    {
		        pixmap = pixmap_hid;
		        mask = mask_hid;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_SOCKET:
		/* Hidden */
		if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		{
		    if(pixmap_hid != NULL)
		    {
		        pixmap = pixmap_hid;
		        mask = mask_hid;
		    }
		}
		break;
	      case EDV_OBJECT_TYPE_ERROR:
		break;
 	    }

	    /* Set this thumb's pixmap */
	    TListSetPixmap(tlist, thumb_num, pixmap, mask);

	    /* Mark this thumb as loaded so that no pixmap gets
	     * loaded over it during any incidental calls to the
	     * loading process
	     */
	    TListSetLoadState(tlist, thumb_num, TLIST_LOAD_STATE_LOADED);
	}
	else
	{
	    /* Is an image, so do not set any pixmap on it and leave
	     * its load state as is
	     */
	}

	/* Set thumb's text */
	TListSetText(tlist, thumb_num, name);
	if(text_color_fg != NULL)
	    TListSetTextColor(tlist, thumb_num, text_color_fg, NULL);

	g_free(text_color_fg);
}

/*
 *	Appends the object to the Thumbs List.
 *
 *	The tlist specifies the Thumbs List.
 *
 *	The obj specifies the object to add to the Thumbs List. It
 *	will be transfered to the list so it should not be referenced
 *	again after this call.
 *
 *	Returns the new thumb index or -1 on error.
 *
 *	All inputs assumed valid.
 */
static gint EDVImbrTListAppendObject(
	edv_core_struct *core, edv_imbr_struct *imbr,
	tlist_struct *tlist,
	edv_object_struct *obj
)
{
	/* Append the new Thumb */
	gint thumb_num = TListAppend(tlist, "");
	if(thumb_num < 0)
	{
	    EDVObjectDelete(obj);
	    return(-1);
	}

	/* Set new Thumb's values */
	TListSetLoadState(
	    tlist, thumb_num,
	    TLIST_LOAD_STATE_NOT_LOADED
	);
	TListSetThumbDataFull(
	    tlist, thumb_num,
	    obj, EDVImbrTListItemDestroyCB
	);

	EDVImbrTListSetThumbName(
	    core, imbr,
	    tlist,
	    obj,
	    thumb_num
	);

	return(thumb_num);
}

/*
 *	Finds the thumb who's object matches the location index.
 *
 *	The device_index specifies the index of the device that the
 *	object resides on.
 *
 *	The index specifies the index on the device that the object
 *	resides at.
 *
 *	Returns the matched thumb or -1 on error.
 */
gint EDVImbrTListFindThumbByIndex(
	edv_imbr_struct *imbr, 
	const gulong device_index, const gulong index
)
{
	gint i;
	tlist_struct *tlist;
	edv_object_struct *obj;

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

	tlist = imbr->tlist;
	if(tlist == NULL)
	    return(-1);

	/* Iterate through the thumbs list and look for a thumb
	 * who's object matches the specified location index
	 */
	for(i = 0; i < tlist->total_thumbs; i++)
	{
	    obj = EDV_OBJECT(TListGetThumbData(tlist, i));
	    if(obj == NULL)
		continue;

	    /* Location indices match? */
	    if((obj->device_index == device_index) &&
	       (obj->index == index)
	    )
		return(i);
	}

	return(-1);
}

/*
 *	Finds the thumb who's object matches the path.
 *
 *	The path specifies the full path of the object.
 *
 *	Returns the matched thumb or -1 on error.
 */
gint EDVImbrTListFindThumbByPath(
	edv_imbr_struct *imbr, const gchar *path
)
{
	gint i;
	tlist_struct *tlist;
	edv_object_struct *obj;

	if((imbr == NULL) || (path == NULL))
	    return(-1);

	tlist = imbr->tlist;
	if(tlist == NULL)
	    return(-1);

	/* Iterate through the thumbs list and look for a thumb
	 * who's object matches the specified path
	 */
	for(i = 0; i < tlist->total_thumbs; i++)
	{
	    obj = EDV_OBJECT(TListGetThumbData(tlist, i));
	    if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
		continue;

	    /* Full paths match? */
	    if(!strcmp(obj->full_path, path))
		return(i);
	}

	return(-1);
}


/*
 *	Returns a list of full paths describing the selected items.
 *
 *	The calling function must delete the list.
 */
GList *EDVImbrTListGetSelectedPaths(edv_imbr_struct *imbr)
{
	GList *glist, *paths_list = NULL;
	edv_object_struct *obj;
	tlist_struct *tlist = (imbr != NULL) ? imbr->tlist : NULL;
	if(tlist == NULL)
	    return(paths_list);

	for(glist = tlist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    obj = EDV_OBJECT(TListGetThumbData(
		tlist, (gint)glist->data
	    ));
	    if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
		continue;

	    paths_list = g_list_append(
		paths_list,
		STRDUP(obj->full_path)
	    );
	}

	return(paths_list);
}


/*
 *	Updates all thumbs on the Thumbs List with each thumb's
 *	associated object.
 *
 *	This should be called when MIME Types or Devices have been
 *	added/modified/removed or after reconfiguration.
 *
 *	This function should not be used to refresh the list (get a
 *	new listing of new objects), use EDVImbrTListGetListing()
 *	instead.
 */
void EDVImbrTListRealizeListing(edv_imbr_struct *imbr)
{
	gint i;
	tlist_thumb_struct *thumb;
	tlist_struct *tlist;
	edv_object_struct *obj;
	edv_core_struct *core;

	if(imbr == NULL)
	    return;

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

	for(i = 0; i < tlist->total_thumbs; i++)
	{
	    thumb = tlist->thumb[i];
	    if(thumb == NULL)
		continue;

	    obj = EDV_OBJECT(thumb->data);
	    if(obj != NULL)
		EDVImbrTListSetThumbName(
		    core, imbr, tlist, obj, i
		);
	}
}

/*
 *	Deletes all Thumbs in the Thumbs List and gets a new listing
 *	of objects (does not load each Thumb's image) from the location
 *	specified by path.
 */
void EDVImbrTListGetListing(
	edv_imbr_struct *imbr, const gchar *path,
	gboolean update_status_bar
)
{
#ifdef HAVE_REGEX
	regex_t *regex_filter;
#endif
	gboolean	hide_object_hidden,
			hide_object_noaccess,
			hide_object_nonimages;
	const gchar *filter;
	gint nobjs;
	gchar **names_list;
	const cfg_item_struct *cfg_list;
	tlist_struct *tlist;
	edv_status_bar_struct *sb;
	edv_core_struct *core;

	if((imbr == NULL) || STRISEMPTY(path))
	    return;

	tlist = imbr->tlist;
	sb = imbr->status_bar;
	core = imbr->core;
	if((tlist == NULL) || (core == NULL))
	    return;

	cfg_list = core->cfg_list;

	hide_object_hidden = !EDV_GET_B(
	    EDV_CFG_PARM_IMBR_SHOW_OBJECT_HIDDEN
	);
	hide_object_noaccess = !EDV_GET_B(
	    EDV_CFG_PARM_IMBR_SHOW_OBJECT_NOACCESS
	);
	hide_object_nonimages = !EDV_GET_B(
	    EDV_CFG_PARM_IMBR_SHOW_OBJECT_NONIMAGE
	);

	filter = imbr->thumbs_list_filter;

#if defined(HAVE_REGEX)
	/* Compile the regex search criteria */
	if(STRISEMPTY(filter) ?
	    FALSE : strcmp(filter, "*")
	)
	{
	    regex_filter = (regex_t *)g_malloc(sizeof(regex_t));
	    if(regcomp(
		regex_filter,
		filter,
#ifdef REG_EXTENDED
		REG_EXTENDED |		/* Use POSIX extended regex */
#endif
		REG_NOSUB		/* Do not report subpattern matches */
	    ))
	    {
		g_free(regex_filter);
		regex_filter = NULL;
	    }
	}
	else
	{
	    regex_filter = NULL;
	}
#else
	if(STRISEMPTY(filter) ?
	    TRUE : !strcmp(filter, "*")
	)
	    filter = NULL;
#endif

	/* Begin clearing the thumbs list and getting new listing */

	TListFreeze(tlist);

	TListClear(tlist);

	/* Get listing of contents in the directory specified by path */
	names_list = GetDirEntNames2(path, &nobjs);
	if(names_list != NULL)
	{
	    /* Sort the directory contents alphabetically by name */
	    names_list = StringQSort(names_list, nobjs);
	    if(names_list != NULL)
	    {
		struct stat stat_buf;
		gint	i,
			nobjs_loaded = 0;
/*			last_progress_percent = -1; */
		const gchar *name;
		gchar *full_path = NULL;
		edv_object_struct *obj;
#define UPDATE_PROGRESS	{				\
 if(update_status_bar && (nobjs > 0)) {			\
  const gint progress_percent = nobjs_loaded * 100 / nobjs; \
  if(progress_percent > last_progress_percent) {	\
   EDVStatusBarProgress(				\
    sb,							\
    (gfloat)progress_percent / 100.0f,			\
    TRUE						\
   );							\
   last_progress_percent = progress_percent;		\
  }							\
 }							\
}
		/* Iterate through the directory contents and pick
		 * out just the directories for this first iteration
		 */
		for(i = 0; i < nobjs; i++)
		{
#define FREE_AND_CONTINUE	{	\
 g_free(full_path);			\
 full_path = NULL;			\
					\
 /* Do not delete names_list[i] on this	\
  * first iteration 			\
  */					\
					\
 continue;				\
}

		    name = names_list[i];
		    if(name == NULL)
			FREE_AND_CONTINUE

		    /* Skip special directory notations */
		    if(!strcmp(name, "."))
		    {
			nobjs_loaded++;
/*			UPDATE_PROGRESS */
			FREE_AND_CONTINUE
		    }

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

		    /* Get this object's destination stats */
		    if(stat((const char *)full_path, &stat_buf))
		    {
			/* Failed to get the statistics of this
			 * object but do not add it as an error
			 * object on this directory iteration, it
			 * will be added later in the second
			 * iteration
			 */
			FREE_AND_CONTINUE
		    }

#ifdef S_ISDIR
		    /* Skip if the destination is not a directory */
		    if(!S_ISDIR(stat_buf.st_mode))
			FREE_AND_CONTINUE
#endif

		    /* Get this directory's local stats */
		    if(lstat((char *)full_path, &stat_buf))
		    {
			/* Unable to get the directory's local stats
			 * so add it as an error object
			 */
			obj = EDVImbrNewErrorObject(full_path);
			if(obj != NULL)
			    EDVImbrTListAppendObject(
				core, imbr, tlist,
				obj
			    );
			nobjs_loaded++;
/*			UPDATE_PROGRESS */
			g_free(names_list[i]);
			names_list[i] = NULL;
			FREE_AND_CONTINUE
		    }

		    /* Create a new object */
		    obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, full_path);
			EDVObjectSetStat(obj, &stat_buf);
			EDVObjectUpdateLinkFlags(obj);

			/* Skip hidden or no access objects? */
			if((hide_object_hidden ?
			    EDVIsObjectHidden(obj) : FALSE) ||
			   (hide_object_noaccess ?
			    !EDVIsObjectAccessable(core, obj) : FALSE)
			)
			{
			    EDVObjectDelete(obj);
			}
			else
			{
			    /* Append/transfer this object to the listing */
			    EDVImbrTListAppendObject(
				core, imbr, tlist,
				obj
			    );
			}
		    }

/* Do not update progress when getting listing of thumbs as disk objects,
 * since progress should only be updated when each thumb's image is loaded.
 */
/*		    UPDATE_PROGRESS */
		    g_free(names_list[i]);
		    names_list[i] = NULL;
		    FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
		}

		/* Now iterate through all other objects, skipping
		 * directories
		 *
		 * Note that this iteration will also delete all the
		 * strings in names_list
		 */
		full_path = NULL;
		for(i = 0; i < nobjs; i++)
		{
#define FREE_AND_CONTINUE	{	\
 g_free(full_path);			\
 full_path = NULL;			\
					\
 g_free(names_list[i]);			\
 names_list[i] = NULL;			\
					\
 continue;				\
}
		    name = names_list[i];
		    if(name == NULL)
			FREE_AND_CONTINUE

		    /* Skip special directory notations */
		    if(!strcmp(name, "."))
			FREE_AND_CONTINUE

		    /* Filter check */
#if defined(HAVE_REGEX)
		    if(regex_filter != NULL)
		    {
			if(regexec(
			    regex_filter,
			    name,
			    0, NULL,
			    0
			) == REG_NOMATCH)
			{
			    nobjs_loaded++;
/*			    UPDATE_PROGRESS */
			    FREE_AND_CONTINUE
			}
		    }
#else
		    if(filter != NULL)
		    {
			if(fnmatch(filter, name, 0) == FNM_NOMATCH)
			{
			    nobjs_loaded++;
/*			    UPDATE_PROGRESS */
			    FREE_AND_CONTINUE
			}
		    }
#endif

		    /* Get full path to this object */
		    full_path = STRDUP(PrefixPaths(path, name));
		    if(full_path == NULL)
			FREE_AND_CONTINUE

		    /* Get this object's local stats */
		    if(lstat((const char *)full_path, &stat_buf))
		    {
			/* Unable to get the object's local stats
			 * so add it as an error object
			 */
			obj = EDVImbrNewErrorObject(full_path);
			if(obj != NULL)
			    EDVImbrTListAppendObject(
				core, imbr, tlist,
				obj
			    );
			nobjs_loaded++;
/*			UPDATE_PROGRESS */
			FREE_AND_CONTINUE
		    }

		    /* Create new object */
		    obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, full_path);
			EDVObjectSetStat(obj, &stat_buf);
			EDVObjectUpdateLinkFlags(obj);

			/* Skip hidden or non-image objects? */
			if((hide_object_hidden ?
			       EDVIsObjectHidden(obj) : FALSE) ||
/*
			   (hide_object_noaccess ?
			       !EDVIsObjectAccessable(core, obj) : FALSE) ||
 */
			   (hide_object_nonimages ?
			       !EDVCheckImlibImage(core, name) : FALSE)
			)
			{
			    EDVObjectDelete(obj);
			}
			else
			{
			    /* Append/transfer this object to the listing */
			    EDVImbrTListAppendObject(
				core, imbr, tlist,
				obj
			    );
			}
		    }

/* Do not update progress when getting listing of thumbs as disk objects,
 * since progress should only be updated when each thumb's image is loaded.
 */
/*		    UPDATE_PROGRESS */
		    FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
		}

		/* At this point all the strings in names_list have been
		 * deleted
		 */
#undef UPDATE_PROGRESS
	    }

	    /* Delete the string pointer array, each string should
	     * already be deleted
	     */
	    g_free(names_list);
	}

#ifdef HAVE_REGEX
	if(regex_filter != NULL)
	{
	    regfree(regex_filter);
	    g_free(regex_filter);
	}
#endif

	TListThaw(tlist);
}

/*
 *	Deletes all Thumbs in the Thumbs List.
 */
void EDVImbrTlistClear(edv_imbr_struct *imbr)
{
	tlist_struct *tlist;

	if(imbr == NULL)
	    return;

	tlist = imbr->tlist;
	if(tlist == NULL)
	    return;

	TListFreeze(tlist);
	TListClear(tlist);
	TListThaw(tlist);
}


/*
 *	Loads the Image Browser Thumbs List's next unloaded thumb.
 *
 *	The next unloaded thumb will be the next thumb from the
 *	current scroll position. If there are no unloaded thumbs there
 *	then a second pass will check if there is an unloaded thumb
 *	starting from the top.
 *
 *	Returns:
 *
 *	0	All thumbs loaded
 *	1	Thumb loaded successfully but still might be more thumbs
 *		that need to be loaded
 *	-1	General error
 *	-2	Ambiguous, corrupt image, or unsupported format
 *	-3	Systems error
 */
gint EDVImbrTListLoadIterate(
	edv_imbr_struct *imbr,
	const gboolean update_status_bar,
	const gboolean no_enlarge
)
{
	gint starting_thumb_num, thumb_num, thumb_to_load_num = -1;
	gint thumbs_already_loaded = 0;
	GtkWidget *toplevel;
	tlist_thumb_struct *thumb, *thumb_to_load = NULL;
	 tlist_struct *tlist;
	edv_object_struct *obj;
	edv_status_bar_struct *sb;
	edv_core_struct *core;

#define DO_RESET_PROGRESS	{		\
 if(update_status_bar) {			\
  EDVStatusBarProgress(sb, 0.0f, FALSE);	\
  EDVStatusBarMessage(sb, NULL, FALSE);		\
 }						\
}

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

	toplevel = imbr->toplevel;
	tlist = imbr->tlist;
	sb = imbr->status_bar;
	core = imbr->core;
	if((toplevel == NULL) || (tlist == NULL) || (core == NULL))
	{
	    DO_RESET_PROGRESS
	    return(-1);
	}

	/* Count number of thumbs already loaded */
	if(update_status_bar)
	{
	    for(thumb_num = 0;
		thumb_num < tlist->total_thumbs;
		thumb_num++
	    )
	    {
		thumb = tlist->thumb[thumb_num];
		if(thumb == NULL)
		{
		    thumbs_already_loaded++;	/* Count NULL's as loaded */
		    continue;
		}

		/* This thumb loaded? */
		if(thumb->load_state != TLIST_LOAD_STATE_NOT_LOADED)
		    thumbs_already_loaded++;
	    }
	}

	/* Calculate first starting thumb at scroll position */
	TListGetSelection(tlist, 0, 0, &starting_thumb_num, NULL, NULL);

	/* Look for thumb to load starting from the scroll position
	 * to the end of the list
	 */
	if(starting_thumb_num > -1)
	{
	    for(thumb_num = starting_thumb_num;
		thumb_num < tlist->total_thumbs;
		thumb_num++
	    )
	    {
		thumb = tlist->thumb[thumb_num];
		if(thumb == NULL)
		    continue;

		if(thumb->load_state == TLIST_LOAD_STATE_NOT_LOADED)
		{
		    thumb_to_load = thumb;
		    thumb_to_load_num = thumb_num;
		    break;
		}
	    }
	}
	/* If no thumbs needed to be loaded starting from
	 * starting_thumb_num then start loading from thumb 0
	 */
	if((thumb_to_load == NULL) &&
	   (starting_thumb_num <= tlist->total_thumbs)
	)
	{
	    for(thumb_num = 0; thumb_num < starting_thumb_num; thumb_num++)
	    {
		thumb = tlist->thumb[thumb_num];
		if(thumb == NULL)
		    continue;

		if(thumb->load_state == TLIST_LOAD_STATE_NOT_LOADED)
		{
		    thumb_to_load = thumb;
		    thumb_to_load_num = thumb_num;
		    break;
		}
	    }
	}

	/* No more thumbs need to be loaded? */
	if((thumb_to_load == NULL) || (thumb_to_load_num < 0))
	{
	    if(update_status_bar)
	    {
		EDVStatusBarProgress(sb, 0.0f, FALSE);
		EDVStatusBarMessage(
		    sb,
		    "Loading done",
		    FALSE
		);
	    }
	    return(0);
	}

	/* Get object from thumb data */
	obj = EDV_OBJECT(thumb_to_load->data);
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	{
	    thumb_to_load->load_state = TLIST_LOAD_STATE_FAILED;
	    DO_RESET_PROGRESS
	    return(-1);
	}

	/* Load image for this thumb */
	if(TRUE)
	{
	    gint width, height, bpl, nframes;
	    const gchar	*name = obj->name,
			*full_path = obj->full_path;
	    GList *rgba_list, *delay_list;
	    GdkWindow *window = toplevel->window;

	    if(update_status_bar)
	    {
		const gfloat progress = (tlist->total_thumbs > 0) ?
		    ((gfloat)thumbs_already_loaded / (gfloat)tlist->total_thumbs) : 0.0f;
		gchar *buf = g_strdup_printf(
		    "Loading \"%s\" (%ld byte%s)",
		    name, obj->size,
		    (obj->size == 1) ? "" : "s"
		);
		EDVStatusBarMessage(sb, buf, FALSE);
		g_free(buf);

		EDVStatusBarProgress(sb, progress, FALSE);
	    }

/* Check if image is cached */

	    /* Load image to RGBA data */
	    rgba_list = EDVImbrTListLoadImageRGBA(
		tlist, full_path,
		&width, &height, &bpl, &nframes, &delay_list,
		TRUE,			/* Resize for tlist thumb */
		no_enlarge,		/* Do not enlarge? */
		window
	    );
	    if(rgba_list == NULL)
	    {
		/* Failed to load image for this thumb, mark the thumb's
		 * load state as failed and set the badimage icon for
		 * this thumb's pixmap
		 */
		thumb_to_load->load_state = TLIST_LOAD_STATE_FAILED;
		TListSetPixmap(
		    tlist, thumb_to_load_num,
		    imbr->file_badimage_pixmap,
		    imbr->file_badimage_mask
		);

		DO_RESET_PROGRESS
		return(-1);
	    }

	    /* Update load state on thumb */
	    thumb_to_load->load_state = TLIST_LOAD_STATE_LOADED;

	    /* Set image to thumb */
	    TListSetRGBA(
		tlist, thumb_to_load_num,
		width, height, bpl,
		GDK_RGB_DITHER_NORMAL,
		(guint8 *)rgba_list->data,	/* Use first image */
		no_enlarge
	    );

	    /* Delete the loaded images */
	    g_list_foreach(rgba_list, (GFunc)g_free, NULL);
	    g_list_free(rgba_list);
	    g_list_free(delay_list);

	    /* Report one thumb loaded */
	    return(1);
	}

	return(0);
}


/*
 *	Opens the object.
 *
 *	If thumb_num is -1 then the selected object(s) will be opened.
 *
 *	The state specifies the current key modifiers.
 */
void EDVImbrTListOpen(
	edv_imbr_struct *imbr, const gint thumb_num,
	const guint state			/* Key modifiers */
)
{
	GList *paths_list;
	GtkWidget *toplevel;
	edv_core_struct *core;

	if(imbr == NULL)
	    return;

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

	/* Use selected objects? */
	if(thumb_num < 0)
	{
	    paths_list = EDVImbrTListGetSelectedPaths(imbr);
	}
	else
	{
	    tlist_struct *tlist = imbr->tlist;
	    edv_object_struct *obj = EDV_OBJECT(
		TListGetThumbData(tlist, thumb_num)
	    );
	    if(obj == NULL)
		return;

	    paths_list = g_list_append(
		NULL,
		STRDUP(obj->full_path)
	    );
	}

	/* Check if there is only one path and it refers to a directory */
	if(g_list_length(paths_list) == 1)
	{
	    struct stat stat_buf;
	    gchar *path = EDVEvaluatePath(
		NULL, (gchar *)paths_list->data
	    );
	    if((path != NULL) ? stat(path, &stat_buf) : TRUE)
	    {
		g_list_foreach(paths_list, (GFunc)g_free, NULL);
		g_list_free(paths_list);
		g_free(path);
		return;
	    }
#ifdef S_ISDIR
	    if(S_ISDIR(stat_buf.st_mode))
	    {
		/* Change directory and return */
		EDVImbrGotoDirectoryCB(imbr, path);

		g_list_foreach(paths_list, (GFunc)g_free, NULL);
		g_list_free(paths_list);
		g_free(path);

		return;
	    }
#endif	/* S_ISDIR */
	}

	if(paths_list != NULL)
	{
	    gchar	*stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
	    const gchar *command_name = NULL;

	    if(state & GDK_CONTROL_MASK)
		command_name = "edit";
	    else if(state & GDK_SHIFT_MASK)
		command_name = "edit";

	    EDVImbrSetBusy(imbr, TRUE);
	    EDVOpen(
		core,
		paths_list,		/* Paths List */
		command_name,		/* Command Name */
		toplevel,		/* Toplevel */
		TRUE,			/* Verbose */
		&stdout_path_rtn,
		&stderr_path_rtn
	    );
	    EDVImbrSetBusy(imbr, FALSE);

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	g_list_foreach(paths_list, (GFunc)g_free, NULL);
	g_list_free(paths_list);
}

/*
 *	Maps the Open With list to open the object.
 *
 *	If thumb_num is -1 then the selected object(s) will be opened.
 */
void EDVImbrTListOpenWith(
	edv_imbr_struct *imbr, const gint thumb_num
)
{
	GList *paths_list;
	GtkWidget *toplevel;
	edv_core_struct *core;

	if(imbr == NULL)
	    return;

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

	/* Use selected objects? */
	if(thumb_num < 0)
	{
	    /* Get list of selected object paths */
	    paths_list = EDVImbrTListGetSelectedPaths(imbr);
	}
	else
	{
	    tlist_struct *tlist = imbr->tlist;
	    edv_object_struct *obj = EDV_OBJECT(
		TListGetThumbData(tlist, thumb_num)
	    );
	    if(obj == NULL)
		return;

	    paths_list = g_list_append(
		NULL,
		STRDUP(obj->full_path)
	    );
	}

	if(paths_list != NULL)
	{
	    gchar       *stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;

	    EDVOpenWith(
		core,
		paths_list,		/* Paths List */
		NULL,			/* Command Name */
		toplevel,		/* Toplevel */
		TRUE,			/* Verbose */
		&stdout_path_rtn,
		&stderr_path_rtn
	    );

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	g_list_foreach(paths_list, (GFunc)g_free, NULL);
	g_list_free(paths_list);
}


/*
 *      FPrompt apply callback, set in EDVImbrTListDoFPromptRename().
 */
static void EDVImbrTListFPromptRenameApplyCB(
	gpointer data, const gchar *value
)
{
	gint thumb_num;
	GtkWidget *toplevel;
	tlist_struct *tlist;
	edv_imbr_struct *imbr;
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return;

	/* Get callback data */
	imbr = EDV_IMBR(cb_data[0]);
	tlist = TLIST(cb_data[1]);
	thumb_num = (gint)cb_data[2];

	toplevel = imbr->toplevel;

	/* Inputs valid? */
	if((imbr != NULL) && (tlist != NULL) && (thumb_num >= 0) &&
	   (value != NULL)
	)
	{
	    edv_core_struct *core = imbr->core;
	    edv_object_struct *obj = EDV_OBJECT(
		TListGetThumbData(tlist, thumb_num)
	    );

	    /* Check if the selected object is valid */
	    if((obj != NULL) ? !STRISEMPTY(obj->full_path) : FALSE)
	    {
		gboolean yes_to_all = FALSE;
		const gchar *error_msg;
		gchar *old_full_path = STRDUP(obj->full_path);
		GList *modified_paths_list;

		/* Rename */
		EDVObjectOPRename(
		    core,
		    old_full_path, value,
		    &modified_paths_list,
		    toplevel,
		    FALSE,			/* Do not show progress */
		    TRUE,			/* Interactive */
		    &yes_to_all
		);

		/* Unmap progress dialog, it may have been mapped in the
		 * above operation
		 */
		ProgressDialogBreakQuery(FALSE);
		ProgressDialogSetTransientFor(NULL);

		/* Get error message if any that might have occured in the
		 * above operation
		 */
		error_msg = EDVObjectOPGetError();
		if(!STRISEMPTY(error_msg))
		{
		    EDVPlaySoundError(core);
		    EDVMessageObjectOPError(
			"Rename Object Error",
			error_msg,
			old_full_path,
			toplevel
		    );
		}

		/* Report the modified objects */
		if(modified_paths_list != NULL)
		{
		    struct stat lstat_buf;
		    const gchar *modified_path;
		    GList *glist;

		    for(glist = modified_paths_list;
			glist != NULL;
			glist = g_list_next(glist)
		    )
		    {
			modified_path = (const gchar *)glist->data;
			if(modified_path == NULL)
			    continue;

			if(!lstat(modified_path, &lstat_buf))
			{
			    gchar *s = g_strdup_printf(
				"Object \"%s\" renamed to \"%s\"",
				g_basename(old_full_path),
				g_basename(modified_path)
			    );
			    EDVStatusBarMessage(imbr->status_bar, s, FALSE);
			    g_free(s);

			    EDVObjectModifiedEmit(
				core,
				old_full_path,
				modified_path,
				&lstat_buf
			    );
			}
		    }

		    g_list_foreach(modified_paths_list, (GFunc)g_free, NULL);
		    g_list_free(modified_paths_list);
		}
		else
		{
		    /* Did not get the modified object path so this
		     * implies failure
		     */
		    EDVStatusBarMessage(
			imbr->status_bar,
			"Rename object failed",
			FALSE
		    );
		}

		g_free(old_full_path);
	    }
	}

	/* Delete the callback data, it is no longer needed */
	g_free(cb_data);
}

/*
 *	Procedure to map the floating prompt for renaming of the row
 *	specified by row.
 *
 *      If the column is -1 then it implies any column is valid.
 */
void EDVImbrTListDoFPromptRename(
	edv_imbr_struct *imbr, const gint thumb_num
)
{
	gint cx, cy, px, py, pwidth, pheight;
	tlist_struct *tlist;
	edv_object_struct *obj;
	edv_core_struct *core;

	if((imbr == NULL) || (thumb_num < 0) || FPromptIsQuery())
	    return;

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

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(core, TRUE, imbr->toplevel))
	    return;

	EDVImbrSyncData(imbr);

	/* Make sure given thumb index is in bounds */
	if((thumb_num < 0) || (thumb_num >= tlist->total_thumbs))
	    return;

	pwidth = tlist->thumb_width;
	pheight = 20;

	/* Get root window relative coordinates */
	px = 0;
	py = 0;
	if(tlist->list_da != NULL)
	    gdk_window_get_deskrelative_origin(
		tlist->list_da->window, &px, &py
	    );
	if(TListGetThumbPosition(tlist, thumb_num, &cx, &cy)) 
	    px += cx;
	if(TListGetThumbLabelGeometry(
	    tlist, thumb_num, &cx, &cy, NULL, NULL
	))
	    py += cy - 4;

	/* Get object from thumb data */
	obj = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));
	if(obj == NULL)
	    return;

	/* Check if object name is a special notation that should not
	 * be allowed to be renamed
	 */
	if(obj->name != NULL)
	{
	    const gchar *name = obj->name;
	    if(!strcmp(name, ".") || !strcmp(name, "..") ||
	       !strcmp(name, "/")
	    )
		return;
	}

	if(TRUE)
	{
	    gpointer *cb_data = (gpointer *)g_malloc0(
		3 * sizeof(gpointer)
	    );
	    gchar *value = STRDUP(obj->name);

	    /* Set up callback data */
	    if(cb_data != NULL)
	    {
		cb_data[0] = imbr;
		cb_data[1] = tlist;
		cb_data[2] = (gpointer)thumb_num;
	    }

	    /* Map floating prompt to change values */
	    FPromptSetTransientFor(imbr->toplevel);
	    FPromptSetPosition(px, py);
	    FPromptMapQuery(
		NULL,			/* No label */
		value,			/* Initial value */
		NULL,			/* No tooltip message */
		FPROMPT_MAP_NO_MOVE,	/* Map code */
		pwidth, -1,		/* Width and height */
		0,			/* Flags */
		(gpointer)cb_data,	/* Callback data */
		NULL,			/* No browse callback */
		EDVImbrTListFPromptRenameApplyCB,
		EDVImbrTListFPromptCancelCB
	    );

	    /* Do not reference cb_data after this call, it will be passed
	     * to the callbacks where it will be deallocated.
	     */
	    cb_data = NULL;

	    /* Delete original value */
	    g_free(value);
	}
}


/*
 *	Object added callback.
 */
void EDVImbrTListObjectAddedNotify(
	edv_imbr_struct *imbr, const gchar *path,
	const struct stat *lstat_buf
)
{
	gint thumb_num;
	const gchar *cur_path;
	gchar *parent_path;
	tlist_struct *tlist;
	edv_object_struct *obj;
	edv_core_struct *core;

	if((imbr == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

	cur_path = EDVImbrCurrentLocation(imbr);
	tlist = imbr->tlist;
	core = imbr->core;
	if((cur_path == NULL) || (tlist == NULL) || (core == NULL))
	    return;

	parent_path = g_dirname(path);
	if(parent_path == NULL)
	    return;

	/* Skip if the parent of the added path the same as the
	 * current location
	 */
	if(strcmp(cur_path, parent_path))
	{
	    g_free(parent_path);
	    return;
	}

	/* Check if the added object refers to an object that is
	 * already listed
	 */
	thumb_num = EDVImbrTListFindThumbByIndex(
	    imbr,
	    (gulong)lstat_buf->st_dev,
	    (gulong)lstat_buf->st_ino
	);
	if(thumb_num > -1)
	{
	    /* Update the existing object */
	    obj = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));
	    if(obj != NULL)
	    {
		EDVObjectSetPath(obj, path);
		EDVObjectSetStat(obj, lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		/* Update the thumb's displayed name */
		EDVImbrTListSetThumbName(
		    core, imbr, tlist, obj, thumb_num
		);
	    }
	}
	else
	{
	    /* Add the new object */
	    obj = EDVObjectNew();
	    if(obj != NULL)
	    {
		EDVObjectSetPath(obj, path);
		EDVObjectSetStat(obj, lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		TListFreeze(tlist);

		thumb_num = EDVImbrTListAppendObject(
		    core, imbr, tlist,
		    obj
		);

		TListThaw(tlist);
	    }

	    /* Need to (re)queue loading process so that any newly
	     * added thumb gets its image loaded
	     */
	    EDVImbrQueueLoadingProcess(imbr);
	}

	g_free(parent_path);
}

/*
 *	Object modified callback.
 */
void EDVImbrTListObjectModifiedNotify(
	edv_imbr_struct *imbr, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
)
{
	const gchar *cur_path;
	gchar *parent_path;
	tlist_struct *tlist;
	edv_core_struct *core;

	if((imbr == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

	if(new_path == NULL)
	    new_path = path;

	cur_path = EDVImbrCurrentLocation(imbr);
	tlist = imbr->tlist;
	core = imbr->core;
	if((tlist == NULL) || (core == NULL))
	    return;

	parent_path = g_dirname(path);
	if(parent_path == NULL)
	    return;

	/* Is the new modified path the same as the current location? */
	if(!strcmp(new_path, cur_path))
	{
	    /* Reget the listing */
	    EDVImbrTListGetListing(imbr, new_path, TRUE);
	}
	/* Is the modified path's parent directory the same as the
	 * current location?
	 */
	else if(!strcmp(parent_path, cur_path))
	{
	    /* Update the existing object */
	    const gint thumb_num = EDVImbrTListFindThumbByIndex(
		imbr,
		(gulong)lstat_buf->st_dev,
		(gulong)lstat_buf->st_ino
	    );
	    if(thumb_num > -1)
	    {
		edv_object_struct *obj = EDV_OBJECT(
		    TListGetThumbData(tlist, thumb_num)
		);
		if(obj != NULL)
		{
		    EDVObjectSetPath(obj, new_path);		/* Use new_path */
		    EDVObjectSetStat(obj, lstat_buf);
		    EDVObjectUpdateLinkFlags(obj);

		    /* Update the thumb's displayed name */
		    EDVImbrTListSetThumbName(
			core, imbr, tlist, obj, thumb_num
		    );
		}
	    }
	}

	g_free(parent_path);
}

/*
 *	Object removed callback.
 */
void EDVImbrTListObjectRemovedNotify(
	edv_imbr_struct *imbr, const gchar *path
)
{
	const gchar *cur_path;
	gchar *parent_path;
	tlist_struct *tlist;

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

	cur_path = EDVImbrCurrentLocation(imbr);
	tlist = imbr->tlist;
	if(tlist == NULL)
	    return;

	parent_path = g_dirname(path);
	if(parent_path == NULL)
	    return;

	/* Is the removed path the same as the current location? */
	if(!strcmp(path, cur_path))
	{
	    /* Clear the thumbs list */
	    TListFreeze(tlist);
	    TListClear(tlist);
	    TListThaw(tlist);
	}
	/* Is the removed path's parent directory the same as the
	 * current location?
	 */
	else if(!strcmp(parent_path, cur_path))
	{
	    /* Remove the existing object */
	    const gint thumb_num = EDVImbrTListFindThumbByPath(
		imbr,
		path
	    );
	    if(thumb_num > -1)
	    {
		TListFreeze(tlist);
		TListRemove(tlist, thumb_num);
		TListThaw(tlist);;
	    }
	}

	g_free(parent_path);
}


/*
 *	Device mount/unmount callback.
 */
void EDVImbrTListMountNotify(
	edv_imbr_struct *imbr, edv_device_struct *dev,
	gboolean is_mounted
)
{
	gchar *cur_path, *mount_path;
	tlist_struct *tlist;

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

	tlist = imbr->tlist;
	if(tlist == NULL)
	    return;

	cur_path = STRDUP(EDVImbrCurrentLocation(imbr));
	mount_path = STRDUP(dev->mount_path);
	if((cur_path == NULL) || (mount_path == NULL))
	{
	    g_free(cur_path);
	    g_free(mount_path);
	    return;
	}

	EDVSimplifyPath(cur_path);
	EDVSimplifyPath(mount_path);

	/* Check if mount path is a parent of the current location,
	 * if it is then the Thumbs List needs to be updated
	 */
	if(strpfx(cur_path, mount_path))
	{
	    /* Reget listing */
	    EDVImbrTListGetListing(imbr, cur_path, TRUE);
	}

	g_free(cur_path);
	g_free(mount_path);
}
