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

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

#include "cfg.h"
#include "edvtypes.h"
#include "edvobj.h"
#include "edvrecbin.h"
#include "edvrecbinfio.h"
#include "edvconfirm.h"
#include "edvutils.h"
#include "endeavour.h"
#include "edvrecbinfop.h"
#include "edvrecbinsync.h"
#include "recbin.h"
#include "recbincb.h"
#include "recbinopcb.h"
#include "recbincontents.h"
#include "recbindeskicon.h"
#include "edvcb.h"
#include "edvhelp.h"
#include "edvop.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


void EDVRecBinMenuItemCB(GtkWidget *widget, gpointer data);
gint EDVRecBinMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
gint EDVRecBinMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);

void EDVRecBinOPCB(
	toolbar_item_struct *item, gint id, gpointer data
);
void EDVRecBinOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data
);
void EDVRecBinOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data
);

void EDVRecBinFindBarStartCB(gpointer fb, gpointer data);
void EDVRecBinFindBarEndCB(
	gpointer fb, gint total_matches, gpointer data
);
void EDVRecBinFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
);

void EDVRecBinBarStatusMessageCB(
	gpointer bar, const gchar *message, gpointer data
);
void EDVRecBinBarStatusProgressCB(
	gpointer bar, gfloat progress, gpointer data
);

void EDVRecBinOPSyncRecycleBin(edv_recbin_struct *recbin);

void EDVRecBinOPSyncDisks(edv_recbin_struct *recbin);
void EDVRecBinOPWriteProtect(edv_recbin_struct *recbin);

void EDVRecBinOPClose(edv_recbin_struct *recbin);
void EDVRecBinOPExit(edv_recbin_struct *recbin);

void EDVRecBinOPRecover(edv_recbin_struct *recbin);
void EDVRecBinOPPurge(edv_recbin_struct *recbin);
void EDVRecBinOPPurgeAll(edv_recbin_struct *recbin);
void EDVRecBinOPSelectAll(edv_recbin_struct *recbin);
void EDVRecBinOPUnselectAll(edv_recbin_struct *recbin);
void EDVRecBinOPInvertSelection(edv_recbin_struct *recbin);

void EDVRecBinOPRefresh(edv_recbin_struct *recbin);
void EDVRecBinOPRefreshAll(edv_recbin_struct *recbin);


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


/*
 *      Menu item activate callback.
 *
 *      The given client data must be a edv_recbin_opid_struct *.
 */
void EDVRecBinMenuItemCB(GtkWidget *widget, gpointer data)
{
	EDVRecBinOPCB(NULL, -1, data);
}

/*
 *      Menu item "enter_notify_event" signal callback.
 *
 *      The given client data must be a edv_recbin_opid_struct *.
 */
gint EDVRecBinMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVRecBinOPEnterCB(NULL, -1, data);
	return(TRUE);
}

/*
 *      Menu item "leave_notify_event" signal callback.
 *
 *      The given client data must be a edv_recbin_opid_struct *.
 */
gint EDVRecBinMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVRecBinOPLeaveCB(NULL, -1, data);
	return(TRUE);
}

/*
 *      Operation id callback nexus.
 *
 *      The given client data must be a edv_recbin_opid_struct *.
 */
void EDVRecBinOPCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	edv_recbin_struct *recbin;
	edv_core_struct *core_ptr;
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	if(opid == NULL)
	    return;

	recbin = EDV_RECBIN(opid->recbin);
	if(recbin == NULL)
	    return;

	if(recbin->processing || (recbin->freeze_count > 0))
	    return;

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

	recbin->freeze_count++;

	/* Handle by operation id code */
	switch(opid->op)
	{
	  case EDV_RECBIN_OP_NONE:
	  case EDV_RECBIN_OP_SEPARATOR:
	    break;

	  case EDV_RECBIN_OP_CLOSE:
	    EDVRecBinOPClose(recbin);
	    break;

	  case EDV_RECBIN_OP_EXIT:
	    EDVRecBinOPExit(recbin);
	    break;


	  case EDV_RECBIN_OP_RECOVER:
	    EDVRecBinOPRecover(recbin);
	    break;

	  case EDV_RECBIN_OP_PURGE:
	    EDVRecBinOPPurge(recbin);
	    break;

	  case EDV_RECBIN_OP_PURGE_ALL:
	    EDVRecBinOPPurgeAll(recbin);
	    break;

	  case EDV_RECBIN_OP_SELECT_ALL:
	    EDVRecBinOPSelectAll(recbin);
	    break;

	  case EDV_RECBIN_OP_UNSELECT_ALL:
	    EDVRecBinOPUnselectAll(recbin);
	    break;

	  case EDV_RECBIN_OP_INVERT_SELECTION:
	    EDVRecBinOPInvertSelection(recbin);
	    break;

	  case EDV_RECBIN_OP_FIND:
	    EDVMapRecBinFindWin(core_ptr, recbin);
	    break;

	  case EDV_RECBIN_OP_HISTORY:
	    EDVMapHistoryListWin(core_ptr, recbin->toplevel);
	    break;

	  case EDV_RECBIN_OP_SYNC_RECYCLE_BIN:
	    EDVRecBinOPSyncRecycleBin(recbin);
	    break;

	  case EDV_RECBIN_OP_SYNC_DISKS:
	    EDVRecBinOPSyncDisks(recbin);
	    break;

	  case EDV_RECBIN_OP_WRITE_PROTECT:
	    EDVRecBinOPWriteProtect(recbin);
	    break;

	  case EDV_RECBIN_OP_RUN:
	    EDVMapRunDialogCommand(
		core_ptr,
		NULL,
		NULL,
		recbin->toplevel
	    );
	    break;

	  case EDV_RECBIN_OP_RUN_TERMINAL:
	    EDVRunTerminal(core_ptr, NULL, recbin->toplevel);
	    break;


	  case EDV_RECBIN_OP_REFRESH:
	    EDVRecBinOPRefresh(recbin);
	    break;

	  case EDV_RECBIN_OP_REFRESH_ALL:
	    EDVRecBinOPRefreshAll(recbin);
	    break;

	  case EDV_RECBIN_OP_SHOW_TOOL_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_TOOL_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_TOOL_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;

	  case EDV_RECBIN_OP_SHOW_FIND_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_FIND_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_FIND_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;

	  case EDV_RECBIN_OP_SHOW_STATUS_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_STATUS_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_STATUS_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;


	  case EDV_RECBIN_OP_MIME_TYPES:
	    EDVMapMIMETypesListWin(core_ptr, recbin->toplevel);
	    break;


	  case EDV_RECBIN_OP_NEW_BROWSER:
	    EDVNewBrowser(core_ptr);
	    break;

	  case EDV_RECBIN_OP_NEW_IMBR:
	    EDVNewImbr(core_ptr);
	    break;

	  case EDV_RECBIN_OP_NEW_ARCHIVER:
	    EDVNewArchiver(core_ptr);
	    break;


	  case EDV_RECBIN_OP_OPTIONS:
	    EDVMapOptionsWin(core_ptr, recbin->toplevel);
	    break;

	  case EDV_RECBIN_OP_CUSTOMIZE:
	    EDVMapCustomizeWin(core_ptr, recbin->toplevel);
	    break;


	  case EDV_RECBIN_OP_HELP_ABOUT:
	    EDVAbout(core_ptr, recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_CONTENTS:
	    EDVHelp(core_ptr, "Contents", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_FILE_BROWSER:
	    EDVHelp(core_ptr, "File Browser", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_IMAGE_BROWSER:
	    EDVHelp(core_ptr, "Image Browser", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_ARCHIVER:
	    EDVHelp(core_ptr, "Archiver", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_RECYCLE_BIN:
	    EDVHelp(core_ptr, "Recycle Bin", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_KEYS_LIST:
	    EDVHelp(core_ptr, "Keys List", recbin->toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_COMMON_OPERATIONS:
	    EDVHelp(core_ptr, "Common Operations", recbin->toplevel);
	    break;
	}

	recbin->freeze_count--;
}

/*
 *      Operation id enter notify callback nexus.
 *
 *      The given client data must be a edv_recbin_opid_struct *.
 */
void EDVRecBinOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	const gchar *tooltip;
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	edv_recbin_struct *recbin = EDV_RECBIN(
	    (opid != NULL) ? opid->recbin : NULL
	);
	if(recbin == NULL)
	    return;

	tooltip = opid->tooltip;
	if(tooltip != NULL)
	    EDVStatusBarMessage(recbin->status_bar, tooltip, FALSE);
}

/*
 *      Operation id leave notify callback nexus.
 */
void EDVRecBinOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	edv_recbin_struct *recbin = EDV_RECBIN(
	    (opid != NULL) ? opid->recbin : NULL
	);
	if(recbin == NULL)
	    return;

	EDVStatusBarMessage(recbin->status_bar, NULL, FALSE);
}


/*
 *      Find bar start find callback.
 */
void EDVRecBinFindBarStartCB(gpointer fb, gpointer data)
{
	GtkCList *clist;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if(recbin == NULL)
	    return;

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

	EDVRecBinSetBusy(recbin, TRUE);
/*      GUIBlockInput(recbin->toplevel, TRUE); */

	gtk_clist_freeze(clist);
	gtk_clist_unselect_all(clist);
	gtk_clist_thaw(clist);

	EDVRecBinUpdateMenus(recbin);
}

/*
 *      Find bar end find callback.
 */
void EDVRecBinFindBarEndCB(
	gpointer fb, gint total_matches, gpointer data
)
{
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if(recbin == NULL)
	    return;

/*      GUIBlockInput(recbin->toplevel, FALSE); */
	EDVRecBinSetBusy(recbin, FALSE);
}


/*
 *      Find bar match callback.
 */
void EDVRecBinFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
)
{
	gint row;
	GtkCList *clist;
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if((path == NULL) || (lstat_buf == NULL) || (recbin == NULL))
	    return;

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

	row = EDVRecBinContentsFindRowByIndex(recbin, (guint)atoi(path));
	if((row >= 0) && (row < clist->rows))
	    gtk_clist_select_row(clist, row, 0);
}


/*
 *      Opaque status bar message update callback, the first argument
 *      bar is completely ignored.
 */
void EDVRecBinBarStatusMessageCB(
	gpointer bar, const gchar *message, gpointer data
)
{
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if(recbin == NULL)
	    return;

	EDVStatusBarMessage(recbin->status_bar, message, FALSE);
}

/*
 *      Opaque status bar progress update callback, the first argument
 *      bar is completely ignored.
 */
void EDVRecBinBarStatusProgressCB(
	gpointer bar, gfloat progress, gpointer data
)
{
	edv_recbin_struct *recbin = EDV_RECBIN(data);
	if(recbin == NULL)
	    return;

	EDVStatusBarProgress(recbin->status_bar, progress, FALSE);
}


/*
 *	Compact recycled objects and fix any errors.
 */
void EDVRecBinOPSyncRecycleBin(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	gint status;


	if(recbin == NULL)
	    return;

	if(recbin->processing)
	    return;

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Syncing recycle bin...",
	    TRUE
	);

	EDVRecBinSetBusy(recbin, TRUE);

	/* Sync recycle bin */
	status = EDVRecBinSync(
	    EDV_CORE(recbin->core_ptr),
	    recbin->toplevel,
	    TRUE, TRUE,
	    &yes_to_all,
	    EDVRecBinBarStatusMessageCB,
	    EDVRecBinBarStatusProgressCB,
	    recbin
	);

	/* Refresh listing of recycled objects */
	EDVRecBinOPRefresh(recbin);

	EDVRecBinSetBusy(recbin, FALSE);

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Recycle bin sync done",
	    FALSE
	);
	EDVStatusBarProgress(recbin->status_bar, 0.0f, FALSE);
}


/*
 *	Sync Disks.
 */
void EDVRecBinOPSyncDisks(edv_recbin_struct *recbin)
{
	if(recbin == NULL)
	    return;

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Syncing disks...",
	    TRUE
	);

	EDVRecBinSetBusy(recbin, TRUE);

	EDVSyncDisks(EDV_CORE(recbin->core_ptr));

	EDVRecBinSetBusy(recbin, FALSE);

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Disk sync done",
	    FALSE
	);
	EDVStatusBarProgress(recbin->status_bar, 0.0f, FALSE);
}

/*
 *      Write Protect Toggle.
 */
void EDVRecBinOPWriteProtect(edv_recbin_struct *recbin)
{
	gboolean write_protect;
	cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;


	if(recbin == NULL)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Get current write protect state */
	write_protect = (gboolean)CFGItemListGetValueI(
	    cfg_list, EDV_CFG_PARM_WRITE_PROTECT
	);

	/* Toggle write protect */
	write_protect = !write_protect;

	/* Set new write protect state */
	CFGItemListSetValueI(
	    cfg_list, EDV_CFG_PARM_WRITE_PROTECT,
	    write_protect, FALSE
	);

	/* Emit write protect changed signal */
	EDVWriteProtectChangedEmit(core_ptr, write_protect);
}

/*
 *      Close.
 */
void EDVRecBinOPClose(edv_recbin_struct *recbin)
{
	if(recbin == NULL)
	    return;

	/* Set current properties of the recycle bin to the configuration
	 * list.
	 */
	EDVRecBinSyncConfiguration(recbin);

	/* Unmap the recbin */
	EDVRecBinUnmap(recbin);
}

/*
 *      Close all windows.
 */
void EDVRecBinOPExit(edv_recbin_struct *recbin)
{
	edv_core_struct *core_ptr;


	if(recbin == NULL)
	    return;

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

	/* Set current properties of the recycle bin to the configuration
	 * list.
	 */
	EDVRecBinSyncConfiguration(recbin);

	/* Unmap this recycle bin */
	EDVRecBinUnmap(recbin);

	/* Mark need_close_all_windows on the core structure to TRUE, this
	 * will be checked during the management timeout. In which case
	 * all windows will be deleted.
	 */
	core_ptr->need_close_all_windows = TRUE;
}


/*
 *	Recover callback.
 */
void EDVRecBinOPRecover(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	guint *index = NULL;
	gint total_indices = 0;
	gint recbin_objects_recovered = 0;
	gint i, row, status;
	gchar *new_path;
	const gchar *error_mesg;
	GList *glist;
	GtkWidget *toplevel;
	GtkCList *clist;
	edv_recbin_object_struct *obj = NULL;
	edv_core_struct *core_ptr;
	gchar obj_index_str[80];

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	clist = (GtkCList *)recbin->contents_clist;
	core_ptr = EDV_CORE(recbin->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

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

#define DO_FREE_LOCALS	{	\
 g_free(index);			\
 index = NULL;			\
 total_indices = 0;		\
}

	/* Get a list of selected recycled objects indexes */
	glist = clist->selection;
	while(glist != NULL)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECBIN_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		/* Append this recycled object structure's index to the
		 * list of recycled object indexes to purge
		 */
		i = total_indices;
		total_indices = i + 1;
		index = (guint *)g_realloc(
		    index, total_indices * sizeof(guint)
		);
		if(index == NULL)
		{
		    total_indices = 0;
		    break;
		}
		else
		{
		   index[i] = obj->index;
		}
	    }

	    glist = g_list_next(glist);
	}

	/* No recycled objects to recover? */
	if(index == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}


	/* Format recycled object index number string based on the last
	 * selected recycled object (if any)
	 *
	 * This will be used for passing to the confirmation function
	 */
	if(obj != NULL)
	    g_snprintf(
		obj_index_str, sizeof(obj_index_str),
		"%i", obj->index
	    );
	else
	    *obj_index_str = '\0';

	/* Get recover confirmation */
	status = EDVConfirmRecover(
	    core_ptr, toplevel,
	    obj_index_str,
	    total_indices,
	    (obj != NULL) ? obj->original_path : NULL
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    return;
	    break;
	}

	/* Do not reference last selected recycled object after this
	 * point
	 */
	obj = NULL;


	EDVRecBinSetBusy(recbin, TRUE);

	/* Iterate through list of recycled object indices, purging
	 * each one
	 */
	status = 0;
	for(i = 0; i < total_indices; i++)
	{
	    /* Recover recycled object */
	    new_path = NULL;
	    status = EDVRecBinFOPRecover(
		core_ptr, index[i], NULL,
		&new_path,
		toplevel,
		TRUE, TRUE, &yes_to_all
	    );

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

	    /* Was the recycled object recovered successfully? */
	    if(!status)
	    {
		struct stat lstat_buf;

		/* Mark this as an object recovered */
		recbin_objects_recovered++;

		/* Report new disk object added */
		if((new_path != NULL) ? !lstat(new_path, &lstat_buf) : FALSE)
		    EDVObjectAddedEmit(
			core_ptr, new_path, &lstat_buf
		    );

		/* Report recycled object removed */
		EDVRecycledObjectRemovedEmit(core_ptr, index[i]);
	    }

	    /* Delete path of the recovered object */
	    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;
	}

	/* 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);

	EDVRecBinSetBusy(recbin, FALSE);

	/* Update status bar message */
	if(TRUE)
	{
	    gchar *buf;
	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Recovered %i object%s",
		    recbin_objects_recovered,
		    (recbin_objects_recovered == 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_indices == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(recbin->status_bar, buf, FALSE);
	    g_free(buf);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *      Purge callback.
 */
void EDVRecBinOPPurge(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	gint i, row, status;
	guint *index = NULL;
	gint total_indices = 0;
	gint recbin_objects_purged = 0;
	const gchar *error_mesg;
	GtkWidget *toplevel;
	GList *glist;
	GtkCList *clist;
	edv_recbin_object_struct *obj = NULL;
	edv_core_struct *core_ptr;
	gchar obj_index_str[80];

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	clist = (GtkCList *)recbin->contents_clist;
	core_ptr = EDV_CORE(recbin->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

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

#define DO_FREE_LOCALS	{	\
 g_free(index);			\
 index = NULL;			\
 total_indices = 0;		\
}

	/* Get a list of selected recycled objects indices */
	glist = clist->selection;
	while(glist != NULL)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECBIN_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		/* Append this recycled object structure's index to the
		 * list of recycled object indexes to purge
		 */
		i = total_indices;
		total_indices = i + 1;
		index = (guint *)g_realloc(
		    index, total_indices * sizeof(guint)
		);
		if(index == NULL)
		{
		    total_indices = 0;
		    break;
		}
		else
		{
		   index[i] = obj->index;
		}
	    }

	    glist = g_list_next(glist);
	}

	/* No recycled objects to purge? */
	if(index == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}


	/* Format recycled object index number string based on the last
	 * selected recycled object (if any). This will be used for
	 * passing to the confirmation function
	 */
	if(obj != NULL)
	    g_snprintf(
		obj_index_str, sizeof(obj_index_str),
		"%i", obj->index
	    );
	else
	    *obj_index_str = '\0';

	/* Get purge confirmation */
	status = EDVConfirmPurge(
	    core_ptr, toplevel,
	    obj_index_str,
	    total_indices
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    return;
	    break;
	}

	/* Do not reference the last selected recycled object after
	 * this point
	 */
	obj = NULL;


	EDVRecBinSetBusy(recbin, TRUE);

	/* Iterate through list of recycled object indexes, purging each
	 * one
	 */
	status = 0;
	for(i = 0; i < total_indices; i++)
	{
	    /* Purge recycled object */
	    status = EDVRecBinFOPPurge(
		core_ptr, index[i], toplevel,
		(total_indices > 0) ?
		    (gfloat)i / (gfloat)total_indices : -1.0,
		TRUE, TRUE, &yes_to_all
	    );

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

	    /* Was the recycled object purged successfully? */
	    if(!status)
	    {
		recbin_objects_purged++;
		EDVRecycledObjectRemovedEmit(core_ptr, index[i]);
	    }

	    /* 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;
	}

	/* 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);

	EDVRecBinSetBusy(recbin, FALSE);

	/* Update status bar message */
	if(TRUE)
	{
	    gchar *buf;
	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Purged %i object%s",
		    recbin_objects_purged,
		    (recbin_objects_purged == 1) ? "" : "s"
		);
		break;

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

	      default:  /* Error */
		buf = g_strdup_printf(
		    "Unable to purge object%s",
		    (total_indices == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(recbin->status_bar, buf, FALSE);
	    g_free(buf);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Purge all callback.
 */
void EDVRecBinOPPurgeAll(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	guint *index = NULL;
	gint total_indices = 0;
	gint recbin_objects_purged = 0;
	gint i, status;
	const gchar *recycled_index_file;
	const gchar *error_mesg;
	GtkWidget *toplevel;
	edv_recbin_index_struct *rbi_ptr;
	edv_core_struct *core_ptr;


	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	core_ptr = EDV_CORE(recbin->core_ptr);
	if(core_ptr == NULL)
	    return;

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

	/* Get path to recycled objects index file */
	recycled_index_file = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_FILE_RECYCLED_INDEX
	);
	if(recycled_index_file == NULL)
	    return;

#define DO_FREE_LOCALS	{	\
 g_free(index);			\
 index = NULL;			\
 total_indices = 0;		\
}

	/* Get a list of all recycled object indices */
	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    if(rbi_ptr->index != 0)
	    {
		/* Append this recycled object structure's index to the
		 * list of recycled object indexes to purge
		 */
		i = total_indices;
		total_indices = i + 1;
		index = (guint *)g_realloc(
		    index, total_indices * sizeof(guint)
		);
		if(index == NULL)
		{
		    total_indices = 0;
		    break;
		}
		else
		{
		   index[i] = rbi_ptr->index;
		}
	    }
	}
	EDVRecBinIndexClose(rbi_ptr);
	rbi_ptr = NULL;

	/* No recycled objects to purge? */
	if(index == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}


	/* Get purge all confirmation */
	status = EDVConfirmPurge(
	    core_ptr, toplevel,
	    NULL,		/* No object name, force printing of value
				 * of total_indices */
	    total_indices
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    return;
	    break;
	}


	EDVRecBinSetBusy(recbin, TRUE);

#ifdef EDV_RECBIN_USE_PURGE_ALL
/* Do not define this, this has issues about progress updating,
 * read subsequent comments in this block
 */
	/* Purge all contents from the recycled objects directory
	 * including the recycled objects index file
	 */
	status = EDVRecBinFOPPurgeAll(
	    core_ptr, toplevel,
	    TRUE, TRUE, &yes_to_all
	);

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

	/* All recycled objects purged successfully? */
	if(!status)
	{
	    recbin_objects_purged = total_indices;

	    /* Send recycled object purged signal to all of Endeavour's
	     * resources for each recycled object listed in our recorded
	     * recycled objects index array
	     */
	    for(i = 0; i < total_indices; i++)
	    {
		/* Update progress bar for notifying of resources as well */
/* This might appear a bit confusing since it is not very neat */
		if(ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			(gfloat)i / (gfloat)total_indices,
			EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			/* If user wants to stop, then end updating of
			 * Endeavour's resources prematurly but do not
			 * change the value of status.
			 */
			break;
		    }
		}
		EDVRecycledObjectRemovedEmit(core_ptr, index[i]);
	    }
	}
#else
	/* Iterate through list of recycled object indexes, purging each
	 * one
	 */
	status = 0;
	for(i = 0; i < total_indices; i++)
	{
	    /* Purge recycled object */
	    status = EDVRecBinFOPPurge(
		core_ptr, index[i], recbin->toplevel,
		(total_indices > 0) ?
		    (gfloat)i / (gfloat)total_indices : -1.0,
		TRUE, TRUE, &yes_to_all
	    );

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

	    /* Was the recycled object purged successfully? */
	    if(!status)
	    {
		recbin_objects_purged++;
		EDVRecycledObjectRemovedEmit(core_ptr, index[i]);
	    }

	    /* 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;
	}

	/* If no errors or aborting occured then do one more through
	 * purge all but do not show progress for it. This will also
	 * remove any stray recycled object files and the recycled objects
	 * index file
	 */
	if(!status)
	    EDVRecBinFOPPurgeAll(
		core_ptr, recbin->toplevel,
		FALSE, TRUE, &yes_to_all
	    );
#endif

	/* 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);

	EDVRecBinSetBusy(recbin, FALSE);

	/* Update status bar message */
	if(TRUE)
	{
	    gchar *buf;
	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Purged %i object%s",
		    recbin_objects_purged,
		    (recbin_objects_purged == 1) ? "" : "s"
		);
		break;

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

	      default:  /* Error */
		buf = g_strdup_printf(
		    "Unable to purge object%s",
		    (total_indices == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(recbin->status_bar, buf, FALSE);
	    g_free(buf);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Select all.
 */
void EDVRecBinOPSelectAll(edv_recbin_struct *recbin)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;


	if(recbin == NULL)
	    return;

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

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);

	/* Select all rows on clist */
	gtk_clist_freeze(clist);
	gtk_clist_select_all(clist);
	gtk_clist_thaw(clist);

	/* Assume highest row index as the last selected row */
	recbin->contents_clist_selected_row = clist->rows - 1;

	EDVStatusBarMessage(
	    recbin->status_bar, "All objects selected", FALSE
	);

	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Unselect all.
 */
void EDVRecBinOPUnselectAll(edv_recbin_struct *recbin)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;


	if(recbin == NULL)
	    return;

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

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);

	/* Unselect all rows on clist */
	gtk_clist_freeze(clist);
	gtk_clist_unselect_all(clist);
	gtk_clist_thaw(clist);

	/* Mark contents clist's row as unselected */
	recbin->contents_clist_selected_row = -1;

	EDVStatusBarMessage(
	    recbin->status_bar, "All objects unselected", FALSE
	);

	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *      Invert Selection.
 */
void EDVRecBinOPInvertSelection(edv_recbin_struct *recbin)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;
	GList *glist, *selection;
	gint row, total_rows;


	if(recbin == NULL)
	    return;

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

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);
	gtk_clist_freeze(clist);

	/* Get copy of selected rows list from clist */
	selection = (clist->selection != NULL) ?
	    g_list_copy(clist->selection) : NULL;

	for(row = 0, total_rows = clist->rows;
	    row < total_rows;
	    row++
	)
	{
	    for(glist = selection;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		if(row == (gint)glist->data)
		{
		    gtk_clist_unselect_row(clist, row, 0);
		    break;
		}
	    }
	    /* Row not selected? */
	    if(glist == NULL)
		gtk_clist_select_row(clist, row, 0);
	}

	g_list_free(selection);

	gtk_clist_thaw(clist);
	EDVStatusBarMessage(
	    recbin->status_bar, "Selection inverted", FALSE
	);
	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Refresh.
 */
void EDVRecBinOPRefresh(edv_recbin_struct *recbin)
{
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	EDVRecBinSetBusy(recbin, TRUE);
	GUIBlockInput(recbin->toplevel, TRUE);

	/* Refresh toplevel */
	w = recbin->toplevel;
	if(w != NULL)
	    gtk_widget_queue_resize(w);

	/* Update contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist != NULL)
	{
	    /* Record last scroll position */
	    gfloat last_x = GTK_ADJUSTMENT_GET_VALUE(clist->hadjustment),
		   last_y = GTK_ADJUSTMENT_GET_VALUE(clist->vadjustment);

	    gtk_clist_freeze(clist);

	    /* Refresh list items */
	    EDVRecBinContentsDoUpdate(
		recbin,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );

	    gtk_clist_thaw(clist);

	    /* Scroll back to original position */
	    EDVScrollCListToPosition(clist, last_x, last_y);
	}

	EDVRecBinUpdateMenus(recbin);
	EDVStatusBarMessage(
	    recbin->status_bar, "Refreshed contents listing", FALSE
	);

	GUIBlockInput(recbin->toplevel, FALSE);
	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *      Refresh All.
 */
void EDVRecBinOPRefreshAll(edv_recbin_struct *recbin)
{
	const gchar *recycled_index_file;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Get path of recycled object index file */
	recycled_index_file = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_FILE_RECYCLED_INDEX
	);

	/* Update number of recycled objects on the core structure */
	core_ptr->last_recbin_items = EDVRecBinFIOTotalItems(
	    recycled_index_file
	);

	/* Refresh Recycle Bin Desktop Icon */
	EDVRecBinDeskIconUpdate(core_ptr->recbin_deskicon);


	/* Refresh Recycle Bin */
	EDVRecBinOPRefresh(recbin);
}
