#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_archive_obj.h"
#include "edv_status_bar.h"
#include "archiver.h"
#include "archiver_cb.h"
#include "archiver_op_cb.h"
#include "archiver_dnd.h"
#include "archiver_contents_list.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"


/* GTK+ Signal Callbacks */
void EDVArchiverContentsItemDestroyCB(gpointer data);

static gint EDVArchiverCListColumnSortCompressionCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
static gint EDVArchiverCListColumnSortDateNexus(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2,
	gint sort_code
);
static gint EDVArchiverCListColumnSortDateAccessCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
static gint EDVArchiverCListColumnSortDateModifyCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);
static gint EDVArchiverCListColumnSortDateChangeCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
);

gint EDVArchiverDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
gint EDVArchiverKeyEventCB(
	 GtkWidget *widget, GdkEventKey *key, gpointer data
);
gint EDVArchiverButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);

void EDVArchiverHandleChildAttachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
);
void EDVArchiverHandleChildDetachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
);

void EDVArchiverResizeColumnCB(
	GtkCList *clist, gint column, gint width, gpointer data
);
void EDVArchiverClickColumnCB(
	GtkCList *clist, gint column, gpointer data
);
void EDVArchiverSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);
void EDVArchiverUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
);

void EDVArchiverComboActivateCB(GtkWidget *widget, gpointer data);

/* Menu Item Callbacks */
void EDVArchiverMenuItemCB(GtkWidget *widget, gpointer data);
gint EDVArchiverMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
gint EDVArchiverMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);

/* Find Bar Callbacks */
const gchar *EDVArchiverFindBarLocationCB(
	edv_findbar_struct *fb, gpointer data
);
void EDVArchiverFindBarStartCB(edv_findbar_struct *fb, gpointer data);
void EDVArchiverFindBarEndCB(
	edv_findbar_struct *fb, gint total_matches, gpointer data
);
void EDVArchiverFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
);

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

/* Write Protect Changed Callback */
void EDVArchiverWriteProtectChangedCB(
	edv_archiver_struct *archiver, gboolean state
);

/* Object Callbacks */
void EDVArchiverObjectAddedNotifyCB(
	edv_archiver_struct *archiver, const gchar *path,
	const struct stat *lstat_buf
);
void EDVArchiverObjectModifiedNotifyCB(
	edv_archiver_struct *archiver, const gchar *path,
	const gchar *new_path, const struct stat *lstat_buf
);
void EDVArchiverObjectRemovedNotifyCB(
	edv_archiver_struct *archiver, const gchar *path
);

/* Archive Object Callbacks */
void EDVArchiverArchiveObjectAddedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, edv_archive_object_struct *obj
);
void EDVArchiverArchiveObjectModifiedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
);
void EDVArchiverArchiveObjectRemovedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path
);

/* Recycled Object Callbacks */
void EDVArchiverRecycledObjectAddedNotifyCB(
	edv_archiver_struct *archiver, guint index
);
void EDVArchiverRecycledObjectRemovedNotifyCB(
	edv_archiver_struct *archiver, guint index
);

/* Reconfigured Callback */
void EDVArchiverReconfiguredNotifyCB(edv_archiver_struct *archiver);

/* MIME Type Callbacks */
void EDVArchiverMimeTypeAddedCB(
	edv_archiver_struct *archiver,
	gint mt_num, edv_mime_type_struct *mt
);
void EDVArchiverMimeTypeModifiedCB(
	edv_archiver_struct *archiver,
	gint mt_num, edv_mime_type_struct *mt
);
void EDVArchiverMimeTypeRemovedCB(
	edv_archiver_struct *archiver, gint mt_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)


/*
 *	Archiver contents GtkCList item "destroy" signal callback.
 */
void EDVArchiverContentsItemDestroyCB(gpointer data)
{
	EDVArchObjectDelete(EDV_ARCHIVE_OBJECT(data));
}


/*
 *	Returns the sort code for the rows sorted by compression.
 */
static gint EDVArchiverCListColumnSortCompressionCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	const GtkCListRow *row1 = (const GtkCListRow *)ptr1;
	const GtkCListRow *row2 = (const GtkCListRow *)ptr2;
	const edv_archive_object_struct *obj1, *obj2;

	if((clist == NULL) || (row1 == NULL) || (row2 == NULL))
	    return(0);

	obj1 = EDV_ARCHIVE_OBJECT(row1->data);
	obj2 = EDV_ARCHIVE_OBJECT(row2->data);
	if((obj1 != NULL) && (obj2 != NULL))
	{
	    if(obj1->compression_ratio <= obj2->compression_ratio)
		return((gint)(obj1->compression_ratio < obj2->compression_ratio));
	    else
		return(-1);
	}
	else
	    return(0);
}

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

	if((clist == NULL) || (row1 == NULL) || (row2 == NULL))
	    return(0);

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

	/* Handle by sort code */
	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 EDVArchiverCListColumnSortDateAccessCB(
	GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2
)
{
	return(EDVArchiverCListColumnSortDateNexus(
	    clist, ptr1, ptr2, 0
	));
}

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

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


/*
 *	Archiver toplevel GtkWidget "delete_event" signal callback.
 */
gint EDVArchiverDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return(TRUE);

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

	EDVArchiverOPClose(archiver);

	return(TRUE);
}

/*
 *	Archiver "key_press_event" or "key_release_event" signal callback.
 */
gint EDVArchiverKeyEventCB(
	 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_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((widget == NULL) || (key == NULL) || (archiver == NULL))
	    return(status);

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

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

	w = archiver->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_archiver_op op = (edv_archiver_op)EDVMatchAccelKeyOPID(
		cfg_list, EDV_CFG_PARM_ARCHIVER_ACCELERATOR_KEYS,
		keyval, state
	    );
	    if(op > 0)
	    {
		if(is_press)
		{
		    edv_archiver_opid_struct *opid = EDVArchiverMatchOPID(
			archiver, op
		    );
		    if(opid != NULL)
			EDVArchiverOPCB(NULL, -1, opid);
		}
		DO_STOP_KEY_SIGNAL_EMIT
		status = TRUE;
		return(status);
	    }
	}

	/* Check which widget this signal is for */

	/* Contents List */
	if(widget == archiver->contents_clist)
	{
	    GtkCList *clist = GTK_CLIST(widget);
	    gint row = EDVCListGetSelectedLast(clist, NULL);

	    /* Handle by key value */
	    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_ARCHIVER_CONTENTS_COLUMN
		    );
		    if(intlist != NULL)
		    {
			/* Iterate through column type intlist */
			gint i = 0;
			GList *glist = intlist->list;
			while(glist != NULL)
			{
			    if((edv_archiver_column_type)glist->data ==
				EDV_ARCHIVER_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
}

/*
 *	Archiver GtkWidget "button_press_event" signal callback.
 */
gint EDVArchiverButtonPressEventCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	edv_core_struct *core;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((widget == NULL) || (archiver == NULL))
	    return(status);

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

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

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

	/* Check which widget this signal is for */
	if(widget == archiver->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(CFGItemListGetValueI(
			core->cfg_list, 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 */
		    EDVArchiverUpdateMenus(archiver);
		    menu = (GtkMenu *)archiver->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))
		    {
/* Renaming not allowed */
		    }
		}
		break;

	      case 1:
		/* Double click? */
		if(etype == GDK_2BUTTON_PRESS)
		{
		    if((row >= 0) && (row < clist->rows))
		    {
			/* Do extract archive object */
			EDVArchiverOPExtract(archiver);
			status = TRUE;
		    }
		}
		break;
	    }

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

	return(status);
}


/*
 *      GtkHandleBox "child_attached" signal callback.
 */
void EDVArchiverHandleChildAttachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((handle_box == NULL) || (archiver == NULL))
	    return;

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

/*
 *      GtkHandleBox "child_detached" signal callback.
 */
void EDVArchiverHandleChildDetachedCB(
	GtkHandleBox *handle_box, GtkWidget *child, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((handle_box == NULL) || (archiver == NULL))
	    return;

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


/*
 *      Archiver GtkCList "resize_column" signal callback.
 */
void EDVArchiverResizeColumnCB(
	GtkCList *clist, gint column, gint width, gpointer data
)
{
	edv_core_struct *core;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((clist == NULL) || (archiver == NULL))
	    return;

	if(archiver->processing)
	    return;

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

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == archiver->contents_clist)
	{
	    const gint m = EDV_ARCHIVER_CONTENTS_CLIST_COLUMNS_MAX;
	    edv_archiver_column_type column_type = EDV_ARCHIVER_COLUMN_TYPE_NAME;
	    cfg_intlist_struct *column_types_intlist, *column_width_intlist;

	    /* Get column_type from the given column index */
	    column_types_intlist = CFGItemListGetValueIntList(
		core->cfg_list, EDV_CFG_PARM_ARCHIVER_CONTENTS_COLUMN
	    );
	    if(column_types_intlist != NULL)
	    {
		column_type = (edv_archiver_column_type)g_list_nth_data(
		    column_types_intlist->list, column
		);
	    }

	    /* Get column widths intlist */
	    column_width_intlist = CFGItemListGetValueIntList(
		core->cfg_list, EDV_CFG_PARM_ARCHIVER_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;
		}
	    }
	}
}

/*
 *      Archiver GtkCList "click_column" signal callback.
 */
void EDVArchiverClickColumnCB(
	GtkCList *clist, gint column, gpointer data
)
{
	edv_core_struct *core;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((clist == NULL) || (archiver == NULL))
	    return;

	if(archiver->processing)
	    return;

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

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


	    EDVArchiverSetBusy(archiver, TRUE);
	    GUIBlockInput(archiver->toplevel, TRUE);
	    archiver->processing = TRUE;


	    /* Get column types mapping */
	    column_types_intlist = CFGItemListGetValueIntList(
		core->cfg_list, EDV_CFG_PARM_ARCHIVER_CONTENTS_COLUMN
	    );
	    if(column_types_intlist != NULL)
	    {
		edv_archiver_column_type column_type = (edv_archiver_column_type)g_list_nth_data(
		    column_types_intlist->list,
		    column
		);
		switch(column_type)
		{
		  case EDV_ARCHIVER_COLUMN_TYPE_NAME:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_SIZE:
		    cmp_func = cmp_func_num;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSED_SIZE:
		    cmp_func = cmp_func_num;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_TYPE:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_PERMISSIONS:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_OWNER:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_GROUP:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_DATE_ACCESS:
		    cmp_func = EDVArchiverCListColumnSortDateAccessCB;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_DATE_MODIFIED:
		    cmp_func = EDVArchiverCListColumnSortDateModifyCB;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_DATE_CHANGED:
		    cmp_func = EDVArchiverCListColumnSortDateChangeCB;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_LOCATION:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_LINKED_TO:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_DEVICE_TYPE:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSION:
		    cmp_func = EDVArchiverCListColumnSortCompressionCB;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_METHOD:
		    cmp_func = cmp_func_str;
		    break;
		  case EDV_ARCHIVER_COLUMN_TYPE_CRC:
		    cmp_func = cmp_func_str;
		    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 */
	    gtk_clist_sort(clist);

	    gtk_clist_thaw(clist);

	    archiver->processing = FALSE;
	    GUIBlockInput(archiver->toplevel, FALSE);
	    EDVArchiverSetBusy(archiver, FALSE);

	    EDVArchiverUpdateMenus(archiver);
	}
}

/*
 *      Archiver GtkCList "select_row" signal callback.
 */
void EDVArchiverSelectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((clist == NULL) || (archiver == NULL))
	    return;

	if(archiver->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == archiver->contents_clist)
	{
	    edv_core_struct *core = archiver->core;

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

	    /* Get object */
	    edv_archive_object_struct *obj = EDV_ARCHIVE_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );

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

	    /* Update DND icon */
	    EDVArchiverContentsDNDSetIcon(archiver, row, column);

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

		    /* Get object type string and size string */
		    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(
				archiver->core,
				obj->size
			    ),
			    (obj->size == 1l) ? "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(
			archiver->status_bar, s, FALSE
		    );
		    g_free(s);
		    g_free(size_str);
		}
		else
		{
		    EDVStatusBarMessage(
			archiver->status_bar,
			"Object with no name name selected",
			FALSE
		    );
		}
	    }

	    /* Check if selected row is fully visible, if not then
	     * adjust scroll position to try and make it visible
	     */
	    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 */
		);

	    EDVArchiverUpdateMenus(archiver);
	}
}

/*
 *      Archiver GtkCList "unselect_row" signal callback.
 */
void EDVArchiverUnselectRowCB(
	GtkCList *clist, gint row, gint column, GdkEvent *event,
	gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((clist == NULL) || (archiver == NULL))
	    return;

	if(archiver->processing)
	    return;

	/* Check which clist this signal is for */
	if(GTK_WIDGET(clist) == archiver->contents_clist)
	{
	    gchar *s;
	    edv_core_struct *core = archiver->core;

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

	    /* Update 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(
		archiver->status_bar, s, FALSE
	    );
	    g_free(s);

	    EDVArchiverUpdateMenus(archiver);
	}
}


/*
 *      Location combo activate callback.
 */
void EDVArchiverComboActivateCB(GtkWidget *widget, gpointer data)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((widget == NULL) || (archiver == NULL))
	    return;

	if(archiver->processing)
	    return;

	/* Check which widget was activated */

	/* Location combo */
	if(widget == archiver->location_combo)
	{
	    GtkCombo *combo = GTK_COMBO(widget);
	    gchar *path = EDVEvaluatePath(
		NULL,           /* No parent path, imply use toplevel */
		gtk_entry_get_text(GTK_ENTRY(combo->entry))
	    );
	    if(path != NULL)
	    {
		/* Open the archive */
		EDVArchiverOpenArchive(
		    archiver, path,
		    EDVArchiverCurrentPassword(archiver)
		);

		g_free(path);
	    }
	}
	/* Password entry */
	else if(widget == archiver->location_password_entry)
	{
	    EDVArchiverOpenArchive(
		archiver,
		EDVArchiverCurrentLocation(archiver),
		EDVArchiverCurrentPassword(archiver)
	    );
	}
}


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

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

/*
 *	Menu item "leave_notify_event" signal callback.
 *
 *      The given client data must be a edv_archiver_opid_struct *.
 */
gint EDVArchiverMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVArchiverOPLeaveCB(NULL, -1, data);
	return(TRUE);
}


/*
 *	Find Bar get current location callback.
 */
const gchar *EDVArchiverFindBarLocationCB(edv_findbar_struct *fb, gpointer data)
{
	return(EDVArchiverCurrentLocation(
	    EDV_ARCHIVER(data)
	));
}

/*
 *	Find Bar start find callback.
 */
void EDVArchiverFindBarStartCB(edv_findbar_struct *fb, gpointer data)
{
	GtkCList *clist;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

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

	EDVArchiverSetBusy(archiver, TRUE);

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

	EDVArchiverUpdateMenus(archiver);
}

/*
 *	Find Bar end find callback.
 */
void EDVArchiverFindBarEndCB(
	edv_findbar_struct *fb, gint total_matches, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

	EDVArchiverSetBusy(archiver, FALSE);
}


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

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

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


/*
 *	Status message callback.
 */
void EDVArchiverStatusMessageCB(const gchar *message, gpointer data)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

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

/*
 *	Status progress callback.
 */
void EDVArchiverStatusProgressCB(gfloat progress, gpointer data)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

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

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

	if(archiver->processing)
	    return;

	EDVArchiverUpdateMenus(archiver);
}


/*
 *	Object added notify callback.
 */
void EDVArchiverObjectAddedNotifyCB(
	edv_archiver_struct *archiver, const gchar *path,
	const struct stat *lstat_buf
)
{
	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

	EDVArchiverContentsObjectAddedNotify(
	    archiver, path, lstat_buf
	);

/*	EDVArchiverUpdateMenus(archiver); */
}

/*
 *	Object modified notify callback.
 */
void EDVArchiverObjectModifiedNotifyCB(
	edv_archiver_struct *archiver, const gchar *path,
	const gchar *new_path, const struct stat *lstat_buf
)
{
	if(archiver == NULL)
	    return;

	if(archiver->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
	 */
	if(path != NULL)
	{
	    const gchar *s;

	    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
	     */
	    s = EDVArchiverCurrentLocation(archiver);
	    if((s != NULL) ? !strcmp(s, path) : FALSE)
	    {
		/* Old path matches current location, change values
		 * reflecting the current location to the value of
		 * new_path
		 */
		EDVArchiverSetTitle(archiver, new_path);
		EDVArchiverSetLocation(archiver, new_path, FALSE);
		EDVArchiverUpdateLocationIcon(archiver, new_path);
	    }
	}

	EDVArchiverContentsObjectModifiedNotify(
	    archiver, path, new_path, lstat_buf
	);

/*	EDVArchiverUpdateMenus(archiver); */
}

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

	if(archiver->processing)
	    return;

	EDVArchiverContentsObjectRemovedNotify(archiver, path);

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

	    /* Check if the removed object path matches the current
	     * location. If it matches then the current location needs to
	     * be updated to reflect that.
	     */
	    s = EDVArchiverCurrentLocation(archiver);
	    if((s != NULL) ? !strcmp(s, path) : FALSE)
	    {
		/* Removed object path matches the current location */

/* Do not need to update title or location because of this */

	    }
	}

/*	EDVArchiverUpdateMenus(archiver); */
}


/*
 *	Object added to archive callback.
 */
void EDVArchiverArchiveObjectAddedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, edv_archive_object_struct *obj
)
{
	const gchar *cur_loc;

	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

	/* Compare the archive path with the current archive selected
	 * by this archiver
	 */
	cur_loc = EDVArchiverCurrentLocation(archiver);
	if((cur_loc != NULL) && (arch_path != NULL))
	{
	    if(!strcmp(cur_loc, arch_path))
	    {
		/* Notify contents list */
		EDVArchiverContentsArchiveObjectAddedNotify(
		    archiver, arch_path, path, obj
		);
	    }
	}

/*	EDVArchiverUpdateMenus(archiver); */
}

/*
 *	Object modified in archive callback.
 */
void EDVArchiverArchiveObjectModifiedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
)
{
	const gchar *cur_loc;

	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

	/* Compare the archive path with the current archive selected
	 * by this archiver
	 */
	cur_loc = EDVArchiverCurrentLocation(archiver);
	if((cur_loc != NULL) && (arch_path != NULL))
	{
	    if(!strcmp(cur_loc, arch_path))
	    {
		/* Notify contents list */
		EDVArchiverContentsArchiveObjectModifiedNotify(
		    archiver, arch_path, path, new_path, obj
		);
	    }
	}

/*	EDVArchiverUpdateMenus(archiver); */
}

/*
 *	Object removed from archive callback.
 */
void EDVArchiverArchiveObjectRemovedNotifyCB(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path
)
{
	const gchar *cur_loc;

	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

	/* Compare the archive path with the current archive selected
	 * by this archiver
	 */
	cur_loc = EDVArchiverCurrentLocation(archiver);
	if((cur_loc != NULL) && (arch_path != NULL))
	{
	    if(!strcmp(cur_loc, arch_path))
	    {
		/* Notify contents list */
		EDVArchiverContentsArchiveObjectRemovedNotify(
		    archiver, arch_path, path
		);
	    }
	}

/*	EDVArchiverUpdateMenus(archiver); */
}

/*
 *	Recycled object added callback.
 */
void EDVArchiverRecycledObjectAddedNotifyCB(
	edv_archiver_struct *archiver, guint index
)
{
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	core = archiver->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 != archiver->last_recbin_items)
	    EDVArchiverUpdateMenus(archiver);
}

/*
 *	Recycled object removed callback.
 */
void EDVArchiverRecycledObjectRemovedNotifyCB(
	edv_archiver_struct *archiver, guint index
)
{
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	core = archiver->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 != archiver->last_recbin_items)
	    EDVArchiverUpdateMenus(archiver);
}


/*
 *	Reconfigured callback.
 */
void EDVArchiverReconfiguredNotifyCB(edv_archiver_struct *archiver)
{
	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(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

	core = archiver->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
	 */
	archiver->last_recbin_items = -1;
	archiver->last_write_protect_state = -1;


	/* Get the current location */
	cur_loc = STRDUP(EDVArchiverCurrentLocation(archiver));


	/* Update title */
	EDVArchiverSetTitle(archiver, cur_loc);

	/* Update Accelkey Labels */
	EDVArchiverAccelkeysRegenerate(archiver);

	/* Regenerate Tool Bar */
	EDVArchiverToolBarRegenerate(archiver);

	/* Show tool bar? */
	w = archiver->tool_bar_handle;
	if(w != NULL)
	{
	    archiver->tool_bar_map_state = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_TOOL_BAR
	    );
	    if(archiver->tool_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show location bar? */
	w = archiver->location_bar_handle;
	if(w != NULL)
	{
	    archiver->location_bar_map_state = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_LOCATION_BAR
	    );
	    if(archiver->location_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show find bar? */
	w = archiver->find_bar_handle;
	if(w != NULL)
	{
	    archiver->find_bar_map_state = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_FIND_BAR
	    );
	    if(archiver->find_bar_map_state)
		gtk_widget_show(w);
	    else
		gtk_widget_hide(w);
	}

	/* Show status bar? */
	status_bar = archiver->status_bar;
	if(status_bar != NULL)
	{
	    archiver->status_bar_map_state = CFGItemListGetValueI(
		cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_STATUS_BAR
	    );
	    if(archiver->status_bar_map_state)
		EDVStatusBarMap(status_bar);
	    else
		EDVStatusBarUnmap(status_bar);
	}


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


	/* Realize listings */
	EDVArchiverContentsRealizeListing(archiver);

	EDVArchiverUpdateMenus(archiver);

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

	g_free(cur_loc);
}


/*
 *	MIME Type added callback.
 */
void EDVArchiverMimeTypeAddedCB(
	edv_archiver_struct *archiver,
	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
	 */
	EDVArchiverMimeTypeModifiedCB(archiver, mt_num, mt);
}

/*
 *	MIME Type modified callback.
 */
void EDVArchiverMimeTypeModifiedCB(
	edv_archiver_struct *archiver,
	gint mt_num, edv_mime_type_struct *mt
)
{
	gchar *cur_loc;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

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

	/* Get the current location */
	cur_loc = STRDUP(EDVArchiverCurrentLocation(archiver));

	/* Realize listings */
	EDVArchiverContentsRealizeListing(archiver);

/*	EDVArchiverUpdateMenus(archiver); */

	g_free(cur_loc);
}

/*
 *	MIME Type removed callback.
 */
void EDVArchiverMimeTypeRemovedCB(
	edv_archiver_struct *archiver, gint mt_num
)
{
	gchar *cur_loc;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	if(archiver->processing)
	    return;

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

	/* Get the current location */
	cur_loc = STRDUP(EDVArchiverCurrentLocation(archiver));

	/* Realize listings */
	EDVArchiverContentsRealizeListing(archiver);

/*	EDVArchiverUpdateMenus(archiver); */

	g_free(cur_loc);
}
