#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.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 "edv_types.h"
#include "edv_obj.h"
#include "edv_recycled_obj.h"
#include "edv_archive_obj.h"
#include "edv_obj_op.h"
#include "edv_recbin_stat.h"
#include "edv_recbin_recover.h"
#include "edv_archive_stat.h"
#include "edv_archive_extract.h"
#include "edv_confirm.h"
#include "archive_options_dlg.h"
#include "browser.h"
#include "browser_dnd.h"
#include "endeavour2.h"
#include "edv_op.h"
#include "edv_cb.h"
#include "edv_utils.h"

#include "edv_utils_gtk.h"
#include "config.h"


static void EDVBrowserDNDUpdateStatusBar(
	edv_browser_struct *browser,
	const gint gdk_action, const guint info,
	const gint total_src_objects,
	const gint total_objects_processed, const gint status
);

static void EDVBrowserDragDataReceivedNexus(
	edv_browser_struct *browser,
	edv_core_struct *core,
	GdkDragContext *dc, const guint info,
	GtkSelectionData *selection_data,
	edv_object_struct *obj
);

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

void EDVBrowserDirTreeDNDSetIcon(
	edv_browser_struct *browser, GtkCTreeNode *node
);
void EDVBrowserDirTreeDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVBrowserDirTreeDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVBrowserDirTreeDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
);

void EDVBrowserContentsDNDSetIcon(
	edv_browser_struct *browser, gint row, gint column
);
void EDVBrowserContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVBrowserContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVBrowserContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, 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 File Browser's Status Bar to indicate the result
 *	of the specified operation.
 */
static void EDVBrowserDNDUpdateStatusBar(
	edv_browser_struct *browser,
	const gint gdk_action, const guint info,
	const gint total_src_objects,
	const gint total_objects_processed, const gint status
)
{
	gchar *buf = NULL;
	edv_status_bar_struct *sb = (browser != NULL) ?
	    browser->status_bar : NULL;
	if(sb == NULL)
	    return;

	/* Format the status bar message */
	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:
		if(status == -4)
		    buf = STRDUP(
			"Copy operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Coppied %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to copy %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		break;
	      case GDK_ACTION_MOVE:
		if(status == -4)
		    buf = STRDUP(
			"Move operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Moved %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to move %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		break;
	      case GDK_ACTION_LINK:
		if(status == -4)
		    buf = STRDUP(
			"Link operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Linked %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to link %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		if(status == -4)
		    buf = STRDUP(
			"Recover operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Recovered %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to recover %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    switch(gdk_action)
	    {
	      case GDK_ACTION_COPY:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		if(status == -4)
		    buf = STRDUP(
			"Extract operation canceled"
		    );
		else if(total_objects_processed > 0)
		    buf = g_strdup_printf(
			"Extracted %i %s",
			total_objects_processed,
			(total_objects_processed == 1) ? "object" : "objects"
		    );
		else
		    buf = g_strdup_printf(
			"Unable to extract %s",
			(total_src_objects == 1) ? "object" : "objects"
		    );
		break;
	    }
	}

	/* Set the status bar message */
	EDVStatusBarMessage(sb, buf, FALSE);

	g_free(buf);
}


/*
 *	File Browser drag data received nextus.
 *
 *	The obj specifies the object that the drop occured on. It may
 *	be NULL if the drop did not occure on an object in which
 *	case the File Browser's current location will be used.
 */
static void EDVBrowserDragDataReceivedNexus(
	edv_browser_struct *browser,
	edv_core_struct *core,
	GdkDragContext *dc, const guint info,
	GtkSelectionData *selection_data,
	edv_object_struct *obj
)
{
	gint initial_confirmation_result, nurls;
	gchar *parent_path;
	GList *url_list;
	GtkWidget *toplevel = browser->toplevel;
	const url_struct *url;

#define DO_FREE_LOCALS	{		\
 g_free(parent_path);			\
					\
 if(url_list != NULL) {			\
  g_list_foreach(			\
   url_list, (GFunc)URLDelete, NULL	\
  );					\
  g_list_free(url_list);		\
 }					\
}

	/* Get copy of parent_path as the object's full path if the
	 * object is not NULL
	 */
	parent_path = (obj != NULL) ? STRDUP(obj->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(EDVBrowserCurrentLocation(browser));

	/* 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, 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, (gint)dc->action, info,
	    toplevel, url_list,
	    parent_path
	);
	/* Regular 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 *path, *error_title, *error_msg;
	    GList *glist;
	    struct stat lstat_buf;

	    EDVBrowserSetBusy(browser, TRUE);
	    browser->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;

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

		/* Handle by drag action type */
		status = -1;
		new_path = NULL;
		error_msg = NULL;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    status = EDVObjectOPCopy(
			core, path, parent_path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    error_title = "Copy Object Error";
		    break;
		  case GDK_ACTION_MOVE:
		    status = EDVObjectOPMove(
			core, path, parent_path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    error_title = "Move Object Error";
		    break;
		  case GDK_ACTION_LINK:
		    status = EDVObjectOPLink(
			core, parent_path, path,
			&new_path, toplevel,
			TRUE, TRUE,
			&yes_to_all
		    );
		    error_title = "Link Object Error";
		    break;
		  default:
		    error_title = "Error";
		    error_msg = "Unsupported drag operation";
		    break;
		}

		/* Check for and report any errors */
		if(error_msg == NULL)
		    error_msg = EDVObjectOPGetError();
		if(!STRISEMPTY(error_msg))
		{
		    EDVPlaySoundError(core);
		    EDVMessageObjectOPError(
			error_title,
			error_msg,
			path,
			toplevel
		    );
		}

		/* Get the source object's statistics for reporting */
		if(lstat(path, &lstat_buf))
		{
#ifdef ENOENT
		    /* Report the source object being removed */
		    const gint error_code = (gint)errno;
		    if(error_code == ENOENT)
		    {
			browser->processing = FALSE;
			EDVObjectRemovedEmit(core, path);
			browser->processing = TRUE;
		    }
#endif
		}

		/* Report the new object being added? */
		if(new_path != NULL)
		{
		    if(!lstat(new_path, &lstat_buf))
		    {
			objects_processed++;

			browser->processing = FALSE;
			EDVObjectAddedEmit(
			    core,
			    new_path,
			    &lstat_buf
			);
			browser->processing = TRUE;
		    }

		    g_free(new_path);
		}

		/* User aborted? */
		if(status == -4)
		    break;
	    }

	    /* Update the status bar message */
	    EDVBrowserDNDUpdateStatusBar(
		browser, (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);

	    browser->processing = FALSE;
	    EDVBrowserSetBusy(browser, FALSE);
	}
	/* Recycled Object? */
	else if((initial_confirmation_result == CDIALOG_RESPONSE_YES) &&
		(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	)
	{
	    struct stat lstat_buf;
	    gboolean yes_to_all = FALSE;
	    gint status = -1, objects_recovered = 0;
	    guint index;
	    gchar *new_path;
	    const gchar *error_msg;
	    GList *glist;

	    EDVBrowserSetBusy(browser, TRUE);
	    browser->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;

		index = (guint)ATOI(url->path);

		/* Handle by drag action type */
		status = -1;
		new_path = NULL;
		error_msg = NULL;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    error_msg = "Recycled objects may not be coppied";
		    break;
		  case GDK_ACTION_MOVE:
		    status = EDVRecBinRecover(
			core,
			index,		/* Recycled object to recover */
			parent_path,	/* Alternate recovery location */
			&new_path,	/* Recovered object path return */
			toplevel,
			TRUE, TRUE, &yes_to_all
		    );
		    break;
		  case GDK_ACTION_LINK:
		    error_msg = "Recycled objects may not be linked";
		    break;
		  default:
		    error_msg = "Unsupported drag operation";
		    break;
		}

		/* Check for and report any errors */
		if(error_msg == NULL)
		    error_msg = EDVRecBinRecoverGetError();
		if(!STRISEMPTY(error_msg))
		{
		    EDVPlaySoundError(core);
		    EDVMessageObjectOPError(
			"Recover Recycled Object Error",
			error_msg,
			url->path_arg,
			toplevel
		    ); 
		}

		/* Object recovered? */
		if(new_path != NULL)
		{
		    if(!lstat(new_path, &lstat_buf))
		    {
			objects_recovered++;

			browser->processing = FALSE;
			EDVRecycledObjectRemovedEmit(core, index);
			EDVObjectAddedEmit(
			    core,
			    new_path,
			    &lstat_buf
			);
			browser->processing = TRUE;
		    }

		    g_free(new_path);
		}

		/* User aborted? */
		if(status == -4)
		    break;
	    }

	    /* Update the status bar message */
	    EDVBrowserDNDUpdateStatusBar(
		browser, (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);

	    browser->processing = FALSE;
	    EDVBrowserSetBusy(browser, FALSE);
	}
	/* 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 status, objects_extracted = 0;
	    const gchar *arch_obj = NULL, *error_msg;
	    GList *obj_list = NULL, *new_paths_list;

	    EDVBrowserSetBusy(browser, TRUE);
	    browser->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, toplevel, arch_obj,
		&preserve_directories,
		&preserve_timestamps
	    ))
	    {
		/* User canceled */
		browser->processing = FALSE;
		EDVBrowserSetBusy(browser, FALSE);
		DO_FREE_LOCALS
		return;
	    }

	    /* Get the stats for all the objects found in the archive */
	    if(nurls >= 2)
	    {
		GList *glist, *paths_list = NULL;

		/* Add all the subsequent URL paths to the paths_list */
		for(glist = g_list_next(url_list);	/* Skip the first URL */
		    glist != NULL;
		    glist = g_list_next(glist)
		)
		{
		    url = URL(glist->data);
		    if(url != NULL)
			paths_list = g_list_append(
			    paths_list, url->path
			);
		}

		obj_list = EDVArchStatList(
		    core,
		    arch_obj,
		    paths_list,
		    NULL,			/* No filter */
		    NULL,			/* No password */
		    NULL, NULL			/* No progress callback */
		);

		g_list_free(paths_list);
	    }

	    /* Extract the object(s) from the archive */
	    new_paths_list = NULL;
	    status = EDVArchExtract(
		core, arch_obj,
		obj_list,
		parent_path, &new_paths_list,
		NULL,			/* No password */
/* TODO: Prompt for password? */
		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_msg = EDVArchExtractGetError();
	    if(!STRISEMPTY(error_msg))
	    {
		EDVPlaySoundError(core);
		EDVMessageObjectOPError(
		    "Extract Object Error",
		    error_msg,
		    arch_obj,
		    toplevel
		);
	    }

	    /* Report the extracted objects being added */
	    if(new_paths_list != NULL)
	    {
		const gchar *path;
		struct stat lstat_buf;
		GList *glist;

		browser->processing = FALSE;

		for(glist = new_paths_list;
		    glist != NULL;
		    glist = g_list_next(glist)
		)
		{
		    path = (const gchar *)glist->data;
		    if(!STRISEMPTY(path) ? lstat(path, &lstat_buf) : TRUE)
			continue;

		    EDVObjectAddedEmit(
			core,
			path,
			&lstat_buf
		    );
		    objects_extracted++;
		}

		browser->processing = TRUE;

		/* Delete the list of the extracted object paths */
		g_list_foreach(new_paths_list, (GFunc)g_free, NULL);
		g_list_free(new_paths_list);
	    }

	    /* Update the status bar message */
	    EDVBrowserDNDUpdateStatusBar(
		browser, (gint)dc->action, info,
		g_list_length(obj_list), 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);

	    browser->processing = FALSE;
	    EDVBrowserSetBusy(browser, FALSE);

	    /* Delete the list of archive object stats */
	    g_list_foreach(obj_list, (GFunc)EDVArchObjectDelete, NULL);
	    g_list_free(obj_list);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	File Browser Location Bar icon "enter_notify_event" or
 *	"leave_notify_event" signal callback.
 */
gint EDVBrowserLocBarIconCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	gint status = FALSE;
	GdkBitmap *mask;
	GdkPixmap *pixmap;
	GtkWidget *w;
	edv_status_bar_struct *sb;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (crossing == NULL) || (browser == NULL))
	    return(status);

	w = browser->location_icon_pm;
	sb = browser->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 from the File Browser's Directory Tree node.
 */
void EDVBrowserDirTreeDNDSetIcon(
	edv_browser_struct *browser, GtkCTreeNode *node
)
{
	GtkCTree *ctree;
	edv_object_struct *obj;

	if((browser == NULL) || (node == NULL))
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	if(ctree == NULL)
	    return;

	obj = EDV_OBJECT(gtk_ctree_node_get_row_data(ctree, node));
	if(obj != NULL)
	{
	    GdkPixmap *pixmap = NULL;
	    GdkBitmap *mask = NULL;
	    GtkCTreeRow *row_ptr = GTK_CTREE_ROW(node);

	    /* Check if the node's row pointer is valid and obtain the
	     * pixmap and mask pair that will be used as the icon
	     */
	    if(row_ptr != NULL)
	    {
		/* If node is expanded then use the opened pixmap */
		if(row_ptr->expanded)
		{
		    pixmap = row_ptr->pixmap_opened;
		    mask = row_ptr->mask_opened;

		    /* If the opened pixmap was not available then
		     * revert to the closed pixmap
		     */
		    if(pixmap == NULL)
		    {
			pixmap = row_ptr->pixmap_closed;
			mask = row_ptr->mask_closed;
		    }
		}
		else
		{
		    pixmap = row_ptr->pixmap_closed;
		    mask = row_ptr->mask_closed;
		}
	    }

	    /* Got icon pixmap? */
	    if(pixmap != NULL)
	    {
		gint w = 15, h = 15;

		/* Get size of icon pixmap */
		gdk_window_get_size(pixmap, &w, &h);

		/* Set DND icon */
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    w / 2, h / 2
		);
	    }
	}
}

/*
 *	File Browser Directory Tree "drag_data_get" signal callback.
 */
void EDVBrowserDirTreeDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	GList *glist, *url_list = NULL;
	GtkWidget *w;
	GtkCList *clist;
	GtkCTree *ctree;
	GtkCListRow *row_ptr;
	url_struct *url;
	edv_object_struct *obj;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((dc == NULL) || (browser == NULL))
	    return;

	w = browser->directory_ctree;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	ctree = GTK_CTREE(w);
	clist = GTK_CLIST(w);

	EDVBrowserSyncData(browser);

	/* Generate a list of URLs from the selected nodes */
	for(glist = clist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    row_ptr = (GtkCListRow *)glist->data;
	    obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, (GtkCTreeNode *)row_ptr)
	    );
	    if(obj == NULL)
		continue;

	    url = URLNew();
	    url->path = STRDUP(obj->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 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;
	}
}

/*
 *	File Browser Directory Tree "drag_data_received" signal
 *	callback.
 */
void EDVBrowserDirTreeDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	GtkWidget *w;
	GtkCList *clist;
	GtkCTree *ctree;
	GtkCTreeNode *node;
	edv_object_struct *obj;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((dc == NULL) || (browser == NULL))
	    return;

	w = browser->directory_ctree;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	ctree = GTK_CTREE(w);
	clist = GTK_CLIST(w);

	/* 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, TRUE, browser->toplevel))
	    return;

	EDVBrowserSyncData(browser);

	/* Find node that this drop occured on, then get the object
	 * referenced by the node
	 */
	node = EDVNodeGetByCoordinates(
	    ctree,
	    x,
	    y - ((clist->flags & GTK_CLIST_SHOW_TITLES) ?
		clist->column_title_area.height +
		clist->column_title_area.y : 0)
	);
	obj = EDV_OBJECT((node != NULL) ?
	    gtk_ctree_node_get_row_data(ctree, node) : NULL
	);

	/* If unable to obtain object or node then that implies we have
	 * nothing to drop on
	 *
	 * Since dropping on the directory tree requires that the drop
	 * occurs on a node, we need to return due to having no node to
	 * drop on
	 */
	if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
	    return;

	/* Handle received drag data */
	EDVBrowserDragDataReceivedNexus(
	    browser, core,
	    dc, info,
	    selection_data,
	    obj
	);
}

/*
 *	File Browser Directory Tree "drag_data_delete" signal callback.
 */
void EDVBrowserDirTreeDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{

}


/*
 *	Sets the DND icon from the File Browser's Contents List cell
 *	specified by row and column.
 */
void EDVBrowserContentsDNDSetIcon(
	edv_browser_struct *browser, gint row, gint column
)
{
	GtkCList *clist;
	edv_object_struct *obj;

	if(browser == NULL)
	    return;

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

	obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));
	if(obj != NULL)
	{
	    gint i;
	    gchar *text = NULL;
	    guint8 spacing = 0;
	    GdkPixmap *pixmap = NULL;
	    GdkBitmap *mask = NULL;

	    /* Iterate through each cell of this row, looking for a
	     * useable pixmap
	     */
	    for(i = 0; i < clist->columns; i++)
	    {
		switch(gtk_clist_get_cell_type(clist, row, i))
		{
		  case GTK_CELL_PIXMAP:
		    gtk_clist_get_pixmap(
			clist, row, i,
			&pixmap, &mask
		    );
		    break;
		  case GTK_CELL_PIXTEXT:
		    gtk_clist_get_pixtext(
			clist, row, i,
			&text, &spacing, &pixmap, &mask
		    );
		    break;
		  case GTK_CELL_TEXT:
		  case GTK_CELL_WIDGET:
		  case GTK_CELL_EMPTY:
		    break;
		}
		if(pixmap != NULL)
		    break;
	    }

	    /* Got icon pixmap? */
	    if(pixmap != NULL)
	    {
		gint w = 15, h = 15;

		/* Get size of icon pixmap */
		gdk_window_get_size(pixmap, &w, &h);

		/* Set DND icon */
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    w / 2, h / 2
		);
	    }
	}
}

/*
 *	File Browser Contents List "drag_data_get" signal callback.
 */
void EDVBrowserContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	gint row;
	GList *glist, *url_list = NULL;
	GtkWidget *w;
	GtkCList *clist;
	url_struct *url;
	edv_object_struct *obj;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((dc == NULL) || (browser == NULL))
	    return;

	w = browser->contents_clist;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	EDVBrowserSyncData(browser);

	/* Generate a list of URLs from the selected rows */
	for(glist = clist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    row = (gint)glist->data;
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));
	    if(obj == NULL)
		continue;

	    url = URLNew();
	    url->path = STRDUP(obj->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;
	}
}

/*
 *	File Browser Contents List "drag_data_received" signal callback.
 */
void EDVBrowserContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gint row, column;
	GtkWidget *w;
	GtkCList *clist;
	edv_object_struct *obj;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((dc == NULL) || (browser == NULL))
	    return;

	w = browser->contents_clist;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* 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, TRUE, browser->toplevel))
	    return;

	EDVBrowserSyncData(browser);

	/* Find row that this drop occured on, then get the object
	 * referenced by the row
	 */
	/* Find row and column based on given coordinates */
	if(!gtk_clist_get_selection_info(
	    clist,
	    x,
	    y - ((clist->flags & GTK_CLIST_SHOW_TITLES) ?
		clist->column_title_area.height +
		clist->column_title_area.y : 0),
	    &row, &column
	))
	{
	    row = -1;
	    column = 0;
	}
	/* Get object from selected row */
	if((row >= 0) && (row < clist->rows))
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));
	else
	    obj = NULL;

	/* Handle received drag data */
	EDVBrowserDragDataReceivedNexus(
	    browser, core,
	    dc, info,
	    selection_data,
	    obj
	);
}

/*
 *	File Browser Contents List "drag_data_delete" signal callback.
 */
void EDVBrowserContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{

}
