#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <gtk/gtk.h>

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

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

#include "url.h"
#include "edvtypes.h"
#include "edvobj.h"
#include "edvrecbin.h"
#include "edvarchobj.h"
#include "edvfop.h"
#include "edvconfirm.h"
#include "edvrecbinfio.h"
#include "edvrecbinfop.h"
#include "edvarchfio.h"
#include "edvarchop.h"
#include "archiveopts.h"
#include "imbr.h"
#include "imbrdnd.h"
#include "endeavour.h"
#include "edvop.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "config.h"


static void EDVImbrDNDUpdateStatusBar(
	edv_imbr_struct *imbr,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
);

static void EDVImbrDragDataReceivedNexus(
	edv_core_struct *core_ptr, edv_imbr_struct *imbr,
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data,
	edv_object_struct *object       /* Can be NULL */
);

gint EDVImbrLocBarIconCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);

void EDVImbrTListDNDSetIcon(
	edv_imbr_struct *imbr, gint thumb_num
);
gboolean EDVImbrTListDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
);
void EDVImbrTListDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVImbrTListDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVImbrTListDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
);

void EDVImbrImgViewDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);


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


/*
 *	Updates the Image Browser's Status Bar to indicate the results
 *	of the DND operation.
 */
static void EDVImbrDNDUpdateStatusBar(
	edv_imbr_struct *imbr,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
)
{
	gchar *buf = NULL;
	edv_status_bar_struct *sb = (imbr != NULL) ?
	    imbr->status_bar : NULL;
	if(sb == NULL)
	    return;

	/* Generate message based on the operation */
	/* Copy, Move, or Link */
	if((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	   (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	   (info == EDV_DND_TYPE_INFO_STRING)
	)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:	/* Copy */
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Coppied %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:	/* Cancel */
		    buf = g_strdup_printf(
			"Copy operation canceled"
		    );
		    break;
		  default:	/* Error */
		    buf = g_strdup_printf(
			"Unable to copy object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	      case GDK_ACTION_MOVE:	/* Move */
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Moved %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:	/* Cancel */
		    buf = g_strdup_printf(
			"Move operation canceled"
		    );
		    break;
		  default:	/* Error */
		    buf = g_strdup_printf(
			"Unable to move object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	      case GDK_ACTION_LINK:	/* Link */
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Linked %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:	/* Cancel */
		    buf = g_strdup_printf(
			"Link operation canceled"
		    );
		    break;
		  default:	/* Error */
		    buf = g_strdup_printf(
			"Unable to link object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	    }
	}
	/* Recover Recycled Object */
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Recovered %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:  /* Cancel */
		    buf = g_strdup_printf(
			"Recover operation canceled"
		    );
		    break;
		  default:  /* Error */
		    buf = g_strdup_printf(
			"Unable to recover object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	    }
	}
	/* Extract Object From Archive */
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Extracted %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;
		  case -4:  /* Cancel */
		    buf = g_strdup_printf(
			"Extract operation canceled"
		    );
		    break;
		  default:  /* Error */
		    buf = g_strdup_printf(
			"Unable to extract object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	    }
	}

	/* Set Status Bar message */
	EDVStatusBarMessage(sb, buf, FALSE);

	g_free(buf);
}


/*
 *	Image Browser drag data received nexus.
 *
 *	All inputs assumed valid.
 */
static void EDVImbrDragDataReceivedNexus(
	edv_core_struct *core_ptr, edv_imbr_struct *imbr,
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data,
	edv_object_struct *object		/* Can be NULL */
)
{
	gint initial_confirmation_result, nurls;
	gchar *parent_path;
	GList *url_list;
	GtkWidget *toplevel = imbr->toplevel;
	const url_struct *url;

#define DO_FREE_LOCALS	{				\
 g_free(parent_path);					\
 parent_path = NULL;					\
							\
 /* Delete URL list */					\
 g_list_foreach(url_list, (GFunc)URLDelete, NULL);	\
 g_list_free(url_list);					\
 url_list = NULL;					\
}

	/* Get copy of parent_path as the disk object's full path if the
	 * disk object is not NULL
	 */
	parent_path = (object != NULL) ?
	    STRDUP(object->full_path) : NULL;

	/* If parent_path is not obtainable from the disk object then
	 * use the image browser's current location as the parent_path
	 */
	if(parent_path == NULL)
	    parent_path = STRDUP(EDVImbrCurrentLocation(imbr));

	/* Check if parent_path does not lead to a directory, if it does
	 * not lead to a directory then we need to make the parent_path's
	 * parent the directory to drop to
	 */
	if(!ISPATHDIR(parent_path))
	{
	    /* Droped on object does not lead to a directory, so change
	     * the parent_path to reflect its own parent so we know that
	     * it is a directory
	     */
	    gchar *s = g_dirname(parent_path);
	    g_free(parent_path);
	    parent_path = s;
	}

	/* Decode the DDE buffer into a list of URLs */  
	url_list = URLDecode(
	    (const guint8 *)selection_data->data,
	    selection_data->length
	);                        
	nurls = g_list_length(url_list);

	/* External drag source checks go here, for example sources
	 * that have a url with a http or ftp protocol
	 */
	url = (url_list != NULL) ? URL(url_list->data) : NULL;
	if((url != NULL) ? !STRISEMPTY(url->path) : FALSE)
	{
	    const gchar *protocol = url->protocol;
	    if(!g_strcasecmp(protocol, "http") ||
	       !g_strcasecmp(protocol, "ftp") ||
	       !g_strcasecmp(protocol, "https")
	    )
	    {
		EDVInternetDownloadObject(
		    core_ptr, url, parent_path, toplevel
		);

		/* Do not continue with standard dnd handling */
		DO_FREE_LOCALS
		return;
	    }
	}


	/* Make initial user confirmation querying to proceed with
	 * this operation
	 */
	initial_confirmation_result = EDVConfirmDND(
	    core_ptr, (gint)dc->action, info,
	    toplevel, url_list,
	    parent_path
	);
	/* User confirmed and dnd target type is a disk object? */
	if((initial_confirmation_result == CDIALOG_RESPONSE_YES) &&
	   ((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	    (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	    (info == EDV_DND_TYPE_INFO_STRING)
	   )
	)
	{
	    gint status = -1;
	    gboolean yes_to_all = FALSE;
	    gint objects_processed = 0;
	    gchar *new_path;
	    const gchar *error_mesg;
	    GList *glist;
	    struct stat lstat_buf;

	    EDVImbrSetBusy(imbr, TRUE);
	    imbr->processing = TRUE;

	    /* Iterate through URL list */
	    for(glist = url_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		url = URL(glist->data);
		if(url == NULL)
		    continue;

		if(STRISEMPTY(parent_path) || STRISEMPTY(url->path))
		    continue;  

		/* Handle by drag action type */
		status = -1;
		new_path = NULL;
		error_mesg = NULL;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    status = EDVFOPCopy(
			core_ptr, url->path, parent_path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    break;
		  case GDK_ACTION_MOVE:
		    status = EDVFOPMove(
			core_ptr, url->path, parent_path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    break;
		  case GDK_ACTION_LINK:
		    status = EDVFOPLink(
			core_ptr, url->path, parent_path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    break;
		  default:
		    error_mesg = "Unsupported drag operation";
		    break;
		}

		/* Get error message (if any) describing the error that
		 * might have occured in the above operation
		 */
		if(error_mesg == NULL)
		    error_mesg = EDVFOPGetError();
		if(!STRISEMPTY(error_mesg) && (status != -4))
		{
		    EDVPlaySoundError(core_ptr);
		    EDVMessageError(
			"Operation Error",
			error_mesg,
			NULL,
			toplevel
		    );
		}

		/* Check if new_path is valid and that no error occured
		 *
		 * This implies that the operation was successful and
		 * we must emit an object added signal
		 */
		if((!STRISEMPTY(new_path) && !status) ?
		    !lstat(new_path, &lstat_buf) : FALSE
		)
		{
		    objects_processed++;

		    imbr->processing = FALSE;
		    EDVObjectAddedEmit(
			core_ptr, new_path, &lstat_buf
		    );
		    imbr->processing = TRUE;
		}

		/* Delete copy of new object's path */
		g_free(new_path);
		new_path = NULL;

		/* Skip handling of the rest of the objects on error
		 * (status != 0) and that the error was not a user    
		 * response of no (status != -5)
		 */
		if(status && (status != -5))
		    break;
	    }

	    /* Update Status Bar message */
	    EDVImbrDNDUpdateStatusBar(
		imbr, (gint)dc->action, info,
		nurls, objects_processed, status
	    );

	    /* Unmap progress dialog, it may have been mapped if any
	     * operations occured in the above loop
	     */
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);

	    /* Play completed sound on success */
	    if(status == 0)
		EDVPlaySoundCompleted(core_ptr);

	    imbr->processing = FALSE;
	    EDVImbrSetBusy(imbr, FALSE);
	}
	/* User confirmed and dnd target type is a recycled object? */
	else if((initial_confirmation_result == CDIALOG_RESPONSE_YES) &&
		(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	)
	{
	    gboolean yes_to_all = FALSE;
	    gint status = -1;
	    gint objects_recovered = 0;
	    guint index;
	    gchar *new_path;
	    const gchar *error_mesg;
	    GList *glist;
	    struct stat lstat_buf;

	    EDVImbrSetBusy(imbr, TRUE);
	    imbr->processing = TRUE;

	    /* Iterate through URL list */
	    for(glist = url_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {  
		url = URL(glist->data);
		if(url == NULL)
		    continue;

		if(STRISEMPTY(url->path))
		    continue;

		/* Handle by drag action type */
		status = -1;
		new_path = NULL;
		error_mesg = NULL;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    error_mesg = "Recycled objects may not be coppied";
		    break;
		  case GDK_ACTION_MOVE:
		    index = (guint)ATOI(url->path);
		    status = EDVRecBinFOPRecover(
			core_ptr, index, parent_path,
			&new_path,
			toplevel,
			TRUE, TRUE, &yes_to_all
		    );
		    break;
		  case GDK_ACTION_LINK:
		    error_mesg = "Recycled objects may not be linked";
		    break;
		  default:
		    error_mesg = "Unsupported drag operation";
		    break;
		}

		/* Get error message (if any) describing the error that
		 * might have occured in the above operation
		 */
		if(error_mesg == NULL)
		    error_mesg = EDVRecBinFOPGetError();
		if(!STRISEMPTY(error_mesg) && (status != -4))
		{
		    EDVPlaySoundError(core_ptr);
		    EDVMessageError(
			"Operation Error",
			error_mesg,
			NULL,
			toplevel
		    );
		}

		/* Check if new_path is valid and that no error
		 * occured
		 *
		 * This implies the operation was successful
		 * and that we need to emit an object added signal
		 */
		if(((new_path != NULL) && !status) ?
		    !lstat(new_path, &lstat_buf) : FALSE
		)
		{
		    objects_recovered++;

		    imbr->processing = FALSE;
		    EDVObjectAddedEmit(
			core_ptr, new_path, &lstat_buf
		    );
		    imbr->processing = TRUE;
		}

		/* Delete copy of the new object's path */
		g_free(new_path);
		new_path = NULL;

		/* Skip handling of the rest of the objects on error
		 * (status != 0) and that the error was not a user
		 * response of no (status != -5)
		 */
		if(status && (status != -5))
		    break;
	    }

	    /* Update status bar message */
	    EDVImbrDNDUpdateStatusBar(
		imbr, (gint)dc->action, info,
		nurls, objects_recovered, status
	    );

	    /* Unmap progress dialog, it may have been mapped if any
	     * operations occured in the above loop
	     */
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);

	    /* Play completed sound on success */
	    if(status == 0)
		EDVPlaySoundCompleted(core_ptr);

	    imbr->processing = FALSE;
	    EDVImbrSetBusy(imbr, FALSE);
	}
	/* User confirmed and dnd target type is an archive object? */
	else if((initial_confirmation_result == CDIALOG_RESPONSE_YES) &&
		(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	)
	{
	    gboolean	yes_to_all = FALSE;
	    gboolean	preserve_directories = TRUE,
			preserve_timestamps = TRUE;
	    gint i, status;
	    gint objects_extracted = 0;
	    const gchar *arch_obj = NULL;
	    const gchar *error_mesg;
	    edv_archive_object_struct **aobj = NULL;
	    gint total_aobjs = 0;
	    gchar **new_path = NULL;
	    gint total_new_paths = 0;
	    GList *glist;

	    EDVImbrSetBusy(imbr, TRUE);
	    imbr->processing = TRUE;

	    /* Get location of the archive itself (the first object in
	     * the list)
	     */
	    if(nurls >= 1)
	    {
		url = URL(url_list->data);
		arch_obj = (url != NULL) ? url->path : NULL;
	    }

	    /* Query user for extract from archive options */
	    if(!EDVArchExtractOptsGetResponse(
		core_ptr, toplevel, arch_obj,
		&preserve_directories,
		&preserve_timestamps
	    ))
	    {
		/* User canceled */
		imbr->processing = FALSE;
		EDVImbrSetBusy(imbr, FALSE);
		DO_FREE_LOCALS
		return;
	    }

	    /* Get stats for all objects in the archive */
	    if(nurls >= 2)
	    {
		gint strc = nurls - 1;
		gchar **strv = (gchar **)g_malloc0(strc * sizeof(gchar *));

		glist = g_list_next(url_list);
		for(i = 0; i < strc; i++)
		{
		    if(glist == NULL)
			break;

		    url = URL(glist->data);
		    strv[i] = (url != NULL) ? url->path : NULL;
		    glist = g_list_next(glist);
		}

		aobj = EDVArchFIOGetListing(
		    core_ptr, arch_obj, &total_aobjs,
		    (const gchar **)strv, strc,
		    NULL		/* No password */
		);

		/* Delete string list (but not each string) */
		g_free(strv);
	    }

	    /* Extract objects from archive */
	    status = EDVArchOPExtract(
		core_ptr, arch_obj, aobj, total_aobjs,
		parent_path, &new_path, &total_new_paths,
		toplevel, TRUE, TRUE, &yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );

	    /* Get error message (if any) describing the error that
	     * might have occured in the above operation
	     */
	    error_mesg = EDVArchOPGetError();
	    if(!STRISEMPTY(error_mesg))
	    {
		EDVPlaySoundError(core_ptr);
		EDVMessageError(
		    "Operation Error",
		    error_mesg,
		    NULL,
		    toplevel
		);
	    }

	    /* Report new objects added if operation was a success */
	    if((new_path != NULL) && !status)
	    {
		const gchar *s;
		struct stat lstat_buf;

		imbr->processing = FALSE;

		for(i = 0; i < total_new_paths; i++)
		{
		    s = new_path[i];
		    if(!STRISEMPTY(s) ? lstat(s, &lstat_buf) : TRUE)
			continue;

		    objects_extracted++;

		    EDVObjectAddedEmit(
			core_ptr, s, &lstat_buf
		    );
		}

		imbr->processing = TRUE;
	    }

	    /* Delete coppies of the new object paths */
	    strlistfree(new_path, total_new_paths);
	    new_path = NULL;                
	    total_new_paths = 0;            

	    /* Update Status Bar message */
	    EDVImbrDNDUpdateStatusBar(
		imbr, (gint)dc->action, info,
		total_aobjs, objects_extracted, status
	    );

	    /* Unmap progress dialog, it may have been mapped if any
	     * operations occured in the above loop
	     */
	    ProgressDialogBreakQuery(TRUE);
	    ProgressDialogSetTransientFor(NULL);

	    /* Play completed sound on success */
	    if(status == 0)
		EDVPlaySoundCompleted(core_ptr);

	    imbr->processing = FALSE;
	    EDVImbrSetBusy(imbr, FALSE);

	    /* Delete list of archive object stats */
	    for(i = 0; i < total_aobjs; i++)
		EDVArchObjectDelete(aobj[i]);
	    g_free(aobj);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}


/*
 *	Image Browser Location Bar icon "enter_notify_event" or
 *	"leave_notify_event" signal callback.
 */
gint EDVImbrLocBarIconCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	gint status = FALSE;
	GdkBitmap *mask;
	GdkPixmap *pixmap;
	GtkWidget *w;
	edv_status_bar_struct *sb;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((crossing == NULL) || (imbr == NULL))
	    return(status);

	w = imbr->location_icon_pm;
	sb = imbr->status_bar;
	if((w == NULL) || (sb == NULL))
	    return(status);

	switch((gint)crossing->type)
	{
	  case GDK_ENTER_NOTIFY:
	    EDVStatusBarMessage(
		sb,
#if defined(PROG_LANGUAGE_SPANISH)
"Arrastre esto crear una conexión a esta ubicación"
#elif defined(PROG_LANGUAGE_FRENCH)
"Traîner ceci pour créer un lien à cet emplacement"
#elif defined(PROG_LANGUAGE_GERMAN)
"Schleppen sie dies, eine verbindung zu diesem ort zu schaffen"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Trascinare questo per creare una maglia a questa posizione"
#elif defined(PROG_LANGUAGE_DUTCH)
"Sleep dit om een schakel aan deze plaats te creëren"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arraste isto criar um elo a esta localidade"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Slep dette skape et ledd til denne plasseringen"
#else
"Drag this to create a link to this location"
#endif
		, FALSE
	    );
	    gtk_pixmap_get(GTK_PIXMAP(w), &pixmap, &mask);
	    if(pixmap != NULL)
	    {
		gint width = 15, height = 15;
		gdk_window_get_size(pixmap, &width, &height);
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    width / 2, height / 2
		);
	    }
	    status = TRUE;
	    break;

	  case GDK_LEAVE_NOTIFY:
	    EDVStatusBarMessage(sb, NULL, FALSE);
	    status = TRUE;
	    break;
	}

	return(status);
}


/*
 *	Sets the DND icon based on the Image Browser Thumb List's
 *	selected item.
 */
void EDVImbrTListDNDSetIcon(
	edv_imbr_struct *imbr, gint thumb_num
)
{
	tlist_thumb_struct *thumb_ptr;
	tlist_struct *tlist = (imbr != NULL) ? imbr->tlist : NULL;
	if(tlist == NULL)
	    return;

	/* Get pointer to thumb structure */
	if((thumb_num >= 0) && (thumb_num < tlist->total_thumbs))
	    thumb_ptr = tlist->thumb[thumb_num];
	else
	    thumb_ptr = NULL;
	if(thumb_ptr != NULL)
	{
	    GdkPixmap *pixmap = thumb_ptr->pixmap;
	    GdkBitmap *mask = thumb_ptr->mask;
	    if(pixmap != NULL)
	    {
		gint width = 15, height = 15;
		gdk_window_get_size(pixmap, &width, &height);
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    width / 2, height /2
		);
	    }
	}
}

/*
 *	Redraws the Image Browser Thumb List's focus thumb.
 */
gboolean EDVImbrTListDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
)
{
	gint status = FALSE;
	gint thumb_num;
	tlist_struct *tlist;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((dc == NULL) || (imbr == NULL))
	    return(status);

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

	if(!TListGetSelection(
	    tlist, x, y, &thumb_num, NULL, NULL
	))
	    thumb_num = -1;

	if((thumb_num >= 0) && (thumb_num < tlist->total_thumbs))
	{
	    tlist->focus_thumb = thumb_num;
	    TListDraw(tlist);
	    status = TRUE;
	}

	return(status);
}

/*
 *	Image Browser Thumbs List "drag_data_get" signal callback.
 */
void EDVImbrTListDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	gint thumb_num;
	edv_core_struct *core_ptr;
	GList *glist, *url_list = NULL;
	url_struct *url;
	edv_object_struct *object;
	tlist_struct *tlist;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((dc == NULL) || (imbr == NULL))
	    return;

	if(imbr->processing)
	    return;

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

	EDVImbrSyncData(imbr);

	/* Generate a list of URLs from the selected thumbs */
	for(glist = tlist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    thumb_num = (gint)glist->data;
	    object = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));
	    if(object == NULL)
		continue;

	    url = URLNew();
	    url->path = STRDUP(object->full_path);
	    url_list = g_list_append(url_list, url);
	}

	/* Encode DDE buffer from the URL list */
	if(url_list != NULL)
	{
	    gint buf_len;
	    guint8 *buf = URLEncode(url_list, &buf_len);
	    if(buf != NULL)
	    {
		/* Send out DND data buffer */ 
		gtk_selection_data_set(
		    selection_data,
		    GDK_SELECTION_TYPE_STRING,
		    8,			/* Bits Per Character */
		    buf,		/* Data */
		    buf_len		/* Length */
		);
		data_sent = TRUE;
		g_free(buf);
	    }
	}

	/* Delete URL list */
	g_list_foreach(url_list, (GFunc)URLDelete, NULL);
	g_list_free(url_list);  

	/* If failed to send out data then respond with error */
	if(!data_sent)
	{
	    const gchar *s = "Error";
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,			/* Bits Per Character */
		s,			/* Data */
		STRLEN(s)		/* Length */
	    );
	    data_sent = TRUE;
	}
}

/*
 *	Image Browser Thumbs List "drag_data_received" signal callback.
 */
void EDVImbrTListDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gint thumb_num = -1;
	edv_object_struct *object;
	edv_core_struct *core_ptr;
	tlist_struct *tlist;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((dc == NULL) || (imbr == NULL))
	    return;

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

	/* Check if received data is not empty */
	if((selection_data != NULL) ? (selection_data->length <= 0) : TRUE)
	    return;

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

	EDVImbrSyncData(imbr);

	/* Find the thumb that this drop occured on, it is okay if no
	 * thumb is matched in which case thumb_num is -1 and object
	 * is NULL
	 *
	 * The drop can still continue using the current location
	 * instead of a droped on thumb since we know the drop occured
	 * on the thumbs list atleast
	 */
	if(!TListGetSelection(tlist, x, y, &thumb_num, NULL, NULL))
	    thumb_num = -1;
	object = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));

	/* Handle received drag data */
	EDVImbrDragDataReceivedNexus(
	    core_ptr, imbr, dc, info, selection_data, object
	);
}

/*
 *	Image browser thumbs list DND "drag_data_delete" signal callback.
 */
void EDVImbrTListDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{
	const gchar *path;
	gchar **strv;
	gint i, thumb_num, strc;
	edv_core_struct *core_ptr;
	GList *glist;
	edv_object_struct *object;
	struct stat lstat_buf;
	tlist_struct *tlist;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((dc == NULL) || (imbr == NULL))
	    return;

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

	/* Iterate through selected thumbs, getting a list of paths */
	strv = NULL;
	strc = 0;
	for(glist = tlist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    thumb_num = (gint)glist->data;
	    object = EDV_OBJECT(TListGetThumbData(tlist, thumb_num));
	    if((object != NULL) ? !STRISEMPTY(object->full_path) : FALSE)
	    {
		i = strc;
		strc = i + 1;
		strv = (gchar **)g_realloc(
		    strv, strc * sizeof(gchar *)
		);
		if(strv == NULL)
		{
		    strc = 0;
		    break;
		}
		strv[i] = STRDUP(object->full_path);
	    }
	}

	/* Emit object removed signal and delete each path in the list */
	for(i = 0; i < strc; i++)
	{
	    path = strv[i];
	    if(!STRISEMPTY(path))
	    {
		if(lstat(path, &lstat_buf))
		    EDVObjectRemovedEmit(core_ptr, path);
	    }
	    g_free(strv[i]);
	}
	g_free(strv);
}

/*
 *	Image Browser ImgView "drag_data_received" signal callback.
 */
void EDVImbrImgViewDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	edv_core_struct *core_ptr;
	edv_imbr_struct *imbr = EDV_IMBR(data);
	if((dc == NULL) || (imbr == NULL))
	    return;

	core_ptr = EDV_CORE(imbr->core_ptr);
	if(core_ptr == NULL)
	    return;

	/* Check if received data is not empty */
	if((selection_data != NULL) ? (selection_data->length <= 0) : TRUE)
	    return;

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

	EDVImbrSyncData(imbr);

	/* Handle received drag data */
	EDVImbrDragDataReceivedNexus(
	    core_ptr, imbr, dc, info, selection_data,
	    NULL		/* No object, suggest use current location */
	);
}
