#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

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

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

#include "edvtypes.h"
#include "cfg.h"
#include "edvobj.h"
#include "edvdevices.h"
#include "edvstatusbar.h"
#include "browser.h"
#include "browsercb.h"
#include "browseropcb.h"
#include "browserdirtree.h"
#include "browsercontents.h"
#include "browserdnd.h"
#include "endeavour.h"
#include "edvop.h"
#include "edvcb.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvlistseek.h"
#include "edvcfglist.h"
#include "config.h"


void EDVBrowserContentsItemDestroyCB(gpointer data);
void EDVBrowserDirTreeItemDestroyCB(gpointer data);

static gint EDVBrowserCListColumnSortDateNexus(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2,
	gint sort_code
);
static gint EDVBrowserCListColumnSortDateAccessCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
static gint EDVBrowserCListColumnSortDateModifyCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
static gint EDVBrowserCListColumnSortDateChangeCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);

gint EDVBrowserDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
gint EDVBrowserKeyEventCB(
	 GtkWidget *widget, GdkEventKey *key, gpointer data
);
gint EDVBrowserButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);

void EDVBrowserHandleChildAttachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
);
void EDVBrowserHandleChildDetachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
);

static gint EDVBrowserTreeExpandOptimizeIdleCB(gpointer data);
void EDVBrowserTreeSelectRowCB(
	GtkCTree *ctree, GtkCTreeNode *node, gint column,
	gpointer data
);
void EDVBrowserTreeUnselectRowCB(
	GtkCTree *ctree, GtkCTreeNode *node, gint column,
	gpointer data
);
void EDVBrowserTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
);
void EDVBrowserTreeCollapseCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
);

void EDVBrowserResizeColumnCB(
	GtkCList *clist, gint column, gint width, gpointer data
);
void EDVBrowserClickColumnCB(
	GtkCList *clist, gint column, gpointer data
);
void EDVBrowserSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
void EDVBrowserUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);


void EDVBrowserComboActivateCB(GtkWidget *widget, gpointer data);

void EDVBrowserWriteProtectChangedCB(
	edv_browser_struct *browser, gboolean state
);
void EDVBrowserObjectAddedNotifyCB(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
);
void EDVBrowserObjectModifiedNotifyCB(
	edv_browser_struct *browser, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
);
void EDVBrowserObjectRemovedNotifyCB(
	edv_browser_struct *browser, const gchar *path
);

void EDVBrowserMountNotifyCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr,
	gboolean is_mounted
);

void EDVBrowserRecycledObjectAddedNotifyCB(
	edv_browser_struct *browser, guint index
);
void EDVBrowserRecycledObjectRemovedNotifyCB(
	edv_browser_struct *browser, guint index
);

void EDVBrowserReconfiguredNotifyCB(edv_browser_struct *browser);

void EDVBrowserMimeTypeAddedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mimetype_struct *mt_ptr
);
void EDVBrowserMimeTypeModifiedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mimetype_struct *mt_ptr
);
void EDVBrowserMimeTypeRemovedCB(
	edv_browser_struct *browser, gint mt_num
);

void EDVBrowserDeviceAddedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr
);
void EDVBrowserDeviceModifiedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr
);
void EDVBrowserDeviceRemovedCB(
	edv_browser_struct *browser, gint dev_num
);


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


/*
 *	Browser contents GtkCList item "destroy" signal callback.
 */
void EDVBrowserContentsItemDestroyCB(gpointer data)
{
	EDVObjectDelete(EDV_OBJECT(data));
}

/*
 *	Browser directory GtkCTree item "destroy" signal callback.
 */
void EDVBrowserDirTreeItemDestroyCB(gpointer data)
{
	EDVObjectDelete(EDV_OBJECT(data));
}


/*
 *	Returns the sort code for the rows sorted by date.
 *
 *	The disk object structures are obtained from each row and the
 *	dates are compared.
 */
static gint EDVBrowserCListColumnSortDateNexus(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2,
	gint sort_code
)
{
	gint sort_column;
	const GtkCListRow *row1 = (const GtkCListRow *)ptr1;
	const GtkCListRow *row2 = (const GtkCListRow *)ptr2;
	edv_object_struct *obj1, *obj2;


	if((clist == NULL) || (row1 == NULL) || (row2 == NULL))
	    return(-1);

	sort_column = clist->sort_column;
	if((sort_column < 0) || (sort_column >= clist->columns))
	    return(-1);

	obj1 = EDV_OBJECT(row1->data);
	obj2 = EDV_OBJECT(row2->data);
	if((obj1 == NULL) || (obj2 == NULL))
	    return(-1);

	switch(sort_code)
	{
	  case 0:	/* Access time */
	    if(obj1->access_time <= obj2->access_time)
		    return((gint)(obj1->access_time < obj2->access_time));
		else
		    return(-1);
	    break;

	  case 1:       /* Modify time */
	    if(obj1->modify_time <= obj2->modify_time)
		    return((gint)(obj1->modify_time < obj2->modify_time));
		else
		    return(-1);
	    break;

	  case 2:       /* Change time */
	    if(obj1->change_time <= obj2->change_time)
		    return((gint)(obj1->change_time < obj2->change_time));
		else
		    return(-1);
	    break;
	}

	return(-1);
}

static gint EDVBrowserCListColumnSortDateAccessCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVBrowserCListColumnSortDateNexus(
	    clist, ptr1, ptr2, 0
	));
}

static gint EDVBrowserCListColumnSortDateModifyCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVBrowserCListColumnSortDateNexus(
	    clist, ptr1, ptr2, 1
	));
}

static gint EDVBrowserCListColumnSortDateChangeCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVBrowserCListColumnSortDateNexus(
	    clist, ptr1, ptr2, 2
	));
}


/*
 *	Browser toplevel GtkWidget "delete_event" signal callback.
 */
gint EDVBrowserDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return(TRUE);

	if(browser->processing)
	    return(TRUE);

	EDVBrowserOPClose(browser);

	return(TRUE);
}

/*
 *	File Browser "key_press_event" or "key_release_event" signal callback.
 */
gint EDVBrowserKeyEventCB(
	 GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	guint keyval, state;
	gboolean press;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (key == NULL) || (browser == NULL))
	    return(status);

	if(browser->processing)
	    return(status);

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

	cfg_list = core_ptr->cfg_list;

	/* Get event type */
	etype = key->type;

	/* Get other key event values */
	press = (etype == GDK_KEY_PRESS) ? TRUE : FALSE;
	keyval = key->keyval;
	state = key->state;

/* Stop emit of signal */
#define DO_STOP_KEY_SIGNAL_EMIT	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
  press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}

	/* Check which widget this signal is for */

	/* Directory Tree */
	if(widget == browser->directory_ctree)
	{
	    GtkCTreeNode *node;
	    GtkCList *clist = GTK_CLIST(widget);
	    GtkCTree *ctree = GTK_CTREE(widget);

	    /* Get last selected node */
	    node = EDVCTreeGetSelectedLast(ctree, NULL);

	    /* Handle by key value */
	    switch(keyval)
	    {
	      case GDK_Delete:
		if(press)
		    EDVBrowserOPDelete(browser);
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_BackSpace:
		if(press)
		    EDVBrowserOPGoToParent(browser);
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_space:
	      case GDK_KP_Space:
		node = gtk_ctree_node_nth(ctree, clist->focus_row);
		if((node != NULL) && press)
		{
		    gboolean already_selected = FALSE;

		    /* Check if this node is already selected */
		    GList *glist = clist->selection;
		    while(glist != NULL)
		    {
			if(node == (GtkCTreeNode *)glist->data)
			{
			    already_selected = TRUE;
			    break;
			}
			glist = g_list_next(glist);
		    }

		    gtk_clist_freeze(clist);
		    if(already_selected)
			gtk_ctree_unselect(ctree, node);
		    else
			gtk_ctree_select(ctree, node);
		    gtk_clist_thaw(clist);
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Insert:
		if(state & GDK_CONTROL_MASK)
		{
		    if(press)
			EDVBrowserOPCopyPath(browser);
		    DO_STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		}
		break;
	    }
	}
	/* Contents List */
	else if(widget == browser->contents_clist)
	{
	    gint row;
	    GtkCList *clist = GTK_CLIST(widget);

	    /* Get last selected row */
	    row = EDVCListGetSelectedLast(clist, NULL);

	    /* Handle by key value */
	    switch(keyval)
	    {
	      case GDK_Delete:
		if(press)
		    EDVBrowserOPDelete(browser);
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_space:
	      case GDK_KP_Space:
		row = clist->focus_row;
		if((row >= 0) && (row < clist->rows) && press)
		{
		    gboolean already_selected = FALSE;

		    /* Check if this row is already selected */
		    GList *glist = clist->selection;
		    while(glist != NULL)
		    {
			if(row == (gint)glist->data)
			{
			    already_selected = TRUE;
			    break;
			}
			glist = g_list_next(glist);
		    }

		    gtk_clist_freeze(clist);
		    if(already_selected)
			gtk_clist_unselect_row(clist, row, 0);
		    else
			gtk_clist_select_row(clist, row, 0);
		    gtk_clist_thaw(clist);
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		break;

	      case GDK_Insert:
		if(state & GDK_CONTROL_MASK)
		{
		    if(press)
			EDVBrowserOPCopyPath(browser);
		    DO_STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		}
		break;

	      default:
		/* For all other alphanumeric character keys and while
		 * no modifier keys are held, attempt to seek to the
		 * item who's name starts with the letter of the key
		 * that was pressed
		 */
		if(isalnum((char)keyval) && press &&
		   ((state == 0x00000000) || (state == GDK_SHIFT_MASK))
		)
		{
		    /* Get the column that is displaying the object
		     * name as column_type_name
		     */
		    gint i, column_type_name = -1;
		    const cfg_intlist_struct *intlist = EDV_GET_INTLIST(
			EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN
		    );
		    if(intlist != NULL)
		    {
			/* Iterate through column type intlist */
			for(i = 0; i < intlist->total; i++)
			{
			    /* Is column i displaying the name? */
			    if(intlist->i[i] == EDV_BROWSER_COLUMN_TYPE_NAME)
			    {
				column_type_name = i;
				break;
			    }
			}
		    }

		    gtk_clist_freeze(clist);
		    EDVCListSeekCharacter(
			clist, column_type_name, 0,
			(gchar)((state & GDK_SHIFT_MASK) ?
			    toupper((char)keyval) : keyval
			)
		    );
		    gtk_clist_thaw(clist);

		    DO_STOP_KEY_SIGNAL_EMIT
		    status = TRUE;
		}
		break;

	    }
	}

	return(status);
#undef DO_STOP_KEY_SIGNAL_EMIT
}

/*
 *	File Browser GtkWidget "button_press_event" signal callback.
 */
gint EDVBrowserButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	guint state;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (button == NULL) || (browser == NULL))
	    return(status);

	if(browser->processing)
	    return(status);

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

	cfg_list = core_ptr->cfg_list;

	/* Get event type */
	etype = button->type;

	state = button->state;

	/* Check which ctree this signal is for */
	if(widget == browser->directory_ctree)
	{
	    gint row, column, nodes_selected = 0;
	    GList *glist;
	    GtkCTreeNode *selected_node = NULL;
	    GtkCList *clist = GTK_CLIST(widget);
	    GtkCTree *ctree = GTK_CTREE(widget);
	    GtkCTreeNode *node = EDVNodeGetByCoordinates(
		ctree,
		button->x,
		button->y - ((clist->flags & GTK_CLIST_SHOW_TITLES) ?
		clist->column_title_area.height +
		clist->column_title_area.y : 0)
	    );


	    /* Find row and column based on given coordinates */
	    if(!gtk_clist_get_selection_info(
		clist, button->x, button->y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Get number of selected rows and highest selected row */
	    glist = clist->selection;
	    while(glist != NULL)
	    {
		selected_node = (GtkCTreeNode *)glist->data;
		if(selected_node != NULL)
		    nodes_selected++;
		glist = g_list_next(glist);
	    }

	    /* Handle by button number */
	    switch(button->button)
	    {
	      case 3:
		if(etype == GDK_BUTTON_PRESS)
		{
		    GtkMenu *menu;

		    /* Select item before mapping menu? */
		    if(EDV_GET_B(EDV_CFG_PARM_RIGHT_CLICK_MENU_SELECTS) &&
		       (node != NULL)
		    )
		    {
			/* Select the node that the button was pressed over */
			gtk_clist_freeze(clist);
			if((row >= 0) && (row < clist->rows))
			    clist->focus_row = row;
			gtk_ctree_select(ctree, node);
			gtk_clist_thaw(clist);
		    }

		    /* Update all menus and map right click menu */
		    EDVBrowserUpdateMenus(browser);
		    menu = (GtkMenu *)browser->directory_ctree_menu;
		    if(menu != NULL)
			gtk_menu_popup(
			    menu, NULL, NULL,
			    NULL, NULL,
			    button->button, button->time
			);
		}
		status = TRUE;
		break;

	      case 2:
		if(etype == GDK_BUTTON_PRESS)
		{
		    if(node != NULL)
			EDVBrowserDirTreeDoFPromptRename(browser, node);
		}
		status = TRUE;
		break;
	    }

	    if(etype == GDK_BUTTON_PRESS)
		gtk_widget_grab_focus(widget);
	}
	else if(widget == browser->contents_clist)
	{
	    gint row, column;
	    gint rows_selected = 0, selected_row = -1;
	    GList *glist;
	    GtkCList *clist = GTK_CLIST(widget);


	    /* Find row and column based on given coordinates */
	    if(!gtk_clist_get_selection_info(
		clist, button->x, button->y, &row, &column
	    ))
	    {
		row = -1;
		column = 0;
	    }

	    /* Get number of selected rows and highest selected row */
	    glist = clist->selection;
	    while(glist != NULL)
	    {
		rows_selected++;
		selected_row = (gint)glist->data;
		glist = g_list_next(glist);
	    }

	    /* Handle by button number */
	    switch(button->button)
	    {
	      case 3:
		if(etype == GDK_BUTTON_PRESS)
		{
		    GtkMenu *menu;

		    /* Select item before mapping menu? */
		    if(EDV_GET_B(EDV_CFG_PARM_RIGHT_CLICK_MENU_SELECTS) &&
		       (row >= 0) && (row < clist->rows)
		    )
		    {
			/* Select the row that the button was pressed over.
			 * if no key modifiers are held then this will also
			 * unselect all previously selected rows
			 */
			gtk_clist_freeze(clist);
			if(!(button->state & GDK_CONTROL_MASK) &&
			   !(button->state & GDK_SHIFT_MASK)
			)
			    gtk_clist_unselect_all(clist);
			clist->focus_row = row;
			gtk_clist_select_row(clist, row, 0);
			gtk_clist_thaw(clist);
		    }

		    /* Update all menus and map right click menu */
		    EDVBrowserUpdateMenus(browser);
		    menu = (GtkMenu *)browser->contents_clist_menu;
		    if(menu != NULL)
			gtk_menu_popup(
			    menu, NULL, NULL,
			    NULL, NULL,
			    button->button, button->time
			);
		}
		status = TRUE;
		break;

	      case 2:
		if(etype == GDK_BUTTON_PRESS)
		{
		    if((row >= 0) && (row < clist->rows))
			EDVBrowserContentsDoFPromptRename(
			    browser, row, column
			);
		}
		status = TRUE;
		break;

	      case 1:
		/* Double click? */
		if(etype == GDK_2BUTTON_PRESS)
		{
		    if((row >= 0) && (row < clist->rows))
		    {
			EDVBrowserContentsDoOpenObject(
			    browser, row, column, state
			);
			status = TRUE;
		    }
		}
		break;
	    }

	    if(etype == GDK_BUTTON_PRESS)
		gtk_widget_grab_focus(widget);
	}

	return(status);
}

/*
 *	GtkHandleBox "child_attached" signal callback.
 */
void EDVBrowserHandleChildAttachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((handle_box == NULL) || (browser == NULL))
	    return;

	gtk_widget_queue_resize(
	    gtk_widget_get_toplevel(GTK_WIDGET(handle_box))
	);
}

/*
 *      GtkHandleBox "child_detached" signal callback.
 */
void EDVBrowserHandleChildDetachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((handle_box == NULL) || (browser == NULL))
	    return;

	gtk_widget_queue_resize(
	    gtk_widget_get_toplevel(GTK_WIDGET(handle_box))
	);
}


/*
 *	Browser GtkCTree expand optimize idle callback.
 */
static gint EDVBrowserTreeExpandOptimizeIdleCB(gpointer data)
{
	GtkCTreeNode *node;
	GtkCTree *ctree;
	edv_browser_struct *browser;
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return(FALSE);

	browser = EDV_BROWSER(cb_data[0]);
	ctree = (GtkCTree *)cb_data[1];
	node = (GtkCTreeNode *)cb_data[2];

	/* Scroll to optimul viewing position */
	GUICTreeOptimizeExpandPosition(ctree, node);

	/* Reset idle ID */
	browser->directory_ctree_expand_optimize_idleid = 0;

	/* Delete callback data */
	g_free(cb_data);

	return(FALSE);
}

/*
 *	Browser GtkCTree "tree_select_row" signal callback.
 */
void EDVBrowserTreeSelectRowCB(
	GtkCTree *ctree, GtkCTreeNode *node, gint column,
	gpointer data
)
{
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Check which ctree this signal is for */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
	    /* Change in selection? */
	    if((browser->directory_ctree_selected_node != node) &&
	       (node != NULL)
	    )
	    {
		GtkCList *clist;
		edv_object_struct *obj = EDV_OBJECT(
		    gtk_ctree_node_get_row_data(ctree, node)
		);

		EDVBrowserSetBusy(browser, TRUE);
		GUIBlockInput(browser->toplevel, TRUE);
		browser->processing = TRUE;

		/* If node is not fully visible then scroll to position
		 * the node in the middle of the list
		 */
		if(gtk_ctree_node_is_visible(ctree, node)
		    != GTK_VISIBILITY_FULL
		)
		{
		    GtkCTreeRow *row_ptr = GTK_CTREE_ROW(node);
		    GtkCList *clist = GTK_CLIST(ctree);
		    GtkAdjustment *adj = clist->hadjustment;
		    GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(clist));
		    GdkFont *font = (style != NULL) ? style->font : NULL;

		    gtk_ctree_node_moveto(
			ctree,
			node, -1,
			0.5f, 0.0f      /* Row align, column align */
		    );

		    if((row_ptr != NULL) && (adj != NULL))
		    {
			gint x_min, x_max;
			const gint level = MAX(row_ptr->level - 1, 0);
			guint8 spacing;
			gchar *text;
			GdkBitmap *mask, *mask_opened;
			GdkPixmap *pixmap, *pixmap_opened;
			gboolean is_leaf, expanded;

			gtk_ctree_get_node_info(
			    ctree, node,
			    &text, &spacing,
			    &pixmap, &mask,
			    &pixmap_opened, &mask_opened,
			    &is_leaf, &expanded
			);

			if(font == NULL)
			{
			    style = gtk_ctree_node_get_row_style(ctree, node);
			    font = (style != NULL) ? style->font : NULL;
			}

			if(pixmap != NULL)
			{
			    gint width, height;
			    gdk_window_get_size(pixmap, &width, &height);
			    x_min = level * width;
			    x_max = x_min + width;
			}
			else
			{
			    x_min = 0;
			    x_max = x_min;
			}
			x_max += spacing;
			if((font != NULL) && !STRISEMPTY(text))
			{
			    GdkTextBounds b;
			    gdk_string_bounds(font, text, &b);
			    x_max += b.width;
			}

#if 0
g_print("%i  %i %i\n", level, x_min, x_max);
			if(x_min < adj->value)
			{
g_print("%i %i\n", x_min, (gint)adj->value);
			}
			else if(x_max > (adj->value + adj->page_size))
			{
g_print("%i %i\n", x_max, (gint)(adj->value + adj->page_size));
			}
#endif
		    }
		}

		/* Update current selected node */
		browser->directory_ctree_selected_node = node;

		/* Unselect last object on the contents clist so that
		 * the operations will focus on the selected node
		 */
		browser->contents_clist_selected_row = -1;

		/* Match device index number from core structure's list of
		 * devices who's mount path matches the selected disk
		 * object's path
		 */
		if(obj != NULL)
		    EDVDeviceListMatchMountPath(
			core_ptr->device, core_ptr->total_devices,
			&browser->selected_dev_num,
			obj->full_path
		    );

		/* Update DND icon */
		EDVBrowserDirTreeDNDSetIcon(browser, node);

		/* Get contents clist */
		clist = (GtkCList *)browser->contents_clist;
		if((obj != NULL) && (clist != NULL))
		{
		    gint objects_in_directory;

		    /* Update value of title, location combo (do not
		     * record history), and location icon
		     */
		    EDVBrowserSetTitle(browser, obj->full_path);
		    EDVBrowserSetLocation(
			browser, obj->full_path, FALSE
		    );
		    EDVBrowserUpdateLocationIcon(browser, obj->full_path);

		    /* Update contents clist */
		    gtk_clist_freeze(clist);
		    EDVBrowserContentsDoUpdate(
			browser,
			obj->full_path,
			EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
		    );
		    gtk_clist_thaw(clist);

		    /* Get number of objects in the directory by the
		     * number of rows in the just updated contents
		     * clist minus one (to get rid of the parent
		     * directory notation row)
		     */
		    objects_in_directory = MAX(clist->rows - 1, 0);


		    /* Update status bar message */
		    if(!STRISEMPTY(obj->name))
		    {
			gchar *buf = g_strdup_printf(
 "Directory \"%s\" selected (containing %i objects)",
			    obj->name, objects_in_directory
			);

			EDVStatusBarMessage(
			    browser->status_bar, buf, FALSE
			);

			g_free(buf);
		    }
		    else
		    {
			EDVStatusBarMessage(
			    browser->status_bar,
			    "Directory with NULL name selected",
			    FALSE
			);
		    }
		}

		browser->processing = FALSE;
		GUIBlockInput(browser->toplevel, FALSE);
		EDVBrowserSetBusy(browser, FALSE);

		EDVBrowserUpdateMenus(browser);
	    }
	}
}

/*
 *      File Browser GtkCTree "tree_unselect_row" signal callback.
 */
void EDVBrowserTreeUnselectRowCB(
	GtkCTree *ctree, GtkCTreeNode *node, gint column,
	gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

	/* Check which ctree this signal is for */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
	    if(browser->directory_ctree_selected_node != NULL)
	    {
#if 0
		EDVBrowserSetBusy(browser, TRUE);
		GUIBlockInput(browser->toplevel, TRUE);
		browser->processing = TRUE;
#endif

		/* Mark node as unselected */
		browser->directory_ctree_selected_node = NULL;

		/* Mark that no device is selected */
		browser->selected_dev_num = -1;

#if 0
		browser->processing = FALSE;
		GUIBlockInput(browser->toplevel, FALSE);
		EDVBrowserSetBusy(browser, FALSE);
#endif

		EDVBrowserSetTitle(browser, NULL);
		EDVBrowserUpdateMenus(browser);
	    }
	}
}

/*
 *	Browser GtkCTree "tree_expand" signal callback.
 */
void EDVBrowserTreeExpandCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
)
{
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core_ptr->cfg_list;


	/* Check which ctree this signal is for */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
	    EDVBrowserSetBusy(browser, TRUE);
	    GUIBlockInput(browser->toplevel, TRUE);
	    browser->processing = TRUE;

	    gtk_clist_freeze(GTK_CLIST(ctree));

	    /* Expand node and get child directories for each node */
	    EDVBrowserDirTreeDoExpand(browser, node, TRUE);

	    gtk_clist_thaw(GTK_CLIST(ctree));

	    /* Scroll to optimize viewing of expand nodes? */
	    if(EDV_GET_B(EDV_CFG_PARM_TREE_EXPAND_OPTIMIZE_POS))
	    {
		/* Set idle callback to scroll to optimize view of
		 * the expanded nodes
		 */
		gpointer *cb_data = (gpointer *)g_malloc0(3 * sizeof(gpointer));
		cb_data[0] = browser;
		cb_data[1] = ctree;
		cb_data[2] = node;
		browser->directory_ctree_expand_optimize_idleid = gtk_idle_add_priority(
		    G_PRIORITY_LOW,
		    EDVBrowserTreeExpandOptimizeIdleCB,
		    cb_data
		);
	    }

	    browser->processing = FALSE;
	    GUIBlockInput(browser->toplevel, FALSE);
	    EDVBrowserSetBusy(browser, FALSE);

	    EDVBrowserUpdateMenus(browser);
	}
}

/*
 *      File Browser GtkCTree "tree_collapse" signal callback.
 */
void EDVBrowserTreeCollapseCB(
	GtkCTree *ctree, GtkCTreeNode *node, gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

	/* Check which ctree this signal is for */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
#if 0
	    EDVBrowserSetBusy(browser, TRUE);
	    GUIBlockInput(browser->toplevel, TRUE);
	    browser->processing = TRUE;

	    gtk_clist_freeze(GTK_CLIST(ctree));



	    gtk_clist_thaw(GTK_CLIST(ctree));

	    browser->processing = FALSE;
	    GUIBlockInput(browser->toplevel, FALSE);
	    EDVBrowserSetBusy(browser, FALSE);
#endif

	    EDVBrowserUpdateMenus(browser);
	}
}


/*
 *	Browser GtkCList "resize_column" signal callback.
 */
void EDVBrowserResizeColumnCB(
	GtkCList *clist, gint column, gint width, gpointer data
)
{
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    gint column_type = -1;
	    cfg_intlist_struct *column_types_intlist, *column_width_intlist;


	    /* Get column_type from the given column index */
	    column_types_intlist = EDV_GET_INTLIST(
		EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN
	    );
	    if(column_types_intlist != NULL)
	    {
		if((column >= 0) && (column < column_types_intlist->total))
		    column_type = column_types_intlist->i[column];
	    }

	    /* Get column widths intlist */
	    column_width_intlist = EDV_GET_INTLIST(
		EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN_WIDTH
	    );
	    if(column_width_intlist != NULL)
	    {
		/* Need to increase column_width_intlist allocation? */
		if((column_type >= column_width_intlist->total) &&
		   (column_type >= 0)
		)
		{
		    gint n;
		    gint prev_total = column_width_intlist->total;

		    /* Increase array allocation */
		    column_width_intlist->total = column_type + 1;
		    column_width_intlist->i = (gint *)g_realloc(
			column_width_intlist->i,
			column_width_intlist->total * sizeof(gint)
		    );
		    /* Reset newly allocated indexes */
		    for(n = prev_total; n < column_width_intlist->total; n++)
			column_width_intlist->i[n] = 0;
		}

		/* Got column type in bounds as index on column width
		 * intlist?
		 */
		if((column_type >= 0) &&
		   (column_type < column_width_intlist->total)
		)
		{
		    /* Record new width of column on the column width
		     * intlist.
		     */
		    column_width_intlist->i[column_type] = width;
		}
	    }
	}
}

/*
 *	Browser GtkCList "click_column" signal callback.
 */
void EDVBrowserClickColumnCB(
	GtkCList *clist, gint column, gpointer data
)
{
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    cfg_intlist_struct *column_types_intlist;
	    GtkCListCompareFunc cmp_func = NULL;
	    GtkCListCompareFunc cmp_func_str =
		 (GtkCListCompareFunc)EDVCListColumnSortStringCB;
	    GtkCListCompareFunc cmp_func_num =
		(GtkCListCompareFunc)EDVCListColumnSortNumericCB;


	    EDVBrowserSetBusy(browser, TRUE);
	    GUIBlockInput(browser->toplevel, TRUE);
	    browser->processing = TRUE;	    


	    /* Get column types mapping */
	    column_types_intlist = EDV_GET_INTLIST(
		EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN
	    );
	    if(column_types_intlist != NULL)
	    {
		if((column >= 0) && (column < column_types_intlist->total))
		{
		    gint column_type = column_types_intlist->i[column];
		    switch(column_type)
		    {
		      case EDV_BROWSER_COLUMN_TYPE_NAME:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_SIZE:
			cmp_func = cmp_func_num;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_TYPE:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_PERMISSIONS:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_OWNER:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_GROUP:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_DATE_ACCESS:
			cmp_func = EDVBrowserCListColumnSortDateAccessCB;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_DATE_MODIFIED:
			cmp_func = EDVBrowserCListColumnSortDateModifyCB;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_DATE_CHANGED:
			cmp_func = EDVBrowserCListColumnSortDateChangeCB;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_HARD_LINKS:
			cmp_func = cmp_func_num;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_LINKED_TO:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_DEVICE:
			cmp_func = cmp_func_num;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_INODE:
			cmp_func = cmp_func_num;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_DEVICE_TYPE:
			cmp_func = cmp_func_str;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_BLOCK_SIZE:
			cmp_func = cmp_func_num;
			break;
		      case EDV_BROWSER_COLUMN_TYPE_BLOCKS:
			cmp_func = cmp_func_num;
			break;
		    }
		}
	    }


	    gtk_clist_freeze(clist);

	    /* Set sort column settings on the clist */
	    if(column != clist->sort_column)
		gtk_clist_set_sort_column(clist, column);
	    else
		gtk_clist_set_sort_type(
		    clist,
		    (clist->sort_type == GTK_SORT_ASCENDING) ?
			GTK_SORT_DESCENDING : GTK_SORT_ASCENDING
		);
	    if(cmp_func != NULL)
		gtk_clist_set_compare_func(clist, cmp_func);

	    /* Sort rows, this will call the GtkCList column sort
	     * callbacks.
	     */
	    gtk_clist_sort(clist);

	    gtk_clist_thaw(clist);

	    browser->processing = FALSE;
	    GUIBlockInput(browser->toplevel, FALSE);
	    EDVBrowserSetBusy(browser, FALSE);

	    EDVBrowserUpdateMenus(browser);
	}
}

/*
 *	Browser GtkCList "select_row" signal callback.
 */
void EDVBrowserSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    gint total_selected;
	    edv_object_struct *obj;


	    /* Get total number of objects selected */
	    total_selected = (clist->selection != NULL) ?
		g_list_length(clist->selection) : 0;

	    /* Update selected row */
	    browser->contents_clist_selected_row = row;

	    /* Mark last selected directory and device is no longer
	     * selected due to selection of row on the contents clist
	     */
	    browser->directory_ctree_selected_node = NULL;
	    browser->selected_dev_num = -1;


	    /* Update DND icon for contents clist */
	    EDVBrowserContentsDNDSetIcon(browser, row, column);

	    /* Get disk object structure from selected row */
	    obj = EDV_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		/* Update status bar message */
		if(!STRISEMPTY(obj->name))
		{
		    gchar *buf, *size_str = NULL;
		    const gchar *type_str = NULL;

		    /* Get object type and size strings */
		    switch(obj->type)
		    {
		      case EDV_OBJECT_TYPE_UNKNOWN:
			break;
		      case EDV_OBJECT_TYPE_FILE:
			type_str = "File";
			size_str = g_strdup_printf(
			    " (%s byte%s)",
			    EDVGetObjectSizeStr(
				EDV_CORE(browser->core_ptr),
				obj->size
			    ),
			    (obj->size == 1) ? "" : "s"
			);
			break;
		      case EDV_OBJECT_TYPE_DIRECTORY:
			type_str = "Directory";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_LINK:
			type_str = "Link";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
			type_str = "Block device";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
			type_str = "Character device";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_FIFO:
			type_str = "FIFO Pipe";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_SOCKET:
			type_str = "Socket";
			size_str = NULL;
			break;
		    }

		    /* Set status bar message */
		    if(total_selected > 1)
			buf = g_strdup_printf(
			    "%i objects selected",
			    total_selected
			);
		    else if(!strcmp(obj->name, ".."))
			buf = g_strdup_printf(
			    "Parent directory selected"
			);
		    else
			buf = g_strdup_printf(
			    "%s \"%s\" selected%s",
			    type_str, obj->name,
			    (size_str != NULL) ? size_str : ""
			);
		    EDVStatusBarMessage(
			browser->status_bar, buf, FALSE
		    );
		    g_free(buf);
		    g_free(size_str);
		}
		else
		{
		    EDVStatusBarMessage(
			browser->status_bar,
			"Object with no name selected",
			FALSE
		    );
		}
	    }

	    /* If selected row is not fully visible then scroll to
	     * position the selected row in the center of the list
	     */
	    if(gtk_clist_row_is_visible(clist, row) !=
		GTK_VISIBILITY_FULL
	    )
		gtk_clist_moveto(
		    clist,
		    row, -1,	/* Row, column */
		    0.5f, 0.0f	/* Row, column */
		);

	    EDVBrowserUpdateMenus(browser);
	}
}

/*
 *	Browser GtkCList "unselect_row" signal callback.
 */
void EDVBrowserUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    EDVBrowserUpdateMenus(browser);
	}
}


/*
 *	Location combo activate callback.
 */
void EDVBrowserComboActivateCB(GtkWidget *widget, gpointer data)
{
	GtkCombo *combo = (GtkCombo *)widget;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

	/* Check which widget was activated */
	if(widget == browser->location_combo)
	{
	    gchar *new_path = EDVCopyEvaluateInputPath(
		NULL,		/* No parent path, imply use toplevel */
		gtk_entry_get_text(GTK_ENTRY(combo->entry))
	    );
	    if(new_path != NULL)
	    {
		GtkWidget *toplevel = browser->toplevel;
		GtkCTree *ctree = GTK_CTREE(browser->directory_ctree);
		GtkCList *clist = GTK_CLIST(ctree);

		EDVBrowserSetBusy(browser, TRUE);
		GUIBlockInput(toplevel, TRUE);

		/* Do not mark as processing or else
		 * EDVBrowserDirTreeDoSelectPath() will not be able to
		 * expand nodes because the expand signal callback will
		 * detect the processing and not expand the node
		 */

		/* If the specified path's prefix is not the same as
		 * the current Directory Tree Origin then the Directory
		 * Tree Origin needs to be updated first
		 */
		if((browser->directory_ctree_origin_path != NULL) ?
		    !strpfx(new_path, browser->directory_ctree_origin_path) : TRUE
		)
		    EDVBrowserDirTreeSetOriginPath(browser, new_path);

		/* Update location combo to record the new location in
		 * the location history
		 *
		 * The combo will be updated again (without recording
		 * history) if the new path is valid and selected in
		 * the callbacks triggered by
		 * EDVBrowserDirTreeDoSelectPath() farther below
		 */
		EDVBrowserSetLocation(browser, new_path, TRUE);

		/* Update selected node on directory ctree */
		if(clist != NULL)
		{
		    /* Attempt to select the node who's object path
		     * matches new_path (recurse and expand as needed)
		     *
		     * The ctree will be frozen and thawed by the
		     * callbacks each time it is expanded so we do not
		     * need to freeze or thaw it here
		     *
		     * Also, the location prompt will be updated again
		     * (but its history will not be re-recorded) when the
		     * the node is selected
		     */
		    EDVBrowserDirTreeDoSelectPath(browser, new_path);
		}


		GUIBlockInput(toplevel, FALSE);
		EDVBrowserSetBusy(browser, FALSE);

		g_free(new_path);
	    }
	}
}



/*
 *	Called whenever the global write protect has changed.
 *
 *	The new state is given as state.
 */
void EDVBrowserWriteProtectChangedCB(
	edv_browser_struct *browser, gboolean state
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	EDVBrowserUpdateMenus(browser);
}

/*
 *	Called whenever a disk object has been added.
 */
void EDVBrowserObjectAddedNotifyCB(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Notify contents clist and directory ctree about added object */
	EDVBrowserContentsObjectAddedNotify(
	    browser, path, lstat_buf
	);
	EDVBrowserDirTreeObjectAddedNotify(
	    browser, path, lstat_buf
	);

/*	EDVBrowserUpdateMenus(browser); */
}

/*
 *      Called whenever a disk object has had its properties modified.
 */
void EDVBrowserObjectModifiedNotifyCB(
	edv_browser_struct *browser, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Check current location first, if the old path matches the
	 * current location then the current location needs to be updated
	 * to reflect the new path before notifying the contents clist
	 * and directory ctree
	 */
	if(!STRISEMPTY(path))
	{
	    const gchar *cur_location = EDVBrowserCurrentLocation(browser);

	    if(new_path == NULL)
		new_path = path;

	    /* Check if the old path matches the current location, if
	     * it does then the current location needs to be updated
	     * to the new path
	     */
	    if((cur_location != NULL) ?
		!strcmp(cur_location, path) : FALSE
	    )
	    {
		/* Old path matches current location, change values
		 * reflecting the current location to the value of
		 * new_path
		 */
		EDVBrowserSetTitle(browser, new_path);
		EDVBrowserSetLocation(browser, new_path, FALSE);
		EDVBrowserUpdateLocationIcon(browser, new_path);
	    }
	}

	/* Notify contents clist and directory ctree about modified object */
	EDVBrowserContentsObjectModifiedNotify(
	    browser, path, new_path, lstat_buf
	);
	EDVBrowserDirTreeObjectModifiedNotify(
	    browser, path, new_path, lstat_buf
	);

/*	EDVBrowserUpdateMenus(browser); */
}

/*
 *	Called whenever a disk object has been removed.
 */
void EDVBrowserObjectRemovedNotifyCB(
	edv_browser_struct *browser, const gchar *path
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Notify contents clist and directory ctree about the object
	 * that was removed
	 */
	EDVBrowserContentsObjectRemovedNotify(browser, path);
	EDVBrowserDirTreeObjectRemovedNotify(browser, path);

	/* Check if the path that was removed is the current location
	 * (after letting contents clist and directory ctree check on it
	 * first), if the path that was removed is the location then
	 * set the current location to be the parent of the path that
	 * was removed
	 */
	if(!STRISEMPTY(path))
	{
	    const gchar *cur_location = EDVBrowserCurrentLocation(browser);

	    /* Check if the removed object path matches the current
	     * location, if it matches then the current location needs
	     * to be updated to reflect that
	     */
	    if((cur_location != NULL) ?
		!strcmp(cur_location, path) : FALSE
	    )
	    {
		/* Removed object path matches the current location, so
		 * technically the current location needs to be cleared
		 * however we'll instead just change the current location
		 * value to reflect the value of the parent path of the
		 * removed object path
		 */
		gchar *parent_path = g_dirname(path);
		if(parent_path != NULL)
		{
		    EDVBrowserSetTitle(browser, parent_path);
		    EDVBrowserSetLocation(browser, parent_path, FALSE);
		    EDVBrowserUpdateLocationIcon(browser, parent_path);
		    g_free(parent_path);
		}
	    }
	}

/*	EDVBrowserUpdateMenus(browser); */
}


/*
 *      Called whenever a device has been mounted or unmounted.
 */
void EDVBrowserMountNotifyCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr,
	gboolean is_mounted
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Notify contents clist and directory ctree about mount */
	EDVBrowserContentsMountNotify(browser, dev_ptr, is_mounted);
	EDVBrowserDirTreeMountNotify(browser, dev_ptr, is_mounted);


	EDVBrowserUpdateMenus(browser);
}


/*
 *	Called whenever an object has been added to the recycle bin.
 */
void EDVBrowserRecycledObjectAddedNotifyCB(
	edv_browser_struct *browser, guint index
)
{
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

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


	/* There is not much interest when a recycled object has been
	 * added or removed. Only the menus need to be updated if there
	 * is a change in the number of recycled objects
	 */
	if(core_ptr->last_recbin_items != browser->last_recbin_items)
	    EDVBrowserUpdateMenus(browser);
}

/*
 *	Called whenever an object has been removed from the recycle bin.
 */
void EDVBrowserRecycledObjectRemovedNotifyCB(
	edv_browser_struct *browser, guint index
)
{
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

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


	/* There is not much interest when a recycled object has been
	 * added or removed. Only the menus need to be updated if there
	 * is a change in the number of recycled objects
	 */
	if(core_ptr->last_recbin_items != browser->last_recbin_items)
	    EDVBrowserUpdateMenus(browser);
}


/*
 *	Reconfigured notify callback.
 */
void EDVBrowserReconfiguredNotifyCB(edv_browser_struct *browser)
{
	gchar *cur_path;
	GtkRcStyle *standard_rcstyle, *lists_rcstyle;
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_status_bar_struct *status_bar;
	edv_core_struct *core_ptr;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

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


	/* Reset last state markers so that resources get explicitly
	 * checked due to reconfiguring
	 */
	browser->last_recbin_items = -1;
	browser->last_write_protect_state = -1;


	/* Get current location */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));


	/* Update Title */
	EDVBrowserSetTitle(browser, cur_path);

	/* Recreate Tool Bar */
	EDVBrowserToolBarRegenerate(browser);


	/* Show/hide Tool Bar */
	w = browser->tool_bar_handle;
	if(w != NULL)
	{
	    browser->tool_bar_map_state = EDV_GET_B(
		EDV_CFG_PARM_BROWSER_SHOW_TOOL_BAR
	    );
	    if(browser->tool_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show/hide Location Bar */
	w = browser->location_bar_handle;
	if(w != NULL)
	{
	    browser->location_bar_map_state = EDV_GET_B(
		EDV_CFG_PARM_BROWSER_SHOW_LOCATION_BAR
	    );
	    if(browser->location_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show/hide Mount Bar */
	w = browser->mount_bar_handle;
	if(w != NULL)
	{
	    browser->mount_bar_map_state = EDV_GET_B(
		EDV_CFG_PARM_BROWSER_SHOW_MOUNT_BAR
	    );
	    if(browser->mount_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show/hide Find Bar */
	w = browser->find_bar_handle;
	if(w != NULL)
	{
	    browser->find_bar_map_state = EDV_GET_B(
		EDV_CFG_PARM_BROWSER_SHOW_FIND_BAR
	    );
	    if(browser->find_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show/hide Status Bar? */
	status_bar = browser->status_bar;
	if(status_bar != NULL)
	{
	    browser->status_bar_map_state = EDV_GET_B(
		EDV_CFG_PARM_BROWSER_SHOW_STATUS_BAR
	    );
	    if(browser->status_bar_map_state)
		EDVStatusBarMap(status_bar);
	    else
		EDVStatusBarUnmap(status_bar);
	}

	/* Recreate New Object Submenu */
	EDVBrowserNewObjectMenuRegenerate(browser);


	/* Update RC styles */
	w = browser->toplevel;
	if((w != NULL) && (standard_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, standard_rcstyle);
	w = browser->directory_ctree;
	if((w != NULL) && (lists_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, lists_rcstyle);
	w = browser->contents_clist;
	if((w != NULL) && (lists_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, lists_rcstyle);
	w = browser->directory_ctree_menu;
	if((w != NULL) && (standard_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, standard_rcstyle);
	w = browser->contents_clist_menu;
	if((w != NULL) && (standard_rcstyle != NULL))
	    gtk_widget_modify_style_recursive(w, standard_rcstyle);

	EDVBrowserListStylesRegenerate(browser);


	/* Reget listings */

	/* Update all nodes and remove any nodes that reffer to non-existing
	 * disk objects on the directory ctree
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);

	/* Reset contents list column heading and all rows */
	clist = (GtkCList *)browser->contents_clist;
	if(clist != NULL)
	{
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsResetRows(browser);
	    gtk_clist_thaw(clist);
	}


	/* Update menus */
	EDVBrowserUpdateMenus(browser);


	/* Notify browser's toplevel widget to resize */
	w = browser->toplevel;
	if(w != NULL)
	    gtk_widget_queue_resize(w);


	/* Delete copy of current location */
	g_free(cur_path);
}


/*
 *      Called whenever a MIME Type has been added to the core
 *      structure's list of MIME Types.
 */
void EDVBrowserMimeTypeAddedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mimetype_struct *mt_ptr
)
{
	/* Treat a MIME Type added the same as it would be for a
	 * MIME Type modified, forward signal to the MIME Type modified
	 * callback
	 */
	EDVBrowserMimeTypeModifiedCB(browser, mt_num, mt_ptr);
}

/*
 *      Called whenever a MIME Type has been modified on the core
 *      structure's list of MIME Types.
 */
void EDVBrowserMimeTypeModifiedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mimetype_struct *mt_ptr
)
{
	gchar *cur_path;
	GtkCList *clist;
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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


	/* Get copy of current location path */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));


	/* Reget listings */

	/* Update all nodes and remove any nodes that reffer to non-existing
	 * disk objects on the directory ctree.
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);

	/* Reset displayed row values of contents clist */
	clist = (GtkCList *)browser->contents_clist;
	if(clist != NULL)
	{
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsResetRows(browser);
	    gtk_clist_thaw(clist);
	}

/*	EDVBrowserUpdateMenus(browser); */

	/* Delete copy of the current location */
	g_free(cur_path);
}

/*
 *      Called whenever a MIME Type has been removed from the core
 *      structure's list of MIME Types.
 */
void EDVBrowserMimeTypeRemovedCB(
	edv_browser_struct *browser, gint mt_num
)
{
	gchar *cur_path;
	GtkCList *clist;
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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


	/* Get copy of current location path */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));


	/* Reget listings */

	/* Update all nodes and remove any nodes that reffer to non-existing
	 * disk objects on the directory ctree.
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);

	/* Reset displayed row values of contents clist */
	clist = (GtkCList *)browser->contents_clist;
	if(clist != NULL)
	{
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsResetRows(browser);
	    gtk_clist_thaw(clist);
	}

/*	EDVBrowserUpdateMenus(browser); */

	/* Deallocate copy of current location path */
	g_free(cur_path);
	cur_path = NULL;
}


/*
 *      Called whenever a device has been added to the core
 *      structure's list of devices.
 */
void EDVBrowserDeviceAddedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr
)
{
	/* Treat a device added the same as it would be for a device
	 * modified, forward signal to the device modified callback.
	 */
	EDVBrowserDeviceModifiedCB(browser, dev_num, dev_ptr);
}

/*
 *	Called whenever a device has been modified on the core
 *	structure's list of devices.
 */
void EDVBrowserDeviceModifiedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev_ptr
)
{
	gchar *cur_path;
	GtkCList *clist;
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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


	/* Get copy of current location path */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));


	/* Reget listings */

	/* Update all nodes and remove any nodes that reffer to non-existing
	 * disk objects on the directory ctree.
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);

	/* Reset displayed row values of contents clist */
	clist = (GtkCList *)browser->contents_clist;
	if(clist != NULL)
	{
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsResetRows(browser);
	    gtk_clist_thaw(clist);
	}

	/* Update menus */
	EDVBrowserUpdateMenus(browser);

	/* Deallocate copy of current location path */
	g_free(cur_path);
	cur_path = NULL;
}

/*
 *      Called whenever a device has been removed from the core
 *      structure's list of devices.
 */
void EDVBrowserDeviceRemovedCB(
	edv_browser_struct *browser, gint dev_num
)
{
	gchar *cur_path;
	GtkCList *clist;
	edv_mountbar_struct *mb;
	edv_core_struct *core_ptr;


	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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


	/* Get copy of current location path */
	cur_path = STRDUP(EDVBrowserCurrentLocation(browser));


	/* Check if mount bar is currently referencing this device, if it
	 * is then its selected device needs to be set to -1
	 */
	mb = browser->mountbar;
	if(mb != NULL)
	{
	    if(mb->selected_dev_num == dev_num)
		mb->selected_dev_num = -1;

	    /* Mount bar will be updated further below when menus are
	     * updated
	     */
	}


	/* Reget listings */

	/* Update all nodes and remove any nodes that reffer to
	 * non-existing objects on the directory ctree
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);

	/* Reset displayed row values of contents clist */
	clist = (GtkCList *)browser->contents_clist;
	if(clist != NULL)
	{
	    gtk_clist_freeze(clist);
	    EDVBrowserContentsResetRows(browser);
	    gtk_clist_thaw(clist);
	}

	/* Update menus */
	EDVBrowserUpdateMenus(browser);

	/* Deallocate copy of current location path */
	g_free(cur_path);
	cur_path = NULL;
}
