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

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

#include "guiutils.h"
#include "cdialog.h"
#include "fb.h"
#include "csd.h"
#include "fsd.h"
#include "progressdialog.h"
#include "pdialog.h"
#include "fprompt.h"

#include "cfg.h"
#include "url.h"
#include "edv_types.h"
#include "edv_obj.h"
#include "edv_devices_list.h"
#include "edv_recbin_index.h"
#include "edv_archive_obj.h"
#include "edv_recycled_obj.h"
#include "edv_interps.h"
#include "browser.h"
#include "browser_cb.h"
#include "browser_op_cb.h"
#include "imbr.h"
#include "imbr_cb.h"
#include "imbr_op_cb.h"
#include "archiver.h"
#include "archiver_op_cb.h"
#include "archiver_cb.h"
#include "archiver_op_cb.h"
#include "recbin.h"
#include "recbin_cb.h"
#include "recbin_op_cb.h"
#include "recbin_desktop_icon.h"
#include "prop_dlg.h"
#include "find_win.h"
#include "find_win_cb.h"
#include "history_win.h"
#include "about_dlg.h"
#include "endeavour2.h"
#include "edv_cb.h"
#include "edv_op.h"
#include "edv_interps_op.h"
#include "edv_open.h"
#include "edv_utils.h"
#include "edv_cfg_list.h"
#include "config.h"


typedef enum {
	EDV_SORT_TYPE_TEXT,
	EDV_SORT_TYPE_NUMBER
} edv_sort_type;


typedef struct _interps_idle_data_struct	interps_idle_data_struct;
#define INTERPS_IDLE_DATA(p)		((interps_idle_data_struct *)(p))


/* UNIX Signal Callbacks */
void EDVSignalCB(int s);

/* Timeout Callbacks */
static gint EDVInterPSCommandsIdleCB(gpointer data);
gint EDVManageTimeoutCB(gpointer data);
gint EDVCheckDevicesCB(gpointer data);

/* New/Map Window Callbacks */
void EDVNewBrowserCB(GtkWidget *widget, gpointer data);
void EDVNewImbrCB(GtkWidget *widget, gpointer data);
void EDVNewArchiverCB(GtkWidget *widget, gpointer data);
void EDVMapRecBinCB(GtkWidget *widget, gpointer data);
void EDVDevicesCB(GtkWidget *widget, gpointer data);
void EDVMIMETypesCB(GtkWidget *widget, gpointer data);
void EDVOptionsCB(GtkWidget *widget, gpointer data);
void EDVCustomizeCB(GtkWidget *widget, gpointer data);

/* GtkCList Sort Callbacks */
static gint EDVCListColumnSortNexus(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2,
	const edv_sort_type sort_type
);
gint EDVCListColumnSortStringCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
gint EDVCListColumnSortNumberCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);

/* Refresh & Reset Callbacks */
void EDVRefreshCB(GtkWidget *widget, gpointer data);
void EDVResetCB(GtkWidget *widget, gpointer data);

/* Clear Callbacks */
void EDVPurgeAllRecycledObjectsCB(GtkWidget *widget, gpointer data);
void EDVClearHistoryCB(GtkWidget *widget, gpointer data);
void EDVClearLocationsHistoryCB(GtkWidget *widget, gpointer data);
void EDVClearRunHistoryCB(GtkWidget *widget, gpointer data);

/* GtkEntry DND Callbacks */
gboolean EDVEntryDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
	gpointer data
);
void EDVEntryDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
);

/* GtkEntry Complete Path Callback */
gint EDVEntryCompletePathCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
);

/* Location Bar DND Callbacks */
void EDVLocBarIconDragDataGet(
	GtkWidget *widget, GdkDragContext *context,
	GtkSelectionData *selection_data, guint info, guint time,
	gpointer data
);


/* Write Protect Changed Emitters */
void EDVWriteProtectChangedEmit(
	edv_core_struct *core, gboolean state
);

/* Object Emitters */
void EDVObjectAddedEmit(
	edv_core_struct *core, const gchar *path,
	const struct stat *lstat_buf
);
void EDVObjectModifiedEmit(
	edv_core_struct *core, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
);
void EDVObjectRemovedEmit(
	edv_core_struct *core, const gchar *path
);

/* Mount/Unmount Emitters */
void EDVObjectMountEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev,
	gboolean is_mounted
);

/* Archive Emitters */
void EDVArchiveObjectAddedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path, edv_archive_object_struct *obj
);
void EDVArchiveObjectModifiedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
);
void EDVArchiveObjectRemovedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path
);

/* Recycled Object Emitters */
void EDVRecycledObjectAddedEmit(
	edv_core_struct *core, guint index
);
void EDVRecycledObjectRemovedEmit(
	edv_core_struct *core, guint index
);

/* Reconfigure Emitters */
void EDVReconfiguredEmit(edv_core_struct *core);

/* MIME Type Emitters */
void EDVMimeTypeAddedEmitCB(
	const gint mt_num, edv_mime_type_struct *mt, gpointer data
);
void EDVMimeTypeAddedEmit(
	edv_core_struct *core,
	const gint mt_num, edv_mime_type_struct *mt
);
void EDVMimeTypeModifiedEmitCB(
	const gint mt_num, edv_mime_type_struct *mt, gpointer data
);
void EDVMimeTypeModifiedEmit(
	edv_core_struct *core,
	const gint mt_num, edv_mime_type_struct *mt
);
void EDVMimeTypeRemovedEmitCB(
	const gint mt_num, gpointer data
);
void EDVMimeTypeRemovedEmit(
	edv_core_struct *core, const gint mt_num
);

/* Device Emitters */
void EDVDeviceAddedEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev
);
void EDVDeviceModifiedEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev
);
void EDVDeviceRemovedEmit(
	edv_core_struct *core, gint dev_num
);

/* History Event Emitters */
void EDVHistoryAddedEmit(
	edv_core_struct *core,
	gint h_num, edv_history_struct *h_ptr
);
void EDVHistoryRemovedEmit(
	edv_core_struct *core,
	gint h_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)


/*
 *	InterPS Idle Data:
 */
struct _interps_idle_data_struct {
	edv_core_struct	*core;
	gchar		**cmd_list;
};


/*
 *	Signal callback.
 *
 *	No gtk/gdk calls can be made here, since this is actually
 *	called from a separate thread.
 */
void EDVSignalCB(int s)
{
	edv_core_struct *core = edv_core;

	/* If the core was not initialized then ignore the signal */
	if(core == NULL)
	    return;

	switch(s)
	{
#ifdef SIGHUP
	  case SIGHUP:
	    core->pending_flags |= EDV_CORE_PENDING_RESET;
	    break;
#else
#warning SIGHUP is not defined, reset signaling will not function
#endif
#ifdef SIGINT
	  case SIGINT:
	    core->pending_flags |= EDV_CORE_PENDING_CLOSE_ALL_WINDOWS;
	    break;
#endif
#ifdef SIGTERM
	  case SIGTERM:
	    exit(0);	/* Explicit exit */
	    break;
#endif
#ifdef SIGQUIT
	  case SIGQUIT:
	    core->pending_flags |= EDV_CORE_PENDING_CLOSE_ALL_WINDOWS;
	    break;
#endif
#ifdef SIGSEGV
	  case SIGSEGV:
	    exit(1);	/* Explicit exit with error */
	    break;
#endif
#ifdef SIGUSR1
	  case SIGUSR1:
	    /* Increment InterPS signal count */
	    core->interps_signal_count++;
	    break;
#else
#warning SIGUSR1 is not defined, InterPS signaling will not function
#endif

#ifdef SIGUSR2
	  case SIGUSR2:
	    break;
#endif
#ifdef SIGPIPE
	  case SIGPIPE: 
	    break;
#endif
	}
}


/*
 *	Process InterPS commands idle callback.
 */
static gint EDVInterPSCommandsIdleCB(gpointer data)
{
	interps_idle_data_struct *d = INTERPS_IDLE_DATA(data);
	if(d == NULL)
	    return(FALSE);

	/* Process the InterPS commands */
	if(d->cmd_list != NULL)
	{
	    EDVInterPSOPProcessCommands(d->core, d->cmd_list);
	    g_strfreev(d->cmd_list);
	}

	g_free(d);

	return(FALSE);
}

/*
 *	Core timeout callback.
 */
gint EDVManageTimeoutCB(gpointer data)
{
	gboolean	conserve_memory,
			has_changes,
			still_processing,
			all_windows_unmapped;
	gint i;
	const cfg_item_struct *cfg_list;
	edv_browser_struct *browser;
	edv_imbr_struct *imbr;
	edv_archiver_struct *archiver;
	edv_archive_info_dlg_struct *arch_info;
	edv_recbin_struct *recbin;
	edv_find_win_struct *findwin;
	edv_propdlg_struct *propdlg;
	edv_history_win_struct *histwin;
	edv_run_dlg_struct *rundlg;
	edv_devices_list_win_struct *deviceswin;
	edv_mime_types_list_win_struct *mimetypeswin;
	edv_gen_opt_win_struct *optwin;
	about_dlg_struct *aboutdlg;
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return(FALSE);

/* Removes the timeout ID and sets it to 0 */
#define DO_REMOVE_TOID(_id_)		\
{ if(*(_id_) > 0) {			\
  gtk_timeout_remove(*(_id_));		\
  *(_id_) = 0;				\
} }

/* Removes all timeout callbacks and breaks out of the GTK main loop */
#define DO_REMOVE_TIMEOUTS_AND_BREAK_MAIN {	\
 DO_REMOVE_TOID(&core->manage_toid);	\
						\
 gtk_main_quit();				\
}

/* Reschedual timeout callback to this function and return FALSE, since
 * this function need not be called again because a new timeout has
 * been set for this function
 */
#define DO_RETURN_RESCHEDUAL	{		\
 core->manage_toid = gtk_timeout_add(	\
  1000l,		/* 1 second interval */	\
  EDVManageTimeoutCB, core			\
 );						\
						\
 return(FALSE);					\
}

/* Mark timeout callback to this function as no longer valid and
 * return FALSE to never call this function again
 */
#define DO_RETURN_NO_RESCHEDUAL		{	\
 core->manage_toid = 0;			\
						\
 return(FALSE);					\
}

/* Returns TRUE, keeping the current timeout callback to this function */
#define DO_RETURN_CURRENT_SCHEDUAL	{	\
 return(TRUE);					\
}

	/* Get configuration */
	cfg_list = core->cfg_list;
	conserve_memory = CFGItemListGetValueI(
	    cfg_list, EDV_CFG_PARM_CONSERVE_MEMORY
	);


	/* Need to close all windows? */
	if(core->pending_flags & EDV_CORE_PENDING_CLOSE_ALL_WINDOWS)
	{
	    core->pending_flags &= ~EDV_CORE_PENDING_CLOSE_ALL_WINDOWS;

	    /* Check if any windows/dialogs are in query or are still
	     * processing
	     *
	     * Let next call to this function handle rest of closing
	     * all windows
	     */
	    if(CDialogIsQuery())
	    {
		CDialogBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(FileBrowserIsQuery())
	    {
		FileBrowserBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(ProgressDialogIsQuery())
	    {
		ProgressDialogBreakQuery(FALSE);
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(FSDIsQuery())
	    {
		FSDBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(CSDIsQuery())
	    {
		CSDBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(PDialogIsQuery())
	    {
		PDialogBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    if(FPromptIsQuery())
	    {
		FPromptBreakQuery();
		DO_RETURN_CURRENT_SCHEDUAL
	    }
	    /* All dialogs not in query */

	    /* Reset still processing marker, which indicates that
	     * there are still windows that are processing and cannot
	     * be closed
	     */
	    still_processing = FALSE;

	    /* Reset has changes marker, which indicates there is
	     * atleast one window with unsaved modified data
	     */
	    has_changes = FALSE;

	    /* Begin checking through all windows to see if any are
	     * still processing or have changes
	     */

	    /* File Browsers */
	    for(i = 0; i < core->total_browsers; i++)
	    {
		browser = core->browser[i];
		if(browser == NULL)
		    continue;

		if(browser->processing)
		    still_processing = TRUE;
	    }
	    /* Image Browsers */
	    for(i = 0; i < core->total_imbrs; i++)
	    {
		imbr = core->imbr[i];
		if(imbr == NULL)
		    continue;

		if(imbr->processing)
		    still_processing = TRUE;
	    }
	    /* Archivers */
	    for(i = 0; i < core->total_archivers; i++)
	    {
		archiver = core->archiver[i];
		if(archiver == NULL)
		    continue;

		if(archiver->processing)
		    still_processing = TRUE;

		arch_info = archiver->arch_info;
		if(arch_info != NULL)
		{
		    if(arch_info->processing)
			still_processing = TRUE;
		    if(arch_info->has_changes)
			has_changes = TRUE;
		}
	    }
	    /* Recycle Bin */
	    recbin = core->recbin;
	    if(recbin != NULL)
	    {
		if(recbin->processing)
		    still_processing = TRUE;
	    }
	    /* Find Window */
	    findwin = core->find_win;
	    if(findwin != NULL)
	    {
		if(findwin->processing)
		    still_processing = TRUE;
	    }
	    /* Property Dialogs */
	    for(i = 0; i < core->total_propdlgs; i++)
	    {
		propdlg = core->propdlg[i];
		if(propdlg == NULL)
		    continue;

		if(propdlg->processing)
		    still_processing = TRUE;
		if(propdlg->has_changes)
		    has_changes = TRUE;
	    }
	    /* History List Window */
	    histwin = core->history_listwin;
	    if(histwin != NULL)
	    {
		if(histwin->processing)
		    still_processing = TRUE;
	    }
	    /* Run Dialog */
	    rundlg = core->run_dlg;
	    if(rundlg != NULL)
	    {
		if(rundlg->processing)
		    still_processing = TRUE;
	    }
	    /* Devices List Window */
	    deviceswin = core->device_listwin;
	    if(deviceswin != NULL)
	    {
		if(deviceswin->processing)
		    still_processing = TRUE;
	    }
	    /* MIME Types Window */
	    mimetypeswin = core->mimetype_listwin;
	    if(mimetypeswin != NULL)
	    {
		if(mimetypeswin->processing)
		    still_processing = TRUE;
	    }
	    /* Options Window */
	    optwin = core->options_window;
	    if(optwin != NULL)
	    {
		if(optwin->processing)
		    still_processing = TRUE;
		if(optwin->has_changes)
		    has_changes = TRUE;
	    }
	    /* Customize Window */
	    optwin = core->customize_window;
	    if(optwin != NULL)
	    {
		if(optwin->processing)
		    still_processing = TRUE;
		if(optwin->has_changes)
		    has_changes = TRUE;
	    }
	    /* About Dialog */
	    aboutdlg = core->about_dlg;
	    if(aboutdlg != NULL)
	    {
		if(aboutdlg->processing)
		    still_processing = TRUE;
	    }

	    /* Begin checking results, to see if any windows are still
	     * processing or have unsaved changed data
	     */

	    /* Any still processing? */
	    if(still_processing)
	    {
		/* One or more window is still processing, cannot
		 * close now so we need to return
		 *
		 * The next call to this function won't close all
		 * windows since we set the global marker for that
		 * to FALSE
		 */
		DO_RETURN_CURRENT_SCHEDUAL
	    }


	    /* Any have changed data? */
	    if(has_changes)
	    {
/* TODO */

	    }

	    /* Remove all timeout callbacks and break out of the GTK
	     * main loop
	     */
	    DO_REMOVE_TIMEOUTS_AND_BREAK_MAIN

	    /* Return and do not call this function again */
	    DO_RETURN_NO_RESCHEDUAL
	}


	/* Need to reset? */
	if(core->pending_flags & EDV_CORE_PENDING_RESET)
	{
	    core->pending_flags &= ~EDV_CORE_PENDING_RESET;
	    EDVReset(core);
	}


	/* Begin checking to see if at least one window is unmapped
	 * and delete some unmapped windows
	 *
	 * Reset all_windows_unmapped to TRUE, it will be set to FALSE
	 * if at least one window is mapped
	 */
	all_windows_unmapped = TRUE;

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	{
	    browser = core->browser[i];
	    if(browser == NULL)
		continue;

	    /* Mapped? */
	    if(EDVBrowserIsMapped(browser))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Unmapped, so delete it */
		core->browser[i] = NULL;
		EDVBrowserDelete(browser);
		browser = NULL;
	    }
	}
	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	{
	    imbr = core->imbr[i];
	    if(imbr == NULL)
		continue;

	    /* Mapped? */
	    if(EDVImbrIsMapped(imbr))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Unmapped, so delete it */
		core->imbr[i] = NULL;
		EDVImbrDelete(imbr);
		imbr = NULL;
	    }
	}
	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	{
	    archiver = core->archiver[i];
	    if(archiver == NULL)
		continue;

	    /* Mapped? */
	    if(EDVArchiverIsMapped(archiver))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Unmapped, so delete it */
		core->archiver[i] = NULL;
		EDVArchiverDelete(archiver);
		archiver = NULL;
	    }
	}
	/* Recycle Bin */
	recbin = core->recbin;
	if(recbin != NULL)
	{
	    /* Initialized and mapped? */
	    if(EDVRecBinIsMapped(recbin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->recbin = NULL;
		EDVRecBinDelete(recbin);
		recbin = NULL;
	    }
	}
	/* Find Window */
	findwin = core->find_win;
	if(findwin != NULL)
	{
	    /* Initialized and mapped? */
	    if(EDVFindWinIsMapped(findwin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Never delete the find window since it may hold
		 * dynamic data (i.e. find results) that needs to be
		 * preserved
		 */
	    }
	}
	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	{
	    propdlg = core->propdlg[i];
	    if(propdlg == NULL)
		continue;

	    /* Initialized and mapped? */
	    if(EDVPropDlgIsMapped(propdlg))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Unmapped, so delete it */
		core->propdlg[i] = NULL;
		EDVPropDlgDelete(propdlg);
		propdlg = NULL;
	    }
	}
	/* History List Window */
	histwin = core->history_listwin;
	if(histwin != NULL)
	{
	    /* Initialized and mapped? */
	    if(EDVHistoryWinIsMapped(histwin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->history_listwin = NULL;
		EDVHistoryWinDelete(histwin);
		histwin = NULL;
	    }
	}
	/* Run Dialog */
	rundlg = core->run_dlg;
	if(rundlg != NULL)
	{
	    if(EDVRunDlgIsMapped(rundlg))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->run_dlg = NULL;
		EDVRunDlgDelete(rundlg);
		rundlg = NULL;
	    }
	}

	/* Devices List Window */
	deviceswin = core->device_listwin;
	if(deviceswin != NULL)
	{
	    if(EDVDevicesListWinIsMapped(deviceswin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->device_listwin = NULL;
		EDVDevicesListWinDelete(deviceswin);
		deviceswin = NULL;
	    }
	}
	/* MIME Types Window */
	mimetypeswin = core->mimetype_listwin;
	if(mimetypeswin != NULL)
	{
	    if(EDVMimeTypesListWinIsMapped(mimetypeswin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->mimetype_listwin = NULL;
		EDVMimeTypesListWinDelete(mimetypeswin);
		mimetypeswin = NULL;
	    }
	}
	/* Options Window */
	optwin = core->options_window;
	if(optwin != NULL)
	{
	    if(EDVGenOptWinIsMapped(optwin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->options_window = NULL;
		EDVGenOptWinDelete(optwin);
		optwin = NULL;
	    }
	}
	/* Customize Window */
	optwin = core->customize_window;
	if(optwin != NULL)
	{
	    if(EDVGenOptWinIsMapped(optwin))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else if(conserve_memory)
	    {
		/* Unmapped and need to conserve memory, so delete it */
		core->customize_window = NULL;
		EDVGenOptWinDelete(optwin);
		optwin = NULL;
	    }
	}
	/* About Dialog */
	aboutdlg = core->about_dlg;
	if(aboutdlg != NULL)
	{
	    /* Initialized and mapped? */
	    if(AboutDlgIsMapped(aboutdlg))
	    {
		all_windows_unmapped = FALSE;
	    }
	    else
	    {
		/* Unmapped, so delete it */
		core->about_dlg = NULL;
		AboutDlgDelete(aboutdlg);
		aboutdlg = NULL;
	    }
	}


	/* Were all windows unmapped? */
	if(all_windows_unmapped)
	{
	    /* All windows are unmapped, this implies that the
	     * application must begin exiting
	     */

	    /* Removes all timeout callbacks and breaks out of one GTK
	     * main loop
	     */
	    DO_REMOVE_TIMEOUTS_AND_BREAK_MAIN

	    /* Return and do not call this function again */
	    DO_RETURN_NO_RESCHEDUAL
	}



	/* Check and handle any InterPS commands if the InterPS
	 * signal count is positive
	 */
	if(core->interps_signal_count > 0)
	{
	    /* Get all the InterPS commands from the InterPS command
	     * file (if any)
	     */
	    interps_idle_data_struct *d;
	    gchar **cmd_list = EDVInterPSGetCommandsList(cfg_list);

	    /* Remove the InterPS command file */
	    EDVInterPSRemoveCommandsList(cfg_list);

	    /* Reduce the InterPS signal count per this handling */
	    core->interps_signal_count--;
	    if(core->interps_signal_count < 0)
		core->interps_signal_count = 0;

	    /* Schedual to process the InterPS commands */
	    d = INTERPS_IDLE_DATA(g_malloc(
		sizeof(interps_idle_data_struct)
	    ));
	    if(d != NULL)
	    {
		d->core = core;
		d->cmd_list = cmd_list;
		gtk_idle_add_priority(
		    G_PRIORITY_LOW,
		    EDVInterPSCommandsIdleCB, d
		);
	    }
	    else
	    {
		if(cmd_list != NULL)
		    g_strfreev(cmd_list);
	    }
	}


	DO_RETURN_RESCHEDUAL
#undef DO_RETURN_RESCHEDUAL
#undef DO_RETURN_NO_RESCHEDUAL
#undef DO_RETURN_CURRENT_SCHEDUAL
#undef DO_REMOVE_TIMEOUTS_AND_BREAK_MAIN
#undef DO_REMOVE_TOID
}


/*
 *	Check devices timeout callback.
 */
gint EDVCheckDevicesCB(gpointer data)
{
	gboolean last_mount_state;
	gint i;
	edv_device_struct *d;
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return(FALSE);

/* TODO */

	/* Iterate through each device */
	for(i = 0; i < core->total_devices; i++)
	{
	    d = core->device[i];
	    if(d == NULL)
		continue;

	    /* Record previous mount state */
	    last_mount_state = EDV_DEVICE_IS_MOUNTED(d);

	    /* Get current mount state */
	    EDVDeviceUpdateMountState(d);

	    /* Change in mount state? */
	    if(EDV_DEVICE_IS_MOUNTED(d) != last_mount_state)
	    {
		/* Report mount/unmount */
		EDVObjectMountEmit(core, i, d, EDV_DEVICE_IS_MOUNTED(d));
	    }
	}


	return(TRUE);
}


/*
 *	New File Browser callback.
 */
void EDVNewBrowserCB(GtkWidget *widget, gpointer data)
{
	EDVNewBrowser(EDV_CORE(data), NULL);
}

/*
 *	New Image Browser callback.
 */
void EDVNewImbrCB(GtkWidget *widget, gpointer data)
{
	EDVNewImbr(EDV_CORE(data), NULL);
}

/*
 *	New Archiver callback.
 */
void EDVNewArchiverCB(GtkWidget *widget, gpointer data)
{
	EDVNewArchiver(EDV_CORE(data), NULL, NULL);
}

/*
 *	Map Recycle Bin callback.
 */
void EDVMapRecBinCB(GtkWidget *widget, gpointer data)
{
	EDVMapRecBin(EDV_CORE(data));
}

/*
 *	Map Devices List callback.
 */
void EDVDevicesCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;

	EDVMapDevicesListWin(EDV_CORE(data), toplevel);
}

/*
 *	Map MIME Types List callback.
 */
void EDVMIMETypesCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;

	EDVMapMIMETypesListWin(EDV_CORE(data), NULL, toplevel);
}

/*
 *	Map Options Window callback.
 */
void EDVOptionsCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;

	EDVMapOptionsWin(EDV_CORE(data), toplevel);
}

/*
 *	Map Customize Window callback.
 */
void EDVCustomizeCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;

	EDVMapCustomizeWin(EDV_CORE(data), toplevel);
}


/*
 *	GtkCList column sort callback nexus.
 *
 *	This function only sorts by the cell's text value and not the
 *	row's data. Only cells of type GTK_CELL_TEXT and
 *	GTK_CELL_PIXTEXT will be sorted, all other types will
 *	reutrn -1.
 *
 *	The clist specifies the GtkCList.
 *
 *	The ptr1 specifies the first GtkCListRow.
 *
 *	The ptr2 specifies the second GtkCListRow.
 *
 *	The sort_type specifies what the text value describes (string,
 *	number, etc).
 */
static gint EDVCListColumnSortNexus(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2,
	const edv_sort_type sort_type
)
{
	gint sort_column;
	const gchar	*text1 = NULL,
			*text2 = NULL;
	const GtkCListRow	*row1 = (const GtkCListRow *)ptr1,
				*row2 = (const GtkCListRow *)ptr2;

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

	/* Get the column number that we are to sort by and what
	 * type of sorting (ascending or descending)
	 */
	sort_column = clist->sort_column;
	if((sort_column < 0) || (sort_column >= clist->columns))
	    return(-1);

	/* Get the first cell's text value */
	switch(row1->cell[sort_column].type)
	{
	  case GTK_CELL_TEXT:
	    text1 = GTK_CELL_TEXT(row1->cell[sort_column])->text;
	    break;
	  case GTK_CELL_PIXTEXT:
	    text1 = GTK_CELL_PIXTEXT(row1->cell[sort_column])->text;
	    break;
	  default:
	    break;
	}

	/* Get the second cell's text value */
	switch(row2->cell[sort_column].type)
	{
	  case GTK_CELL_TEXT:
	    text2 = GTK_CELL_TEXT(row2->cell[sort_column])->text;
	    break;
	  case GTK_CELL_PIXTEXT:
	    text2 = GTK_CELL_PIXTEXT(row2->cell[sort_column])->text;
	    break;
	  default:
	    break;
	}

	if(text2 == NULL)
	    return((text1 != NULL) ? 1 : -1);
	if(text1 == NULL)
	    return(-1);

	/* Sort by the text value's descriptive type */
	switch(sort_type)
	{
	  case EDV_SORT_TYPE_TEXT:
	    /* The text value is plain text, sort alphabetically */
	    return(strcmp(text1, text2));
	    break;

	  case EDV_SORT_TYPE_NUMBER:
	    /* The text value is a number, sort numerically */
	    {
		/* Number strings may contain notation characters such
		 * as ',', '#', or '$', so copy them to tempory strings
		 * and then remove those characters them
		 */
		gchar	ns1[80], ns2[80];
		gint	n1, n2;

		/* Copy the strings to tempory and modifyable number
		 * strings
		 */
		strncpy(ns1, text1, sizeof(ns1));
		ns1[sizeof(ns1) - 1] = '\0';
		strncpy(ns2, text2, sizeof(ns2));
		ns2[sizeof(ns2) - 1] = '\0';

		/* Remove any notation characters */
		substr(ns1, ",", "");
		substr(ns1, "#", "");
		substr(ns1, "$", "");
		substr(ns2, ",", "");
		substr(ns2, "#", "");
		substr(ns2, "$", "");

		n1 = strtod(ns1, NULL),
		n2 = strtod(ns2, NULL);

		if(n1 >= n2)
		    return((gint)(n1 > n2));
		else
		    return(-1);
	    }
	    break;

	  default:
	    return(-1);
	    break;
	}
}

/*
 *	GtkCList column sort by string callback.
 */
gint EDVCListColumnSortStringCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVCListColumnSortNexus(
	    clist, ptr1, ptr2, EDV_SORT_TYPE_TEXT
	));
}

/*
 *	GtkCList column sort by numeric callback.
 */
gint EDVCListColumnSortNumberCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVCListColumnSortNexus(
	    clist, ptr1, ptr2, EDV_SORT_TYPE_NUMBER
	));
}


/*
 *	Refresh Callback.
 */
void EDVRefreshCB(GtkWidget *widget, gpointer data)
{
	EDVRefresh(EDV_CORE(data));
}


/*
 *	Purge Recycled Objects callback.
 */
void EDVPurgeAllRecycledObjectsCB(GtkWidget *widget, gpointer data)
{
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return;

	/* Map the Recycle Bin */
	EDVMapRecBin(core);

	/* Purge all the objects from the Recycle Bin */
	EDVRecBinOPPurgeAll(core->recbin);
}

/*
 *	Clear History Events callback.
 */
void EDVClearHistoryCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return;

	EDVClearHistoryEvents(core, toplevel);
}

/*
 *	Clear locations history callback.
 */
void EDVClearLocationsHistoryCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return;

	EDVClearLocationsHistory(core, toplevel);
}

/*
 *	Clear run history callback.
 */
void EDVClearRunHistoryCB(GtkWidget *widget, gpointer data)
{
	GtkWidget *toplevel = (widget != NULL) ?
	    gtk_widget_get_toplevel(widget) : NULL;
	edv_core_struct *core = EDV_CORE(data);
	if(core == NULL)
	    return;

	EDVClearRunHistory(core, toplevel);
}


/*
 *	GtkEntry DND "drag_motion" signal callback.
 *
 *	This is used to constrain all drags (regardless of its type
 *	or source data type) to be a drag action of copy.
 */
gboolean EDVEntryDragMotionCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y, guint t,
	gpointer data
)
{
	if(dc == NULL)
	    return(FALSE);

	if(dc->actions & GDK_ACTION_COPY)
	    gdk_drag_status(dc, GDK_ACTION_COPY, t);
	else
	    gdk_drag_status(dc, 0, t);

	return(TRUE);
}

/*
 *	GtkEntry DND "drag_data_received" signal callback.
 *
 *	This is used as an opaque callback to support the dragging of any
 *	disk object to any GtkEntry widget. The value in the GtkEntry
 *	will be replaced with a space separated list of absolute disk
 *	object paths.
 *
 *	Note that only the text is updated, no "activate" signal will
 *	be emitted.
 */
void EDVEntryDragDataReceivedCB(
	GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
	GtkSelectionData *selection_data, guint info, guint t,
	gpointer data
)
{
	GList *url_list = NULL;
	GtkEntry *entry;
	edv_core_struct *core = EDV_CORE(data);
	if((widget == NULL) || (dc == NULL) || (selection_data == NULL) ||
	   (core == NULL)
	)
	    return;

#define DO_FREE_LOCALS	{				\
 g_list_foreach(url_list, (GFunc)URLDelete, NULL);	\
 g_list_free(url_list);					\
 url_list = NULL;					\
}

	/* Given widget must have or be a GtkEntry */
	if(GTK_IS_COMBO(widget))
	{
	    GtkCombo *combo = GTK_COMBO(widget);
	    entry = GTK_ENTRY(combo->entry);
	}
	else if(GTK_IS_ENTRY(widget))
	{
	    entry = GTK_ENTRY(widget);
	}
	else
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Decode buffer into a list of URLs */
	url_list = URLDecode(
	    (const guint8 *)selection_data->data,
	    selection_data->length
	);
	if(url_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Drag data is a disk object path? */
	if((info == EDV_DND_TYPE_INFO_TEXT_PLAIN) ||
	   (info == EDV_DND_TYPE_INFO_TEXT_URI_LIST) ||
	   (info == EDV_DND_TYPE_INFO_STRING)
	)
	{
	    gchar *s = STRDUP("");
	    GList *glist;
	    const url_struct *url;

	    /* Generate a new text value as a space separated list
	     * of paths obtained from the URL list
	     */
	    for(glist = url_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		url = URL(glist->data);
		if(url == NULL)
		    continue;

		if(STRISEMPTY(url->path))
		    continue;

		/* Prefix a space if this is not the first path */
		if(*s != '\0')
		    s = strinschr(s, -1, ' ');

		/* Append this path to the string */
		s = strinsstr(s, -1, url->path);
	    }

	    /* Generated new text value? */
	    if(s != NULL)
	    {
		gtk_entry_set_text(entry, s);
		gtk_entry_set_position(entry, -1);
	    }

	    g_free(s);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}


/*
 *	GtkEntry complete path "key_press_event" or "key_release_event"
 *	signal callback.
 */
gint EDVEntryCompletePathCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
	gint status = 0;
	gboolean press;
	GtkEntry *entry;
	edv_core_struct *core = EDV_CORE(data);
	if((widget == NULL) || (key == NULL) || (core == NULL))
	    return(status);

	press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;

	/* Given widget must have or be a GtkEntry */
	if(GTK_IS_COMBO(widget))
	{
	    GtkCombo *combo = GTK_COMBO(widget);
	    entry = GTK_ENTRY(combo->entry);
	}
	else if(GTK_IS_ENTRY(widget))
	{
	    entry = GTK_ENTRY(widget);
	}
	else
	{
	    return(status);
	}

#define SIGNAL_EMIT_STOP	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
  press ?					\
   "key_press_event" : "key_release_event"	\
 );						\
}

	switch(key->keyval)
	{
	  case GDK_Tab:
	    /* Skip this if the shift or ctrl keys are held */
	    if((key->state & GDK_CONTROL_MASK) ||
	       (key->state & GDK_SHIFT_MASK)
	    )
		return(status);

	    if(press)
	    {
		gchar *s = STRDUP(gtk_entry_get_text(entry));
		if(s != NULL)
		{
		    gint status;

		    s = CompletePath(s, &status);
		    gtk_entry_set_text(entry, (s != NULL) ? s : "");
		    gtk_entry_set_position(entry, -1);
		    g_free(s);

		    switch(status)
		    {
		      case COMPLETE_PATH_NONE:
		      case COMPLETE_PATH_AMBIGUOUS:
			gdk_beep();
			break;
		    }
		}
	    }
	    SIGNAL_EMIT_STOP
	    status = TRUE;
	    break;
	}

#undef SIGNAL_EMIT_STOP

	return(status);
}


/*
 *	Location bar icon "drag_data_get" signal callback.
 *
 *	The data is a GtkCombo.
 */
void EDVLocBarIconDragDataGet(
	GtkWidget *widget, GdkDragContext *context,
	GtkSelectionData *selection_data, guint info, guint time,
	gpointer data
)
{
	gboolean data_sent = FALSE;
	guint8 *buf;
	gint buf_len;
	GList *url_list = NULL;
	GtkEntry *entry;
	GtkCombo *combo = (GtkCombo *)data;
	url_struct *url;
	if((selection_data == NULL) || (combo == NULL))
	    return;

	entry = GTK_ENTRY(combo->entry);
	if(entry == NULL)
	    return;

	/* Generate a URL list from the current entry text */
	url = URLNew();
	url->path = STRDUP(gtk_entry_get_text(entry));
	url_list = g_list_append(url_list, url);

	/* Encode the buffer from the URL list */
	buf = URLEncode(url_list, &buf_len);
	if(buf != NULL)
	{
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,		/* 8 bits per character */
		buf,
		buf_len
	    );
	    data_sent = TRUE;
	}

	g_free(buf);

	g_list_foreach(url_list, (GFunc)URLDelete, NULL);
	g_list_free(url_list);

	/* If failed to send data then send error response */
	if(!data_sent)
	{
	    const gchar *s = "Error";
	    gtk_selection_data_set(
		selection_data,
		GDK_SELECTION_TYPE_STRING,
		8,		/* 8 bits per character */
		s,
		STRLEN(s)
	    );
	    data_sent = TRUE;
	}
}


/*
 *	Write protect changed emit.
 *
 *	Notifies all relivant windows about the write protect state
 *	being changed.
 *
 *	The state should be the new write protect state.
 */
void EDVWriteProtectChangedEmit(
	edv_core_struct *core, gboolean state
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserWriteProtectChangedCB(
		core->browser[i], state
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrWriteProtectChangedCB(
		core->imbr[i], state
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverWriteProtectChangedCB(
		core->archiver[i], state
	    );

	/* Recycle Bin Desktop Icon */
	EDVRecBinDeskIconUpdate(core->recbin_deskicon);

	/* Recycle Bin */
	EDVRecBinWriteProtectChangedCB(core->recbin, state);

	/* Find Window */
	EDVFindWinWriteProtectChangedCB(core->find_win, state);

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgWriteProtectChangedCB(
		core->propdlg[i], state
	    );

	/* History window */
	EDVHistoryWinWriteProtectChangedCB(
	    core->history_listwin, state
	);
}

/*
 *	Emits an object added signal to all relivant windows.
 */
void EDVObjectAddedEmit(
	edv_core_struct *core, const gchar *path,
	const struct stat *lstat_buf
)
{
	gint i;
	gchar *dpath;

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

	/* Copy the specified path */
	dpath = STRDUP(path);

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserObjectAddedNotifyCB(
		core->browser[i], dpath, lstat_buf
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrObjectAddedNotifyCB(
		core->imbr[i], dpath, lstat_buf
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverObjectAddedNotifyCB(
		core->archiver[i], dpath, lstat_buf
	    );

	/* Skip the Recycle Bin */

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgObjectAddedNotifyCB(
		core->propdlg[i], dpath, lstat_buf
	    );

	g_free(dpath);
}

/*
 *	Emits an object modified signal to all relivant windows.
 */
void EDVObjectModifiedEmit(
	edv_core_struct *core, const gchar *path,
	const gchar *new_path,
	const struct stat *lstat_buf
)
{
	gint i;
	gchar *dpath;

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

	/* Copy the specified path */
	dpath = STRDUP(path);

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserObjectModifiedNotifyCB(
		core->browser[i], dpath, new_path, lstat_buf
	    );

	/* Image browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrObjectModifiedNotifyCB(
		core->imbr[i], dpath, new_path, lstat_buf
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverObjectModifiedNotifyCB(
		core->archiver[i], dpath, new_path, lstat_buf
	    );

	/* Skip the Recycle Bin */

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgObjectModifiedNotifyCB(
		core->propdlg[i], dpath, new_path, lstat_buf
	    );

	g_free(dpath);
}

/*
 *	Emits an object removed signal to all relivant windows.
 */
void EDVObjectRemovedEmit(edv_core_struct *core, const gchar *path)
{
	gint i;
	gchar *dpath;

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

	/* Copy the specified path */
	dpath = STRDUP(path);

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserObjectRemovedNotifyCB(
		core->browser[i], dpath
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrObjectRemovedNotifyCB(
		core->imbr[i], dpath
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverObjectRemovedNotifyCB(
		core->archiver[i], dpath
	    );

	/* Skip the Recycle Bin */

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgObjectRemovedNotifyCB(
		core->propdlg[i], dpath
	    );

	g_free(dpath);
}

/*
 *	Emits a device mount/unmount signal to all relivant windows.
 */
void EDVObjectMountEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev,
	gboolean is_mounted
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Begin emitting signal */

	/* Devices List */
	EDVDevicesListWinMountNotifyCB(
	    core->device_listwin, dev_num, dev, is_mounted
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserMountNotifyCB(
		core->browser[i], dev_num, dev, is_mounted
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrMountNotifyCB(
		core->imbr[i], dev_num, dev, is_mounted
	    );

	/* Skip Archivers */

	/* Skip the Recycle Bin */

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgMountNotifyCB(
		core->propdlg[i], dev_num, dev, is_mounted
	    );
}


/*
 *	Emits an object added to archive signal to all relivant windows.
 *
 *	The arch_path specifies the location of the archive that the
 *	object specified by path was added to.
 *
 *	The obj contains the archive object's statistics.
 */
void EDVArchiveObjectAddedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path, edv_archive_object_struct *obj
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverArchiveObjectAddedNotifyCB(
		core->archiver[i], arch_path, path, obj
	    );
}

/*
 *	Emits an object modified in an archive signal to all relivant
 *	windows.
 *
 *	The arch_path specifies the location of the archive that the
 *	object specified by path was added to.
 *
 *	The obj contains the archive object's statistics.
 */
void EDVArchiveObjectModifiedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverArchiveObjectModifiedNotifyCB(
		core->archiver[i], arch_path, path, new_path, obj
	    );
}

/*
 *	Emits an object removed from archive signal to all relivant
 *	windows.
 *
 *	The arch_path specifies the location of the archive that the
 *	object specified by path was added to.
 */
void EDVArchiveObjectRemovedEmit(
	edv_core_struct *core, const gchar *arch_path,
	const gchar *path
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

	/* Begin emitting signal */

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverArchiveObjectRemovedNotifyCB(
		core->archiver[i], arch_path, path
	    );
}


/*
 *	Emits a recycled object added signal to all relivant windows.
 */
void EDVRecycledObjectAddedEmit(
	edv_core_struct *core, guint index
)
{
	gint i;
	const gchar *recbin_index_file;
	const cfg_item_struct *cfg_list;

	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	recbin_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

#if 1
	/* Update number of recycled objects */
	core->last_recbin_items = EDVRecBinIndexGetTotal(
	    recbin_index_file
	);
#else
	/* Use simplified method by just checking of the index file
	 * exists which implies there is at least one object
	 *
	 * This limits the result to be 0 or 1 items
	 */
	if(recbin_index_file != NULL)
	{

	    struct stat stat_buf;
	    if(stat(recbin_index_file, &stat_buf))
		core->last_recbin_items = 0;
	    else
		core->last_recbin_items = (stat_buf.st_size > 0) ?
		    1 : 0;
	}
	else
	{
	    core->last_recbin_items = 0;
	}
#endif

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserRecycledObjectAddedNotifyCB(
		core->browser[i], index
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrRecycledObjectAddedNotifyCB(
		core->imbr[i], index
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverRecycledObjectAddedNotifyCB(
		core->archiver[i], index
	    );

	/* Recycle Bin Desktop Icon */
	EDVRecBinDeskIconUpdate(core->recbin_deskicon);

	/* Recycle Bin */
	EDVRecBinRecycledObjectAddedNotifyCB(
	    core->recbin, index
	);
}

/*
 *	Emits a recycled object removed signal to all relivant windows.
 */
void EDVRecycledObjectRemovedEmit(
	edv_core_struct *core, guint index
)
{
	gint i;
	const gchar *recbin_index_file;
	const cfg_item_struct *cfg_list;

	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	recbin_index_file = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);

	/* Update the devices list */
	EDVDevicesListUpdateStats(
	    core->device, core->total_devices
	);

	/* Update the devices list window */
	EDVDevicesListWinUsageChangedCB(core->device_listwin);

#if 1
	/* Update number of recycled objects on the core structure */
	core->last_recbin_items = EDVRecBinIndexGetTotal(
	    recbin_index_file
	);
#else
	/* Use simplified method by just checking of the index file
	 * exists which implies there is at least one object
	 *
	 * This limits the result to be 0 or 1 items
	 */
	if(recbin_index_file != NULL)
	{
	    struct stat stat_buf;
	    if(stat(recbin_index_file, &stat_buf))
		core->last_recbin_items = 0;
	    else
		core->last_recbin_items = (stat_buf.st_size > 0) ?
		    1 : 0;
	}
	else
	{
	    core->last_recbin_items = 0;
	}
#endif

	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserRecycledObjectRemovedNotifyCB(
		core->browser[i], index
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrRecycledObjectRemovedNotifyCB(
		core->imbr[i], index
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverRecycledObjectRemovedNotifyCB(
		core->archiver[i], index
	    );

	/* Recycle Bin Desktop Icon */
	EDVRecBinDeskIconUpdate(core->recbin_deskicon);

	/* Recycle Bin */
	EDVRecBinRecycledObjectRemovedNotifyCB(
	    core->recbin, index
	);
}


/*
 *	Emits a reconfigured notify signal.
 */
void EDVReconfiguredEmit(edv_core_struct *core)
{
	gint i;
	GtkRcStyle *standard_rcstyle, *lists_rcstyle;
	const cfg_item_struct *cfg_list;

	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	/* Update the RC styles */
	EDVUpdateRCStyles(core);
	standard_rcstyle = core->standard_rcstyle;
	lists_rcstyle = core->lists_rcstyle;

	/* Update the global Tool Tips state */
	GUISetGlobalTipsState(EDV_GET_B(EDV_CFG_PARM_SHOW_TOOLTIPS));


	/* Update the popup lists */

	/* Users & Groups popup lists */
	PUListDelete(core->users_pulist);
	core->users_pulist = NULL;
	PUListDelete(core->groups_pulist);
	core->groups_pulist = NULL;
	EDVUpdateIDPULists(core);

	/* Devices popup list */
	PUListDelete(core->devices_pulist);
	core->devices_pulist = NULL;
	EDVUpdateDevicesPUList(core);

	/* Open With popup list */
	PUListDelete(core->open_with_pulist);
	core->open_with_pulist = NULL;
	EDVUpdateOpenWithPUList(core);


	/* Begin emitting signal */

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserReconfiguredNotifyCB(
		core->browser[i]
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrReconfiguredNotifyCB(
		core->imbr[i]
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverReconfiguredNotifyCB(
		core->archiver[i]
	    );

	/* Recycle Bin Desktop Icon */
	if(CFGItemListGetValueI(
	    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_DESKTOP_ICON
	))
	    EDVMapRecBinDeskIcon(core);
	else
	    EDVRecBinDeskIconUnmap(core->recbin_deskicon);

	/* Recycle Bin */
	EDVRecBinReconfiguredNotifyCB(core->recbin);

	/* Find Window */
	EDVFindWinReconfiguredNotifyCB(core->find_win);

	/* Property Dialogs */
	for(i = 0; i < core->total_propdlgs; i++)
	    EDVPropDlgReconfiguredNotifyCB(
		core->propdlg[i]
	    );

	/* History List Window */
	EDVHistoryWinReconfiguredNotifyCB(
	    core->history_listwin
	);

	/* Devices List Window */
	EDVDevicesListWinReconfiguredNotifyCB(
	    core->device_listwin
	);

	/* MIME Types Window */
	EDVMimeTypesListWinReconfiguredNotifyCB(
	    core->mimetype_listwin
	);

}


/*
 *	Emits a MIME Type added signal.
 */
void EDVMimeTypeAddedEmitCB(
	const gint mt_num, edv_mime_type_struct *mt, gpointer data
)
{
	EDVMimeTypeAddedEmit(EDV_CORE(data), mt_num, mt);
}
void EDVMimeTypeAddedEmit(
	edv_core_struct *core,
	const gint mt_num, edv_mime_type_struct *mt
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update MIME Types class list index hints due to change in
	 * the MIME Types list ordering
	 */
	EDVUpdateMIMETypeHintIndices(core);

	/* Reget list of application MIME Types on the `open with'
	 * popup list
	 */
	EDVUpdateOpenWithPUList(core);


	/* Begin emitting signal */

	/* MIME Types Window */
	EDVMimeTypesListWinMimeTypeAddedCB(
	    core->mimetype_listwin, mt_num, mt
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserMimeTypeAddedCB(
		core->browser[i], mt_num, mt
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrMimeTypeAddedCB(
		core->imbr[i], mt_num, mt
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverMimeTypeAddedCB(
		core->archiver[i], mt_num, mt
	    );

	/* Recycle Bin */
	EDVRecBinMimeTypeAddedCB(
	    core->recbin, mt_num, mt
	);
}

/*
 *	Emits a MIME Type modified signal.
 */
void EDVMimeTypeModifiedEmitCB(
	const gint mt_num, edv_mime_type_struct *mt, gpointer data
)
{
	EDVMimeTypeModifiedEmit(EDV_CORE(data), mt_num, mt);
}
void EDVMimeTypeModifiedEmit(
	edv_core_struct *core,
	const gint mt_num, edv_mime_type_struct *mt
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update MIME Types class list index hints due to change in
	 * the MIME Types list ordering
	 */
	EDVUpdateMIMETypeHintIndices(core);

	/* Reget list of application MIME Types on the `open with'
	 * popup list
	 */
	EDVUpdateOpenWithPUList(core);


	/* Begin emitting signal */

	/* MIME Types Window */
	EDVMimeTypesListWinMimeTypeModifiedCB(
	    core->mimetype_listwin, mt_num, mt
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserMimeTypeModifiedCB(
		core->browser[i], mt_num, mt
	    );

	/* Image browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrMimeTypeModifiedCB(
		core->imbr[i], mt_num, mt
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverMimeTypeModifiedCB(
		core->archiver[i], mt_num, mt
	    );

	/* Recycle Bin */
	EDVRecBinMimeTypeModifiedCB(
	    core->recbin, mt_num, mt
	);
}

/*
 *	Emits a MIME Type removed signal to all of endeavour's windows who
 *	are interested.
 */
void EDVMimeTypeRemovedEmitCB(
	const gint mt_num, gpointer data
)
{
	EDVMimeTypeRemovedEmit(EDV_CORE(data), mt_num);
}
void EDVMimeTypeRemovedEmit(
	edv_core_struct *core, const gint mt_num
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update MIME Types class list index hints due to change in
	 * the MIME Types list ordering
	 */
	EDVUpdateMIMETypeHintIndices(core);

	/* Reget list of application MIME Types on the `open with'
	 * popup list
	 */
	EDVUpdateOpenWithPUList(core);


	/* Begin emitting signal */

	/* MIME Types Window */
	EDVMimeTypesListWinMimeTypeRemovedCB(
	    core->mimetype_listwin, mt_num
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserMimeTypeRemovedCB(
		core->browser[i], mt_num
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrMimeTypeRemovedCB(
		core->imbr[i], mt_num
	    );

	/* Archivers */
	for(i = 0; i < core->total_archivers; i++)
	    EDVArchiverMimeTypeRemovedCB(
		core->archiver[i], mt_num
	    );

	/* Recycle Bin */
	EDVRecBinMimeTypeRemovedCB(
	    core->recbin, mt_num
	);
}


/*
 *	Emits a device reference added signal.
 */
void EDVDeviceAddedEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the Devices Popup List */
	EDVUpdateDevicesPUList(core);


	/* Begin emitting signal */

	/* Devices list window */
	EDVDevicesListWinDeviceAddedCB(
	    core->device_listwin, dev_num, dev
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserDeviceAddedCB(
		core->browser[i], dev_num, dev
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrDeviceAddedCB(
		core->imbr[i], dev_num, dev
	    );
}

/*
 *	Emits a device reference modified signal.
 */
void EDVDeviceModifiedEmit(
	edv_core_struct *core,
	gint dev_num, edv_device_struct *dev
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the Devices Popup List */
	EDVUpdateDevicesPUList(core);


	/* Begin emitting signal */

	/* Devices Window */
	EDVDevicesListWinDeviceModifiedCB(
	    core->device_listwin, dev_num, dev
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserDeviceModifiedCB(
		core->browser[i], dev_num, dev
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrDeviceModifiedCB(
		core->imbr[i], dev_num, dev
	    );
}

/*
 *	Emits a device reference removed signal.
 */
void EDVDeviceRemovedEmit(
	edv_core_struct *core, gint dev_num
)
{
	gint i;

	if(core == NULL)
	    return;

	/* Update the Devices Popup List */
	EDVUpdateDevicesPUList(core);


	/* Begin emitting signal */

	/* Devices Window */
	EDVDevicesListWinDeviceRemovedCB(
	    core->device_listwin, dev_num
	);

	/* File Browsers */
	for(i = 0; i < core->total_browsers; i++)
	    EDVBrowserDeviceRemovedCB(
		core->browser[i], dev_num
	    );

	/* Image Browsers */
	for(i = 0; i < core->total_imbrs; i++)
	    EDVImbrDeviceRemovedCB(
		core->imbr[i], dev_num
	    );
}


/*
 *	Emits a history event added signal.
 */
void EDVHistoryAddedEmit(
	edv_core_struct *core,
	gint h_num, edv_history_struct *h_ptr
)
{
	if(core == NULL)
	    return;

	/* Begin emitting signal */

	/* History Window */
	EDVHistoryWinHistoryAddedCB(
	    core->history_listwin,
	    h_num, h_ptr
	);
}

/*
 *	Emits a history event removed signal.
 */
void EDVHistoryRemovedEmit(
	edv_core_struct *core,
	gint h_num
)
{
	if(core == NULL)
	    return;

	/* Begin emitting signal */

	/* History Window */
	EDVHistoryWinHistoryRemovedCB(
	    core->history_listwin,
	    h_num
	);
}
