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

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

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

#include "cfg.h"
#include "edvtypes.h"
#include "edvdate.h"
#include "edvid.h"
#include "edvobj.h"
#include "edvrecbin.h"
#include "edvrecbinfio.h"
#include "edvmimetypes.h"
#include "recbin.h"
#include "recbincb.h"
#include "recbincontents.h"
#include "endeavour.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


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

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

static gint EDVRecBinContentsAppendObject(
	edv_core_struct *core_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj
);
static void EDVRecBinContentsGetListing(
	edv_recbin_struct *recbin, gboolean update_status_bar
);

gint EDVRecBinContentsFindRowByIndex(
	edv_recbin_struct *recbin, guint index
);

void EDVRecBinContentsResetRows(edv_recbin_struct *recbin);
void EDVRecBinContentsDoUpdate(
	edv_recbin_struct *recbin, gboolean update_status_bar
);

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)


/*
 *      Clears the titles of each column on the given clist.
 */
static void EDVRecBinContentsResetColumns(
	edv_core_struct *core_ptr, edv_recbin_struct *recbin,
	GtkCList *clist
)
{
	gint i, m, width, justify = GTK_JUSTIFY_LEFT;
	const gchar *title = NULL;
	edv_recbin_column_type column_type;
	GtkRcStyle *lists_rcstyle;
	const cfg_intlist_struct	*column_types_intlist,
					*column_width_intlist;
	const cfg_item_struct *cfg_list;


	if((core_ptr == NULL) || (recbin == NULL) || (clist == NULL))
	    return;

	cfg_list = core_ptr->cfg_list;
	lists_rcstyle = core_ptr->lists_rcstyle;

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

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


	/* Update clist 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

	/* Get number of columns defined */
	m = MIN(
	    MIN(clist->columns, column_types_intlist->total),
	    column_width_intlist->total
	);

	/* Iterate through defined columns */
	for(i = 0; i < m; i++)
	{
	    /* Get column type */
	    column_type = (edv_recbin_column_type)column_types_intlist->i[i];

	    /* Get width of the column displaying this type */
	    if((column_type >= 0) && (column_type < column_width_intlist->total))
		width = column_width_intlist->i[column_type];
	    else
		width = 0;

	    /* Get column title and attributes based on 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;
	    }

	    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);
	}
	/* Hide the rest of the columns on the clist */
	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
	    );
}

/*
 *      Deletes all items in the given clist.
 */
static void EDVRecBinContentsClear(GtkCList *clist)
{
	if(clist == NULL)
	    return;

	gtk_clist_clear(clist);
}


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

	/* Get icon */
	EDVMatchObjectIcon(
	    NULL, 0,
	    core_ptr->mimetype, core_ptr->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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column,
	gboolean hide_dir_size, gboolean hide_link_size
)
{
	gint	border_minor = 2,
		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
	    s = EDVGetObjectSizeStr(core_ptr, obj->size);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column
)
{
	gchar *type_str;

	/* Get MIME Type type string for the given object */
	EDVMatchObjectTypeString(
	    core_ptr->mimetype, core_ptr->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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column, gboolean hide_link_permissions
)
{
	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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column
)
{
	/* Get owner name from object's user id */
	const gchar *owner_name = EDVUIDGetNameFromUID(
	    core_ptr->uid, core_ptr->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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column
)
{
	/* Get group name from object's group id */
	const gchar *group_name = EDVGIDGetNameFromGID(
	    core_ptr->gid, core_ptr->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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, 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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, 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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, 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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get date string */
	gulong t = obj->date_deleted;
	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_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column
)
{
	/* Get link destination */
	const gchar *link_dest = ((obj->linked_to != NULL) &&
	    (obj->type == EDV_OBJECT_TYPE_LINK)) ?
	    obj->linked_to : "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, link_dest
	);
	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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, 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);
	    loc_obj->link_valid = TRUE;
	    EDVMatchObjectIcon(
		core_ptr->device, core_ptr->total_devices,
		core_ptr->mimetype, core_ptr->total_mimetypes,
		loc_obj->type,
		loc_obj->full_path,
		loc_obj->link_valid,
		loc_obj->permissions,
		0,			/* Small icons */
		&pixmap, &mask,
		NULL, NULL,
		NULL, NULL,
		NULL, NULL
	    );
	}
	else
	{
	    edv_mimetype_struct *m = EDVMimeTypeMatchListByType(
		core_ptr->mimetype, core_ptr->total_mimetypes,
		NULL,
		EDV_MIMETYPE_STR_DIRECTORY,
		FALSE
	    );
	    if(m != NULL)
	    {
		pixmap = m->small_pixmap[EDV_MIMETYPE_ICON_STATE_STANDARD];
		mask = m->small_mask[EDV_MIMETYPE_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_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
 	gchar num_str[80];

	/* Get 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 all cells of the specified row of the given contents clist
 *      with the values of the given object.
 *
 *      The clist row's client data will not be updated.
 */
static void EDVRecBinContentsSetRow(
	edv_core_struct *core_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj,
	gint row
)
{
	gboolean hide_dir_size, hide_link_size, hide_link_permissions;
	gint i, columns;
	edv_date_relativity date_relativity;
	const gchar *date_format;
	edv_recbin_column_type column_type;
	const cfg_item_struct *cfg_list;
	cfg_intlist_struct *column_types_intlist;


	if((core_ptr == NULL) || (recbin == NULL) ||
	   (clist == NULL) || (obj == NULL)
	)
	    return;

	cfg_list = core_ptr->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);


	/* Get number of columns defined */
	columns = MIN(clist->columns, column_types_intlist->total);

	/* Iterate through defined columns */
	for(i = 0; i < columns; i++)
	{
	    /* Check column references on configuration list, see which
	     * one this column should show
	     */
	    column_type = (edv_recbin_column_type)column_types_intlist->i[i];
	    switch(column_type)
	    {
	      case EDV_RECBIN_COLUMN_TYPE_NAME:
		EDVRecBinContentsSetCellName(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_SIZE:
		EDVRecBinContentsSetCellSize(
		    core_ptr, recbin, clist, obj,
		    row, i, hide_dir_size, hide_link_size
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_TYPE:
		EDVRecBinContentsSetCellType(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_PERMISSIONS:
		EDVRecBinContentsSetCellPermissions(
		    core_ptr, recbin, clist, obj,
		    row, i, hide_link_permissions
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_OWNER:
		EDVRecBinContentsSetCellOwner(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_GROUP:
		EDVRecBinContentsSetCellGroup(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_ACCESS:
		EDVRecBinContentsSetCellDateAccess(
		    core_ptr, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_MODIFIED:
		EDVRecBinContentsSetCellDateModified(
		    core_ptr, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_CHANGED:
		EDVRecBinContentsSetCellDateChanged(
		    core_ptr, recbin, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_DATE_DELETED:
		EDVRecBinContentsSetCellDateDeleted(
		    core_ptr, recbin, clist, obj,
		    row, i, date_relativity, date_format

		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_LINKED_TO:
		EDVRecBinContentsSetCellLinkedTo(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_ORIGINAL_LOCATION:
		EDVRecBinContentsSetCellOriginalLocation(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	      case EDV_RECBIN_COLUMN_TYPE_INDEX:
		EDVRecBinContentsSetCellIndex(
		    core_ptr, recbin, clist, obj,
		    row, i
		);
		break;
	    }
	}
}

/*
 *	Appends the specified recycled object to the Contents List. 
 *
 *	The specified object will be transfered to the new row data    
 *	and should not be referenced again after this call.
 *
 *	Returns the row number that it was appended to or -1 on failure.
 */
static gint EDVRecBinContentsAppendObject(
	edv_core_struct *core_ptr, edv_recbin_struct *recbin,
	GtkCList *clist, edv_recbin_object_struct *obj
)
{
	gint i, new_row, columns;
	gchar **strv;

	if((core_ptr == NULL) || (recbin == NULL) ||
	   (clist == NULL) || (obj == NULL)
	)
	{
	    EDVRecBinObjectDelete(obj);
	    return(-1);
	}

	columns = MAX(clist->columns, 1);

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

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

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

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

	/* Set the row cell values to the values from the specified
	 * object and set the object as the row data
	 */
	EDVRecBinContentsSetRow(
	    core_ptr, recbin, clist, obj, new_row
	);
	gtk_clist_set_row_data_full(
	    clist, new_row,
	    obj, EDVRecBinContentsItemDestroyCB
	);

	return(new_row);
}

/*
 *	Gets all recycled objects in the recycled objects directory and
 *	appends them to the contents clist.
 */
static void EDVRecBinContentsGetListing(
	edv_recbin_struct *recbin, gboolean update_status_bar
)
{
	gint last_progress_percent, progress_percent;
	gint recycled_objects_loaded, total_recycled_objects;
	const gchar *recycled_index_file;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_recbin_object_struct *obj;
	edv_recbin_index_struct *rbi_ptr;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Get path to recycled objects index file */
	recycled_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLED_INDEX);
	if(STRISEMPTY(recycled_index_file))
	    return;

	/* Get total number of recycled objects */
	total_recycled_objects = EDVRecBinFIOTotalItems(recycled_index_file);


	last_progress_percent = -1;	/* None/undefined */
	progress_percent = 0;
	recycled_objects_loaded = 0;

	/* Get listing of contents in the directory specified by path */
	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    obj = rbi_ptr->obj;
	    if(obj != NULL)
	    {
		/* Make a copy of the recycled object obtained from the
		 * index file
		 */
		edv_recbin_object_struct *tar_obj = EDVRecBinObjectCopy(obj);
		if(tar_obj != NULL)
		{
		    /* Append row, transfering the recycled object
		     * tar_obj to the new row
		     *
		     * The recycled object tar_obj should not be
		     * referenced after this point
		     */
		    EDVRecBinContentsAppendObject(
			core_ptr, recbin, clist, tar_obj
		    );
		    tar_obj = NULL;

		    recycled_objects_loaded++;
		}

		/* Update progress? */
		if(update_status_bar && (total_recycled_objects > 0))
		{
		    progress_percent = recycled_objects_loaded * 100 /
			total_recycled_objects;
		    if(progress_percent > last_progress_percent)
		    {
			EDVStatusBarProgress(
			    recbin->status_bar,
			    (gfloat)recycled_objects_loaded /
				(gfloat)total_recycled_objects,
			    TRUE
			);
			progress_percent = last_progress_percent;
		    }
		}
	    }
	}

	/* Close recycled index file */
	EDVRecBinIndexClose(rbi_ptr);
}

/*
 *      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, guint index
)
{
	gint i;
	GtkCList *clist;
	edv_recbin_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_RECBIN_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 EDVRecBinContentsDoUpdate()
 *      instead.
 */
void EDVRecBinContentsResetRows(edv_recbin_struct *recbin)
{
	gint i;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core_ptr;
	edv_recbin_object_struct *obj;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);

	/* Reset columns */
	EDVRecBinContentsResetColumns(core_ptr, recbin, clist);

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

	    EDVRecBinContentsSetRow(
		core_ptr, recbin, clist, obj, i
	    );
	}
}

/*
 *	Procedure to clear and reupdate the contents list on the recycle
 *	bin with the contents found in the recycled objects directory.
 */
void EDVRecBinContentsDoUpdate(
	edv_recbin_struct *recbin, gboolean update_status_bar
)
{
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

	clist = GTK_CLIST(w);


	/* Begin updating the Contents List */

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

	/* Delete all entries */
	EDVRecBinContentsClear(clist);

	/* Update columns */
	EDVRecBinContentsResetColumns(
	    core_ptr, recbin, clist
	);

	/* Get listing of recycled objects */
	EDVRecBinContentsGetListing(recbin, update_status_bar);

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


/*
 *	This should be called whenever a new object has been added to the
 *	recycled objects directory.
 */
void EDVRecBinContentsObjectAddedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	gint row;
	const gchar *recycled_index_file;
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

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

	/* Get path to recycled objects index file */
	recycled_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLED_INDEX);
	if(STRISEMPTY(recycled_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_recbin_object_struct *obj = EDVRecBinObjectStat(
		recycled_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_ptr, recbin, clist, obj
		);
		obj = NULL;
	    }
	}
}

/*
 *	This should be called whenever an existing recycled object has been
 *	modified in the recycled objects directory.
 */
void EDVRecBinContentsObjectModifiedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	gint row;
	const gchar *recycled_index_file;
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if(recbin == NULL)
	    return;

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

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

	/* Get path to recycled objects index file */
	recycled_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLED_INDEX);
	if(STRISEMPTY(recycled_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, update it */
	    edv_recbin_object_struct *obj = EDV_RECBIN_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		gtk_clist_set_row_data_full(clist, row, NULL, NULL);
		EDVRecBinObjectDelete(obj);
		obj = NULL;
	    }

	    /* Create a new recycled object from the index and update
	     * this row
	     */
	    obj = EDVRecBinObjectStat(
		recycled_index_file, index
	    );
	    if(obj != NULL)
	    {
		gtk_clist_set_row_data_full(
		    clist, row,
		    obj, EDVRecBinContentsItemDestroyCB
		);

		EDVRecBinContentsSetRow(
		    core_ptr, recbin, clist, obj, row
		);
	    }
	}
	else
	{
	    /* Recycled object not in list, so add it */
	    edv_recbin_object_struct *obj = EDVRecBinObjectStat(
		recycled_index_file, index
	    );
	    if(obj != NULL)
	    {
		/* Append row with the recycled object */
		row = EDVRecBinContentsAppendObject(
		    core_ptr, recbin, clist, obj
		);
		obj = NULL;
	    }
	}
}

/*
 *      This should be called whenever an existing recycled object has been
 *      removed from the recycled objects directory.
 */
void EDVRecBinContentsObjectRemovedNotify(
	edv_recbin_struct *recbin, guint index
)
{
	GtkWidget *w;
	GtkCList *clist;
	gint row;
	edv_core_struct *core_ptr;


	if(recbin == NULL)
	    return;

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

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

	/* Check if the recycled object specified by the given index
	 * already exists in the list.
	 */
	row = EDVRecBinContentsFindRowByIndex(recbin, index);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Remove matched row */
	    gtk_clist_remove(clist, row);
	}
}
