#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if defined(HAVE_REGEX)
# include <regex.h>
#else
# include <fnmatch.h>
#endif
#include <gtk/gtk.h>

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

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

#include "cfg.h"
#include "edv_types.h"
#include "edv_date.h"
#include "edv_id.h"
#include "edv_obj.h"
#include "edv_mime_type.h"
#include "edv_mime_types_list.h"
#include "edv_recycled_obj.h"
#include "edv_recbin_index.h"
#include "edv_recbin_stat.h"
#include "edv_mime_type.h"
#include "recbin.h"
#include "recbin_cb.h"
#include "recbin_contents_list.h"
#include "endeavour2.h"
#include "edv_cb.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"

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


static edv_recycled_object_struct *EDVRecBinNewErrorObject(
	const guint index,
	const gchar *name
);

static void EDVRecBinContentsResetColumns(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist
);

static void EDVRecBinContentsSetCellName(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellSize(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	gboolean hide_dir_size, gboolean hide_link_size
);
static void EDVRecBinContentsSetCellType(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellPermissions(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	gboolean hide_link_permissions
);
static void EDVRecBinContentsSetCellOwner(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellGroup(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellDateAccess(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVRecBinContentsSetCellDateModified(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVRecBinContentsSetCellDateChanged(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVRecBinContentsSetCellDateDeleted(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVRecBinContentsSetCellLinkedTo(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellOriginalLocation(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellIndex(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
);
static void EDVRecBinContentsSetCellCapacityUsed(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	const gulong recbin_size_max
);
static void EDVRecBinContentsSetRow(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row
);

static gint EDVRecBinContentsAppendObject(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist,
	edv_recycled_object_struct *obj
);
static void EDVRecBinContentsAppendListing(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist,
	const gboolean update_status_bar
);

gint EDVRecBinContentsFindRowByIndex(
	edv_recbin_struct *recbin, const guint index
);

/* Realize Listing */
void EDVRecBinContentsRealizeListing(edv_recbin_struct *recbin);

/* Get Listing */
void EDVRecBinContentsGetListing(
	edv_recbin_struct *recbin,
	const gboolean update_status_bar
);
void EDVRecBinContentsClear(edv_recbin_struct *recbin);

/* Callbacks */
void EDVRecBinContentsObjectAddedNotify(
	edv_recbin_struct *recbin, guint index
);
void EDVRecBinContentsObjectModifiedNotify(
	edv_recbin_struct *recbin, guint index
);
void EDVRecBinContentsObjectRemovedNotify(
	edv_recbin_struct *recbin, guint index
);


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)


/*
 *	Creates a new error object.
 *
 *	The name specifies the name of the error object.
 */
static edv_recycled_object_struct *EDVRecBinNewErrorObject(
	const guint index,
	const gchar *name
)
{
	edv_recycled_object_struct *obj = EDVRecycledObjectNew();
	if(obj == NULL)
	    return(NULL);

	obj->name = STRDUP(name);
	obj->index = index;
	obj->type = EDV_OBJECT_TYPE_ERROR;

	return(obj);
}


/*
 *	Resets the Contents List's column headings to the names and
 *	ordering specified by the configuration.
 *
 *	All inputs assumed valid.
 */
static void EDVRecBinContentsResetColumns(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist
)
{
	gint i, width;
	const gchar *title = NULL;
	GList *glist;             
	GtkJustification justify = GTK_JUSTIFY_LEFT;
	GtkRcStyle *lists_rcstyle = core->lists_rcstyle;
	cfg_intlist_struct	*column_types_intlist,
				*column_width_intlist;
	const cfg_item_struct *cfg_list = core->cfg_list;
	edv_recbin_column_type column_type;

	/* Get the column types mapping */
	column_types_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_RECBIN_CONTENTS_COLUMN
	);
	if(column_types_intlist == NULL)
	    return;

	/* Get the column widths */
	column_width_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_RECBIN_CONTENTS_COLUMN_WIDTH
	);
	if(column_width_intlist == NULL)
	    return;


	/* Update the clist's column settings */
	gtk_clist_column_titles_active(clist);
	gtk_clist_column_titles_show(clist);
	gtk_clist_set_auto_sort(clist, FALSE);
	gtk_clist_set_sort_type(clist, GTK_SORT_DESCENDING);

#if 0
/* Already set */
	/* Change clist selection mode to GTK_SELECTION_EXTENDED
	 *
	 * The selection mode can change whenever the contents list is
	 * updated
	 */
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_EXTENDED);
#endif

	/* Iterate through each column */
	for(i = 0, glist = column_types_intlist->list;
	    glist != NULL;
	    i++, glist = g_list_next(glist)
	)
	{
	    column_type = (edv_recbin_column_type)glist->data;

	    /* Get the width for this column type */
	    width = (gint)g_list_nth_data(
		column_width_intlist->list,
		(guint)column_type
	    );

	    /* Get column title and justification  based on the
	     * column type
	     */
	    switch(column_type)
	    {
	      case EDV_RECBIN_COLUMN_TYPE_NAME:
		title = "Name";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_SIZE:
		title = "Size";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_TYPE:
		title = "Type";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_PERMISSIONS:
		title = "Permissions";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_OWNER:
		title = "Owner";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_GROUP:
		title = "Group";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_ACCESS:
		title = "Date Access";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_MODIFIED:
		title = "Date Modified";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_CHANGED:
		title = "Date Changed";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_DELETED:
		title = "Date Deleted";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_LINKED_TO:
		title = "Linked To";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_ORIGINAL_LOCATION:
		title = "Original Location";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_INDEX:
		title = "Index";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_RECBIN_COLUMN_TYPE_CAPACITY_USED:
		title = "Capacity Used";
		justify = GTK_JUSTIFY_LEFT;
		break;
	    }

	    gtk_clist_set_column_visibility(clist, i, TRUE);
	    gtk_clist_set_column_auto_resize(
		clist, i, FALSE
	    );
	    gtk_clist_set_column_title(clist, i, title);
	    gtk_clist_set_column_width(clist, i, width);
	    gtk_clist_set_column_justification(clist, i, justify);
	}
	/* Reset the rest of the columns */
	for(; i < clist->columns; i++)
	    gtk_clist_set_column_visibility(clist, i, FALSE);

	/* Set RC style after column headings have been mapped */
	if(lists_rcstyle != NULL)
	    gtk_widget_modify_style_recursive(
		GTK_WIDGET(clist), lists_rcstyle
	    );
}


/*
 *	Sets the recycled object name for the specified cell on the
 *	given clist.
 */
static void EDVRecBinContentsSetCellName(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	const gchar *name = obj->name;
	GdkPixmap *pixmap, *pixmap_hid;
	GdkBitmap *mask, *mask_hid;

	/* Get icon */
	EDVMatchObjectIcon(
	    NULL, 0,
	    core->mimetype, core->total_mimetypes,
	    obj->type, name,
	    TRUE, obj->permissions,
	    0,			/* Small icons */
	    &pixmap, &mask,
	    NULL, NULL,
	    NULL, NULL,
	    &pixmap_hid, &mask_hid
	);
	/* Checking an alternate state icon should be used */
	/* Hidden */
	if(!STRISEMPTY(name) ?
	    ((*name == '.') && (pixmap_hid != NULL)) : FALSE
	)
	{
	    pixmap = pixmap_hid;
	    mask = mask_hid;
	}

	/* Set cell */
	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, row, column,
		(name != NULL) ? name : "(null)",
		EDV_LIST_PIXMAP_TEXT_SPACING,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column,
		(name != NULL) ? name : "(null)"
	    );
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object size for the specified cell on the
 *	given clist.
 */
static void EDVRecBinContentsSetCellSize(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	gboolean hide_dir_size, gboolean hide_link_size
)
{
	const gint border_minor = 2;
	const edv_object_type type = obj->type;
	const gchar *s;

	/* Get size string */
	if(hide_dir_size && (type == EDV_OBJECT_TYPE_DIRECTORY))
	    s = "";
	else if(hide_link_size && (type == EDV_OBJECT_TYPE_LINK))
	    s = "";
	else if((type == EDV_OBJECT_TYPE_DEVICE_BLOCK) ||
		(type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	)
	    s = "";
	else
	    s = EDVGetObjectSizeStr(core, obj->size);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
}

/*
 *	Sets the recycled object type for the specified cell on the
 *	given clist.
 */
static void EDVRecBinContentsSetCellType(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	gchar *type_str;

	/* Get MIME Type type string for the given object */
	EDVMatchObjectTypeString(
	    core->mimetype, core->total_mimetypes,
	    obj->type, obj->permissions,
	    obj->name,
	    &type_str
	);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, (type_str != NULL) ? type_str : ""
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object permissions for the specified cell on
 *	the given clist.
 */
static void EDVRecBinContentsSetCellPermissions(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column, gboolean hide_link_permissions
)
{
	const edv_permission_flags permissions = obj->permissions;
	gchar s[80];

	/* Get permissions string */
	if(hide_link_permissions && (obj->type == EDV_OBJECT_TYPE_LINK))
	    *s = '\0';
	else
	    g_snprintf(
		s, sizeof(s),
		"%c%c%c%c%c%c%c%c%c",
	    (permissions & EDV_PERMISSION_UREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_UWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETUID) ?
		'S' :
		((permissions & EDV_PERMISSION_UEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_GREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_GWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETGID) ?
		'G' :
		((permissions & EDV_PERMISSION_GEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_AREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_AWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_STICKY) ?
		'T' :
		((permissions & EDV_PERMISSION_AEXECUTE) ? 'x' : '-')
	    );

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object owner for the specified cell on the
 *	given clist.
 */
static void EDVRecBinContentsSetCellOwner(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get owner name from object's user id */
	const gchar *owner_name = EDVUIDGetNameFromUID(
	    core->uid, core->total_uids,
	    obj->owner_id, NULL
	);
	if(owner_name == NULL)
	    owner_name = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, owner_name
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object owner for the specified cell on the
 *	given clist.
 */
static void EDVRecBinContentsSetCellGroup(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get group name from object's group id */
	const gchar *group_name = EDVGIDGetNameFromGID(
	    core->gid, core->total_gids,
	    obj->group_id, NULL
	);
	if(group_name == NULL)
	    group_name = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, group_name
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object last access date for the specified
 *	cell on the given clist.
 */
static void EDVRecBinContentsSetCellDateAccess(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date accessed */
	gulong t = obj->access_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object last modified date for the specified
 *	cell on the given clist.
 */
static void EDVRecBinContentsSetCellDateModified(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date modified */
	gulong t = obj->modify_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object last changed date for the specified
 *	cell on the given clist.
 */
static void EDVRecBinContentsSetCellDateChanged(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date changed */
	gulong t = obj->change_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object delete date for the specified cell
 *	on the given clist.
 */
static void EDVRecBinContentsSetCellDateDeleted(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get date string */
	const gulong t = obj->deleted_time;
	if(t > 0l)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}


/*
 *	Sets the recycled object linked to path for the specified cell
 *	on the given clist.
 */
static void EDVRecBinContentsSetCellLinkedTo(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get link destination */
	const gchar *link_dest = ((obj->link_value != NULL) &&
	    (obj->type == EDV_OBJECT_TYPE_LINK)) ?
	    obj->link_value : "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, link_dest
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object original location for the specified
 *	cell on the given clist.
 */
static void EDVRecBinContentsSetCellOriginalLocation(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	GdkBitmap *mask;
	GdkPixmap *pixmap;
	struct stat lstat_buf;
	const gchar	*location = (obj->original_path != NULL) ?
			    obj->original_path : "";
	edv_object_struct *loc_obj = EDVObjectNew();
	EDVObjectSetPath(loc_obj, location);
	if(!lstat(location, &lstat_buf))
	{
	    EDVObjectSetStat(loc_obj, &lstat_buf);
	    EDVObjectUpdateLinkFlags(loc_obj);
	    EDVMatchObjectIcon(
		core->device, core->total_devices,
		core->mimetype, core->total_mimetypes,
		loc_obj->type,
		loc_obj->full_path,
		EDV_OBJECT_IS_LINK_VALID(loc_obj),
		loc_obj->permissions,
		0,			/* Small icons */
		&pixmap, &mask,
		NULL, NULL,
		NULL, NULL,
		NULL, NULL
	    );
	}
	else
	{
	    edv_mime_type_struct *m = EDVMimeTypesListMatchType(
		core->mimetype, core->total_mimetypes,
		NULL,
		EDV_MIME_TYPE_TYPE_INODE_DIRECTORY,
		FALSE
	    );
	    if(m != NULL)
	    {
		pixmap = m->small_pixmap[EDV_MIME_TYPE_ICON_STATE_STANDARD];
		mask = m->small_mask[EDV_MIME_TYPE_ICON_STATE_STANDARD];
	    }
	}
	EDVObjectDelete(loc_obj);

	/* Set cell */
	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, row, column,
		location,
		EDV_LIST_PIXMAP_TEXT_SPACING,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column, location
	    );
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the recycled object index to path for the specified cell
 *	on the given clist.
 */
static void EDVRecBinContentsSetCellIndex(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column
)
{
	const gint border_minor = 2;
 	gchar num_str[40];

	/* Format the index string */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "#%i",
	    obj->index
	);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, num_str
	);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
}

/*
 *	Sets the recycled object capacity used for the specified cell
 *	on the given clist.
 */
static void EDVRecBinContentsSetCellCapacityUsed(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row, const gint column,
	const gulong recbin_size_max
)
{
	gfloat usage_coeff;
        GdkBitmap *mask = NULL;
        GdkPixmap *pixmap;
        GtkCListColumn *column_ptr = ((column >= 0) && (column < clist->columns)) ?
            &clist->column[column] : NULL;
        const gint	width = (column_ptr != NULL) ?
	    column_ptr->width : 60,
			height = GTK_CLIST_ROW_HEIGHT_SET(clist) ?
	    (clist->row_height - 4) : -1;

	if(recbin_size_max == 0l)
	{
	    gtk_clist_set_text(
		clist, row, column, ""
	    );
	    return;
	}

	/* Calculate this recycled object's usage coefficient */
	usage_coeff = CLIP(
	    ((gfloat)obj->size / (gfloat)recbin_size_max), 0.0f, 1.0f
	);

	/* Create the usage pixmap */
	pixmap = EDVNewProgressPixmap(
	    width, height,
	    usage_coeff,
	    TRUE,				/* Draw value */
	    GTK_ORIENTATION_HORIZONTAL,
	    FALSE,				/* Not reverse */
	    gtk_widget_get_style(GTK_WIDGET(clist)),
	    &mask
	);

        /* Set the cell */
        if(pixmap != NULL)
            gtk_clist_set_pixmap(clist, row, column, pixmap, mask);
        else 
            gtk_clist_set_text(clist, row, column, "");
        gtk_clist_set_cell_style(clist, row, column, NULL);
        GDK_PIXMAP_UNREF(pixmap);
        GDK_BITMAP_UNREF(mask);
}

/*
 *	Sets the Contents List's row to the values of the recycled
 *	object.
 *
 *	The clist specifies the Contents List.
 *
 *	The obj specifies the recycled object.
 *
 *	The row specifies the row.
 *
 *	All inputs assumed valid.
 */
static void EDVRecBinContentsSetRow(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recycled_object_struct *obj,
	const gint row
)
{
	gboolean hide_dir_size, hide_link_size, hide_link_permissions;
	gulong recbin_size_max;
	gint i;
	const gchar *date_format;
	GList *glist;
	edv_date_relativity date_relativity;
	edv_recbin_column_type column_type;
	cfg_intlist_struct *column_types_intlist;
	const cfg_item_struct *cfg_list = core->cfg_list;

	/* Get column types mapping */
	column_types_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_RECBIN_CONTENTS_COLUMN
	);
	if(column_types_intlist == NULL)
	    return;

	/* Get additional display options */
	date_relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	date_format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);
	hide_dir_size = EDV_GET_B(EDV_CFG_PARM_RECBIN_CONTENTS_HIDE_DIR_SIZE);
	hide_link_size = EDV_GET_B(EDV_CFG_PARM_RECBIN_CONTENTS_HIDE_LINK_SIZE);
	hide_link_permissions = EDV_GET_B(EDV_CFG_PARM_RECBIN_CONTENTS_HIDE_LINK_PERMISSIONS);

	recbin_size_max = (gulong)EDV_GET_L(
	    EDV_CFG_PARM_RECBIN_SIZE_WARN
	);

	/* Iterate through each column */
	for(i = 0, glist = column_types_intlist->list;
	    glist != NULL;
	    i++, glist = g_list_next(glist)
	)
	{
	    column_type = (edv_recbin_column_type)glist->data;
	    switch(column_type)
	    {
	      case EDV_RECBIN_COLUMN_TYPE_NAME:
		EDVRecBinContentsSetCellName(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_SIZE:
		EDVRecBinContentsSetCellSize(
		    core, recbin, clist, obj,
		    row, i, hide_dir_size, hide_link_size
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_TYPE:
		EDVRecBinContentsSetCellType(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_PERMISSIONS:
		EDVRecBinContentsSetCellPermissions(
		    core, recbin, clist, obj,
		    row, i, hide_link_permissions
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_OWNER:
		EDVRecBinContentsSetCellOwner(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_GROUP:
		EDVRecBinContentsSetCellGroup(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_ACCESS:
		EDVRecBinContentsSetCellDateAccess(
		    core, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_MODIFIED:
		EDVRecBinContentsSetCellDateModified(
		    core, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_CHANGED:
		EDVRecBinContentsSetCellDateChanged(
		    core, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_DELETED:
		EDVRecBinContentsSetCellDateDeleted(
		    core, recbin, clist, obj,
		    row, i, date_relativity, date_format

		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_LINKED_TO:
		EDVRecBinContentsSetCellLinkedTo(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_ORIGINAL_LOCATION:
		EDVRecBinContentsSetCellOriginalLocation(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_INDEX:
		EDVRecBinContentsSetCellIndex(
		    core, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_CAPACITY_USED:
		EDVRecBinContentsSetCellCapacityUsed(
		    core, recbin, clist, obj,
		    row, i,
		    recbin_size_max
		);
		break;
	    }
	}
}

/*
 *	Appends the recycled object to the Contents List.
 *
 *	The clist specifies the Contents List.
 *
 *	The obj specifies the recycled object who's values will be
 *	used to append a new row on the Contents List and will be
 *	transfered to the Content List's row data. The obj should
 *	not be referenced again after this call.
 *
 *	Returns the new row index or -1 on error.
 *
 *	All inputs assumed valid.
 */
static gint EDVRecBinContentsAppendObject(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist,
	edv_recycled_object_struct *obj
)
{
	const gint columns = MAX(clist->columns, 1);
	gint i, new_row;
	gchar **strv;

	/* Allocate the row cell values */
	strv = (gchar **)g_malloc0(columns * sizeof(gchar *));
	for(i = 0; i < columns; i++)
	    strv[i] = "";

	/* Append the new row */
	new_row = gtk_clist_append(clist, strv);

	/* Delete the row cell values */
	g_free(strv);

	/* Failed to append row? */
	if(new_row < 0)
	{
	    EDVRecycledObjectDelete(obj);
	    return(-1);
	}

	/* Set the new row's values */
	EDVRecBinContentsSetRow(
	    core, recbin, clist,
	    obj,
	    new_row
	);

	/* Set this recycled object as the new row's data */
	gtk_clist_set_row_data_full(
	    clist, new_row,
	    obj, EDVRecBinContentsItemDestroyCB
	);

	return(new_row);
}

/*
 *	Appends the recycled objects in the recycle bin directory to
 *	the Contents List.
 *
 *	The clist specifies the Contents List.  
 *
 *	If update_status_bar is TRUE then the status bar will be
 *	updated during this operation.
 *
 *	All inputs assumed valid.
 */
static void EDVRecBinContentsAppendListing(
	edv_core_struct *core, edv_recbin_struct *recbin,
	GtkCList *clist,
	const gboolean update_status_bar
)
{
#ifdef HAVE_REGEX
	regex_t *regex_filter;
#endif
	gint nobjs, nobjs_loaded, last_progress_percent;
	const gchar	*recbin_index_file,
			*filter = recbin->contents_list_filter;
	const cfg_item_struct *cfg_list = core->cfg_list;
	edv_recycled_object_struct *obj;
	edv_recbin_index_struct *rbi_ptr;
	edv_status_bar_struct *sb = recbin->status_bar;

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

	/* Get the full path to the recycle bin index file */
	recbin_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);
	if(STRISEMPTY(recbin_index_file))
	    return;

	/* Get the total number of recycled objects */
	nobjs = EDVRecBinIndexGetTotal(recbin_index_file);

	/* Open the recycle bin index file */
	rbi_ptr = EDVRecBinIndexOpen(recbin_index_file);

	last_progress_percent = -1;
	nobjs_loaded = 0;

#define UPDATE_PROGRESS	{				\
 if(update_status_bar && (nobjs > 0)) {			\
  const gint progress_percent = nobjs_loaded * 100 / nobjs; \
  if(progress_percent > last_progress_percent) {	\
   EDVStatusBarProgress(				\
    sb,							\
    (gfloat)progress_percent / 100.0f,			\
    TRUE						\
   );							\
   last_progress_percent = progress_percent;		\
  }							\
 }							\
}
	/* Begin reading the recycle bin index file */
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    obj = rbi_ptr->obj;
	    if((obj != NULL) ? (obj->name != NULL) : FALSE)
	    {
		const gchar *name = obj->name;
		edv_recycled_object_struct *tar_obj;

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

		/* Make a copy of the recycled object obtained from
		 * the index file
		 */
		tar_obj = EDVRecycledObjectCopy(obj);
		if(tar_obj != NULL)
		{
		    /* Append/transfer the object to the listing */
		    EDVRecBinContentsAppendObject(
			core, recbin, clist,
			tar_obj
		    );
		    nobjs_loaded++;
		    UPDATE_PROGRESS
		}
	    }
	    else
	    {
		edv_recycled_object_struct *tar_obj = EDVRecBinNewErrorObject(
		    rbi_ptr->index,
		    NULL
		);
		if(tar_obj != NULL)
		{
		    /* Append/transfer the object to the listing */
		    EDVRecBinContentsAppendObject(
			core, recbin, clist,
			tar_obj
		    );
		    nobjs_loaded++;
		    UPDATE_PROGRESS
		}
	    }
	}
#undef UPDATE_PROGRESS

	/* Close the recycle bin index file */
	EDVRecBinIndexClose(rbi_ptr);

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

/*
 *	Returns the row index of the contents clist that contains a
 *	recycled object structure who's index matches the given index.
 *
 *	Can return -1 on failed match.
 */
gint EDVRecBinContentsFindRowByIndex(
	edv_recbin_struct *recbin, const guint index
)
{
	gint i;
	GtkCList *clist;
	edv_recycled_object_struct *obj;

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

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

	/* Iterate through all rows */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_RECYCLED_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    /* Full path not specified on disk object structure? */
	    if(obj->index == index)
		return(i);
	}

	return(-1);
}

/*
 *	Updates all rows on the contents clist by getting the row data
 *	and updates the clist's rows.
 *
 *	This is designed to be called whenever the displayed values
 *	of each row need to be set again from internal data, usually
 *	when a MIME Type has been added/modified/removed. This function
 *	should not be used to `refresh' the list (get new values of
 *	disk object structures), use EDVRecBinContentsGetListing()
 *	instead.
 */
void EDVRecBinContentsRealizeListing(edv_recbin_struct *recbin)
{
	gint i;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;
	edv_recycled_object_struct *obj;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);

	gtk_clist_freeze(clist);

	/* Reset the columns */
	EDVRecBinContentsResetColumns(core, recbin, clist);

	/* Update the rows */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_RECYCLED_OBJECT(gtk_clist_get_row_data(clist, i));
	    if(obj == NULL)
		continue;

	    EDVRecBinContentsSetRow(
		core, recbin, clist,
		obj,
		i				/* Row number */
	    );
	}

	gtk_clist_thaw(clist);
}


/*
 *	Clears the Contents List, resets the columns, and gets a new
 *	listing of recycled objects.
 *
 *	The path specifies the full path to the location.
 *
 *	If update_status_bar is TRUE then the status bar will be updated
 *	during this operation.
 */
void EDVRecBinContentsGetListing(
	edv_recbin_struct *recbin,
	const gboolean update_status_bar
)
{
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);

	/* Report the initial progress? */
	if(update_status_bar)
	{
	    EDVStatusBarMessage(
		recbin->status_bar,
		"Loading recycled objects...",
		FALSE
	    );
	    EDVStatusBarProgress(
		recbin->status_bar, 0.0f, TRUE
	    );
	}

	gtk_clist_freeze(clist);

	/* Clear the listing */
	EDVRecBinContentsClear(recbin);

	/* Reset the columns */
	EDVRecBinContentsResetColumns(core, recbin, clist);

	/* Append the list of recycled objects to the listing */
	EDVRecBinContentsAppendListing(
	    core, recbin, clist,
	    update_status_bar
	);

	gtk_clist_thaw(clist);

	/* Report the final progress? */
	if(update_status_bar)
	{
	    /* Reset progress */
	    EDVStatusBarMessage(
		recbin->status_bar,
		"Recycled objects loaded",
		FALSE
	    );
	    EDVStatusBarProgress(
		recbin->status_bar, 0.0f, TRUE
	    );
	}
}

/*
 *	Deletes all items in the given clist.
 */
void EDVRecBinContentsClear(edv_recbin_struct *recbin)
{
	GtkCList *clist;

	if(recbin == NULL)
	    return;

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

	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);
}


/*
 *	Recycled object added notify.
 */
void EDVRecBinContentsObjectAddedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	gint row;
	const gchar *recbin_index_file;
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);
	cfg_list = core->cfg_list;

	/* Get the full path to recycle bin index file */
	recbin_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);
	if(STRISEMPTY(recbin_index_file))
	    return;

	/* Check if the recycled object specified by the index already
	 * exists in the list
	 */
	row = EDVRecBinContentsFindRowByIndex(recbin, index);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Recycled object already in list, do nothing (no update) */
	}
	else
	{
	    /* Recycled object not in list, so add it */
	    edv_recycled_object_struct *obj = EDVRecBinObjectStat(
		recbin_index_file, index
	    );
	    if(obj != NULL)
	    {
		/* Append row, transfering the recycled object structure
		 * tar_obj to the new row. The tar_obj should not be
		 * referenced after this point.
		 */
		row = EDVRecBinContentsAppendObject(
		    core, recbin, clist,
		    obj
		);
		obj = NULL;
	    }
	}
}

/*
 *	Recycled object modified notify.
 */
void EDVRecBinContentsObjectModifiedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	gint row;
	const gchar *recbin_index_file;
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);
	cfg_list = core->cfg_list;

	/* Get the full path to recycle bin index file */
	recbin_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);
	if(STRISEMPTY(recbin_index_file))
	    return;

	/* Update modified recycled object in the list */
	row = EDVRecBinContentsFindRowByIndex(recbin, index);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Update the existing recycled object in the list */
	    edv_recycled_object_struct *obj;
	    EDVRecycledObjectDelete(EDV_RECYCLED_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    ));
	    obj = EDVRecBinObjectStat(recbin_index_file, index);
	    if(obj != NULL)
	    {
		gtk_clist_set_row_data_full(
		    clist, row,
		    obj, EDVRecBinContentsItemDestroyCB
		);
		EDVRecBinContentsSetRow(
		    core, recbin, clist,
		    obj,
		    row
		);
	    }
	}
	else
	{
	    /* Append this recycled object to the list */
	    edv_recycled_object_struct *obj = EDVRecBinObjectStat(
		recbin_index_file, index
	    );
	    if(obj != NULL)
		row = EDVRecBinContentsAppendObject(
		    core, recbin, clist,
		    obj
		);
	}
}

/*
 *	Recycled object removed notify.
 */
void EDVRecBinContentsObjectRemovedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	GtkWidget *w;
	GtkCList *clist;
	gint row;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	w = recbin->contents_clist;
	if(w == NULL)
	    return;
	else
	    clist = GTK_CLIST(w);

	/* Remove the recycled object from the list */
	row = EDVRecBinContentsFindRowByIndex(recbin, index);
	if((row >= 0) && (row < clist->rows))
	    gtk_clist_remove(clist, row);
}
