#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 "progressdialog.h"
#include "fprompt.h"

#include "edvtypes.h"
#include "edvdate.h"
#include "cfg.h"
#include "edvid.h"
#include "edvobj.h"
#include "edvfop.h"
#include "edvmimetypes.h"
#include "browser.h"
#include "browsercb.h"
#include "browserdirtree.h"
#include "browsercontents.h"
#include "endeavour.h"
#include "edvopen.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


static void EDVBrowserContentsFPromptCancelCB(gpointer data);

static void EDVBrowserContentsResetColumns(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist
);
static void EDVBrowserContentsClear(GtkCList *clist);

static void EDVBrowserContentsSetCellName(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellSize(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	gboolean hide_dir_size, gboolean hide_link_size
);
static void EDVBrowserContentsSetCellType(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellPermissions(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column, gboolean hide_link_permissions
);
static void EDVBrowserContentsSetCellOwner(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellGroup(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellDateAccess(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVBrowserContentsSetCellDateModified(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVBrowserContentsSetCellDateChanged(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
);
static void EDVBrowserContentsSetCellHardLinks(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellLinkedTo(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellDevice(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellINode(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellDeviceType(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellBlockSize(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetCellBlocks(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
);
static void EDVBrowserContentsSetRow(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row
);

static gint EDVBrowserContentsAppendObject(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj
);
static void EDVBrowserContentsGetListing(
	edv_browser_struct *browser, const gchar *path,
	gboolean update_status_bar
);

gint EDVBrowserContentsFindRowByPath(
	edv_browser_struct *browser, const gchar *path
);

void EDVBrowserContentsResetRows(edv_browser_struct *browser);
void EDVBrowserContentsDoUpdate(
	edv_browser_struct *browser, const gchar *path,
	gboolean update_status_bar
);

void EDVBrowserContentsDoOpenObject(
	edv_browser_struct *browser,
	gint row, gint column,
	guint state		/* Modifier Keys */
);
void EDVBrowserContentsDoOpenWithObject(
	edv_browser_struct *browser, gint row, gint column
);

static void EDVBrowserContentsFPromptRenameApplyCB(
	gpointer data, const gchar *value
);
void EDVBrowserContentsDoFPromptRename(
	edv_browser_struct *browser, gint row, gint column
);

void EDVBrowserContentsObjectAddedNotify(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
);
void EDVBrowserContentsObjectModifiedNotify(
	edv_browser_struct *browser, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
);
void EDVBrowserContentsObjectRemovedNotify(
	edv_browser_struct *browser, const gchar *path
);

void EDVBrowserContentsMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev_ptr,
	gboolean is_mounted
);


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


/*
 *	FPrompt cancel callback.
 */
static void EDVBrowserContentsFPromptCancelCB(gpointer data)
{
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return;

	g_free(cb_data);
}


/*
 *	Clears the titles of each column on the given clist.
 */
static void EDVBrowserContentsResetColumns(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist
)
{
	gint i, m, width, justify = GTK_JUSTIFY_LEFT;
	const gchar *title = NULL;
	edv_browser_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) || (browser == 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_BROWSER_CONTENTS_COLUMN
	);
	if(column_types_intlist == NULL)
	    return;

	column_width_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_BROWSER_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

	/* Iterate through each column but not exceeding the number of
	 * columns specified by the column types or column widths 
	 * intlists
	 */
	m = MIN(
	    MIN(clist->columns, column_types_intlist->total),
	    column_width_intlist->total
	);
	for(i = 0; i < m; i++)
	{
	    /* Get column's type which determines what will be listed on
	     * the current column i
	     */
	    column_type = (edv_browser_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;

	    /* Title based on column type */
	    switch(column_type)
	    {
	      case EDV_BROWSER_COLUMN_TYPE_NAME:
		title = "Name";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_SIZE:
		title = "Size";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_TYPE:
		title = "Type";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_PERMISSIONS:
		title = "Permissions";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_OWNER:
		title = "Owner";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_GROUP:
		title = "Group";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_ACCESS:
		title = "Date Access";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_MODIFIED:
		title = "Date Modified";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_CHANGED:
		title = "Date Changed";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_HARD_LINKS:
		title = "Hard Links";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_LINKED_TO:
		title = "Linked To";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DEVICE:
		title = "Device";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_INODE:
		title = "INode";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DEVICE_TYPE:
		title = "Device Type";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_BLOCK_SIZE:
		title = "Block Size";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_BROWSER_COLUMN_TYPE_BLOCKS:
		title = "Blocks";
		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);
	}
	/* Reset the rest of the columns incase the number of columns
	 * in the intlists were less than the columns specified by 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 EDVBrowserContentsClear(GtkCList *clist)
{
	if(clist == NULL)
	    return;

	gtk_clist_clear(clist);
}


/*
 *	Sets the object name for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellName(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gboolean is_broken_link = FALSE;
	const gchar *name = obj->name;
	GdkPixmap *pixmap, *pixmap_exp, *pixmap_ext, *pixmap_hid;
	GdkBitmap *mask, *mask_exp, *mask_ext, *mask_hid;
	GtkStyle *style = NULL, **cell_styles = browser->cell_style;

	/* Get icon */
	EDVMatchObjectIcon(
	    core_ptr->device, core_ptr->total_devices,
	    core_ptr->mimetype, core_ptr->total_mimetypes,
	    obj->type,
	    obj->full_path,
	    obj->link_valid,
	    obj->permissions,
	    0,			/* Small icons */
	    &pixmap, &mask,
	    &pixmap_exp, &mask_exp,
	    &pixmap_ext, &mask_ext,
	    &pixmap_hid, &mask_hid
	);
	/* Checking for which GtkStyle and alternate state icon
	 * should be used
	 */
	switch(obj->type)
	{
	  case EDV_OBJECT_TYPE_UNKNOWN:
	    break;
	  case EDV_OBJECT_TYPE_FILE:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    break;
	  case EDV_OBJECT_TYPE_DIRECTORY:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    /* Special directory notations? */
	    if(!strcmp(name, ".."))
	    {
		pixmap = browser->folder_parent_pixmap;
		mask = browser->folder_parent_mask;
	    }
	    /* Home directory? */
	    if((!STRISEMPTY(obj->full_path) &&
		!STRISEMPTY(core_ptr->home_dir)) ?
		!strcmp(obj->full_path, core_ptr->home_dir) : FALSE
	    )
	    {
		pixmap = browser->folder_home_pixmap;
		mask = browser->folder_home_mask;
	    }
	    /* Directory not accessable? */
	    if(!EDVIsObjectAccessable(core_ptr, obj))
	    {
		style = cell_styles[
		    EDV_BROWSER_CELL_STYLE_NO_ACCESS
		];
		pixmap = browser->folder_noaccess_pixmap;
		mask = browser->folder_noaccess_mask;
	    }
	    break;
	  case EDV_OBJECT_TYPE_LINK:
	    if(!obj->link_valid)
	    {
		style = cell_styles[
		    EDV_BROWSER_CELL_STYLE_DANGLING_LINK
		];
		is_broken_link = TRUE;
	    }
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL)) 
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    break;
	  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    break;
	  case EDV_OBJECT_TYPE_FIFO:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    break;
	  case EDV_OBJECT_TYPE_SOCKET:
	    /* Hidden */
	    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	    break;
	}

	/* Set cell */
	if(is_broken_link)
	{
	    if(pixmap_ext != NULL)
		gtk_clist_set_pixtext(
		    clist, row, column,
		    (name != NULL) ? name : "(null)",
		    EDV_LIST_PIXMAP_TEXT_SPACING,
		    pixmap_ext, mask_ext
		);
	    else 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)"
		);
	}
	else
	{
	    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, style);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *      Sets the object size for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellSize(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	gboolean hide_dir_size, gboolean hide_link_size
)
{
	gint border_minor = 2;
	const gchar *s;

	if(hide_dir_size && (obj->type == EDV_OBJECT_TYPE_DIRECTORY))
	    s = "";
	else if(hide_link_size && (obj->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 object type for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellType(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_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->full_path,
	    &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 object permissions for the specified cell on the given
 *	clist.
 */
static void EDVBrowserContentsSetCellPermissions(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column, gboolean hide_link_permissions
)
{
	edv_permission_flags permissions = obj->permissions;
	gchar s[80];

	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 object owner for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellOwner(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_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 object group for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellGroup(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_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
	);
}

/*
 *	Sets the object access date for the specified cell on the given
 *	clist.
 */
static void EDVBrowserContentsSetCellDateAccess(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get date string for last access date */
	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 object modified date for the specified cell on the given
 *      clist.
 */
static void EDVBrowserContentsSetCellDateModified(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get date string for last modify date */
	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 object change date for the specified cell on the given
 *      clist.
 */
static void EDVBrowserContentsSetCellDateChanged(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column,
	edv_date_relativity date_relativity, const gchar *date_format
)
{
	const gchar *date_str;

	/* Get date string for last change date */
	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 object hard links for the specified cell on the given
 *      clist.
 */
static void EDVBrowserContentsSetCellHardLinks(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
	gchar num_str[80];

	/* Get string for number of hard links */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%i",
	    obj->hard_links
	);

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

/*
 *	Sets the object link destination for the specified cell on the
 *	given clist.
 */
static void EDVBrowserContentsSetCellLinkedTo(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	const gchar *link_dest;
	GdkBitmap	*mask = NULL,
			*mask_ext = NULL,
			*mask_hid = NULL;
	GdkPixmap	*pixmap = NULL,
			*pixmap_ext = NULL,
			*pixmap_hid = NULL;
	GtkStyle *style;

	/* Begin checking for which GtkStyle to be used */

	/* Dangling link?
	 *
	 * Note that we use the no access style for the link
	 * destination of a dangling link. The actual dangling link
	 * style is only used for the name of a dangling link
	 */
	if(obj->link_valid)
	    style = NULL;
	else
	    style = browser->cell_style[EDV_BROWSER_CELL_STYLE_NO_ACCESS];

	/* Get link destination */
	link_dest = (obj->linked_to != NULL) ? obj->linked_to : "";
	if(!STRISEMPTY(link_dest) && obj->link_valid)
	{
	    gchar *full_path = STRDUP(PrefixPaths(
		EDVBrowserCurrentLocation(browser), link_dest
	    ));
	    struct stat lstat_buf;
	    if((full_path != NULL) ?
		!lstat((char *)full_path, &lstat_buf) : FALSE
	    )
	    {
		edv_object_struct *dest_obj = EDVObjectNew();
		EDVObjectSetPath(dest_obj, full_path);
		EDVObjectSetStat(dest_obj, &lstat_buf);
		EDVObjectValidateLink(dest_obj);

		/* Get icon for the link destination */
		EDVMatchObjectIcon(
		    core_ptr->device, core_ptr->total_devices,
		    core_ptr->mimetype, core_ptr->total_mimetypes,
		    dest_obj->type,
		    dest_obj->full_path,
		    dest_obj->link_valid,
		    dest_obj->permissions,
		    0,				/* Small icons */
		    &pixmap, &mask,
		    NULL, NULL,
		    &pixmap_ext, &mask_ext,
		    &pixmap_hid, &mask_hid
		);
		/* Checking for which GtkStyle and alternate state
		 * icon should be used
		 */
		switch(dest_obj->type)
		{
		  case EDV_OBJECT_TYPE_UNKNOWN:
		    break;
		  case EDV_OBJECT_TYPE_FILE:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;
		    }
		    break;
		  case EDV_OBJECT_TYPE_DIRECTORY:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;
		    }
		    /* Special directory notations? */
		    if(!STRISEMPTY(dest_obj->name) ?
			!strcmp(dest_obj->name, "..") : FALSE
		    )
		    {
			pixmap = browser->folder_parent_pixmap;
			mask = browser->folder_parent_mask;
		    }
		    /* Home directory? */
		    if((!STRISEMPTY(dest_obj->full_path) &&
			!STRISEMPTY(core_ptr->home_dir)) ?
			!strcmp(dest_obj->full_path, core_ptr->home_dir) : FALSE
		       )
		    {
			pixmap = browser->folder_home_pixmap;
			mask = browser->folder_home_mask;
		    }
		    /* Directory not accessable? */
		    if(!EDVIsObjectAccessable(core_ptr, obj))
		    {
			pixmap = browser->folder_noaccess_pixmap;
			mask = browser->folder_noaccess_mask;
		    }
		    break;
		  case EDV_OBJECT_TYPE_LINK:
		    break;
		  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;
		    }
		    break;
		  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;    
		    }
		    break;
		  case EDV_OBJECT_TYPE_FIFO:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;    
		    }
		    break;
		  case EDV_OBJECT_TYPE_SOCKET:
		    /* Hidden */
		    if(EDVIsObjectHidden(obj) && (pixmap_hid != NULL))
		    {
			pixmap = pixmap_hid;
			mask = mask_hid;    
		    }
		    break;
		}

		EDVObjectDelete(dest_obj);
	    }
	    g_free(full_path);
	}

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

/*
 *      Sets the object device for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellDevice(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
	gchar num_str[80];

	/* Get string for device */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%ld",
	    obj->device
	);

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

/*
 *      Sets the object inode for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellINode(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
	gchar num_str[80];

	/* Get string for inode */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%ld",
	    obj->inode
	);

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

/*
 *      Sets the object device type for the specified cell on the given
 *	clist.
 */
static void EDVBrowserContentsSetCellDeviceType(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint	border_minor = 2,
		type = obj->type,
		major, minor;
	gchar num_str[2 * 80];

	/* Is object a block or character device? */
	if((type == EDV_OBJECT_TYPE_DEVICE_BLOCK) ||
	   (type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	)
	{
	    EDVGetDeviceNumbers(
		(dev_t)obj->device_type, &major, &minor
	    );
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i, %i",
		major, minor
	    );
	}
	else
	{
	    *num_str = '\0';
	}

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

/*
 *      Sets the object block size for the specified cell on the given
 *	clist.
 */
static void EDVBrowserContentsSetCellBlockSize(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
	gchar num_str[80];

	/* Get string for block size */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%ld",
	    obj->block_size
	);

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

/*
 *      Sets the object blocks for the specified cell on the given clist.
 */
static void EDVBrowserContentsSetCellBlocks(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row, gint column
)
{
	gint border_minor = 2;
	gchar num_str[80];

	/* Get string for number of blocks */
	g_snprintf(
	    num_str, sizeof(num_str),
	    "%ld",
	    obj->blocks
	);

	/* 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 List
 *	with the values of the given object.
 *
 *	The clist row's client data will not be updated.
 */
static void EDVBrowserContentsSetRow(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj,
	gint row
)
{
	gint i, columns;
	edv_date_relativity date_relativity;
	gboolean hide_dir_size, hide_link_size, hide_link_permissions;
	const gchar *date_format;
	edv_browser_column_type column_type;
	cfg_intlist_struct *column_types_intlist;
	const cfg_item_struct *cfg_list;


	if((core_ptr == NULL) || (browser == 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_BROWSER_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_BROWSER_CONTENTS_HIDE_DIR_SIZE);
	hide_link_size = EDV_GET_B(EDV_CFG_PARM_BROWSER_CONTENTS_HIDE_LINK_SIZE);
	hide_link_permissions = EDV_GET_B(EDV_CFG_PARM_BROWSER_CONTENTS_HIDE_LINK_PERMISSIONS);


	/* Get total number of columns on the Contents List */
	columns = MIN(clist->columns, column_types_intlist->total);

	/* Iterate through each column but not more than the columns
	 * specified by the column types intlist
	 */
	for(i = 0; i < columns; i++)
	{
	    /* Check column references on configuration list, see which
	     * one this column should show
	     */
	    column_type = (edv_browser_column_type)column_types_intlist->i[i];
	    switch(column_type)
	    {
	      case EDV_BROWSER_COLUMN_TYPE_NAME:
		EDVBrowserContentsSetCellName(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_SIZE:
		EDVBrowserContentsSetCellSize(
		    core_ptr, browser, clist, obj,
		    row, i, hide_dir_size, hide_link_size
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_TYPE:
		EDVBrowserContentsSetCellType(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_PERMISSIONS:
		EDVBrowserContentsSetCellPermissions(
		    core_ptr, browser, clist, obj,
		    row, i, hide_link_permissions
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_OWNER:
		EDVBrowserContentsSetCellOwner(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_GROUP:
		EDVBrowserContentsSetCellGroup(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_ACCESS:
		EDVBrowserContentsSetCellDateAccess(
		    core_ptr, browser, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_MODIFIED:
		EDVBrowserContentsSetCellDateModified(
		    core_ptr, browser, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DATE_CHANGED:
		EDVBrowserContentsSetCellDateChanged(
		    core_ptr, browser, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_HARD_LINKS:
		EDVBrowserContentsSetCellHardLinks(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_LINKED_TO:
		EDVBrowserContentsSetCellLinkedTo(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DEVICE:
		EDVBrowserContentsSetCellDevice(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_INODE:
		EDVBrowserContentsSetCellINode(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_DEVICE_TYPE:
		EDVBrowserContentsSetCellDeviceType(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_BLOCK_SIZE:
		EDVBrowserContentsSetCellBlockSize(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	      case EDV_BROWSER_COLUMN_TYPE_BLOCKS:
		EDVBrowserContentsSetCellBlocks(
		    core_ptr, browser, clist, obj,
		    row, i
		);
		break;
	    }
	}
}

/*
 *	Appends the specified 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 EDVBrowserContentsAppendObject(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCList *clist, edv_object_struct *obj
)
{
	gint i, new_row, columns;
	gchar **strv;

	if((core_ptr == NULL) || (browser == NULL) ||
	   (clist == NULL) || (obj == NULL)
	)
	{
	    EDVObjectDelete(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)
	{
	    EDVObjectDelete(obj);
	    return(-1);
	}

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

	return(new_row);
}

/*
 *	Gets all child objects of the specified path and appends them
 *	to the Contents List.
 */
static void EDVBrowserContentsGetListing(
	edv_browser_struct *browser, const gchar *path,
	gboolean update_status_bar
)
{
	gboolean hide_object_hidden, hide_object_noaccess;
	gint strc;
	gchar **strv;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if((browser == NULL) || STRISEMPTY(path))
	    return;

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

	cfg_list = core_ptr->cfg_list;

	hide_object_hidden = !EDV_GET_B(
	    EDV_CFG_PARM_BROWSER_SHOW_OBJECT_HIDDEN
	);
	hide_object_noaccess = !EDV_GET_B(
	    EDV_CFG_PARM_BROWSER_SHOW_OBJECT_NOACCESS
	);


	/* Get directory entries from the location specified by path */
	strv = GetDirEntNames2(path, &strc);
	if(strv != NULL)
	{
	    gint i, objects_loaded = 0;
	    gint last_progress_percent, progress_percent;
	    const gchar *name;
	    gchar *obj_path;
	    edv_object_struct *obj;
	    gint stat_result;
	    struct stat stat_buf, lstat_buf;

	    /* Sort directory entries */
	    strv = StringQSort(strv, strc);
	    if(strv != NULL)
	    {
		/* Iterate through directory entry names and pick out
		 * just the directories for the first itteration
		 */
		last_progress_percent = -1;	/* None/undefined */
		progress_percent = 0;
		obj_path = NULL;
		for(i = 0; i < strc; i++)
		{
#define FREE_AND_CONTINUE	{	\
 g_free(obj_path);			\
 obj_path = NULL;			\
					\
 /* Do not delete strv[i] on this first iteration */ \
					\
 continue;				\
}

		    name = strv[i];
		    if(name == NULL)
			FREE_AND_CONTINUE

		    /* Skip special dir notations */
		    if(!strcmp(name, "."))
			FREE_AND_CONTINUE

		    /* Allocate full path to object as obj_path */
		    obj_path = STRDUP(PrefixPaths(path, name));
		    if(obj_path == NULL)
			FREE_AND_CONTINUE

		    /* Get destination object statistics */
		    if(stat(obj_path, &stat_buf))
			FREE_AND_CONTINUE
		    /* Skip if object's destination is not a directory */
		    if(!S_ISDIR(stat_buf.st_mode))
			FREE_AND_CONTINUE

		    /* Object's destination is a directory, now get
		     * local stats.
		     */
		    lstat(obj_path, &lstat_buf);

		    /* Create a new object, set its values from the
		     * stats, and append it with a new row on the
		     * Contents List
		     */
		    obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, obj_path);
			EDVObjectSetStat(obj, &lstat_buf);
			obj->link_valid = TRUE;

			/* Begin filter checks */
			if((hide_object_hidden ?
			    EDVIsObjectHidden(obj) : FALSE) ||
			   (hide_object_noaccess ?
			    !EDVIsObjectAccessable(core_ptr, obj) : FALSE)
			)
			{
			    /* Filter check did not pass so delete this
			     * object instead of appending it
			     */
			    EDVObjectDelete(obj);
			    obj = NULL;
			}
			else
			{
			    /* Append row with this object */
			    EDVBrowserContentsAppendObject(
				core_ptr, browser, clist, obj
			    );
			    obj = NULL;

			    objects_loaded++;   /* Count this as an object loading */
			}
		    }

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

		    FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
		}

		/* Now iterate through all other objects, skipping
		 * directories
		 *
		 * This iteration will delete all strings in strv
		 *
		 * Do not reset last_progress_percent, leave it as the
		 * last value from the previous loop
		 */
		obj_path = NULL;
		for(i = 0; i < strc; i++)
		{
#define FREE_AND_CONTINUE	{	\
 g_free(obj_path);			\
 obj_path = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		    name = strv[i];
		    if(name == NULL)
			FREE_AND_CONTINUE

		    /* Skip special dir notations */
		    if(!strcmp(name, "."))
			FREE_AND_CONTINUE

		    /* Allocate full path to object as obj_path */
		    obj_path = STRDUP(PrefixPaths(path, name));
		    if(obj_path == NULL)
			FREE_AND_CONTINUE

		    /* Get local stats of the object */
		    if(lstat(obj_path, &lstat_buf))
			FREE_AND_CONTINUE

		    /* Get destination stats of the object */
		    stat_result = stat(obj_path, &stat_buf);
		    if(!stat_result)
		    {
			/* Skip if object destination is a directory */
			if(S_ISDIR(stat_buf.st_mode))
			    FREE_AND_CONTINUE
		    }

		    /* Create a new object, set its values from the
		     * stats, and append it with a new row on the
		     * Contents List
		     */
		    obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, obj_path);
			EDVObjectSetStat(obj, &lstat_buf);
			obj->link_valid = stat_result ? FALSE : TRUE;

			/* Begin filter checks */
			if((hide_object_hidden ?
			    EDVIsObjectHidden(obj) : FALSE)
/*                         ||
			   (hide_object_noaccess ?
			    !EDVIsObjectAccessable(core_ptr, obj) : FALSE)
 */
			)
			{
			    /* Filter check did not pass so delete this
			     * object instead of appending it
			     */
			    EDVObjectDelete(obj);
			    obj = NULL;
			}
			else
			{
			    /* Append row with this object */
			    EDVBrowserContentsAppendObject(
				core_ptr, browser, clist, obj
			    );
			    obj = NULL;

			    objects_loaded++;	/* Count this as an object loaded */
			}
		    }

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

		    FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
		}

		/* At this point all strings in strv have been deleted */
	    }

	    /* Delete the string pointer array, each string should
	     * already be deleted
	     */
	    g_free(strv);
	    strv = NULL;
	}

}

/*
 *	Returns the row index of the Contents List that contains a disk
 *	object structure who's path matches the given path.
 *
 *	Can return -1 on failed match.
 */
gint EDVBrowserContentsFindRowByPath(
	edv_browser_struct *browser, const gchar *path
)
{
	gint i;
	GtkCList *clist;
	edv_object_struct *obj;

	if((browser == NULL) || STRISEMPTY(path))
	    return(-1);

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

	/* Iterate through all rows */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, i));
	    if((obj != NULL) ? STRISEMPTY(obj->full_path) : TRUE)
		continue;

	    /* Full paths match? */
	    if(!strcmp(obj->full_path, path))
		return(i);
	}

	return(-1);
}

/*
 *	Updates all rows on the Contents List 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 EDVBrowserContentsDoUpdate() 
 *	instead.
 */
void EDVBrowserContentsResetRows(edv_browser_struct *browser)
{
	gint i;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core_ptr;
	edv_object_struct *object;


	if(browser == NULL)
	    return;

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

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

	/* Reset columns */
	EDVBrowserContentsResetColumns(core_ptr, browser, clist);

	/* Update rows */
	for(i = 0; i < clist->rows; i++)
	{
	    object = EDV_OBJECT(
		gtk_clist_get_row_data(clist, i)
	    );
	    if(object != NULL)
		EDVBrowserContentsSetRow(
		    core_ptr, browser, clist, object, i
		);
	}
}

/*
 *	Updates the File Browser's Contents List with the objects in
 *	the directory specified by path.
 */
void EDVBrowserContentsDoUpdate(
	edv_browser_struct *browser, const gchar *path,
	gboolean update_status_bar
)
{
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core_ptr;

	if(browser == NULL)
	    return;

	w = browser->contents_clist;
	core_ptr = EDV_CORE(browser->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(
		browser->status_bar,
		"Loading directory contents...",
		FALSE
	    );
	    EDVStatusBarProgress(
		browser->status_bar, 0.0f, TRUE
	    );
	}

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

	/* Update columns */
	EDVBrowserContentsResetColumns(
	    core_ptr, browser, clist
	);

	/* Get listing of objects in the location specified by the
	 * path and add them to the Contents List
	 */
	EDVBrowserContentsGetListing(browser, path, update_status_bar);

	/* Report final status */
	if(update_status_bar)
	{
	    EDVStatusBarMessage(
		browser->status_bar,
		"Directory contents loaded",
		FALSE
	    );
	    EDVStatusBarProgress(
		browser->status_bar, 0.0f, TRUE
	    );
	}
}


/*
 *	Procedure to open the object listed on the given browser's
 *	Contents List specified by row and column.
 */
void EDVBrowserContentsDoOpenObject(
	edv_browser_struct *browser,
	gint row, gint column,
	guint state		/* Modifier Keys */
)
{
	GtkWidget *w;
	GtkCList *clist;
	edv_object_struct *object;
	const gchar *name;
	gchar *full_path;
	struct stat stat_buf;


	if(browser == NULL)
	    return;

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

	clist = GTK_CLIST(w);


	/* Get object from row data */
	object = EDV_OBJECT(
	    gtk_clist_get_row_data(clist, row)
	);
	if(object == NULL)
	    return;

	/* Check name of object for special notations */
	name = object->name;
	if(name != NULL)
	{
	    if(!strcmp(name, "."))
	    {
		full_path = NULL;
	    }
	    else if(!strcmp(name, ".."))
	    {
		full_path = g_dirname(
		    EDVBrowserCurrentLocation(browser)
		);
	    }
	    else
	    {
		full_path = STRDUP(object->full_path);
	    }
	}
	else
	{
	    /* No name available, get a copy of the full path */
	    full_path = STRDUP(object->full_path);
	}
	if(full_path == NULL)
	    return;

	/* Get stats of destination object, the object structure may
	 * have that information already but we want to ensure we have
	 * the most up to date information
	 */
	if(stat(full_path, &stat_buf))
	{
	    g_free(full_path);
	    return;
	}

	/* Is destination a directory? */
	if(S_ISDIR(stat_buf.st_mode))
	{
	    /* Change selected path on the directory ctree to the given
	     * object if possible
	     */
	    EDVBrowserDirTreeDoSelectPath(browser, full_path);
	}
	else
	{
	    gchar	*stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
	    const gchar *command_name = NULL;

	    if(state & GDK_CONTROL_MASK)
		command_name = "edit";
	    else if(state & GDK_SHIFT_MASK)
		command_name = "edit";

	    EDVBrowserSetBusy(browser, TRUE);
	    EDVOpenObjectPath(
		EDV_CORE(browser->core_ptr), full_path,
		command_name,		/* Command name */
		browser->toplevel, TRUE,
		&stdout_path_rtn, &stderr_path_rtn
	    );
	    EDVBrowserSetBusy(browser, FALSE);

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	/* Delete copy of full path */
	g_free(full_path);
}

/*
 *	Procedure to `open with' the object listed on the given
 *	browser's Contents List specified by row and column.
 */
void EDVBrowserContentsDoOpenWithObject(
	edv_browser_struct *browser, gint row, gint column
)
{
	GtkWidget *w;
	GtkCList *clist;
	edv_object_struct *object;
	const gchar *name;
	gchar *full_path;
	struct stat stat_buf;


	if(browser == NULL)
	    return;

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

	/* Get object from row data */
	object = EDV_OBJECT(
	    gtk_clist_get_row_data(clist, row)
	);
	if(object == NULL)
	    return;

	/* Check name of object for special notations */
	name = object->name;
	if(name != NULL)
	{
	    if(!strcmp(name, "."))
	    {
		full_path = NULL;
	    }
	    else if(!strcmp(name, ".."))
	    {
		full_path = g_dirname(
		    EDVBrowserCurrentLocation(browser)
		);
	    }
	    else
	    {
		full_path = STRDUP(object->full_path);
	    }
	}
	else
	{
	    /* No name available, get copy of the full path */
	    full_path = STRDUP(object->full_path);
	}
	if(full_path == NULL)
	    return;

	/* Get stats of destination object, the object structure may
	 * have that information already but we want to ensure we have
	 * the most up to date information
	 */
	if(stat(full_path, &stat_buf))
	{
	    g_free(full_path);
	    return;
	}

	if(TRUE)
	{
	    gchar       *stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;

	    EDVOpenWithObjectPath(
		EDV_CORE(browser->core_ptr), full_path,
		NULL,			/* Command name */
		browser->toplevel, TRUE,
		&stdout_path_rtn, &stderr_path_rtn
	    );

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	/* Delete copy of full path */
	g_free(full_path);
}


/*
 *      FPrompt apply callback.
 */
static void EDVBrowserContentsFPromptRenameApplyCB(
	gpointer data, const gchar *value
)
{
	gint row;
	GtkCList *clist;
	edv_browser_struct *browser;
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return;

	/* Get callback data */
	browser = EDV_BROWSER(cb_data[0]);
	clist = (GtkCList *)cb_data[1];
	row = (gint)cb_data[2];

	/* Inputs valid? */
	if((browser != NULL) && (clist != NULL) && (row >= 0) &&
	   (value != NULL)
	)
	{
	    GtkWidget *toplevel = browser->toplevel;
	    edv_core_struct *core_ptr = EDV_CORE(browser->core_ptr);
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );

	    /* Check if the selected object's disk object structure
	     * is valid
	     */
	    if((obj != NULL) ? (obj->full_path != NULL) : FALSE)
	    {
		gboolean yes_to_all = FALSE;
		gint status;
		const gchar *error_mesg;
		gchar	*old_full_path = STRDUP(obj->full_path),
			*new_obj = NULL;

		/* Perform rename */
		status = EDVFOPRename(
		    core_ptr, old_full_path, value,
		    &new_obj, toplevel,
		    FALSE, TRUE,
		    &yes_to_all
		);

		/* Unmap progress dialog, it may have been mapped in the
		 * above operation
		 */
		ProgressDialogBreakQuery(FALSE);
		ProgressDialogSetTransientFor(NULL);

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

		/* Got new object full path name (implying success)? */
		if((new_obj != NULL) && (old_full_path != NULL))
		{
		    struct stat lstat_buf;
		    const gchar	*new_child = EDVGetPathName(new_obj),
				*old_child = EDVGetPathName(old_full_path); 

		    /* Get new local statistics for the renamed object */
		    if(!lstat(new_obj, &lstat_buf))
		    {
			gchar *buf = g_strdup_printf(
			    "Object \"%s\" renamed to \"%s\"",
			    old_child, new_child
			);
			EDVStatusBarMessage(
			    browser->status_bar, buf, FALSE
			);
			g_free(buf);

			/* Emit object modified signal to all windows */
			EDVObjectModifiedEmit(
			    core_ptr, old_full_path,
			    new_obj, &lstat_buf
			);
		    }
		}
		else
		{
		    /* Rename failed */
		    EDVStatusBarMessage(
			browser->status_bar,
			"Rename object failed",
			FALSE
		    );
		}

#if 0
		/* The disk object structure may now be invalid if the
		 * object modified signal was emitted
		 */
		object = NULL;
#endif

		/* Delete coppies of paths */
		g_free(new_obj);
		g_free(old_full_path);
	    }
	}

	g_free(cb_data);
}

/*
 *	Maps the FPrompt over the File Browser contents list item
 *	to rename the object.
 *
 *      If the column is -1 then it implies any column is valid.
 */
void EDVBrowserContentsDoFPromptRename(
	edv_browser_struct *browser, gint row, gint column
)
{
	gint i, column_name, cx, cy, px, py, pwidth, pheight;
	cfg_intlist_struct *column_type_intlist;
	const cfg_item_struct *cfg_list;
	edv_object_struct *obj;
	edv_core_struct *core_ptr;
	GtkWidget *w;
	GtkCList *clist;

	if((browser == NULL) || (row < 0) || FPromptIsQuery())
	    return;

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

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

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

	EDVBrowserSyncData(browser);

	/* Get column index for column that is displaying name (if any) */
	column_name = -1;
	column_type_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN
	);
	if(column_type_intlist != NULL)
	{
	    /* Iterate through column type intlist */
	    for(i = 0; i < column_type_intlist->total; i++)
	    {
		/* Is column i displaying the name? */
		if(column_type_intlist->i[i] == EDV_BROWSER_COLUMN_TYPE_NAME)
		{
		    column_name = i;
		    break;
		}
	    }
	}
	/* No column displaying name? */
	if(column_name < 0)
	    return;

	/* Make sure given row index is in bounds */
	if((row < 0) || (row >= clist->rows))
	    return;

	/* Get clist cell geometry */
	if(!gtk_clist_get_cell_geometry(
	    clist, column_name, row,
	    &cx, &cy, &pwidth, &pheight
	))
	    return;

	/* Get root window relative coordinates */
	px = 0;
	py = 0;
	gdk_window_get_deskrelative_origin(
	    clist->clist_window, &px, &py
	);
	px += cx + 0;
	py += cy - 2;   /* Move up a little */

	/* Get object from the specified row on the Contents List */
	obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));
	if(obj == NULL)
	    return;

	/* Check if the object name is a special notation that cannot
	 * be renamed
	 */
	if(!STRISEMPTY(obj->name))
	{
	    const gchar *name = obj->name;
	    if(!strcmp(name, ".") || !strcmp(name, "..") ||
	       !strcmp(name, "/")
	    )
		return;
	}

	/* Is the clicked on column the name column or given column is -1
	 * implying any column is acceptable?
	 */
	if((column == column_name) || (column < 0))
	{
	    gpointer *cb_data = (gpointer *)g_malloc0(
		3 * sizeof(gpointer)
	    );
	    gchar *value = STRDUP(obj->name);

	    /* Set up callback data */
	    if(cb_data != NULL)
	    {
		cb_data[0] = browser;
		cb_data[1] = clist;
		cb_data[2] = (gpointer)row;
	    }

	    /* Map floating prompt to change values */
	    FPromptSetTransientFor(browser->toplevel);
	    FPromptSetPosition(px, py);
	    FPromptMapQuery(
		NULL,			/* No label */
		value,
		NULL,			/* No tooltip message */
		FPROMPT_MAP_NO_MOVE,	/* Map code */
		pwidth, -1,		/* Width and height */
		0,			/* No buttons */
		cb_data,		/* Callback data */
		NULL,			/* No browse callback */
		EDVBrowserContentsFPromptRenameApplyCB,
		EDVBrowserContentsFPromptCancelCB
	    );

	    g_free(value);
	}
}


/*
 *      This should be called whenever a new object has been added, it
 *      will add a new row as needed to represent the new object.
 *
 *      The given path must be an absolute path to the object.
 */
void EDVBrowserContentsObjectAddedNotify(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
)
{
	gchar *cur_path = NULL, *parent_path = NULL;
	GtkWidget *w;
	GtkCList *clist;
	gint row;
	edv_core_struct *core_ptr;
	edv_object_struct *object;
	gint stat_result;
	struct stat stat_buf;


	if((browser == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

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

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

#define DO_FREE_LOCALS	{	\
 g_free(cur_path);		\
 cur_path = NULL;		\
 g_free(parent_path);		\
 parent_path = NULL;		\
}
	/* Get parent of the given path and current location */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));
	parent_path = g_dirname(path);
	if((cur_path == NULL) || (parent_path == NULL))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Is location of what contents list is displaying is different
	 * from the location of the new object to add?
	 */
	if(strcmp(cur_path, parent_path))
	{
	    DO_FREE_LOCALS
	    return;
	}


	/* Get destination stats */
	stat_result = stat(path, &stat_buf);


	/* Check if the new path of the object to add reffers to an object
	 * that already exists in the list.
	 */
	row = EDVBrowserContentsFindRowByPath(browser, path);
	if((row >= 0) && (row < clist->rows))
	{
	    /* The new object to add already exists in the list, so just
	     * update the row.
	     */

	    /* Get disk object structure from matched node */
	    object = EDV_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(object != NULL)
	    {
		/* Update disk object structure */
		EDVObjectSetPath(object, path);
		EDVObjectSetStat(object, lstat_buf);
		object->link_valid = stat_result ? FALSE : TRUE;

		/* Update the Contents List row with the new values from
		 * this disk object structure.
		 */
		EDVBrowserContentsSetRow(
		    core_ptr, browser, clist, object,
		    row
		);
	    }
	}
	else
	{
	    /* Create a new disk object structure */
	    object = EDVObjectNew();
	    if(object != NULL)
	    {
		gint new_row;


		/* Set disk object structure */
		EDVObjectSetPath(object, path);
		EDVObjectSetStat(object, lstat_buf);
		object->link_valid = stat_result ? FALSE : TRUE;

		/* Append row using this disk object structure */
		new_row = EDVBrowserContentsAppendObject(
		    core_ptr, browser, clist, object
		);
		/* The object structure is now invalid after passing it to
		 * EDVBrowserContentsAppendObject().
		 */
		object = NULL;
	    }
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *      This should be called whenever a object has been modified, it will
 *      search for the object and then reupdate the matching row.
 *
 *      The given path must be an absolute path to the object and must be
 *      the path of the object's original name. The new_path must be an
 *      absolute path to the object at its new name, the new_path may be
 *      NULL if there was no name change.
 */
void EDVBrowserContentsObjectModifiedNotify(
	edv_browser_struct *browser, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
)
{
	gchar *cur_path = NULL, *parent_path = NULL;
	GtkWidget *w;
	GtkCList *clist;
	gint row;
	edv_core_struct *core_ptr;
	edv_object_struct *object;
	gint stat_result;
	struct stat stat_buf;


	if((browser == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

	if(new_path == NULL)
	    new_path = path;

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

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

#define DO_FREE_LOCALS	{	\
 g_free(cur_path);		\
 cur_path = NULL;		\
 g_free(parent_path);		\
 parent_path = NULL;		\
}
	/* Get parent of the given path and current location */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));
	parent_path = g_dirname(new_path);
	if((cur_path == NULL) || (parent_path == NULL))
	{
	    DO_FREE_LOCALS
	    return;
	}


	/* Get destination stats */
	stat_result = stat(new_path, &stat_buf);


	/* Is given path that has been modified the same as the current
	 * location?
	 */
	if(!strcmp(new_path, cur_path))
	{
	    /* Reget contents listing for the new path */
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsDoUpdate(browser, new_path, TRUE);
	    gtk_clist_thaw(clist);
	}
	else
	{
	    /* Look for a row who's disk object full path matches the
	     * old path.
	     */
	    row = EDVBrowserContentsFindRowByPath(browser, path);
	    if((row >= 0) && (row < clist->rows))
	    {
		/* Get disk object structure from matched row */
		object = EDV_OBJECT(
		    gtk_clist_get_row_data(clist, row)
		);
		if(object != NULL)
		{
		    /* Update disk object structure */
		    EDVObjectSetPath(object, new_path);		/* Use new_path */
		    EDVObjectSetStat(object, lstat_buf);
		    object->link_valid = stat_result ? FALSE : TRUE;

		    /* Update the Contents List row with the new values
		     * from the disk object structure.
		     */
		    EDVBrowserContentsSetRow(
			core_ptr, browser, clist, object, row
		    );
		}
	    }
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *      This should be called whenever a object has been removed, it will
 *      search for the object and then remove the matching row.
 *
 *      The given path must be an absolute path to the object.
 */
void EDVBrowserContentsObjectRemovedNotify(
	edv_browser_struct *browser, const gchar *path
)
{
	gchar *cur_path = NULL, *parent_path = NULL;
	GtkWidget *w;
	GtkCList *clist;
	gint row;


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

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

#define DO_FREE_LOCALS	{	\
 g_free(cur_path);		\
 cur_path = NULL;		\
 g_free(parent_path);		\
 parent_path = NULL;		\
}
	/* Get parent of the given path and current location */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));
	parent_path = g_dirname(path);
	if((cur_path == NULL) || (parent_path == NULL))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Is given path that has been removed the same as the current
	 * location?
	 */
	if(!strcmp(path, cur_path))
	{
	    /* Clear entire Contents List */
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsClear(clist);
	    gtk_clist_thaw(clist);
	}
	else
	{
	    /* Look for a row that matches the given path */
	    row = EDVBrowserContentsFindRowByPath(browser, path);
	    if((row >= 0) && (row < clist->rows))
	    {
		/* Remove matched row */
		gtk_clist_remove(clist, row);
	    }
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}


/*
 *      This should be called whenever a device has been mounted or
 *	unmounted.
 */
void EDVBrowserContentsMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev_ptr,
	gboolean is_mounted
)
{
	gchar *cur_path = NULL, *mount_path = NULL;
	GtkWidget *w;
	GtkCList *clist;


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

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

#define DO_FREE_LOCALS	{	\
 g_free(cur_path);		\
 cur_path = NULL;		\
 g_free(mount_path);		\
 mount_path = NULL;		\
}
	/* Get coppies of current location and mount paths */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));
	mount_path = STRDUP(dev_ptr->mount_path);
	if((cur_path == NULL) || (mount_path == NULL))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Need to simplify coppies of current and mount paths */
	EDVSimplifyPath(cur_path);
	EDVSimplifyPath(mount_path);


	/* Check if mount path was the current location, if it was
	 * then the Contents List needs to be updated
	 */
	if(!strcmp(cur_path, mount_path))
	{
	    /* Reget contents listing for the new path */
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsDoUpdate(browser, cur_path, TRUE);
	    gtk_clist_thaw(clist);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}
