#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 "edv_types.h"
#include "cfg.h"
#include "edv_obj.h"
#include "edv_mime_type.h"
#include "edv_mime_types_list.h"
#include "edv_device.h"
#include "edv_device_mount.h"
#include "edv_devices_list.h"
#include "edv_status_bar.h"
#include "browser.h"
#include "browser_cb.h"
#include "browser_op_cb.h"
#include "browser_dir_tree.h"
#include "browser_contents_list.h"
#include "browser_dnd.h"
#include "endeavour2.h"
#include "edv_op.h"
#include "edv_cb.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_list_seek.h"
#include "edv_cfg_list.h"
#include "config.h"


typedef struct _browser_dir_tree_cb_data_struct	browser_dir_tree_cb_data_struct;
#define BROWSER_DIR_TREE_CB_DATA(p)	((browser_dir_tree_cb_data_struct *)(p))


/* GTK+ Signal Callbacks */
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 EDVBrowserTreeSelectRowIdleCB(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);

/* Menu Item Callbacks */
void EDVBrowserMenuItemCB(GtkWidget *widget, gpointer data);
gint EDVBrowserMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
gint EDVBrowserMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);

/* Paths List Get Callback */
GList *EDVBrowserGetSelectedPathsListCB(gpointer data);

/* Goto Directory Callback */
void EDVBrowserGotoDirectoryCB(gpointer data, const gchar *path);

/* Mount Bar Callbacks */
void EDVBrowserMountBarMountCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
);
void EDVBrowserMountBarEjectCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
);
void EDVBrowserMountBarGoToCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
);

/* Find Bar Callbacks */
const gchar *EDVBrowserFindBarLocationCB(
	edv_findbar_struct *fb, gpointer data
);
void EDVBrowserFindBarStartCB(edv_findbar_struct *fb, gpointer data);
void EDVBrowserFindBarEndCB(
	edv_findbar_struct *fb, gint total_matches, gpointer data
);
void EDVBrowserFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
);

/* Status Bar Callbacks */
void EDVBrowserStatusMessageCB(const gchar *message, gpointer data);
void EDVBrowserStatusProgressCB(gfloat progress, gpointer data);

/* Write Protect Changed Callback */
void EDVBrowserWriteProtectChangedCB(
	edv_browser_struct *browser, gboolean state
);

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

/* Mount/Unmount Callback */
void EDVBrowserMountNotifyCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev,
	gboolean is_mounted
);

/* Recycled Object Callbacks */
void EDVBrowserRecycledObjectAddedNotifyCB(
	edv_browser_struct *browser, guint index
);
void EDVBrowserRecycledObjectRemovedNotifyCB(
	edv_browser_struct *browser, guint index
);

/* Reconfigured Callback */
void EDVBrowserReconfiguredNotifyCB(edv_browser_struct *browser);

/* MIME Type Callbacks */
void EDVBrowserMimeTypeAddedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mime_type_struct *mt
);
void EDVBrowserMimeTypeModifiedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mime_type_struct *mt
);
void EDVBrowserMimeTypeRemovedCB(
	edv_browser_struct *browser, gint mt_num
);

/* Device Callbacks */
void EDVBrowserDeviceAddedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev
);
void EDVBrowserDeviceModifiedCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev
);
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)


struct _browser_dir_tree_cb_data_struct {
	edv_browser_struct	*browser;
	GtkCTreeNode		*node;
};


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

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

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

	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(0);
}

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;
	gboolean is_press;
	gint etype;
	guint keyval, state;
	GtkWidget *w, *focus_widget;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (key == NULL) || (browser == NULL))
	    return(status);

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

	core = browser->core;
	if(core == NULL)
	    return(status);

	w = browser->toplevel;
	focus_widget = (w != NULL) ? GTK_WINDOW(w)->focus_widget : NULL;
	cfg_list = core->cfg_list;
	etype = key->type;
	is_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),				\
  is_press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}

	/* If the focus_widget is not a GtkEditable then check if the
	 * keyval is an accelerator key before all subsequence checks
	 */
	if((focus_widget != NULL) ?
	    !GTK_IS_EDITABLE(focus_widget) : TRUE
	)
	{
	    edv_browser_op op = (edv_browser_op)EDVMatchAccelKeyOPID(
		cfg_list, EDV_CFG_PARM_BROWSER_ACCELERATOR_KEYS,
		keyval, state
	    );
	    if(op > 0)
	    {
		if(is_press)
		{
		    edv_browser_opid_struct *opid = EDVBrowserMatchOPID(
			browser, op
		    );
		    if(opid != NULL)
			EDVBrowserOPCB(NULL, -1, opid);
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		return(status);
	    }
	}

	/* Check which widget this signal is for */
	/* Directory Tree */
	if(widget == browser->directory_ctree)
	{
	    GtkCList *clist = GTK_CLIST(widget);
	    GtkCTree *ctree = GTK_CTREE(widget);
	    GtkCTreeNode *node = EDVCTreeGetSelectedLast(ctree, NULL);

	    switch(keyval)
	    {
	      case GDK_space:
	      case GDK_KP_Space:
		node = gtk_ctree_node_nth(ctree, clist->focus_row);
		if((node != NULL) && is_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;
	    }
	}
	/* Contents List */
	else if(widget == browser->contents_clist)
	{
	    GtkCList *clist = GTK_CLIST(widget);
	    gint row = EDVCListGetSelectedLast(clist, NULL);

	    switch(keyval)
	    {
	      case GDK_space:
	      case GDK_KP_Space:
		row = clist->focus_row;
		if((row >= 0) && (row < clist->rows) && is_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;

	      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((int)keyval) && is_press &&
		   !(state & GDK_CONTROL_MASK) &&
		   !(state & GDK_SHIFT_MASK)
		)
		{
		    /* Get the column that is displaying the object
		     * name as column_type_name
		     */
		    gint 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 */
			gint i = 0;
			GList *glist = intlist->list;
			while(glist != NULL)
			{
			    if((edv_browser_column_type)glist->data ==
				EDV_BROWSER_COLUMN_TYPE_NAME
			    )
			    {
				column_type_name = i;
				break;
			    }
			    i++;
			    glist = g_list_next(glist);
			}
		    }

		    gtk_clist_freeze(clist);
		    EDVCListSeekCharacter(
			clist, column_type_name, 0, 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;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((widget == NULL) || (button == NULL) || (browser == NULL))
	    return(status);

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

	core = browser->core;
	if(core == NULL)
	    return(status);

	cfg_list = core->cfg_list;

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

	state = button->state;

	/* Check which widget this signal is for */
	/* Directory Tree */
	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);
	}
	/* Contents List */
	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))
		    {
			EDVBrowserContentsOpen(
			    browser,
			    row, column,
			    state		/* Modifier keys */
			);
			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 selct row idle callback.
 */
static gint EDVBrowserTreeSelectRowIdleCB(gpointer data)
{
	GtkWidget *toplevel;
	GtkCTreeNode *node;
	GtkCList *clist;
	GtkCTree *ctree;
	cfg_item_struct *cfg_list;
	edv_object_struct *obj;
	edv_browser_struct *browser;
	edv_core_struct *core;
	browser_dir_tree_cb_data_struct *d = BROWSER_DIR_TREE_CB_DATA(data);
	if(d == NULL)
	    return(FALSE);

	browser = d->browser;
	if(browser == NULL)
	{
	    g_free(d);
	    return(FALSE);
	}

	toplevel = browser->toplevel;
	ctree = (GtkCTree *)browser->directory_ctree;
	clist = GTK_CLIST(ctree);
	core = browser->core;
	cfg_list = core->cfg_list;
	node = d->node;
	obj = EDV_OBJECT(gtk_ctree_node_get_row_data(ctree, node));

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

	/* If the selected 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
	)
	{
#if 0
	    GtkCTreeRow *row_ptr = GTK_CTREE_ROW(node);
	    GtkAdjustment *adj = clist->hadjustment;
	    GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(clist));
	    GdkFont *font = (style != NULL) ? style->font : NULL;
#endif

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

#if 0
	    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
	    }
#endif
	}

	/* Update the current selected node and reset the last selected
	 * object on the contents list
	 */
	browser->directory_ctree_selected_node = node;
	browser->contents_clist_selected_row = -1;

	/* Match the device index of the selected object */
	if(obj != NULL)
	    EDVDevicesListMatchMountPath(
		core->device, core->total_devices,
		&browser->selected_dev_num,
		obj->full_path
	    );

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

	/* Update the contents list with the contents of the
	 * selected directory
	 */
	clist = (GtkCList *)browser->contents_clist;
	if((obj != NULL) && (clist != NULL))
	{
	    gint objects_in_directory;
	    const gchar *path = obj->full_path;
	    edv_mime_type_struct *m = EDVMimeTypesListMatchPath(
		core->mimetype, core->total_mimetypes,
		path
	    );

	    /* Update the title, location, and location icon */
	    EDVBrowserSetTitle(browser, path);
	    EDVBrowserSetLocation(browser, path, FALSE);
	    EDVBrowserUpdateLocationIcon(browser, path);

	    /* Update the open to menus */
	    EDVBrowserOpenToMenuRegenerate(browser, m);

	    /* Get listing of contents in the directory */
	    EDVBrowserContentsGetListing(
		browser,
		path,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );

	    /* Get the number of objects in the directory by
	     * calculating the number of rows in the contents list
	     * (excluding the parent directory notation row)
	     *
	     * Note that this calculation may be inaccurate if the
	     * contents list filter is set
	     */
	    objects_in_directory = MAX(clist->rows - 1, 0);

	    /* Update the status bar message */
	    if(!STRISEMPTY(obj->name))
	    {
		gchar *s;
		if(objects_in_directory > 0)
		    s = g_strdup_printf(
"Directory \"%s\" selected (containing %i %s)",
			obj->name,
			objects_in_directory,
			(objects_in_directory == 1) ? "object" : "objects"
		    );
		else
		    s = g_strdup_printf(
"Directory \"%s\" selected (empty)",
			obj->name
		    );
		EDVStatusBarMessage(
		    browser->status_bar, s, FALSE
		);
		g_free(s);
	    }
	    else
	    {
		EDVStatusBarMessage(
		    browser->status_bar,
		    "Directory with no name selected",
		    FALSE
		);
	    }
	}

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

	EDVBrowserUpdateMenus(browser);

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

	/* Delete the callback data */
	g_free(d);

	return(FALSE);
}

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

	browser = d->browser;
	if(browser == NULL)
	{
	    g_free(d);
	    return(FALSE);
	}

	ctree = (GtkCTree *)browser->directory_ctree;
	node = d->node;

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

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

	/* Delete the callback data */
	g_free(d);

	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;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core->cfg_list;

	/* Check which widget this signal is for */
	/* Directory Tree */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
	    /* Change in selection? */
	    if((browser->directory_ctree_selected_node != node) &&
	       (node != NULL)
	    )
	    {
		/* Set the idle callback to handle the selected row */
		browser_dir_tree_cb_data_struct *d = BROWSER_DIR_TREE_CB_DATA(g_malloc(
		    sizeof(browser_dir_tree_cb_data_struct)
		));
		if(d != NULL)
		{
		    d->browser = browser;
		    d->node = node;
		    browser->directory_ctree_expand_optimize_idleid = gtk_idle_add_priority(
			G_PRIORITY_LOW,
			EDVBrowserTreeSelectRowIdleCB, d
		    );
		}
	    }
	}
}

/*
 *      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 widget this signal is for */
	/* Directory Tree */
	if(GTK_WIDGET(ctree) == browser->directory_ctree)
	{
	    gchar *s;
	    GtkCList *clist = GTK_CLIST(ctree);
	    edv_core_struct *core = browser->core;

	    /* Get the total number of objects selected */
	    const gint nselected = g_list_length(clist->selection);

	    /* Was an item selected? */
	    if(browser->directory_ctree_selected_node != NULL)
	    {
		/* Mark that no node or device is selected */
		browser->directory_ctree_selected_node = NULL;
		browser->selected_dev_num = -1;
	    }

	    /* Update the title */
	    EDVBrowserSetTitle(browser, NULL);

	    /* Update the open to menus */
	    EDVBrowserOpenToMenuRegenerate(browser, NULL);

	    /* Update the status bar message */
	    if(nselected > 0)
		s = g_strdup_printf(
		    "%s %s selected",
		    EDVGetObjectSizeStr(core, nselected),
		    (nselected == 1) ? "object" : "objects"
		);
	    else
		s = STRDUP("No objects selected");
	    EDVStatusBarMessage(
		browser->status_bar, s, FALSE
	    );
	    g_free(s);

	    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;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((ctree == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core->cfg_list;

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

	    /* Expand node and get child nodes */
	    EDVBrowserDirTreeExpand(browser, node, TRUE);

	    /* Scroll to optimize viewing of expand nodes? */
	    if(EDV_GET_B(EDV_CFG_PARM_TREE_EXPAND_OPTIMIZE_POS))
	    {
		/* Set the idle callback to scroll to optimize view of
		 * the expanded nodes
		 */
		browser_dir_tree_cb_data_struct *d = BROWSER_DIR_TREE_CB_DATA(g_malloc(
		    sizeof(browser_dir_tree_cb_data_struct)
		));
		if(d != NULL)
		{
		    d->browser = browser;
		    d->node = node;
		    browser->directory_ctree_expand_optimize_idleid = gtk_idle_add_priority(
			G_PRIORITY_LOW,
			EDVBrowserTreeExpandOptimizeIdleCB, d
		    );
		}
	    }

	    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 widget this signal is for */
	/* Directory Tree */
	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;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core->cfg_list;

	/* Check which widget this signal is for */
	/* Contents List */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    const gint m = EDV_BROWSER_CONTENTS_CLIST_COLUMNS_MAX;
	    edv_browser_column_type column_type = EDV_BROWSER_COLUMN_TYPE_NAME;
	    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)
	    {
		column_type = (edv_browser_column_type)g_list_nth_data(
		    column_types_intlist->list, column
		);
	    }

	    /* Get column widths intlist */
	    column_width_intlist = EDV_GET_INTLIST(
		EDV_CFG_PARM_BROWSER_CONTENTS_COLUMN_WIDTH
	    );
	    if((column_width_intlist != NULL) &&
	       (column_type >= 0) && (column_type < m)
	    )
	    {
		GList *glist = g_list_nth(
		    column_width_intlist->list, (guint)column_type
		);
		if(glist != NULL)
		{
		    glist->data = (gpointer)width;
		}
		else
		{
		    /* The column type is not in the column widths      
		     * list so create a new column widths list from
		     * the current column widths list and include   
		     * the missing column type                   
		     */
		    gint i;
		    GList *glist_new = NULL;

		    glist = column_width_intlist->list;

		    for(i = 0; i < m; i++)
		    {
			if(glist != NULL)
			{
			    glist_new = g_list_append(
				glist_new, glist->data
			    );
			    glist = g_list_next(glist);
			}
			else
			    glist_new = g_list_append(glist_new, NULL);
		    }
		     
		    g_list_free(column_width_intlist->list);
		    column_width_intlist->list = glist_new;

		    glist = g_list_nth(glist_new, (guint)column_type);
		    if(glist != NULL)
			glist->data = (gpointer)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;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((clist == NULL) || (browser == NULL))
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core->cfg_list;

	/* Check which widget this signal is for */
	/* Contents List */
	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)EDVCListColumnSortNumberCB;


	    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)
	    {
		edv_browser_column_type column_type = (edv_browser_column_type)g_list_nth_data(
		    column_types_intlist->list,
		    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_INDEX:
		    cmp_func = cmp_func_num;
		    break;
		  case EDV_BROWSER_COLUMN_TYPE_INDEX:
		    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 widget this signal is for */
	/* Contents List */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    edv_object_struct *obj;
	    edv_core_struct *core = browser->core;

	    /* Get total number of objects selected */
	    const gint nselected = g_list_length(clist->selection);

	    /* 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 this selected object */
	    obj = EDV_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
	    {
		edv_mime_type_struct *m = EDVMimeTypesListMatchPath(
		    core->mimetype, core->total_mimetypes,
		    obj->full_path
		);

		/* Update the open to menus */
		EDVBrowserOpenToMenuRegenerate(browser, m);

		/* Update the status bar message */
		if(!STRISEMPTY(obj->name))
		{
		    gchar *s, *size_str = NULL;
		    const gchar *type_str = NULL;

		    /* Get object type and size strings */
		    switch(obj->type)
		    {
		      case EDV_OBJECT_TYPE_UNKNOWN:
			type_str = "Object";
			size_str = NULL;
			break;
		      case EDV_OBJECT_TYPE_FILE:
			type_str = "File";
			size_str = g_strdup_printf(
			    " (%s %s)",
			    EDVGetObjectSizeStr(
				core, obj->size
			    ),
			    (obj->size == 1) ? "byte" : "bytes"
			);
			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;
		      case EDV_OBJECT_TYPE_ERROR:
			type_str = "Error";
			size_str = NULL;
			break;
		    }

		    /* Set status bar message */
		    if(nselected > 1)
			s = g_strdup_printf(
			    "%s objects selected",
			    EDVGetObjectSizeStr(core, nselected)
			);
		    else if(!strcmp(obj->name, ".."))
			s = g_strdup_printf(
			    "Parent directory selected"
			);
		    else
			s = g_strdup_printf(
			    "%s \"%s\" selected%s",
			    type_str, obj->name,
			    (size_str != NULL) ? size_str : ""
			);
		    EDVStatusBarMessage(
			browser->status_bar, s, FALSE
		    );
		    g_free(s);
		    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 widget this signal is for */
	/* Contents List */
	if(GTK_WIDGET(clist) == browser->contents_clist)
	{
	    gint nselected;
	    gchar *s;
	    GList *glist;
	    edv_object_struct *obj;
	    edv_mime_type_struct *m;
	    edv_core_struct *core = browser->core;

	    /* Get the total number of objects selected */
	    nselected = g_list_length(clist->selection);

	    /* Get the last selected object (if any) */
	    glist = clist->selection_end;
	    if(glist != NULL)
		obj = EDV_OBJECT(gtk_clist_get_row_data(clist, (gint)glist->data));
	    else
		obj = NULL;

	    if(obj != NULL)
		m = EDVMimeTypesListMatchPath(
		    core->mimetype, core->total_mimetypes,
		    obj->full_path
		);
	    else
		m = NULL;

	    /* Update the open to menus */
	    EDVBrowserOpenToMenuRegenerate(browser, m);

	    /* Update the status bar message */
	    if(nselected > 0)
		s = g_strdup_printf(
		    "%s object%s selected",
		    EDVGetObjectSizeStr(core, nselected),
		    (nselected == 1) ? "" : "s"
		);
	    else
		s = STRDUP("No objects selected");
	    EDVStatusBarMessage(
		browser->status_bar, s, FALSE
	    );
	    g_free(s);

	    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 *path = EDVEvaluatePath(
		NULL,		/* No parent path, imply use toplevel */
		gtk_entry_get_text(GTK_ENTRY(combo->entry))
	    );
	    if(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
		 * EDVBrowserDirTreeSelectPath() 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(!EDVBrowserIsPathFromDirTreeOrigin(browser, path))
		    EDVBrowserDirTreeSetOriginPath(browser, 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
		 * EDVBrowserDirTreeSelectPath() farther below
		 */
		EDVBrowserSetLocation(browser, path, TRUE);

		/* Update selected node on directory ctree */
		if(clist != NULL)
		{
		    /* Attempt to select the node who's object's path
		     * matches 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
		     */
		    EDVBrowserDirTreeSelectPath(browser, path);
		}


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

		g_free(path);
	    }
	}
}


/*
 *	Menu item activate callback.
 *
 *	The data must be a edv_browser_opid_struct *.
 */
void EDVBrowserMenuItemCB(GtkWidget *widget, gpointer data)
{
	EDVBrowserOPCB(NULL, -1, data);
}

/*
 *	Menu item "enter_notify_event" signal callback.
 *
 *	The data must be a edv_browser_opid_struct *.
 */
gint EDVBrowserMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVBrowserOPEnterCB(NULL, -1, data);
	return(TRUE);
}

/*
 *	Menu item "leave_notify_event" signal callback.
 *
 *	The data must be a edv_browser_opid_struct *.
 */
gint EDVBrowserMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVBrowserOPLeaveCB(NULL, -1, data);
	return(TRUE);
}


/*
 *	Paths list get callback.
 */
GList *EDVBrowserGetSelectedPathsListCB(gpointer data)
{
	GList *paths_list = NULL;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return(paths_list);

	/* Contents clist was last selected? */
	if(browser->contents_clist_selected_row > -1)
	{
	    GtkCList *clist = (GtkCList *)browser->contents_clist;
	    if(clist != NULL)
	    {
		gint row;
		GList *glist = clist->selection;
		edv_object_struct *obj;
		while(glist != NULL)
		{
		    row = (gint)glist->data;
		    obj = EDV_OBJECT(gtk_clist_get_row_data(clist, row));
		    if(obj != NULL)
			paths_list = g_list_append(
			    paths_list, STRDUP(obj->full_path)
			);
		    glist = g_list_next(glist);
		}
	    }
	}
	else if(browser->directory_ctree_selected_node != NULL)
	{
	    GtkCTree *ctree = (GtkCTree *)browser->directory_ctree;
	    GtkCList *clist = (GtkCList *)ctree;
	    if(clist != NULL)
	    {
		GList *glist = clist->selection;
		GtkCTreeNode *node;
		edv_object_struct *obj;
		while(glist != NULL)
		{
		    node = (GtkCTreeNode *)glist->data;
		    obj = EDV_OBJECT(gtk_ctree_node_get_row_data(ctree, node));
		    if(obj != NULL)
			paths_list = g_list_append(
			    paths_list, STRDUP(obj->full_path)
			);
		    glist = g_list_next(glist);
		}
	    }
	}

	return(paths_list);
}

/*
 *	Go to directory callback.
 */
void EDVBrowserGotoDirectoryCB(gpointer data, const gchar *path)
{
	GtkWidget *toplevel;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((browser == NULL) || (path == NULL))
	    return;

	toplevel = browser->toplevel;

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

	/* Is the path within the Directory Tree? */
	if(EDVBrowserIsPathFromDirTreeOrigin(browser, path))
	{
	    /* Select the path */
	    EDVBrowserDirTreeSelectPath(browser, path);
	}
	else
	{
	    /* Set the Directory Tree origin and select the path */
	    EDVBrowserDirTreeSetOriginPath(browser, path);
	}

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


/*
 *	Mount Bar mount callback.
 */
void EDVBrowserMountBarMountCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
)
{
	gboolean original_mount_state;
	gint status;
	GtkWidget *toplevel;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((browser == NULL) || (dev == NULL))
	    return;

	if(browser->processing)
	    return;

	toplevel = browser->toplevel;
	core = browser->core;
	if(core == NULL)
	    return;

	EDVBrowserSetBusy(browser, TRUE);

	/* Record original mount state */
	original_mount_state = EDV_DEVICE_IS_MOUNTED(dev);

	/* Unmount or mount? */
	if(EDV_DEVICE_IS_MOUNTED(dev))
	    status = EDVDeviceUnmount(
		core, dev,
		TRUE, TRUE,
		toplevel
	    );
	else
	    status = EDVDeviceMount(
		core, dev,
		TRUE, TRUE,
		toplevel
	    );

	/* Update all device mount states and stats */
	EDVDevicesListUpdateMountStates(
	    core->device, core->total_devices
	);
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	EDVBrowserSetBusy(browser, FALSE);

	/* Mount error? */
	if(status)
	{
	    const gchar *last_error = EDVDeviceUnmountGetError();
	    if(!STRISEMPTY(last_error))
	    {
		EDVPlaySoundError(core);
		EDVMessageError(
		    original_mount_state ?
			"Unmount Failed" : "Mount Failed",
		    last_error,
		    NULL,
		    browser->toplevel
		);
	    }
	}
	else
	{
	    /* Report mount signal to all of endeavour's resources */
	    EDVObjectMountEmit(core, dev_num, dev, EDV_DEVICE_IS_MOUNTED(dev));
	}
}

/*
 *	Mount bar eject callback.
 */
void EDVBrowserMountBarEjectCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
)
{
	gboolean original_mount_state;
	gint status;
	GtkWidget *toplevel;
	edv_core_struct *core;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((browser == NULL) || (dev == NULL))
	    return;

	if(browser->processing)
	    return;

	toplevel = browser->toplevel;
	core = browser->core;
	if(core == NULL)
	    return;

	EDVBrowserSetBusy(browser, TRUE);

	/* Record original mount state */
	original_mount_state = EDV_DEVICE_IS_MOUNTED(dev);

	/* Need to unmount first? */
	if(EDV_DEVICE_IS_MOUNTED(dev))
	    status = EDVDeviceUnmount(
		core, dev,
		TRUE, TRUE,
		toplevel
	    );
	/* Now eject */
	status = EDVDeviceEject(
	    core, dev,
	    TRUE, TRUE,
	    toplevel
	);

	/* Update all device mount states and stats */
	EDVDevicesListUpdateMountStates(
	    core->device, core->total_devices
	);
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	EDVBrowserSetBusy(browser, FALSE);

	/* Mount error? */
	if(status)
	{
	    const gchar *last_error = EDVDeviceUnmountGetError();
	    if(!STRISEMPTY(last_error))
	    {
		EDVPlaySoundError(core);
		EDVMessageError(
		    "Eject Failed",
		    last_error,
		    NULL,
		    browser->toplevel
		);
	    }
	}
	else
	{
	    /* Report eject (unmount) signal to all of endeavour's
	     * resources
	     */
	    EDVObjectMountEmit(core, dev_num, dev, EDV_DEVICE_IS_MOUNTED(dev));
	}
}

/*
 *	Mount bar go to mount path callback.
 */
extern void EDVBrowserMountBarGoToCB(
	edv_mount_bar_struct *mb, gint dev_num, edv_device_struct *dev,
	gpointer data
)
{
	gchar *path;
	GtkWidget *toplevel;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((browser == NULL) || (dev == NULL))
	    return;

	if(browser->processing)
	    return;

	path = STRDUP(dev->mount_path);
	toplevel = browser->toplevel;

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

	/* Is the path within the Directory Tree? */
	if(EDVBrowserIsPathFromDirTreeOrigin(browser, path))
	{
	    /* Select the path */
	    EDVBrowserDirTreeSelectPath(browser, path);
	}
	else
	{
	    /* Set the Directory Tree origin and select the path */
	    EDVBrowserDirTreeSetOriginPath(browser, path);
	}

	g_free(path);

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


/*
 *	Find Bar get current location callback.
 */
const gchar *EDVBrowserFindBarLocationCB(edv_findbar_struct *fb, gpointer data)
{
	return(EDVBrowserCurrentLocation(
	    EDV_BROWSER(data)
	));
}

/*
 *	Find Bar start find callback.
 */
void EDVBrowserFindBarStartCB(edv_findbar_struct *fb, gpointer data)
{
	GtkCList *clist;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return;

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

	EDVBrowserSetBusy(browser, TRUE);

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

	EDVBrowserUpdateMenus(browser);
}

/*
 *	Find Bar end find callback.
 */
void EDVBrowserFindBarEndCB(
	edv_findbar_struct *fb, gint total_matches, gpointer data
)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return;

	EDVBrowserSetBusy(browser, FALSE);
}

/*
 *	Find Bar match callback.
 */
void EDVBrowserFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
)
{
	gint row;
	GtkCList *clist;
	edv_browser_struct *browser = EDV_BROWSER(data);
	if((path == NULL) || (lstat_buf == NULL) || (browser == NULL))
	    return;

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

	row = EDVBrowserContentsFindRowByPath(browser, path);
	if((row >= 0) && (row < clist->rows))
	    gtk_clist_select_row(clist, row, 0);
}


/*
 *	Status Message callback.
 */
void EDVBrowserStatusMessageCB(const gchar *message, gpointer data)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return;

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

/*
 *	Status Progress callback.
 */
void EDVBrowserStatusProgressCB(gfloat progress, gpointer data)
{
	edv_browser_struct *browser = EDV_BROWSER(data);
	if(browser == NULL)
	    return;

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


/*
 *	Write protect changed callback.
 */
void EDVBrowserWriteProtectChangedCB(
	edv_browser_struct *browser, gboolean state
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	EDVBrowserUpdateMenus(browser);
}

/*
 *	Object added callback.
 */
void EDVBrowserObjectAddedNotifyCB(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Contents Tree */
	EDVBrowserContentsObjectAddedNotify(
	    browser, path, lstat_buf
	);

	/* Directory List */
	EDVBrowserDirTreeObjectAddedNotify(
	    browser, path, lstat_buf
	);

	/* Mount Bar */
	EDVMountBarUpdateMenus(browser->mount_bar);

/*	EDVBrowserUpdateMenus(browser); */
}

/*
 *	Object modified callback.
 */
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);
	    }
	}

	/* Contents List */
	EDVBrowserContentsObjectModifiedNotify(
	    browser, path, new_path, lstat_buf
	);

	/* Directory Tree */
	EDVBrowserDirTreeObjectModifiedNotify(
	    browser, path, new_path, lstat_buf
	);

	/* Mount Bar */
	EDVMountBarUpdateMenus(browser->mount_bar);

/*	EDVBrowserUpdateMenus(browser); */
}

/*
 *	Object removed callback.
 */
void EDVBrowserObjectRemovedNotifyCB(
	edv_browser_struct *browser, const gchar *path
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Contents List */
	EDVBrowserContentsObjectRemovedNotify(browser, path);

	/* Directory Tree */
	EDVBrowserDirTreeObjectRemovedNotify(browser, path);

	/* Mount Bar */
	EDVMountBarUpdateMenus(browser->mount_bar);

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


/*
 *	Device mount/unmount callback.
 */
void EDVBrowserMountNotifyCB(
	edv_browser_struct *browser,
	gint dev_num, edv_device_struct *dev,
	gboolean is_mounted
)
{
	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

	/* Contents List */
	EDVBrowserContentsMountNotify(browser, dev, is_mounted);

	/* Directory Tree */
	EDVBrowserDirTreeMountNotify(browser, dev, is_mounted);

	EDVBrowserUpdateMenus(browser);
}


/*
 *	Recycled object added callback.
 */
void EDVBrowserRecycledObjectAddedNotifyCB(
	edv_browser_struct *browser, guint index
)
{
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	core = browser->core;
	if(core == 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->last_recbin_items != browser->last_recbin_items)
	    EDVBrowserUpdateMenus(browser);
}

/*
 *	Recycled object removed callback.
 */
void EDVBrowserRecycledObjectRemovedNotifyCB(
	edv_browser_struct *browser, guint index
)
{
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	core = browser->core;
	if(core == 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->last_recbin_items != browser->last_recbin_items)
	    EDVBrowserUpdateMenus(browser);
}


/*
 *	Reconfigured callback.
 */
void EDVBrowserReconfiguredNotifyCB(edv_browser_struct *browser)
{
	gchar *cur_loc;
	GtkRcStyle *standard_rcstyle, *lists_rcstyle;
	GtkWidget *w;
	const cfg_item_struct *cfg_list;
	edv_status_bar_struct *status_bar;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

	cfg_list = core->cfg_list;
	standard_rcstyle = core->standard_rcstyle;
	lists_rcstyle = core->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_loc = STRDUP(EDVBrowserCurrentLocation(browser));


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

	/* Recreate Accelkeys labels */
	EDVBrowserAccelkeysRegenerate(browser);

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


	/* Realize listings */
	EDVBrowserDirTreeRealizeListing(browser, NULL);
	EDVBrowserContentsRealizeListing(browser);

	EDVBrowserUpdateMenus(browser);

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

	g_free(cur_loc);
}


/*
 *	MIME Type added callback.
 */
void EDVBrowserMimeTypeAddedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mime_type_struct *mt
)
{
	/* 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);
}

/*
 *	MIME Type modified callback.
 */
void EDVBrowserMimeTypeModifiedCB(
	edv_browser_struct *browser,
	gint mt_num, edv_mime_type_struct *mt
)
{
	gchar *cur_loc;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

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

	/* Realize listings */
	EDVBrowserDirTreeRealizeListing(browser, NULL);
	EDVBrowserContentsRealizeListing(browser);

/*	EDVBrowserUpdateMenus(browser); */

	g_free(cur_loc);
}

/*
 *	MIME Type removed callback.
 */
void EDVBrowserMimeTypeRemovedCB(
	edv_browser_struct *browser, gint mt_num
)
{
	gchar *cur_loc;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

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

	/* Realize listings */
	EDVBrowserDirTreeRealizeListing(browser, NULL);
	EDVBrowserContentsRealizeListing(browser);

/*	EDVBrowserUpdateMenus(browser); */

	g_free(cur_loc);
}


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

/*
 *	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
)
{
	gchar *cur_loc;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

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

	/* Realize listings */
	EDVBrowserDirTreeRealizeListing(browser, NULL);
	EDVBrowserContentsRealizeListing(browser);

	EDVBrowserUpdateMenus(browser);

	g_free(cur_loc);
}

/*
 *      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_loc;
	edv_mount_bar_struct *mb;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	if(browser->processing)
	    return;

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

	/* Get current location */
	cur_loc = 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->mount_bar;
	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
	     */
	}

	/* Realize listings */
	EDVBrowserDirTreeRealizeListing(browser, NULL);
	EDVBrowserContentsRealizeListing(browser);

	EDVBrowserUpdateMenus(browser);

	g_free(cur_loc);
}
