#include <stdlib.h>
#include <string.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 "edvtypes.h"
#include "edvobj.h"
#include "edvarchobj.h"
#include "edvconfirm.h"
#include "edvarchfio.h"
#include "edvarchop.h"
#include "archiveopts.h"
#include "archiver.h"
#include "archivercb.h"
#include "archivercontents.h"
#include "archiverdnd.h"
#include "endeavour.h"
#include "edvop.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "config.h"


static gint EDVArchiverDNDConfirm(
	edv_core_struct *core_ptr,
	gint gdk_action, guint info,
	GtkWidget *toplevel,
	GList *url_list,
	const gchar *arch_path
);
static void EDVArchiverDNDUpdateStatusBar(
	edv_archiver_struct *archiver,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
);

static void EDVArchiverDragDataReceivedNexus(
	edv_core_struct *core_ptr,
	edv_archiver_struct *archiver,	/* Can be NULL */
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data
);

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

void EDVArchiverContentsDNDSetIcon(
	edv_archiver_struct *archiver, gint row, gint column
);
gboolean EDVArchiverContentsDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
);
void EDVArchiverContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVArchiverContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);
void EDVArchiverContentsDragDataDeleteCB(
	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)


/*
 *	Archiver DND operation confirmation.
 *
 *	Queries user for confirmation (as needed) for the given
 *	operation and returns one of CDIALOG_RESPONSE_*.
 */
static gint EDVArchiverDNDConfirm(
	edv_core_struct *core_ptr,
	gint gdk_action, guint info,
	GtkWidget *toplevel,
	GList *url_list,
	const gchar *arch_path
)
{
	const gint nurls = g_list_length(url_list);
	const gchar *src_path = NULL;

	if(nurls <= 0)
	    return(CDIALOG_RESPONSE_NOT_AVAILABLE);

	/* If exactly one source object then get source path of that
	 * object, otherwise leave src_path as NULL
	 */
	if(nurls == 1)
	{
	    const url_struct *url = URL(url_list->data);
	    if(url != NULL)
		src_path = url->path;
	}

	/* Do confirmation, handle by dnd target type */
	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:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		return(EDVConfirmArchiveAdd(
		    core_ptr, toplevel,
		    src_path, nurls,
		    arch_path
		));
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    /* Recycled objects may not be put to an archive */
	}
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    /* Objects in archives may not be put to an archive */
	}

	/* Unsupported DND target type or drag action, return response not
	 * available just to be on the safe side so that the operation
	 * does not continue
	 */
	return(CDIALOG_RESPONSE_NOT_AVAILABLE);
}

/*
 *	Updates the Archiver's Status Bar to indicate the result of the
 *	specified operation.
 */
static void EDVArchiverDNDUpdateStatusBar(
	edv_archiver_struct *archiver,
	gint gdk_action, guint info,
	gint total_src_objects,
	gint total_objects_processed, gint status
)
{
	gchar *buf = NULL;
	edv_status_bar_struct *sb = (archiver != NULL) ?
	    archiver->status_bar : NULL;
	if(sb == NULL)
	    return;

	/* Begin formatting status message, handle by dnd target type */
	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:
	      case GDK_ACTION_MOVE:
	      case GDK_ACTION_LINK:
		switch(status)
		{
		  case 0: case -5:
		    buf = g_strdup_printf(
			"Added %i object%s",
			total_objects_processed,
			(total_objects_processed == 1) ? "" : "s"
		    );
		    break;

		  case -4:  /* Cancel */
		    buf = g_strdup_printf(
			"Add operation canceled"
		    );
		    break;

		  default:  /* Error */
		    buf = g_strdup_printf(
			"Unable to add object%s",
			(total_src_objects == 1) ? "" : "s"
		    );
		    break;
		}
		break;
	    }
	}
	else if(info == EDV_DND_TYPE_INFO_RECYCLED_OBJECT)
	{
	    /* Recycled objects may not be added to an archive */
	}
	else if(info == EDV_DND_TYPE_INFO_ARCHIVE_OBJECT)
	{
	    /* Objects in archives are already in the archive */
	}
	else
	{
	    /* Unsupported target type */
	}

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

	g_free(buf);
}


/*
 *	Archiver drag data received nexus.
 *
 *	All inputs assumed valid.
 */
static void EDVArchiverDragDataReceivedNexus(
	edv_core_struct *core_ptr,
	edv_archiver_struct *archiver,	/* Can be NULL */
	GdkDragContext *dc, guint info, GtkSelectionData *selection_data
)
{
	gint initial_confirmation_result;
	GtkWidget *toplevel = (archiver != NULL) ? archiver->toplevel : NULL;
	const url_struct *url;

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

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

	/* Check if there is exactly one URL and if it is an archive,
	 * in which case we should give the user a choice to open
	 * this archive instead of adding it to the archive
	 */
	if((nurls == 1) && (archiver != NULL))
	{
	    url = URL(url_list->data);
	    if((url != NULL) ? (url->path != NULL) : FALSE)
	    {
		if(EDVCheckEDVArchiverArchive(
		    core_ptr, EDVGetPathName(url->path)
		))
		{
		    /* There is exactly one DND object reference and
		     * if is an archive, so query user about opening
		     * it instead of adding it to the current archive
		     */
		    gint status;
		    const gchar *name = EDVGetPathName(url->path);
		    gchar *buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"¿El archivo abierto \"%s\" en vez de agregarlo al\n\
archivo actual?\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"\"%s\" ouvert d'archive au lieu d'ajouter il à\n\
l'archive actuelle?\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Offenes archiv \"%s\" statt hinzufügen es zum\n\
jetzigen archiv?\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"\"%s\" di archivio aperto invece dell'aggiunta esso\n\
all'archivio attuale?\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Open archief \"%s\" in plaats van toevoegen het aan het\n\
huidig archief?\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"\"%s\" aberto de arquivo em vez de adicionar ele ao\n\
arquivo atual?\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpen arkiv \"%s\" i stedet for å tilføye det til det\n\
nåværendee arkivet?\n"
#else
"Open archive \"%s\" instead of adding it to the\n\
current archive?\n"
#endif
			, name
		    );
		    EDVPlaySoundQuestion(core_ptr);
		    CDialogSetTransientFor(toplevel);
		    status = CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"¿El Archivo abierto?"
#elif defined(PROG_LANGUAGE_FRENCH)
"L'Archive ouverte?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Offenes Archiv?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Archivio Aperta?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Open Archief?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Arquivo Aberto?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Åpent Arkiv?"
#else
"Open Archive?"
#endif
			, buf, NULL,
			CDIALOG_ICON_QUESTION,
			CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);

		    g_free(buf);

		    if(status == CDIALOG_RESPONSE_YES)
		    {
			EDVArchiverSetBusy(archiver, TRUE);
			GUIBlockInput(toplevel, TRUE);

			/* Clear the list and load the listing from the
			 * new archive
			 */
			EDVArchiverSelectArchive(
			    archiver, url->path,
			    EDVArchiverCurrentPassword(archiver)
			);

			EDVArchiverUpdateMenus(archiver);

			GUIBlockInput(toplevel, FALSE);
			EDVArchiverSetBusy(archiver, FALSE);

			DO_FREE_LOCALS
			return;
		    }
		}
	    }
	}

	/* Check and warn if write protect is enabled, this needs to be
	 * checked here because prior call to this function will not
	 * check this because the drag operation may be just to open an
	 * archive
	 */
	if(EDVCheckWriteProtect(core_ptr, TRUE, toplevel))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Make initial user confirmation querying to proceed with this
	 * operation
	 */
	initial_confirmation_result = EDVArchiverDNDConfirm(
	    core_ptr, (gint)dc->action, info,
	    toplevel, url_list,
	    EDVArchiverCurrentLocation(archiver)
	);
	/* 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 i, status = -1;
	    gboolean	yes_to_all = FALSE,
			recurse = TRUE,
			dereference_links = FALSE;
	    gint	compression = 50;	/* 0 to 100 */
	    gint	objects_added = 0;
	    gchar	**new_aobj_path;
	    gint	total_new_aobj_paths;
	    const gchar	*arch_obj;
	    const gchar	*error_mesg;


	    /* Get current archive path */
	    arch_obj = EDVArchiverCurrentLocation(archiver);

	    if(archiver != NULL)
	    {
		EDVArchiverSetBusy(archiver, TRUE);
		archiver->processing = TRUE;
	    }

	    /* Get add to archive options */
	    if(!EDVArchAddOptsGetResponse(
		core_ptr, toplevel, arch_obj,
		&recurse, &compression, &dereference_links
	    ))
	    {
		/* User canceled */
		if(archiver != NULL)
		{
		    archiver->processing = FALSE;
		    EDVArchiverSetBusy(archiver, FALSE);
		}
		DO_FREE_LOCALS
		return;
	    }

	    if(nurls > 0)
	    {
		/* Create list of object paths to be added to the
		 * archive from the URL list
		 */
		GList *glist;
		gint total_tar_paths = nurls;
		gchar **tar_path = (gchar **)g_malloc0(
		    total_tar_paths * sizeof(gchar *)
		);

		for(i = 0,
		    glist = url_list;
		    (i < total_tar_paths) && (glist != NULL);
		    i++, glist = g_list_next(glist)
		)
		{
		    url = URL(glist->data);
		    if((url != NULL) ? STRISEMPTY(url->path) : TRUE)
			continue;

		    tar_path[i] = STRDUP(url->path);
		}

		error_mesg = NULL;
		status = -1;
		new_aobj_path = NULL;
		total_new_aobj_paths = 0;
		switch((gint)dc->action)
		{
		  case GDK_ACTION_COPY:
		    /* Add object to the archive */
		    status = EDVArchOPAdd(
			core_ptr, arch_obj, tar_path, total_tar_paths,
			&new_aobj_path, &total_new_aobj_paths,
			toplevel, TRUE, TRUE, &yes_to_all,
			recurse, compression, dereference_links
		    );
		    break;
		  case GDK_ACTION_MOVE:
		    error_mesg = "Objects may not be moved to the archive";
		    break;
		  case GDK_ACTION_LINK:
		    error_mesg = "Objects may not be linked to the archive";
		    break;
		  default:
		    error_mesg = "Unsupported drag operation";
		    break;
		}

		/* Delete list of objects to be added to the archive */
		strlistfree(tar_path, total_tar_paths);
		tar_path = NULL;
		total_tar_paths = 0;

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

		/* Object was added successfully? */
		if((total_new_aobj_paths > 0) && !status)
		{
		    /* Count objects added to the archive */
		    objects_added += total_new_aobj_paths;
		}

		/* Delete list of objects added to the archive */
		strlistfree(new_aobj_path, total_new_aobj_paths);
		new_aobj_path = NULL;
		total_new_aobj_paths = 0;
	    }

	    /* Emit object modified signal to signal that objects
	     * have been added to the archive
	     */
	    if(!status && !STRISEMPTY(arch_obj))
	    {
		struct stat lstat_buf;
		if(!lstat(arch_obj, &lstat_buf))
		{
		    if(archiver != NULL)
			archiver->processing = FALSE;

		    EDVObjectModifiedEmit(
			core_ptr, arch_obj, arch_obj, &lstat_buf
		    );

		    if(archiver != NULL)
			archiver->processing = TRUE;
		}
	    }

	    /* Update Status Bar message */
	    if(archiver != NULL)
		EDVArchiverDNDUpdateStatusBar(
		    archiver, (gint)dc->action, info,
		    nurls, objects_added, 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);

	    if(archiver != NULL)
	    {
		archiver->processing = FALSE;
		EDVArchiverSetBusy(archiver, 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)
	)
	{
	    /* Recycled objects may not be added to an archive */
	}
	/* 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)
	)
	{
	    /* Archive objects may not be added back into an archive */
	}


	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}


/*
 *	Archiver Location Bar icon "enter_notify_event" or
 *	"leave_notify_event" signal callback.
 */
gint EDVArchiverLocBarIconCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	gint status = FALSE;
	GdkBitmap *mask;
	GdkPixmap *pixmap;
	GtkWidget *w;
	edv_status_bar_struct *sb;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((widget == NULL) || (crossing == NULL) || (archiver == NULL))
	    return(status);

	w = archiver->location_icon_pm;
	sb = archiver->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 Archiver Contents List's
 *	cell specified by row and column.
 */
void EDVArchiverContentsDNDSetIcon(
	edv_archiver_struct *archiver, gint row, gint column
)
{
	edv_archive_object_struct *obj;
	GtkCList *clist = (GtkCList *)((archiver != NULL) ?
	    archiver->contents_clist : NULL
	);
	if(clist == NULL)
	    return;

	/* Get archive object */
	obj = EDV_ARCHIVE_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;
	    }
	    if(pixmap != NULL)
	    {
		gint width = 15, height = 15;
		gdk_window_get_size(pixmap, &width, &height);
		GUIDNDSetDragIcon(
		    pixmap, mask,
		    width / 2, height / 2
		);
	    }
	}
}

/*
 *	Archiver Contents List "drag_motion" signal callback.
 *
 *	This is used to constrain all drags (regardless of its type
 *	or source data type) to be a drag action of move.
 */
gboolean EDVArchiverContentsDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y, guint t,
	gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((dc == NULL) || (archiver == NULL))
	    return(FALSE);

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

	return(TRUE);
}

/*
 *	Archiver Contents List "drag_data_get" signal callback.
 */
void EDVArchiverContentsDragDataGetCB(
	GtkWidget *widget, GdkDragContext *dc,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	edv_core_struct *core_ptr;
	GList *glist, *url_list = NULL;
	GtkWidget *w;
	GtkCList *clist;
	gint row;
	url_struct *url;
	const edv_archive_object_struct *obj;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((dc == NULL) || (archiver == NULL))
	    return;

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

	/* Get contents clist and make sure it matches the given widget */
	w = archiver->contents_clist;
	if((w == NULL) || (w != widget))
	    return;

	clist = GTK_CLIST(w);

	EDVArchiverSyncData(archiver);

	/* Add first URL as the archive itself */
	url = URLNew();
	url->path = STRDUP(EDVArchiverCurrentLocation(archiver));
	url_list = g_list_append(url_list, url);

	/* Add subsequent URLs from the selected rows */
	for(glist = clist->selection;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    row = (gint)glist->data;
	    obj = EDV_ARCHIVE_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 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 buffer then send out error response */
	if(!data_sent)
	{
	    const char *s = "Error";
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,			/* Bits Per Character */
		s,			/* Data */
		STRLEN(s)		/* Length */
	    );
	    data_sent = TRUE;
	}
}

/*
 *	Archiver Contents List "drag_data_received" signal callback.
 */
void EDVArchiverContentsDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc,
	gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	edv_core_struct *core_ptr;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((dc == NULL) || (archiver == NULL))
	    return;

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

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

#if 0
	/* Check and warn if write protect is enabled */
/* Do not check this here, drag may be just to open the archive.
 * this will be checked later in EDVArchiverDragDataReceivedNexus().
 */
	if(EDVCheckWriteProtect(core_ptr, TRUE, archiver->toplevel))
	    return;
#endif

	EDVArchiverSyncData(archiver);

	/* Handle received drag data */
	EDVArchiverDragDataReceivedNexus(
	    core_ptr, archiver, dc, info, selection_data
	);
}

/*
 *	Archiver Contents List DND "drag_data_delete" signal callback.
 */
void EDVArchiverContentsDragDataDeleteCB(
	GtkWidget *widget, GdkDragContext *dc, gpointer data
)
{
	/* Ignore this, archive objects are not to be deleted when
	 * extracted
	 */
}
